Skip to content

Commit fbd2a71

Browse files
committed
Specify type when type variables are remained in inherited classmethod.
When the method is classmethod, replacing type variables with its specified type in bases attribute by expand_type_by_instance.
1 parent e12be3b commit fbd2a71

File tree

4 files changed

+231
-1
lines changed

4 files changed

+231
-1
lines changed

mypy/checkmember.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
)
1414
from mypy.messages import MessageBuilder
1515
from mypy.maptype import map_instance_to_supertype
16-
from mypy.expandtype import expand_type_by_instance, expand_type, freshen_function_type_vars
16+
from mypy.expandtype import (
17+
expand_type_by_instance, expand_type, freshen_function_type_vars,
18+
expand_type_by_instance_bases,
19+
)
1720
from mypy.infer import infer_type_arguments
1821
from mypy.typevars import fill_typevars
1922
from mypy.plugin import AttributeContext
@@ -559,6 +562,14 @@ def analyze_class_attribute_access(itype: Instance,
559562
msg.fail(messages.GENERIC_INSTANCE_VAR_CLASS_ACCESS, context)
560563
is_classmethod = ((is_decorated and cast(Decorator, node.node).func.is_class)
561564
or (isinstance(node.node, FuncBase) and node.node.is_class))
565+
if is_classmethod and isinstance(itype, Instance) and not isinstance(original_type, TypeType) and itype.args:
566+
unbound_types = [arg for arg in itype.args if isinstance(arg, TypeVarType)]
567+
if unbound_types:
568+
message = messages.GENERIC_CLASSMETHOD_OF_GENERIC_CLASS.format(
569+
', '.join(typevar.name for typevar in unbound_types))
570+
msg.fail(message, context)
571+
572+
t = expand_type_by_instance_bases(t, itype)
562573
result = add_class_tvars(t, itype, is_classmethod, builtin_type, original_type)
563574
if not is_lvalue:
564575
result = analyze_descriptor_access(original_type, result, builtin_type,

mypy/expandtype.py

+8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ def expand_type_by_instance(typ: Type, instance: Instance) -> Type:
2929
return expand_type(typ, variables)
3030

3131

32+
def expand_type_by_instance_bases(typ: Type, instance: Instance) -> Type:
33+
if instance.args:
34+
return expand_type_by_instance(typ, instance)
35+
for base in instance.type.bases:
36+
typ = expand_type_by_instance_bases(typ, base)
37+
return typ
38+
39+
3240
F = TypeVar('F', bound=FunctionLike)
3341

3442

mypy/messages.py

+2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@
104104
DUPLICATE_TYPE_SIGNATURES = 'Function has duplicate type signatures' # type: Final
105105
GENERIC_INSTANCE_VAR_CLASS_ACCESS = \
106106
'Access to generic instance variables via class is ambiguous' # type: Final
107+
GENERIC_CLASSMETHOD_OF_GENERIC_CLASS = \
108+
'Access to classmethod of generic type remains type variables {} is ambiguous.' # type: Final
107109
CANNOT_ISINSTANCE_TYPEDDICT = 'Cannot use isinstance() with a TypedDict type' # type: Final
108110
CANNOT_ISINSTANCE_NEWTYPE = 'Cannot use isinstance() with a NewType type' # type: Final
109111
BARE_GENERIC = 'Missing type parameters for generic type' # type: Final

test-data/unit/check-generics.test

+209
Original file line numberDiff line numberDiff line change
@@ -1815,3 +1815,212 @@ def g(x: T) -> T: return x
18151815
[out]
18161816
main:3: error: Revealed type is 'def [b.T] (x: b.T`-1) -> b.T`-1'
18171817
main:4: error: Revealed type is 'def [T] (x: T`-1) -> T`-1'
1818+
1819+
[case testClassMethodOfGenericClass]
1820+
from typing import TypeVar, Generic
1821+
T = TypeVar('T')
1822+
class A(Generic[T]):
1823+
@classmethod
1824+
def f(cls) -> T: ...
1825+
class B(A[str]): pass
1826+
class C(A[int]): pass
1827+
reveal_type(B.f) # E: Revealed type is 'def () -> builtins.str*'
1828+
reveal_type(B.f()) # E: Revealed type is 'builtins.str*'
1829+
reveal_type(C.f) # E: Revealed type is 'def () -> builtins.int*'
1830+
reveal_type(C.f()) # E: Revealed type is 'builtins.int*'
1831+
[builtins fixtures/classmethod.pyi]
1832+
1833+
[case testClassMethodOfGenericClassMultipleInheritance]
1834+
from typing import TypeVar, Generic
1835+
T = TypeVar('T')
1836+
class A(Generic[T]):
1837+
@classmethod
1838+
def f(cls) -> T: pass
1839+
class B(Generic[T]):
1840+
@classmethod
1841+
def g(cls) -> T: pass
1842+
class C(A[str], B[int]):
1843+
@classmethod
1844+
def f(cls) -> str: pass
1845+
@classmethod
1846+
def g(cls) -> int: pass
1847+
reveal_type(C.f()) # E: Revealed type is 'builtins.str'
1848+
reveal_type(C.g()) # E: Revealed type is 'builtins.int'
1849+
[builtins fixtures/classmethod.pyi]
1850+
1851+
[case testClassMethodOfSubcClassOfSubClassOfGenericClass]
1852+
from typing import TypeVar, Generic
1853+
T = TypeVar('T')
1854+
class A(Generic[T]):
1855+
@classmethod
1856+
def f(cls) -> T: ...
1857+
class B(A[str]): pass
1858+
class C(B): pass
1859+
class D(C): pass
1860+
reveal_type(B.f) # E: Revealed type is 'def () -> builtins.str*'
1861+
reveal_type(B.f()) # E: Revealed type is 'builtins.str*'
1862+
reveal_type(C.f) # E: Revealed type is 'def () -> builtins.str*'
1863+
reveal_type(C.f()) # E: Revealed type is 'builtins.str*'
1864+
reveal_type(D.f) # E: Revealed type is 'def () -> builtins.str*'
1865+
reveal_type(D.f()) # E: Revealed type is 'builtins.str*'
1866+
[builtins fixtures/classmethod.pyi]
1867+
1868+
[case testClassMethodOfGenericClassMultipleLevelInheritance]
1869+
from typing import TypeVar, Generic
1870+
T = TypeVar('T')
1871+
class A(Generic[T]):
1872+
@classmethod
1873+
def f(cls) -> T: ...
1874+
class B(A[str]): pass
1875+
class C(B): pass
1876+
class D(C): pass
1877+
class E(D): pass
1878+
reveal_type(C.f) # E: Revealed type is 'def () -> builtins.str*'
1879+
reveal_type(C.f()) # E: Revealed type is 'builtins.str*'
1880+
reveal_type(D.f) # E: Revealed type is 'def () -> builtins.str*'
1881+
reveal_type(D.f()) # E: Revealed type is 'builtins.str*'
1882+
reveal_type(E.f) # E: Revealed type is 'def () -> builtins.str*'
1883+
reveal_type(E.f()) # E: Revealed type is 'builtins.str*'
1884+
[builtins fixtures/classmethod.pyi]
1885+
1886+
[case testClassMethodOfGenericClassMultipleLevelInheritanceWithChangingTypeVariable]
1887+
from typing import TypeVar, Generic
1888+
T = TypeVar('T')
1889+
S = TypeVar('S')
1890+
class A(Generic[T]):
1891+
@classmethod
1892+
def f(cls) -> T: ...
1893+
class B(A[S]): pass
1894+
class C(B[str]): pass
1895+
class D(B[int]): pass
1896+
reveal_type(B[str].f()) # E: Revealed type is 'builtins.str'
1897+
reveal_type(B[int].f()) # E: Revealed type is 'builtins.int'
1898+
reveal_type(C.f()) # E: Revealed type is 'builtins.str*'
1899+
reveal_type(D.f()) # E: Revealed type is 'builtins.int*'
1900+
[builtins fixtures/classmethod.pyi]
1901+
1902+
[case testClassMethodOfMultipleGenericClass]
1903+
from typing import TypeVar, Generic
1904+
T = TypeVar('T')
1905+
class A(Generic[T]):
1906+
@classmethod
1907+
def f(cls) -> T: ...
1908+
class B(Generic[T]):
1909+
@classmethod
1910+
def f(cls) -> T: ...
1911+
class C(A[str], B[int]): pass
1912+
class D(A[int], B[str]): pass
1913+
reveal_type(C.f) # E: Revealed type is 'def () -> builtins.str'
1914+
reveal_type(C.f()) # E: Revealed type is 'builtins.str'
1915+
reveal_type(D.f) # E: Revealed type is 'def () -> builtins.int'
1916+
reveal_type(D.f()) # E: Revealed type is 'builtins.int'
1917+
[builtins fixtures/classmethod.pyi]
1918+
1919+
[case testClassMethodOfGenericClassOverriding]
1920+
from typing import Generic, TypeVar
1921+
T = TypeVar('T')
1922+
class A(Generic[T]):
1923+
@classmethod
1924+
def f(cls) -> T: pass
1925+
class B(A[str]):
1926+
@classmethod
1927+
def f(cls) -> str: pass
1928+
class C(A[str]):
1929+
@classmethod
1930+
def f(cls) -> int: pass # E: Return type of "f" incompatible with supertype "A"
1931+
[builtins fixtures/classmethod.pyi]
1932+
1933+
[case testClassMethodOfInheritingGenericTypeFromGenericType]
1934+
from typing import Generic, TypeVar
1935+
T = TypeVar('T')
1936+
S = TypeVar('S')
1937+
class A(Generic[T]):
1938+
@classmethod
1939+
def f(cls) -> T: pass
1940+
class B(A[S]): pass
1941+
A.f # E: Access to classmethod of generic type remains type variables T is ambiguous.
1942+
B.f # E: Access to classmethod of generic type remains type variables S is ambiguous.
1943+
[builtins fixtures/classmethod.pyi]
1944+
1945+
[case testClassMethodOfGenericClassComplexType]
1946+
from typing import Generic, TypeVar, Iterable, Tuple
1947+
T = TypeVar('T')
1948+
class A(Generic[T]):
1949+
@classmethod
1950+
def f(cls) -> T: pass
1951+
reveal_type(A[Iterable[str]].f()) # E: Revealed type is 'typing.Iterable[builtins.str]'
1952+
[builtins fixtures/classmethod.pyi]
1953+
1954+
[case testClassMethodOfGenericClassComplexReturnType]
1955+
from typing import Generic, TypeVar, Iterable, Tuple
1956+
T = TypeVar('T')
1957+
class A(Generic[T]):
1958+
@classmethod
1959+
def f(cls) -> Iterable[Tuple[T, T]]: pass
1960+
reveal_type(A[str].f()) # E: Revealed type is 'typing.Iterable[Tuple[builtins.str, builtins.str]]'
1961+
[builtins fixtures/classmethod.pyi]
1962+
1963+
[case testClassMethodOfGenericClassComplexArgumentType]
1964+
from typing import Generic, TypeVar, Iterable, Tuple
1965+
T = TypeVar('T')
1966+
class A(Generic[T]):
1967+
@classmethod
1968+
def f(cls, arg: Iterable[Tuple[T, T]]) -> T: pass
1969+
reveal_type(A[str].f((('x', 'y'),))) # E: Revealed type is 'builtins.str'
1970+
[builtins fixtures/classmethod.pyi]
1971+
1972+
[case testClassMethodOfGenericClassMoreComplexTypeAndReturnTypeAndArgumentType]
1973+
from typing import Generic, TypeVar, Iterable, Tuple
1974+
T = TypeVar('T')
1975+
class A(Generic[T]):
1976+
@classmethod
1977+
def f(cls, arg: Iterable[Tuple[T, T]]) -> Iterable[Tuple[T, T, T]]: pass
1978+
arg = ((('x',), ('y',),),)
1979+
reveal_type(A[Iterable[str]].f(arg)) # E: Revealed type is 'typing.Iterable[Tuple[typing.Iterable[builtins.str], typing.Iterable[builtins.str], typing.Iterable[builtins.str]]]'
1980+
[builtins fixtures/classmethod.pyi]
1981+
1982+
[case testClassMethodOfGenericClassMultipleTypeVariable]
1983+
from typing import Generic, TypeVar, Tuple
1984+
T = TypeVar('T')
1985+
S = TypeVar('S')
1986+
class A(Generic[T, S]):
1987+
@classmethod
1988+
def f(cls) -> Tuple[T, S]: pass
1989+
reveal_type(A[str, int].f()) # E: Revealed type is 'Tuple[builtins.str, builtins.int]'
1990+
reveal_type(A[int, str].f()) # E: Revealed type is 'Tuple[builtins.int, builtins.str]'
1991+
[builtins fixtures/classmethod.pyi]
1992+
1993+
[case testClassMethodOverloadingGenericClass]
1994+
from typing import overload, TypeVar, Generic, Tuple
1995+
T = TypeVar('T')
1996+
class A(Generic[T]):
1997+
@overload
1998+
@classmethod
1999+
def f(cls, s: str) -> Tuple[str, T]: pass
2000+
@overload
2001+
@classmethod
2002+
def f(cls, s: int) -> Tuple[T, int]: pass
2003+
@classmethod
2004+
def f(cls, s): pass
2005+
class B(A[str]): pass
2006+
class C(A[int]): pass
2007+
reveal_type(A[str].f(1)) # E: Revealed type is 'Tuple[builtins.str, builtins.int]'
2008+
reveal_type(A[str].f('a')) # E: Revealed type is 'Tuple[builtins.str, builtins.str]'
2009+
reveal_type(A[int].f(1)) # E: Revealed type is 'Tuple[builtins.int, builtins.int]'
2010+
reveal_type(A[int].f('a')) # E: Revealed type is 'Tuple[builtins.str, builtins.int]'
2011+
reveal_type(B.f(1)) # E: Revealed type is 'Tuple[builtins.str*, builtins.int]'
2012+
reveal_type(B.f('a')) # E: Revealed type is 'Tuple[builtins.str, builtins.str*]'
2013+
reveal_type(C.f(1)) # E: Revealed type is 'Tuple[builtins.int*, builtins.int]'
2014+
reveal_type(C.f('a')) # E: Revealed type is 'Tuple[builtins.str, builtins.int*]'
2015+
[builtins fixtures/classmethod.pyi]
2016+
2017+
[case testClassmethodFromClassMethodOfGenericClass]
2018+
from typing import Generic, TypeVar
2019+
T = TypeVar('T')
2020+
class X(Generic[T]):
2021+
@classmethod
2022+
def a(cls) -> T: pass
2023+
@classmethod
2024+
def b(cls) -> T:
2025+
return cls.a()
2026+
[builtins fixtures/classmethod.pyi]

0 commit comments

Comments
 (0)