mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-79129 Report tuple index out of range
GitOrigin-RevId: 42c382dfaa5fc75244151d24324b479a62aee85f
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7d1376160a
commit
bb55a1b4f0
@@ -1076,6 +1076,7 @@ INSP.type.checker.unexpected.argument.from.paramspec=Unexpected argument (from P
|
||||
INSP.type.checker.unfilled.parameter.for.paramspec=Parameter ''{0}'' unfilled (from ParamSpec ''{1}'')
|
||||
INSP.type.checker.unfilled.vararg=Parameter ''{0}'' unfilled, expected ''{1}''
|
||||
INSP.type.checker.expected.type.from.dunder.set.got.type.instead=Expected type ''{0}'' (from ''__set__''), got ''{1}'' instead
|
||||
INSP.type.checker.tuple.index.out.of.range=Tuple index out of range
|
||||
|
||||
# PyTypedDictInspection
|
||||
INSP.NAME.typed.dict=Invalid TypedDict definition and usages
|
||||
|
||||
+13
-1
@@ -21,6 +21,7 @@ import com.jetbrains.python.documentation.PythonDocumentationProvider;
|
||||
import com.jetbrains.python.inspections.quickfix.PyMakeFunctionReturnTypeQuickFix;
|
||||
import com.jetbrains.python.inspections.quickfix.PyMakeReturnsExplicitFix;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PySubscriptionExpressionImpl;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.types.*;
|
||||
import one.util.streamex.StreamEx;
|
||||
@@ -71,9 +72,20 @@ public class PyTypeCheckerInspection extends PyInspection {
|
||||
@Override
|
||||
public void visitPySubscriptionExpression(@NotNull PySubscriptionExpression node) {
|
||||
// TODO: Support slice PySliceExpressions
|
||||
|
||||
PyType operandType = myTypeEvalContext.getType(node.getOperand());
|
||||
if (operandType instanceof PyTupleType tupleType && !tupleType.isHomogeneous()) {
|
||||
PyExpression indexExpression = node.getIndexExpression();
|
||||
for (int index : PySubscriptionExpressionImpl.getIndexExpressionPossibleValues(indexExpression, myTypeEvalContext, Integer.class)) {
|
||||
int count = tupleType.getElementCount();
|
||||
if (index < -count || index >= count) {
|
||||
registerProblem(indexExpression, PyPsiBundle.message("INSP.type.checker.tuple.index.out.of.range"));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Type check in TypedDict subscription expressions cannot be properly done because each key should have its own value type,
|
||||
// so this case is covered by PyTypedDictInspection
|
||||
if (myTypeEvalContext.getType(node.getOperand()) instanceof PyTypedDictType) return;
|
||||
if (operandType instanceof PyTypedDictType) return;
|
||||
// Don't type check __class_getitem__ calls inside type hints. Normally these are not type hinted as a construct
|
||||
// special-cased by type checkers
|
||||
if (PyTypingTypeProvider.isInsideTypeHint(node, myTypeEvalContext)) return;
|
||||
|
||||
+3
-7
@@ -37,7 +37,7 @@ class PyTypedDictInspection : PyInspection() {
|
||||
|
||||
val indexExpression = node.indexExpression
|
||||
val indexExpressionValueOptions = PySubscriptionExpressionImpl.getIndexExpressionPossibleValues(indexExpression, myTypeEvalContext, String::class.java)
|
||||
if (indexExpressionValueOptions.isNullOrEmpty()) {
|
||||
if (indexExpressionValueOptions.isEmpty()) {
|
||||
if (!operandType.isDefinition) {
|
||||
val keyList = operandType.fields.keys.joinToString(transform = { "'$it'" })
|
||||
registerProblem(indexExpression, PyPsiBundle.message("INSP.typeddict.typeddict.key.must.be.string.literal.expected.one", keyList))
|
||||
@@ -187,9 +187,7 @@ class PyTypedDictInspection : PyInspection() {
|
||||
if (expr !is PySubscriptionExpression) continue
|
||||
val type = myTypeEvalContext.getType(expr.operand)
|
||||
if (type is PyTypedDictType) {
|
||||
val possibleIndexValues = PySubscriptionExpressionImpl.getIndexExpressionPossibleValues(expr.indexExpression, myTypeEvalContext, String::class.java)
|
||||
?: emptyList()
|
||||
for (index in possibleIndexValues) {
|
||||
for (index in PySubscriptionExpressionImpl.getIndexExpressionPossibleValues(expr.indexExpression, myTypeEvalContext, String::class.java)) {
|
||||
if (type.fields[index]?.qualifiers?.isRequired == true) {
|
||||
registerProblem(expr.indexExpression, PyPsiBundle.message("INSP.typeddict.key.cannot.be.deleted", index, type.name))
|
||||
}
|
||||
@@ -269,9 +267,7 @@ class PyTypedDictInspection : PyInspection() {
|
||||
if (target !is PySubscriptionExpression) return@forEach
|
||||
val targetType = myTypeEvalContext.getType(target.operand)
|
||||
if (targetType !is PyTypedDictType) return@forEach
|
||||
val possibleIndexValues = PySubscriptionExpressionImpl.getIndexExpressionPossibleValues(target.indexExpression, myTypeEvalContext, String::class.java)
|
||||
?: emptyList()
|
||||
for (indexString in possibleIndexValues) {
|
||||
for (indexString in PySubscriptionExpressionImpl.getIndexExpressionPossibleValues(target.indexExpression, myTypeEvalContext, String::class.java)) {
|
||||
if (targetType.fields[indexString]?.qualifiers?.isReadOnly == true) {
|
||||
registerProblem(target, PyPsiBundle.message("INSP.typeddict.typeddict.field.is.readonly", indexString))
|
||||
}
|
||||
|
||||
+15
-18
@@ -51,20 +51,17 @@ public class PySubscriptionExpressionImpl extends PyElementImpl implements PySub
|
||||
final PyType type = indexExpression != null ? context.getType(getOperand()) : null;
|
||||
if (type instanceof PyTupleType tupleType) {
|
||||
List<Integer> indexPossibleValues = getIndexExpressionPossibleValues(indexExpression, context, Integer.class);
|
||||
if (indexPossibleValues != null) {
|
||||
List<@Nullable PyType> possibleTypes = ContainerUtil.map(indexPossibleValues, index -> {
|
||||
if (!tupleType.isHomogeneous() && index < 0) {
|
||||
index += tupleType.getElementCount();
|
||||
}
|
||||
return tupleType.getElementType(index);
|
||||
});
|
||||
return PyUnionType.union(possibleTypes);
|
||||
}
|
||||
return null;
|
||||
List<@Nullable PyType> possibleTypes = ContainerUtil.map(indexPossibleValues, index -> {
|
||||
if (!tupleType.isHomogeneous() && index < 0) {
|
||||
index += tupleType.getElementCount();
|
||||
}
|
||||
return tupleType.getElementType(index);
|
||||
});
|
||||
return PyUnionType.union(possibleTypes);
|
||||
}
|
||||
if (type instanceof PyTypedDictType typedDictType) {
|
||||
List<String> indexPossibleValues = getIndexExpressionPossibleValues(indexExpression, context, String.class);
|
||||
return indexPossibleValues != null ? PyUnionType.union(ContainerUtil.map(indexPossibleValues, typedDictType::getElementType)) : null;
|
||||
return PyUnionType.union(ContainerUtil.map(indexPossibleValues, typedDictType::getElementType));
|
||||
}
|
||||
if (type instanceof PyClassType) {
|
||||
PyType parameterizedType = Ref.deref(PyTypingTypeProvider.getType(this, context));
|
||||
@@ -76,11 +73,11 @@ public class PySubscriptionExpressionImpl extends PyElementImpl implements PySub
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static <T> @Nullable List<T> getIndexExpressionPossibleValues(@Nullable PyExpression indexExpression,
|
||||
@NotNull TypeEvalContext context,
|
||||
@NotNull Class<T> indexType) {
|
||||
public static <T> @NotNull List<T> getIndexExpressionPossibleValues(@Nullable PyExpression indexExpression,
|
||||
@NotNull TypeEvalContext context,
|
||||
@NotNull Class<T> indexType) {
|
||||
if (indexExpression == null) {
|
||||
return null;
|
||||
return List.of();
|
||||
}
|
||||
T indexExprValue = PyEvaluator.evaluate(indexExpression, indexType);
|
||||
if (indexExprValue != null) {
|
||||
@@ -88,16 +85,16 @@ public class PySubscriptionExpressionImpl extends PyElementImpl implements PySub
|
||||
}
|
||||
PyType type = context.getType(indexExpression);
|
||||
if (type == null) {
|
||||
return null;
|
||||
return List.of();
|
||||
}
|
||||
List<T> result = new ArrayList<>();
|
||||
for (PyType subType : PyTypeUtil.toStream(type)) {
|
||||
if (!(subType instanceof PyLiteralType literalType)) {
|
||||
return null;
|
||||
return List.of();
|
||||
}
|
||||
T val = PyEvaluator.evaluateNoResolve(literalType.getExpression(), indexType);
|
||||
if (val == null) {
|
||||
return null;
|
||||
return List.of();
|
||||
}
|
||||
result.add(val);
|
||||
}
|
||||
|
||||
@@ -2502,4 +2502,21 @@ def foo(param: str | int) -> TypeGuard[str]:
|
||||
asdict(<warning descr="Expected type 'DataclassInstance', got 'str' instead">"Bob"</warning>)
|
||||
""");
|
||||
}
|
||||
|
||||
public void testTupleIndexOutOfRange() {
|
||||
doTestByText("""
|
||||
from typing import Literal
|
||||
|
||||
def foo(t: tuple[int, str], i: Literal[1], j: Literal[3], k: Literal[-3]):
|
||||
t[i]
|
||||
t[-1]
|
||||
t[<warning descr="Tuple index out of range">j</warning>]
|
||||
t[<warning descr="Tuple index out of range">2</warning>]
|
||||
t[<warning descr="Tuple index out of range">k</warning>]
|
||||
t[<warning descr="Tuple index out of range">-4</warning>]
|
||||
|
||||
def bar(t: tuple[int, ...]):
|
||||
t[10]
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user