[python] PY-76868 Upgrade subscription type form checks

Merge-request: IJ-MR-190288
Merged-by: Nikita Paniukhin <nikita.paniukhin@jetbrains.com>

GitOrigin-RevId: e484c7e6249797b66145149eac58b7c6945758b4
This commit is contained in:
Nikita Paniukhin
2026-02-15 22:44:20 +00:00
committed by intellij-monorepo-bot
parent a8d61ea416
commit 9331cae59f
5 changed files with 409 additions and 66 deletions
@@ -1263,6 +1263,10 @@ INSP.type.hints.at.most.one.unpacked.tuple=Type argument list can have at most o
INSP.type.hints.cannot.use.covariant.in.function.param=Covariant type variable cannot be used in parameter type
INSP.type.hints.cannot.use.contravariant.in.return.type=Contravariant type variable cannot be used in function return type
INSP.type.hints.cannot.use.class.scope.type.variables.in.annotation.for.self.parameter.of__init__=Class-scoped type variables should not be used in the annotation for 'self' parameter of '__init__' method
INSP.type.hints.ellipsis.allowed.only.as.second.argument='...' is allowed only as the second of two arguments
INSP.type.hints.empty.tuple.only.as.lone.argument=Empty tuple is allowed only as a sole argument
INSP.type.hints.ellipsis.cannot.be.used.with.unpacked.type='...' cannot be used with an unpacked 'TypeVarTuple' or tuple
INSP.type.hints.optional.must.have.exactly.one.argument='Optional' must have exactly one argument
QFIX.remove.function.annotations=Remove function annotations
QFIX.replace.with.target.name=Replace with the target name
QFIX.remove.generic.parameters=Remove generic parameters
@@ -1654,9 +1654,14 @@ class PyTypingTypeProvider : PyTypeProviderWithCustomContext<Context?>() {
private fun getOptionalType(element: PsiElement, context: Context): Ref<PyType?>? {
if (element is PySubscriptionExpression) {
if (resolvesToQualifiedNames(element.operand, context.typeContext, OPTIONAL)) {
val indexExpr = element.indexExpression
if (indexExpr != null) {
val typeRef: Ref<PyType?>? = getType(indexExpr, context)
val indexExpr = PyPsiUtils.flattenParens(element.indexExpression)
val argExpr: PyExpression? = when (indexExpr) {
is PyTupleExpression -> indexExpr.elements.singleOrNull() ?: indexExpr
else -> indexExpr
}
if (argExpr != null) {
val typeRef: Ref<PyType?>? = getType(argExpr, context)
if (typeRef != null) {
return Ref(PyUnionType.union(typeRef.get(), getInstance(element).noneType))
}
@@ -2049,7 +2054,7 @@ class PyTypingTypeProvider : PyTypeProviderWithCustomContext<Context?>() {
private fun getUnionType(element: PsiElement, context: Context): Ref<PyType?>? {
if (element is PySubscriptionExpression) {
if (resolvesToQualifiedNames(element.operand, context.typeContext, UNION)) {
val union = PyUnionType.union(getIndexTypes(element, context))
val union = PyUnionType.unionOrNever(getIndexTypes(element, context))
return if (union != null) Ref(union) else null
}
}
@@ -2448,7 +2453,7 @@ class PyTypingTypeProvider : PyTypeProviderWithCustomContext<Context?>() {
private fun getIndexTypes(expression: PySubscriptionExpression, context: Context): MutableList<PyType?> {
val types: MutableList<PyType?> = ArrayList()
val indexExpr = expression.indexExpression
val indexExpr = PyPsiUtils.flattenParens(expression.indexExpression)
if (indexExpr is PyTupleExpression) {
for (expr in indexExpr.elements) {
types.add(Ref.deref<PyType?>(getType(expr, context)))
@@ -2578,12 +2583,34 @@ class PyTypingTypeProvider : PyTypeProviderWithCustomContext<Context?>() {
if (operandType is PyClassType) {
if (operandType !is PyTupleType && PyNames.TUPLE == operandType.pyClass.qualifiedName) {
if (indexExpr is PyTupleExpression) {
val elements = indexExpr.elements
if (elements.size == 2 && elements[1] is PyEllipsisLiteralExpression) {
return PyTupleType.createHomogeneous(element, indexTypes[0])
val indexElements = indexExpr.elements.map { PyPsiUtils.flattenParens(it) }
val lastIsEllipsis =
indexElements.isNotEmpty() && indexElements.last() is PyEllipsisLiteralExpression
if (lastIsEllipsis) {
if (indexElements.size != 2) return null
if (indexElements.first() is PyEllipsisLiteralExpression) return null
val indexType = indexTypes.first()
if (indexType is PyPositionalVariadicType) return null
return PyTupleType.createHomogeneous(element, indexType)
}
else {
for (indexElement in indexElements) {
if (indexElement is PyEllipsisLiteralExpression) return null
if (indexElement is PyTupleExpression && indexElement.elements.isEmpty()) {
if (indexElements.size != 1) return null
}
}
return PyTupleType.create(element, indexTypes)
}
}
return PyTupleType.create(element, indexTypes)
else {
if (indexExpr is PyEllipsisLiteralExpression) return null
return PyTupleType.create(element, indexTypes)
}
}
if (isGeneric(operandType, context.typeContext)) {
@@ -101,6 +101,7 @@ import com.jetbrains.python.psi.types.PyTypeChecker.collectGenerics
import com.jetbrains.python.psi.types.PyTypeChecker.hasGenerics
import com.jetbrains.python.psi.types.PyTypeParameterMapping
import com.jetbrains.python.psi.types.PyTypeParameterType
import com.jetbrains.python.psi.types.PyTypeParser
import com.jetbrains.python.psi.types.PyTypeVarTupleType
import com.jetbrains.python.psi.types.PyTypeVarType
import com.jetbrains.python.psi.types.PyTypeVarType.Variance
@@ -1087,6 +1088,9 @@ class PyTypeHintsInspection : PyInspection() {
val typeAliasExtQName = QualifiedName.fromDottedString(PyTypingTypeProvider.TYPE_ALIAS_EXT)
val typingSelf = QualifiedName.fromDottedString(PyTypingTypeProvider.SELF)
val typingExtSelf = QualifiedName.fromDottedString(PyTypingTypeProvider.SELF_EXT)
val unionQName = QualifiedName.fromDottedString(PyTypingTypeProvider.UNION)
val optionalQName = QualifiedName.fromDottedString(PyTypingTypeProvider.OPTIONAL)
val qNames = PyResolveUtil.resolveImportedElementQNameLocally(operand)
var typingOnly = true
@@ -1100,8 +1104,14 @@ class PyTypeHintsInspection : PyInspection() {
annotatedQName, annotatedExtQName -> checkAnnotatedParameter(index)
typeAliasQName, typeAliasExtQName -> reportParameterizedTypeAlias(index)
typingSelf, typingExtSelf -> reportParameterizedSelf(index)
unionQName -> checkGenericTypeArguments(node)
optionalQName -> {
checkGenericTypeArguments(node)
checkOptionalParameter(index)
}
callableQName -> {
callableExists = true
checkGenericTypeArguments(node, isCallable = true)
checkCallableParameters(index)
}
else -> checkGenericTypeParameterization(node)
@@ -1166,7 +1176,19 @@ class PyTypeHintsInspection : PyInspection() {
}.firstOrNull()
when (declaration) {
is PyTargetExpression -> checkTypeAliasParameterization(node, declaration)
is PyTargetExpression -> {
val builtinName = declaration.qualifiedName?.let { PyTypingTypeProvider.BUILTIN_COLLECTION_CLASSES[it] }
if (builtinName != null) {
val builtinType = PyTypeParser.getTypeByName(node, builtinName, myTypeEvalContext)
val builtinTypeClass = (builtinType as? PyClassType)?.pyClass
if (builtinTypeClass != null) {
checkGenericClassParameterization(node, builtinTypeClass)
return
}
}
checkTypeAliasParameterization(node, declaration)
}
is PyTypeAliasStatement -> checkTypeAliasStatementParameterization(node, declaration)
is PyClass -> checkGenericClassParameterization(node, declaration)
else -> return
@@ -1184,9 +1206,14 @@ class PyTypeHintsInspection : PyInspection() {
}
return
}
val typeArguments = checkGenericTypeArguments(node)
if (typeArguments == null || genericDefinitionType.pyClass.qualifiedName == PyNames.TUPLE) return
val typeArguments = checkGenericTypeArguments(node) ?: return
if (genericDefinitionType.pyClass.qualifiedName == PyNames.TUPLE) {
checkTupleTypeForm(node)
return
}
val typeParameters = genericDefinitionType.elementTypes
val typeParameterListRepresentation = typeParameters.joinToString(prefix = "[", postfix = "]") { it.name!! }
@@ -1241,51 +1268,105 @@ class PyTypeHintsInspection : PyInspection() {
}
}
private fun checkGenericTypeArguments(node: PySubscriptionExpression, isCallable: Boolean = false): List<PyType?>? {
val flatIndexExpr = PyPsiUtils.flattenParens(node.indexExpression) ?: return null
val arguments = (flatIndexExpr as? PyTupleExpression)?.elements ?: arrayOf(flatIndexExpr)
val argumentTypes = mutableListOf<PyType?>()
private fun checkGenericTypeArguments(node: PySubscriptionExpression): List<PyType?>? {
val indexExpression = node.indexExpression ?: return null
val parameters = (indexExpression as? PyTupleExpression)?.elements ?: arrayOf(indexExpression)
val typeArgumentTypes = mutableListOf<PyType?>()
parameters.forEach {
when (it) {
for ((index, argument) in arguments.withIndex()) {
val argumentType = when (val flatArgument = PyPsiUtils.flattenParens(argument)) {
is PyReferenceExpression,
is PySubscriptionExpression,
is PyBinaryExpression,
is PyStarExpression,
is PyStringLiteralExpression,
is PyListLiteralExpression,
is PyCallExpression,
-> {
val typeRef = PyTypingTypeProvider.getType(it, myTypeEvalContext)
val typeRef = PyTypingTypeProvider.getType(argument, myTypeEvalContext)
if (typeRef == null) {
val shouldReportError = when {
it is PyReferenceExpression -> {
val isUnresolved = PyResolveUtil.resolveDeclaration(it.reference, resolveContext) == null
val isOpaque = PyTypingTypeProvider.resolveToQualifiedNames(it, myTypeEvalContext)
val message = when {
argument is PyReferenceExpression -> {
val isResolved = PyResolveUtil.resolveDeclaration(argument.reference, resolveContext) != null
val isOpaque = PyTypingTypeProvider.resolveToQualifiedNames(argument, myTypeEvalContext)
.any { qName -> PyTypingTypeProvider.OPAQUE_NAMES.contains(qName) }
!isOpaque && !isUnresolved
if (isResolved && !isOpaque) PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.must.be.types") else null
}
else -> true
else -> PyPsiBundle.message("INSP.type.hints.invalid.type.argument")
}
if (shouldReportError) {
registerProblem(it, PyPsiBundle.message("INSP.type.hints.invalid.type.argument"))
if (message != null) {
registerProblem(flatArgument, message, ProblemHighlightType.GENERIC_ERROR)
}
}
typeArgumentTypes.add(Ref.deref(typeRef))
Ref.deref(typeRef)
}
is PyNoneLiteralExpression -> {
typeArgumentTypes.add(PyBuiltinCache.getInstance(node).noneType)
PyBuiltinCache.getInstance(node).noneType
}
is PyEllipsisLiteralExpression -> {
typeArgumentTypes.add(null)
is PyEllipsisLiteralExpression if (
node.isBuiltinTupleTypeForm(myTypeEvalContext) ||
node.isParamSpecArgument(index, myTypeEvalContext) ||
(isCallable && index == 0)
) -> {
null
}
is PyTupleExpression if (
(node.isBuiltinTupleTypeForm(myTypeEvalContext) && flatArgument.elements.isEmpty()) ||
(isCallable && index == 0)
) -> {
null
}
else -> {
registerProblem(it, PyPsiBundle.message("INSP.type.hints.invalid.type.argument"))
typeArgumentTypes.add(null)
registerProblem(argument, PyPsiBundle.message("INSP.type.hints.invalid.type.argument"), ProblemHighlightType.GENERIC_ERROR)
null
}
}
argumentTypes.add(argumentType)
}
return argumentTypes
}
private fun checkTupleTypeForm(node: PySubscriptionExpression) {
if (!node.isBuiltinTupleTypeForm(myTypeEvalContext)) return
val flatIndexExpr = PyPsiUtils.flattenParens(node.indexExpression)
val arguments = (flatIndexExpr as? PyTupleExpression)?.elements ?: arrayOf(flatIndexExpr)
for ((index, argument) in arguments.withIndex()) {
when (val flatArgument = PyPsiUtils.flattenParens(argument)) {
is PyEllipsisLiteralExpression if (index != arguments.lastIndex || arguments.size != 2) -> {
registerProblem(flatArgument,
PyPsiBundle.message("INSP.type.hints.ellipsis.allowed.only.as.second.argument"),
ProblemHighlightType.GENERIC_ERROR)
}
is PyTupleExpression if flatArgument.elements.isEmpty() && arguments.size != 1 -> {
registerProblem(flatArgument,
PyPsiBundle.message("INSP.type.hints.empty.tuple.only.as.lone.argument"),
ProblemHighlightType.GENERIC_ERROR)
}
}
}
return typeArgumentTypes
val lastArgument = PyPsiUtils.flattenParens(arguments.lastOrNull())
if (lastArgument is PyEllipsisLiteralExpression) {
val type = Ref.deref(PyTypingTypeProvider.getType(arguments.first(), myTypeEvalContext))
if (type is PyPositionalVariadicType) {
registerProblem(lastArgument,
PyPsiBundle.message("INSP.type.hints.ellipsis.cannot.be.used.with.unpacked.type"),
ProblemHighlightType.GENERIC_ERROR)
}
}
}
private fun checkOptionalParameter(index: PyExpression) {
val flatIndexExpr = PyPsiUtils.flattenParens(index)
val elements = (flatIndexExpr as? PyTupleExpression)?.elements ?: arrayOf(index)
if (elements.size != 1) {
registerProblem(flatIndexExpr,
PyPsiBundle.message("INSP.type.hints.optional.must.have.exactly.one.argument"),
ProblemHighlightType.GENERIC_ERROR)
}
}
private fun checkTypingGenericParameters(node: PySubscriptionExpression, isProtocol: Boolean) {
@@ -1441,25 +1522,25 @@ class PyTypeHintsInspection : PyInspection() {
parameters
.asSequence()
.drop(if (isCallable) 1 else 0)
.forEach {
if (it is PyListLiteralExpression) {
registerProblem(it,
PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.must.be.types"),
ProblemHighlightType.GENERIC_ERROR,
null,
RemoveSquareBracketsQuickFix())
}
else if (it is PyReferenceExpression && multiFollowAssignmentsChain(it).any { resolved -> resolved is PyListLiteralExpression }) {
registerProblem(it, PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.must.be.types"),
ProblemHighlightType.GENERIC_ERROR)
}
else if (it is PyStarExpression) {
if (alreadyHaveUnpacking) {
registerProblem(it, PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.cannot.contain.more.than.one.unpacking"),
ProblemHighlightType.GENERIC_ERROR)
.forEach { argument ->
val flatArgument = PyPsiUtils.flattenParens(argument)
when (flatArgument) {
is PyListLiteralExpression -> {
registerProblem(flatArgument,
PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.must.be.types"),
ProblemHighlightType.GENERIC_ERROR,
null,
RemoveSquareBracketsQuickFix())
}
else {
alreadyHaveUnpacking = true
is PyStarExpression -> {
if (alreadyHaveUnpacking) {
registerProblem(flatArgument,
PyPsiBundle.message("INSP.type.hints.parameters.to.generic.types.cannot.contain.more.than.one.unpacking"),
ProblemHighlightType.GENERIC_ERROR)
}
else {
alreadyHaveUnpacking = true
}
}
}
}
@@ -1569,11 +1650,12 @@ class PyTypeHintsInspection : PyInspection() {
typeArguments: List<PyType?>,
@InspectionMessage message: String,
) {
val flatIndexExpr = PyPsiUtils.flattenParens(node.indexExpression)
val mapping = PyTypeParameterMapping.mapByShape(typeParameters,
typeArguments,
PyTypeParameterMapping.Option.USE_DEFAULTS)
if (mapping == null) {
registerProblem(node.indexExpression, message, ProblemHighlightType.WARNING)
registerProblem(flatIndexExpr, message, ProblemHighlightType.WARNING)
}
else {
for (pair in mapping.mappedTypes) {
@@ -1582,7 +1664,9 @@ class PyTypeHintsInspection : PyInspection() {
if (!matched) {
val expectedName = PythonDocumentationProvider.getVerboseTypeName(pair.getFirst(), myTypeEvalContext)
val actualName = PythonDocumentationProvider.getTypeName(pair.getSecond(), myTypeEvalContext)
registerProblem(node.indexExpression, PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName), ProblemHighlightType.WARNING)
registerProblem(flatIndexExpr,
PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName),
ProblemHighlightType.WARNING)
return
}
}
@@ -1876,3 +1960,37 @@ class PyTypeHintsInspection : PyInspection() {
}
}
}
private fun PySubscriptionExpression.isBuiltinTupleTypeForm(context: TypeEvalContext): Boolean {
val operandType = context.getType(operand)
return operandType is PyClassType && operandType !is PyTupleType && operandType.classQName == PyNames.TUPLE
}
private fun PySubscriptionExpression.isParamSpecArgument(argIndex: Int, context: TypeEvalContext): Boolean {
// Generic class parameterization (class Foo[T, **P]: ...)
val operandType = context.getType(this.operand) as? PyClassType
if (operandType != null) {
val genericDefinitionType = PyTypeChecker.findGenericDefinitionType(operandType.pyClass, context)
if (genericDefinitionType != null) {
val typeParameters = genericDefinitionType.elementTypes
if (argIndex in typeParameters.indices && typeParameters[argIndex] is PyParamSpecType) {
return true
}
}
}
// PEP-695 type alias: `type Alias[S1, **S2] = ...`
val resolveContext = PyResolveContext.defaultContext(context)
val operandRef = this.operand as? PyReferenceExpression
if (operandRef != null) {
val aliasStatement = PyResolveUtil.resolveDeclaration(operandRef.reference, resolveContext) as? PyTypeAliasStatement
if (aliasStatement != null) {
val typeParams = aliasStatement.typeParameterList?.typeParameters ?: emptyList()
if (argIndex in typeParams.indices) {
return typeParams[argIndex].kind == PyAstTypeParameter.Kind.ParamSpec
}
}
}
return false
}
-1
View File
@@ -46,5 +46,4 @@ specialtypes_any.py
specialtypes_never.py
specialtypes_promotions.py
specialtypes_type.py
tuples_type_form.py
typeddicts_extra_items.py
@@ -913,7 +913,7 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
def d(x: AnnotatedExt[AnnotatedExt[str, dict(key="value")], ""]):
pass
def e(x: Annotated[str, list[<warning descr="Invalid type argument">dict(key="value")</warning>]]):
def e(x: Annotated[str, list[<error descr="Invalid type argument">dict(key="value")</error>]]):
pass
def f(x: Annotated[<warning descr="Generics should be specified through square brackets">dict(key="value")</warning>, ""]):
@@ -2251,22 +2251,22 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
class A:...
c1 = Clazz[<warning descr="Invalid type argument">print()</warning>, int]()
c2 = Clazz[int, <warning descr="Invalid type argument">print()</warning>]()
c3 = Clazz[<warning descr="Invalid type argument">1</warning>]
c1 = Clazz[<error descr="Invalid type argument">print()</error>, int]()
c2 = Clazz[int, <error descr="Invalid type argument">print()</error>]()
c3 = Clazz[<error descr="Invalid type argument">1</error>]
c4 = Clazz["int", "str"]
c5 = Clazz[dict[int, str]]
c7 = Clazz[<warning descr="Invalid type argument">True</warning>]
c8 = Clazz[<warning descr="Invalid type argument">list or set</warning>]
c7 = Clazz[<error descr="Invalid type argument">True</error>]
c8 = Clazz[<error descr="Invalid type argument">list or set</error>]
c9 = Clazz[Literal[3]]
c10 = Clazz[<warning descr="Invalid type argument">var</warning>]
c10 = Clazz[<error descr="Parameters to generic types must be types">var</error>]
c11 = Clazz[myInt]
c12 = Clazz[myIntOrStr]
c13 = Clazz[myIntAlias]
c14 = Clazz[A]
c15 = Clazz[<warning descr="Invalid type argument">{"a": "b"}</warning>]
c16 = Clazz[<warning descr="Invalid type argument">(lambda: int)()</warning>]
c17 = Clazz[<warning descr="Invalid type argument">(int, str)</warning>]
c15 = Clazz[<error descr="Invalid type argument">{"a": "b"}</error>]
c16 = Clazz[<error descr="Invalid type argument">(lambda: int)()</error>]
c17 = Clazz[(int, str)]
""");
}
@@ -3200,6 +3200,171 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
""");
}
public void testSubscriptionParenthesesFlattening() {
generateVariableTypeAssertions(new Object[][]{
{"list[((int))]", "list[int]"},
// TODO: type: list[Any]
{"list[((<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'list'\">int, int</warning>))]"},
{"tuple[((int, int))]", "tuple[int, int]"},
{"tuple[<error descr=\"Invalid type argument\">((int, int))</error>, int]", "tuple[Any, int]"},
{"set[((int))]", "set[int]"},
// TODO: type: set[Any]
{"set[((<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'set'\">int, int</warning>))]"},
{"dict[((int)), (((str)))]", "dict[int, str]"},
// TODO: type: dict[Any, Any]
{"dict[((<warning descr=\"Passed type arguments do not match type parameters [_KT, _VT] of class 'dict'\">int</warning>))]"},
{"List[((int))]", "list[int]"},
// TODO: type: list[Any]
{"List[((<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'list'\">int, int</warning>))]"},
{"Tuple[((int)), (((str)))]", "tuple[int, str]"},
{"Tuple[<error descr=\"Invalid type argument\">((int, int))</error>, int]", "tuple[Any, int]"},
{"Set[((int))]", "set[int]"},
// TODO: type: set[Any]
{"Set[((<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'set'\">int, int</warning>))]"},
{"Dict[((int)), (((str)))]", "dict[int, str]"},
// TODO: type: dict[Any, Any]
{"Dict[((<warning descr=\"Passed type arguments do not match type parameters [_KT, _VT] of class 'dict'\">int</warning>))]"},
{"Union[((int, (((str)))))]", "int | str"},
{"Union[<error descr=\"Invalid type argument\">((int, int))</error>, <error descr=\"Invalid type argument\">(int, int)</error>]",
"Any"},
{"Optional[((int))]", "int | None"},
{"Optional[((<error descr=\"'Optional' must have exactly one argument\">int, int</error>))]", "Any"},
{"tuple[((int)), ((...))]", "tuple[int, ...]"},
{"tuple[((<error descr=\"'...' is allowed only as the second of two arguments\">...</error>)), ((int))]", "Any"},
{"tuple[<error descr=\"Invalid type argument\">(int,)</error>, ...]", "tuple[Any, ...]"},
{"C[(((int)))]", "C[int]"},
{"C2[(((int), (str)))]", "C2[int, str]"},
});
}
public void testSubscriptionEmptyParentheses() {
generateVariableTypeAssertions(new Object[][]{
{"tuple[()]", "tuple[()]"},
{"tuple[int, <error descr=\"Empty tuple is allowed only as a sole argument\">()</error>]", "Any"},
{"tuple[<error descr=\"Empty tuple is allowed only as a sole argument\">()</error>, int]", "Any"},
{"tuple[<error descr=\"Empty tuple is allowed only as a sole argument\">()</error>, ...]", "tuple[Any, ...]"},
{"tuple[<error descr=\"Empty tuple is allowed only as a sole argument\">()</error>, <error descr=\"Empty tuple is allowed only as a sole argument\">()</error>]",
"Any"},
{"Tuple[()]", "tuple[()]"},
{"Tuple[int, <error descr=\"Empty tuple is allowed only as a sole argument\">()</error>]", "Any"},
// TODO: type: list[Any]
{"list[<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'list'\">()</warning>]"},
// TODO: type: set[Any]
{"set[<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'set'\">()</warning>]"},
// TODO: type: dict[Any, Any]
{"dict[<error descr=\"Invalid type argument\">()</error>, <error descr=\"Invalid type argument\">()</error>]"},
// TODO: type: list[Any]
{"List[<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'list'\">()</warning>]"},
// TODO: type: set[Any]
{"Set[<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'set'\">()</warning>]"},
// TODO: type: dict[Any, Any]
{"Dict[<error descr=\"Invalid type argument\">()</error>, <error descr=\"Invalid type argument\">()</error>]"},
{"Union[()]", "Never"},
{"Optional[<error descr=\"'Optional' must have exactly one argument\">()</error>]", "Any"},
// TODO: type: C[Any]
{"C[<warning descr=\"Passed type arguments do not match type parameters [T] of class 'C'\">()</warning>]"},
});
}
public void testSubscriptionTypeForm() {
generateVariableTypeAssertions(new Object[][]{
{"list[((int,))]", "list[int]"},
{"tuple[((int,))]", "tuple[int]"},
{"tuple[<error descr=\"Invalid type argument\">(int,)</error>, int]", "tuple[Any, int]"},
{"tuple[<error descr=\"Invalid type argument\">(int, str)</error>, <error descr=\"Invalid type argument\">(int, str)</error>]",
"tuple[Any, Any]"},
{"set[((int,))]", "set[int]"},
{"dict[<error descr=\"Invalid type argument\">((int,))</error>, str]", "dict[Any, str]"},
{"dict[int, <error descr=\"Invalid type argument\">(int, str)</error>]", "dict[int, Any]"},
// TODO: type: dict[Any, Any]
{"dict[((<warning descr=\"Passed type arguments do not match type parameters [_KT, _VT] of class 'dict'\">[int]</warning>))]"},
{"List[((int,))]", "list[int]"},
{"Tuple[((int,))]", "tuple[int]"},
{"Tuple[<error descr=\"Invalid type argument\">(int,)</error>, int]", "tuple[Any, int]"},
{"Set[((int,))]", "set[int]"},
{"Dict[<error descr=\"Invalid type argument\">((int,))</error>, str]", "dict[Any, str]"},
{"Dict[int, <error descr=\"Invalid type argument\">(int, str)</error>]", "dict[int, Any]"},
{"Dict[((<error descr=\"Parameters to generic types must be types\">[int]</error>))]"}, // TODO: type: dict[Any, Any]
{"tuple[Tuple[int, str]]", "tuple[tuple[int, str]]"},
{"Tuple[tuple[int], ...]", "tuple[tuple[int], ...]"},
{"tuple[*Tuple[*tuple[int]]]", "tuple[int]"},
{"tuple[int, *Tuple[*Tuple[int, str]], str]", "tuple[int, int, str, str]"},
{"tuple[*tuple[int], *Tuple[int]]", "tuple[int, int]"},
{"Union[((int, int,))]", "int"},
{"Union[<error descr=\"Invalid type argument\">(int, int,)</error>, int]", "int | Any"},
{"Optional[<error descr=\"'Optional' must have exactly one argument\">int, int</error>]", "Any"},
{"Optional[(int,)]", "int | None"},
{"Callable[<error descr=\"'Callable' first parameter must be a parameter expression\">int</error>, int]", "Any"},
{"Callable[[int], ((<error descr=\"Parameters to generic types must be types\">[int]</error>))]", "Callable[[int], Any]"},
// TODO: type: list[Any]
{"list[((<warning descr=\"Passed type arguments do not match type parameters [_T] of class 'list'\">[int]</warning>))]"},
{"List[((<error descr=\"Parameters to generic types must be types\">[int]</error>))]"},
{"C[((int,))]", "C[int]"},
});
}
public void testSubscriptionEllipsisTypeForm() {
generateVariableTypeAssertions(new Object[][]{
{"tuple[int, ...]", "tuple[int, ...]"},
{"tuple[<error descr=\"'...' is allowed only as the second of two arguments\">...</error>, int]", "Any"},
{"tuple[int, int, <error descr=\"'...' is allowed only as the second of two arguments\">...</error>]", "Any"},
{"tuple[int, <error descr=\"'...' is allowed only as the second of two arguments\">...</error>, int]", "Any"},
{"tuple[<error descr=\"'...' is allowed only as the second of two arguments\">...</error>]", "Any"},
{"tuple[<error descr=\"'...' is allowed only as the second of two arguments\">...</error>, ...]", "Any"},
{"set[<error descr=\"Invalid type argument\">...</error>]", "set[Any]"},
{"list[<error descr=\"Invalid type argument\">...</error>]", "list[Any]"},
{"dict[<error descr=\"Invalid type argument\">...</error>]"}, // TODO: type: "dict[Any, Any]"
{"Union[int, <error descr=\"Invalid type argument\">...</error>]", "int | Any"},
{"Optional[<error descr=\"Invalid type argument\">...</error>]", "Any"},
{"tuple[*tuple[str], <error descr=\"'...' cannot be used with an unpacked 'TypeVarTuple' or tuple\">...</error>]", "Any"},
{"tuple[*tuple[str, ...], <error descr=\"'...' cannot be used with an unpacked 'TypeVarTuple' or tuple\">...</error>]",
"Any"},
{"Set[<error descr=\"Invalid type argument\">...</error>]", "set[Any]"},
{"List[<error descr=\"Invalid type argument\">...</error>]", "list[Any]"},
{"Dict[<error descr=\"Invalid type argument\">...</error>]"}, // TODO: type: "dict[Any, Any]"
{"Tuple[int, ...]", "tuple[int, ...]"},
{"Tuple[<error descr=\"'...' is allowed only as the second of two arguments\">...</error>]", "Any"},
{"C[<error descr=\"Invalid type argument\">...</error>]", "C[Any]"},
});
}
// PY-84289
public void testExponentialAnalysisTimeWhenMapLookupKeyEqualsVariableName() {
@@ -3264,6 +3429,36 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase {
""");
}
private void generateVariableTypeAssertions(@NotNull Object @NotNull [][] cases) {
StringBuilder body = new StringBuilder();
for (int i = 0; i < cases.length; i++) {
Object[] c = cases[i];
String annotationText = (String)c[0];
String variableName = "variable_" + (i + 1);
body.append(variableName).append(": ").append(annotationText).append("\n");
if (c.length > 1) {
String expectedTypeText = (String)c[1];
body.append("assert_type(").append(variableName).append(", ").append(expectedTypeText).append(")\n");
}
}
myFixture.enableInspections(PyAssertTypeInspection.class);
doTestByText(
("""
from typing import assert_type, Any, Never, Generic, List, Set, Dict, Tuple, Union, Optional, Callable
class C[T]: ...
class C2[T1, T2]: ...
"""
+ body).trim()
);
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {