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.
This commit is contained in:
Mikhail Golubev
2017-03-13 20:21:20 +03:00
parent 246fec7c16
commit b46d4a8061
4 changed files with 72 additions and 0 deletions
@@ -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) {
@@ -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(<warning descr="Expected type 'TypeVar('B', str)', got 'Type[str]' instead">str</warning>)
def g1(p):
"""
:type p: T
"""
g1(str)
def g2(p):
"""
:type p: T <= str
"""
g2(<warning descr="Expected type 'TypeVar('T', str)', got 'Type[str]' instead">str</warning>)
xs = list([str])
@@ -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());
@@ -223,4 +223,9 @@ public class Py3TypeCheckerInspectionTest extends PyTestCase {
public void testReplaceCalledOnUnionOfStrAndBytesWithStrArguments() {
doTest();
}
// PY-23053
public void testUnboundTypeVarsMatchClassObjectTypes() {
doTest();
}
}