diff --git a/python/python-psi-impl/resources/messages/PyPsiBundle.properties b/python/python-psi-impl/resources/messages/PyPsiBundle.properties index 6c2d8015363b..bc3a6622b7a0 100644 --- a/python/python-psi-impl/resources/messages/PyPsiBundle.properties +++ b/python/python-psi-impl/resources/messages/PyPsiBundle.properties @@ -1210,6 +1210,8 @@ INSP.type.hints.metaclass.cannot.be.generic=Metaclass cannot be generic INSP.type.hints.unbound.type.variable=Unbound type variable INSP.type.hints.some.type.variables.are.used.by.an.outer.scope=Some type variables ({0}) are used by an outer scope INSP.type.hints.at.most.one.unpacked.tuple=Type argument list can have at most one unpacked TypeVarTuple or tuple +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 QFIX.remove.function.annotations=Remove function annotations QFIX.replace.with.target.name=Replace with the target name QFIX.remove.generic.parameters=Remove generic parameters diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt index 75528139656e..83f38ffb72d6 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt @@ -47,6 +47,7 @@ import com.jetbrains.python.psi.resolve.PyResolveContext import com.jetbrains.python.psi.resolve.PyResolveUtil import com.jetbrains.python.psi.types.* import com.jetbrains.python.sdk.PythonSdkUtil +import com.jetbrains.python.psi.types.PyTypeVarType.Variance class PyTypeHintsInspection : PyInspection() { @@ -357,6 +358,7 @@ class PyTypeHintsInspection : PyInspection() { checkTypeCommentAndParameters(node) + checkTypeVarsInFunctionAnnotations(node) } override fun visitPyTargetExpression(node: PyTargetExpression) { @@ -1340,6 +1342,29 @@ class PyTypeHintsInspection : PyInspection() { } } + private fun checkTypeVarsInFunctionAnnotations(function: PyFunction) { + if (PyUtil.isInitOrNewMethod(function)) return + val parameterList = function.parameterList + val parameters = parameterList.parameters + parameters + .filterIsInstance() + .mapNotNull { parameter -> parameter.annotation?.value } + .forEach { annotationValue -> + val type = Ref.deref(PyTypingTypeProvider.getType(annotationValue, myTypeEvalContext)) + if (type is PyTypeVarType && type.variance == Variance.COVARIANT) { + registerProblem(annotationValue, PyPsiBundle.message("INSP.type.hints.cannot.use.covariant.in.function.param")) + } + } + + val returnAnnotation = function.annotation?.value + if (returnAnnotation != null) { + val type = Ref.deref(PyTypingTypeProvider.getType(returnAnnotation, myTypeEvalContext)) + if (type is PyTypeVarType && type.variance == Variance.CONTRAVARIANT) { + registerProblem(returnAnnotation, PyPsiBundle.message("INSP.type.hints.cannot.use.contravariant.in.return.type")) + } + } + } + private fun checkTypeArgumentsMatchTypeParameters(node: PySubscriptionExpression, typeParameters: List, typeArguments: List, diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java index 0d7dce9f9411..50a1174dba4b 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java @@ -2832,6 +2832,38 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase { """); } + // PY-80166 + public void testCovariantTypeVarsCannotBeUsedInFunctionParameterTypes() { + doTestByText(""" + from typing import TypeVar, Generic + + T_co = TypeVar('T_co', covariant=True) + T_contra = TypeVar('T_contra', contravariant=True) + + def foo(x: T_co) -> None: ... + + class Foo(Generic[T_co]): + def __init__(self, x: T_co) -> None: ... # allowed in __init__ + def dosmth(self, x: T_co) -> None: ... + """); + } + + // PY-80167 + public void testContravariantTypeVarsCannotBeUsedInFunctionReturnType() { + doTestByText(""" + from typing import TypeVar, Generic + + T_co = TypeVar('T_co', covariant=True) + T_contra = TypeVar('T_contra', contravariant=True) + + def foo(x: T_contra) -> T_contra: ... + + class Foo(Generic[T_co]): + def dosmth(self, x: T_contra) -> T_contra: ... + """); + } + + @NotNull @Override protected Class getInspectionClass() {