diff --git a/python/python-psi-impl/resources/messages/PyPsiBundle.properties b/python/python-psi-impl/resources/messages/PyPsiBundle.properties index 67e109b30299..4a716d5e3b2a 100644 --- a/python/python-psi-impl/resources/messages/PyPsiBundle.properties +++ b/python/python-psi-impl/resources/messages/PyPsiBundle.properties @@ -1178,6 +1178,7 @@ INSP.type.hints.type.arguments.class.is.already.parameterized=Class ''{0}'' is a INSP.type.hints.invalid.type.argument=Invalid type argument INSP.type.hints.generic.type.alias.is.not.generic.or.already.parameterized=Type alias is not generic or already specialized INSP.type.hints.default.type.must.be.type.expression=Default type must be a type expression +INSP.type.hints.invalid.type.expression=Invalid type expression INSP.type.hints.illegal.callable.format='Callable' must be used as 'Callable[[arg, ...], result]' INSP.type.hints.illegal.first.parameter='Callable' first parameter must be a parameter expression INSP.type.hints.parameters.to.generic.types.must.be.types=Parameters to generic types must be types diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyNewStyleGenericSyntaxInspection.kt b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyNewStyleGenericSyntaxInspection.kt index 761e095b903b..b7d89a4f0b69 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyNewStyleGenericSyntaxInspection.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyNewStyleGenericSyntaxInspection.kt @@ -13,39 +13,65 @@ import com.jetbrains.python.ast.PyAstTypeParameter import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider import com.jetbrains.python.psi.* -import com.jetbrains.python.psi.types.* +import com.jetbrains.python.psi.types.PyClassLikeType +import com.jetbrains.python.psi.types.PyTypeParameterType +import com.jetbrains.python.psi.types.TypeEvalContext class PyNewStyleGenericSyntaxInspection : PyInspection() { - override fun buildVisitor(holder: ProblemsHolder, - isOnTheFly: Boolean, - session: LocalInspectionToolSession): PsiElementVisitor = Visitor(holder, - PyInspectionVisitor.getContext(session)) + override fun buildVisitor( + holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession, + ): PsiElementVisitor = Visitor(holder, + PyInspectionVisitor.getContext(session)) private class Visitor(holder: ProblemsHolder, context: TypeEvalContext) : PyInspectionVisitor(holder, context) { override fun visitPyTypeParameter(typeParameter: PyTypeParameter) { - val boundExpression = typeParameter.boundExpression + val boundExpression = typeParameter.boundExpression val defaultExpression = typeParameter.defaultExpression - if (boundExpression != null) { - findTypeParameterReferences(boundExpression) { true }.forEach { reference -> - registerProblem(reference, - PyPsiBundle.message("INSP.new.style.generics.are.not.allowed.inside.type.param.bounds"), - ProblemHighlightType.GENERIC_ERROR) + boundExpression?.accept(object : PyRecursiveElementVisitor() { + override fun visitPyElement(node: PyElement) { + if (!(node is PyParenthesizedExpression && node === boundExpression) && + !(node is PyTupleExpression && node.parent === boundExpression)) { + if (node is PyExpression) { + if (!PyTypeHintsInspection.isValidTypeHint(node, myTypeEvalContext)) { + registerProblem( + node, + PyPsiBundle.message("INSP.type.hints.invalid.type.expression"), + ) + } + if (node is PyReferenceExpression) { + node.getTypeParameterType()?.let { + registerProblem(node, + PyPsiBundle.message("INSP.new.style.generics.are.not.allowed.inside.type.param.bounds"), + ProblemHighlightType.GENERIC_ERROR) + } + } + } + } + super.visitPyElement(node) } - } + }) if (defaultExpression != null) { val scopeOwner = ScopeUtil.getScopeOwner(typeParameter) if (scopeOwner == null) return - findTypeParameterReferences(defaultExpression) { scopeOwner != it.scopeOwner || it.declarationElement is PyTargetExpression } - .forEach { reference -> - registerProblem(reference, - PyPsiBundle.message("INSP.new.style.type.parameter.out.of.scope", reference.name), - ProblemHighlightType.WARNING) - } + defaultExpression.accept(object : PyRecursiveElementVisitor() { + override fun visitPyReferenceExpression(node: PyReferenceExpression) { + node.getTypeParameterType()?.let { + if (scopeOwner != it.scopeOwner || it.declarationElement is PyTargetExpression) { + registerProblem(node, + PyPsiBundle.message("INSP.new.style.type.parameter.out.of.scope", node.name), + ProblemHighlightType.WARNING) + } + } + super.visitPyReferenceExpression(node) + } + }) } } @@ -137,10 +163,16 @@ class PyNewStyleGenericSyntaxInspection : PyInspection() { } private fun reportOldStyleTypeVarsUsage(element: PsiElement, @InspectionMessage message: String) { - findTypeParameterReferences(element) { - it.declarationElement is PyTargetExpression - && ScopeUtil.getScopeOwner(it.declarationElement) !is PyTypeAliasStatement - }.forEach { reference -> registerProblem(reference, message, ProblemHighlightType.GENERIC_ERROR)} + element.accept(object : PyRecursiveElementVisitor() { + override fun visitPyReferenceExpression(node: PyReferenceExpression) { + node.getTypeParameterType()?.let { + if (it.declarationElement is PyTargetExpression + && ScopeUtil.getScopeOwner(it.declarationElement) !is PyTypeAliasStatement) { + registerProblem(node, message, ProblemHighlightType.GENERIC_ERROR) + } + } + } + }) } private fun reportAssignmentExpressions(element: PsiElement, @InspectionMessage message: String) { @@ -151,22 +183,8 @@ class PyNewStyleGenericSyntaxInspection : PyInspection() { } } - private fun findTypeParameterReferences(element: PsiElement, - condition: (PyTypeParameterType) -> Boolean): List { - val elementsToProcess = - PsiTreeUtil.findChildrenOfAnyType(element, false, PyReferenceExpression::class.java) - - val list = mutableListOf() - - elementsToProcess - .filterIsInstance() - .associateWith { Ref.deref(PyTypingTypeProvider.getType(it, myTypeEvalContext)) } - .filter { (_, v) -> v is PyTypeParameterType && condition.invoke(v) } - .forEach { (k, _) -> - list.add(k) - } - - return list + private fun PyReferenceExpression.getTypeParameterType(): PyTypeParameterType? { + return Ref.deref(PyTypingTypeProvider.getType(this, myTypeEvalContext)) as? PyTypeParameterType } } } \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyNewStyleGenericSyntaxInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyNewStyleGenericSyntaxInspectionTest.java index b95e0cbc705b..ed0f5b93ac78 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyNewStyleGenericSyntaxInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyNewStyleGenericSyntaxInspectionTest.java @@ -206,6 +206,33 @@ public class PyNewStyleGenericSyntaxInspectionTest extends PyInspectionTestCase """)); } + // PY-76895 + public void testInvalidExpressionInsideBound() { + runWithLanguageLevel(LanguageLevel.PYTHON312, + () -> doTestByText(""" + var = 1 + class ClassA[T: (3, bytes)]: ... + class ClassB[T: (int, [1, 2, 3])]: ... + class ClassC[T: (int, var)]: ... + class ClassC[T: (int, lambda x: x)]: ... + class ClassD[T: (int, ClassA[bytes]())]: ... + """)); + } + + // PY-76895 + public void testInvalidExpressionInDefault() { + runWithLanguageLevel(LanguageLevel.PYTHON312, + () -> doTestByText(""" + var = 1 + class ClassA[T: (3, bytes)]: ... + class ClassB[T: (int, [1, 2, 3])]: ... + class ClassC[T: (int, var)]: ... + class ClassC[T: (int, lambda x: x)]: ... + class ClassD[T: (int, ClassA[bytes]())]: ... + class ClassE[T: 3]: ... + """)); + } + @Override protected @NotNull Class getInspectionClass() {