From c1abea5187ae3d07a4871fb093e36f42dd9c98b9 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Mon, 9 Apr 2018 21:48:52 +0300 Subject: [PATCH] Provide special type for `typing.TypeVar` as a callee (PY-28127) --- .../typing/PyTypingTypeProvider.java | 36 ++++++++++++++++--- .../psi/types/PyCallableParameterImpl.java | 35 ++++++++++++++---- .../initializingTypeVar.py | 33 +++++++++++++++++ .../InitializingTypeVar.py | 4 +++ .../testData/paramInfo/InitializingTypeVar.py | 3 ++ .../jetbrains/python/PyParameterInfoTest.java | 14 ++++++++ .../Py3TypeCheckerInspectionTest.java | 5 +++ .../PyArgumentListInspectionTest.java | 5 +++ 8 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 python/testData/inspections/PyArgumentListInspection/initializingTypeVar.py create mode 100644 python/testData/inspections/PyTypeCheckerInspection/InitializingTypeVar.py create mode 100644 python/testData/paramInfo/InitializingTypeVar.py diff --git a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index d984012c5a19..7d50c0ddae3d 100644 --- a/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -86,9 +86,6 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { private static final String CHAIN_MAP = "typing.ChainMap"; private static final String UNION = "typing.Union"; private static final String OPTIONAL = "typing.Optional"; - private static final String CLASSVAR = "typing.ClassVar"; - - public static final String NAMEDTUPLE_SIMPLE = "NamedTuple"; private static final String PY2_FILE_TYPE = "typing.BinaryIO"; private static final String PY3_BINARY_FILE_TYPE = "typing.BinaryIO"; @@ -176,7 +173,12 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { } } - return getNewTypeForReference(referenceExpression, context); + final PyType newType = getNewTypeForReference(referenceExpression, context); + if (newType != null) { + return newType; + } + + return getTypeVarTypeForCallee(referenceExpression, context); } @Override @@ -295,6 +297,32 @@ public class PyTypingTypeProvider extends PyTypeProviderBase { return new PyCustomType(CALLABLE, null, false, true, PyBuiltinCache.getInstance(anchor).getObjectType()); } + @Nullable + private static PyType getTypeVarTypeForCallee(@NotNull PyReferenceExpression referenceExpression, @NotNull TypeEvalContext context) { + if (PyCallExpressionNavigator.getPyCallExpressionByCallee(referenceExpression) == null) return null; + + if (resolveToQualifiedNames(referenceExpression, context).contains(TYPE_VAR)) { + final List parameters = new ArrayList<>(); + + final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(referenceExpression); + final LanguageLevel languageLevel = LanguageLevel.forElement(referenceExpression); + final PyElementGenerator generator = PyElementGenerator.getInstance(referenceExpression.getProject()); + + parameters.add(PyCallableParameterImpl.nonPsi("name", builtinCache.getStringType(languageLevel))); + parameters.add(PyCallableParameterImpl.positionalNonPsi("constraints", builtinCache.getTypeType())); + parameters.add(PyCallableParameterImpl.nonPsi("bound", builtinCache.getTypeType(), generator.createEllipsis())); + + final PyClassType boolType = builtinCache.getBoolType(); + final PyExpression falseValue = generator.createExpressionFromText(languageLevel, "False"); + parameters.add(PyCallableParameterImpl.nonPsi("covariant", boolType, falseValue)); + parameters.add(PyCallableParameterImpl.nonPsi("contravariant", boolType, falseValue)); + + return new PyCallableTypeImpl(parameters, null); + } + + return null; + } + private static boolean omitFirstParamInTypeComment(@NotNull PyFunction func, @NotNull PyFunctionTypeAnnotation annotation) { return func.getContainingClass() != null && func.getModifier() != PyFunction.Modifier.STATICMETHOD && annotation.getParameterTypeList().getParameterTypes().size() < func.getParameterList().getParameters().length; diff --git a/python/src/com/jetbrains/python/psi/types/PyCallableParameterImpl.java b/python/src/com/jetbrains/python/psi/types/PyCallableParameterImpl.java index 1d47a086f757..a14bb44d4fdf 100644 --- a/python/src/com/jetbrains/python/psi/types/PyCallableParameterImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyCallableParameterImpl.java @@ -35,15 +35,21 @@ public class PyCallableParameterImpl implements PyCallableParameter { @Nullable private final Ref myType; @Nullable private final PyExpression myDefaultValue; @Nullable private final PyParameter myElement; + private final boolean myIsPositional; + private final boolean myIsKeyword; private PyCallableParameterImpl(@Nullable String name, @Nullable Ref type, @Nullable PyExpression defaultValue, - @Nullable PyParameter element) { + @Nullable PyParameter element, + boolean isPositional, + boolean isKeyword) { myName = name; myType = type; myDefaultValue = defaultValue; myElement = element; + myIsPositional = isPositional; + myIsKeyword = isKeyword; } @NotNull @@ -58,17 +64,27 @@ public class PyCallableParameterImpl implements PyCallableParameter { @NotNull public static PyCallableParameter nonPsi(@Nullable String name, @Nullable PyType type, @Nullable PyExpression defaultValue) { - return new PyCallableParameterImpl(name, Ref.create(type), defaultValue, null); + return new PyCallableParameterImpl(name, Ref.create(type), defaultValue, null, false, false); + } + + @NotNull + public static PyCallableParameter positionalNonPsi(@Nullable String name, @Nullable PyType type) { + return new PyCallableParameterImpl(name, Ref.create(type), null, null, true, false); + } + + @NotNull + public static PyCallableParameter keywordNonPsi(@Nullable String name, @Nullable PyType type) { + return new PyCallableParameterImpl(name, Ref.create(type), null, null, false, true); } @NotNull public static PyCallableParameter psi(@NotNull PyParameter parameter) { - return new PyCallableParameterImpl(null, null, null, parameter); + return new PyCallableParameterImpl(null, null, null, parameter, false, false); } @NotNull public static PyCallableParameter psi(@NotNull PyParameter parameter, @Nullable PyType type) { - return new PyCallableParameterImpl(null, Ref.create(type), null, parameter); + return new PyCallableParameterImpl(null, Ref.create(type), null, parameter, false, false); } @Nullable @@ -121,12 +137,16 @@ public class PyCallableParameterImpl implements PyCallableParameter { @Override public boolean isPositionalContainer() { + if (myIsPositional) return true; + final PyNamedParameter namedParameter = PyUtil.as(myElement, PyNamedParameter.class); return namedParameter != null && namedParameter.isPositionalContainer(); } @Override public boolean isKeywordContainer() { + if (myIsKeyword) return true; + final PyNamedParameter namedParameter = PyUtil.as(myElement, PyNamedParameter.class); return namedParameter != null && namedParameter.isKeywordContainer(); } @@ -210,13 +230,16 @@ public class PyCallableParameterImpl implements PyCallableParameter { if (o == null || getClass() != o.getClass()) return false; final PyCallableParameterImpl parameter = (PyCallableParameterImpl)o; - return Objects.equals(myName, parameter.myName) && + return myIsPositional == parameter.myIsPositional && + myIsKeyword == parameter.myIsKeyword && + Objects.equals(myName, parameter.myName) && Objects.equals(Ref.deref(myType), Ref.deref(parameter.myType)) && + Objects.equals(myDefaultValue, parameter.myDefaultValue) && Objects.equals(myElement, parameter.myElement); } @Override public int hashCode() { - return Objects.hash(myName, Ref.deref(myType), myElement); + return Objects.hash(myName, Ref.deref(myType), myDefaultValue, myElement, myIsPositional, myIsKeyword); } } diff --git a/python/testData/inspections/PyArgumentListInspection/initializingTypeVar.py b/python/testData/inspections/PyArgumentListInspection/initializingTypeVar.py new file mode 100644 index 000000000000..ad0b79d2a8e7 --- /dev/null +++ b/python/testData/inspections/PyArgumentListInspection/initializingTypeVar.py @@ -0,0 +1,33 @@ +from typing import TypeVar + +TypeVar("T") +TypeVar("T", int) +TypeVar("T", int, str) + +TypeVar("T", bound=int) +TypeVar("T", int, bound=int) +TypeVar("T", int, str, bound=int) + +TypeVar("T", bd=int) +TypeVar("T", int, bd=int) +TypeVar("T", int, str, bd=int) + +TypeVar("T", bound=int, covariant=True) +TypeVar("T", int, bound=int, covariant=True) +TypeVar("T", int, str, bound=int, covariant=True) + +TypeVar("T", bound=int, cant=True) +TypeVar("T", int, bound=int, cant=True) +TypeVar("T", int, str, bound=int, cant=True) + +TypeVar("T", bound=int, covariant=True, contravariant=True) +TypeVar("T", int, bound=int, covariant=True, contravariant=True) +TypeVar("T", int, str, bound=int, covariant=True, contravariant=True) + +TypeVar("T", bound=int, covariant=True, cant=True) +TypeVar("T", int, bound=int, covariant=True, cant=True) +TypeVar("T", int, str, bound=int, covariant=True, cant=True) + +TypeVar("T", bound=int, covariant=True, contravariant=True, more=5) +TypeVar("T", int, bound=int, covariant=True, contravariant=True, more=5) +TypeVar("T", int, str, bound=int, covariant=True, contravariant=True, more=5) \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/InitializingTypeVar.py b/python/testData/inspections/PyTypeCheckerInspection/InitializingTypeVar.py new file mode 100644 index 000000000000..01fd0044e96a --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/InitializingTypeVar.py @@ -0,0 +1,4 @@ +from typing import TypeVar + +TypeVar("T", int, str, bound=int, covariant=True, contravariant=True) +TypeVar("T", 0, 1, bound=2, covariant=3, contravariant=4) \ No newline at end of file diff --git a/python/testData/paramInfo/InitializingTypeVar.py b/python/testData/paramInfo/InitializingTypeVar.py new file mode 100644 index 000000000000..89c174ff57e4 --- /dev/null +++ b/python/testData/paramInfo/InitializingTypeVar.py @@ -0,0 +1,3 @@ +from typing import TypeVar + +T = TypeVar() \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java index 76914d7e6608..894c9bed4cac 100644 --- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java +++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java @@ -782,6 +782,20 @@ public class PyParameterInfoTest extends LightMarkedTestCase { ); } + // PY-28127 + public void testInitializingTypeVar() { + runWithLanguageLevel( + LanguageLevel.PYTHON34, + () -> { + final int offset = loadTest(1).get("").getTextOffset(); + + feignCtrlP(offset).check("name: str, *constraints: type, bound: type=..., covariant: bool=False, contravariant: bool=False", + new String[]{"name: str, "}, + ArrayUtil.EMPTY_STRING_ARRAY); + } + ); + } + /** * Imitates pressing of Ctrl+P; fails if results are not as expected. * @param offset offset of 'cursor' where Ctrl+P is pressed. diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index a33ee859d97e..1134a469112b 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -301,4 +301,9 @@ public class Py3TypeCheckerInspectionTest extends PyInspectionTestCase { public void testDataclassesReplace() { runWithLanguageLevel(LanguageLevel.PYTHON37, () -> super.doMultiFileTest()); } + + // PY-28127 + public void testInitializingTypeVar() { + doTest(); + } } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java index b6113f358d04..fb4c13d0f75f 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyArgumentListInspectionTest.java @@ -347,4 +347,9 @@ public class PyArgumentListInspectionTest extends PyInspectionTestCase { public void testObjectMethodInPossiblyInheritanceChain() { doTest(); } + + // PY-28127 + public void testInitializingTypeVar() { + doTest(); + } }