From b46d4a80612b88d488541e0489db0c40ba74a770 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Sat, 11 Mar 2017 18:53:54 +0300 Subject: [PATCH] PY-23053 Unbound type vars not wrapped in Type match class object types For instance, TypeVar('T') matches both str and Type[str]. Otherwise general-purpose generic classes cannot be parametrized with class object types. In particular, due to this problem the type checker no longer handled built-in collections containing class objects and lost type of their elements for subsequent operations. However, something like TypeVar('T', SuperClass) matches only instance types, since according to PEP 484 one should explicitlty wrap such type parameter in Type if "class object that is a subclass of SuperClass" is really implied. --- .../python/psi/types/PyTypeChecker.java | 5 +++ .../UnboundTypeVarsMatchClassObjectTypes.py | 38 +++++++++++++++++++ .../com/jetbrains/python/PyTypingTest.java | 24 ++++++++++++ .../Py3TypeCheckerInspectionTest.java | 5 +++ 4 files changed, 72 insertions(+) create mode 100644 python/testData/inspections/PyTypeCheckerInspection/UnboundTypeVarsMatchClassObjectTypes.py diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java index 88ba5d387bca..08b3988cb7c0 100644 --- a/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java +++ b/python/src/com/jetbrains/python/psi/types/PyTypeChecker.java @@ -80,6 +80,7 @@ public class PyTypeChecker { } } if (expected instanceof PyInstantiableType && actual instanceof PyInstantiableType + && !(expected instanceof PyGenericType && typeVarAcceptsBothClassAndInstanceTypes((PyGenericType)expected)) && ((PyInstantiableType)expected).isDefinition() ^ ((PyInstantiableType)actual).isDefinition()) { return false; } @@ -288,6 +289,10 @@ public class PyTypeChecker { return matchNumericTypes(expected, actual); } + private static boolean typeVarAcceptsBothClassAndInstanceTypes(@NotNull PyGenericType typeVar) { + return !typeVar.isDefinition() && typeVar.getBound() == null; + } + private static boolean consistsOfSameElementNumberTuples(@NotNull PyUnionType unionType, int elementCount) { for (PyType type : unionType.getMembers()) { if (type instanceof PyTupleType) { diff --git a/python/testData/inspections/PyTypeCheckerInspection/UnboundTypeVarsMatchClassObjectTypes.py b/python/testData/inspections/PyTypeCheckerInspection/UnboundTypeVarsMatchClassObjectTypes.py new file mode 100644 index 000000000000..05f1376366ea --- /dev/null +++ b/python/testData/inspections/PyTypeCheckerInspection/UnboundTypeVarsMatchClassObjectTypes.py @@ -0,0 +1,38 @@ +from typing import TypeVar + +T = TypeVar('T') +B = TypeVar('B', str) + + +def f1(p: T): + return p + + +f1(str) + + +def f2(p: B): + return p + + +f2(str) + + +def g1(p): + """ + :type p: T + """ + + +g1(str) + + +def g2(p): + """ + :type p: T <= str + """ + + +g2(str) + +xs = list([str]) diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 9342dd00adfe..12420ba99c6e 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -868,6 +868,30 @@ public class PyTypingTest extends PyTestCase { " expr = x"); } + // PY-23053 + public void testUnboundGenericMatchesClassObjectTypes() { + doTest("Type[str]", + "from typing import Generic, TypeVar\n" + + "\n" + + "T = TypeVar('T')\n" + + "\n" + + "class Holder(Generic[T]):\n" + + " def __init__(self, value: T):\n" + + " self._value = value\n" + + "\n" + + " def get(self) -> T:\n" + + " return self._value\n" + + "\n" + + "expr = Holder(str).get()\n"); + } + + // PY-23053 + public void testListContainingClasses() { + doTest("Type[str]", + "xs = [str]\n" + + "expr = xs.pop()"); + } + private void doTestNoInjectedText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject()); diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index 9de54dc04f70..a86c8023dfde 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -223,4 +223,9 @@ public class Py3TypeCheckerInspectionTest extends PyTestCase { public void testReplaceCalledOnUnionOfStrAndBytesWithStrArguments() { doTest(); } + + // PY-23053 + public void testUnboundTypeVarsMatchClassObjectTypes() { + doTest(); + } }