Skip to content

Commit bbfd94c

Browse files
committed
Analyze generic types in classmethod
Analyze generic types in classmethod by map_instance_to_supertype and expand_type_by_instance. Add "get_classmethod" method to TypeInfo because get_method cannot treat classmethod.
1 parent e12be3b commit bbfd94c

File tree

4 files changed

+293
-0
lines changed

4 files changed

+293
-0
lines changed

mypy/checkmember.py

+12
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,19 @@ def analyze_class_attribute_access(itype: Instance,
559559
msg.fail(messages.GENERIC_INSTANCE_VAR_CLASS_ACCESS, context)
560560
is_classmethod = ((is_decorated and cast(Decorator, node.node).func.is_class)
561561
or (isinstance(node.node, FuncBase) and node.node.is_class))
562+
if is_classmethod and not isinstance(original_type, TypeType) and itype.args:
563+
msg.fail(messages.GENERIC_CLASSMETHOD_CLASS_ACCESS, context)
564+
562565
result = add_class_tvars(t, itype, is_classmethod, builtin_type, original_type)
566+
567+
method = itype.type.get_classmethod(name)
568+
if method is not None:
569+
signature = function_type(method, builtin_type('builtins.function'))
570+
signature = freshen_function_type_vars(signature)
571+
signature = bind_self(signature, original_type, is_classmethod=True)
572+
itype = map_instance_to_supertype(itype, method.info)
573+
result = expand_type_by_instance(signature, itype)
574+
563575
if not is_lvalue:
564576
result = analyze_descriptor_access(original_type, result, builtin_type,
565577
msg, context, chk=chk)

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_CLASS_ACCESS = \
108+
'Access to generic classmethods via class 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

mypy/nodes.py

+13
Original file line numberDiff line numberDiff line change
@@ -2315,6 +2315,19 @@ def get_method(self, name: str) -> Optional[FuncBase]:
23152315
return None
23162316
return None
23172317

2318+
def get_classmethod(self, name: str) -> Optional[FuncBase]:
2319+
for cls in self.mro:
2320+
if name in cls.names:
2321+
node = cls.names[name].node
2322+
is_decorated = isinstance(node, Decorator)
2323+
is_classmethod = ((is_decorated and cast(Decorator, node).func.is_class)
2324+
or (isinstance(node, FuncBase) and node.is_class))
2325+
if is_classmethod:
2326+
return cast(FuncBase, node)
2327+
else:
2328+
return None
2329+
return None
2330+
23182331
def calculate_metaclass_type(self) -> 'Optional[mypy.types.Instance]':
23192332
declared = self.declared_metaclass
23202333
if declared is not None and not declared.type.has_base('builtins.type'):

test-data/unit/check-generics.test

+266
Original file line numberDiff line numberDiff line change
@@ -1815,3 +1815,269 @@ 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+
1828+
reveal_type(B.f) # E: Revealed type is 'def () -> builtins.str*'
1829+
reveal_type(B.f()) # E: Revealed type is 'builtins.str*'
1830+
reveal_type(C.f) # E: Revealed type is 'def () -> builtins.int*'
1831+
reveal_type(C.f()) # E: Revealed type is 'builtins.int*'
1832+
[builtins fixtures/classmethod.pyi]
1833+
1834+
[case testClassMethodOfGenericClassMultipleInheritance]
1835+
from typing import TypeVar, Generic
1836+
T = TypeVar('T')
1837+
class A(Generic[T]):
1838+
@classmethod
1839+
def f(cls) -> T: pass
1840+
1841+
class B(Generic[T]):
1842+
@classmethod
1843+
def g(cls) -> T: pass
1844+
1845+
class C(A[str], B[int]):
1846+
@classmethod
1847+
def f(cls) -> str: pass
1848+
@classmethod
1849+
def g(cls) -> int: pass
1850+
1851+
reveal_type(C.f()) # E: Revealed type is 'builtins.str'
1852+
reveal_type(C.g()) # E: Revealed type is 'builtins.int'
1853+
[builtins fixtures/classmethod.pyi]
1854+
1855+
[case testClassMethodOfSubcClassOfSubClassOfGenericClass]
1856+
from typing import TypeVar, Generic
1857+
T = TypeVar('T')
1858+
class A(Generic[T]):
1859+
@classmethod
1860+
def f(cls) -> T: ...
1861+
1862+
class B(A[str]): pass
1863+
class C(B): pass
1864+
class D(C): pass
1865+
1866+
reveal_type(B.f) # E: Revealed type is 'def () -> builtins.str*'
1867+
reveal_type(B.f()) # E: Revealed type is 'builtins.str*'
1868+
reveal_type(C.f) # E: Revealed type is 'def () -> builtins.str*'
1869+
reveal_type(C.f()) # E: Revealed type is 'builtins.str*'
1870+
reveal_type(D.f) # E: Revealed type is 'def () -> builtins.str*'
1871+
reveal_type(D.f()) # E: Revealed type is 'builtins.str*'
1872+
[builtins fixtures/classmethod.pyi]
1873+
1874+
[case testClassMethodOfGenericClassMultipleLevelInheritance]
1875+
from typing import TypeVar, Generic
1876+
T = TypeVar('T')
1877+
class A(Generic[T]):
1878+
@classmethod
1879+
def f(cls) -> T: ...
1880+
1881+
class B(A[str]): pass
1882+
class C(B): pass
1883+
class D(C): pass
1884+
class E(D): pass
1885+
1886+
reveal_type(C.f) # E: Revealed type is 'def () -> builtins.str*'
1887+
reveal_type(C.f()) # E: Revealed type is 'builtins.str*'
1888+
reveal_type(D.f) # E: Revealed type is 'def () -> builtins.str*'
1889+
reveal_type(D.f()) # E: Revealed type is 'builtins.str*'
1890+
reveal_type(E.f) # E: Revealed type is 'def () -> builtins.str*'
1891+
reveal_type(E.f()) # E: Revealed type is 'builtins.str*'
1892+
[builtins fixtures/classmethod.pyi]
1893+
1894+
[case testClassMethodOfGenericClassMultipleLevelInheritanceWithChangingTypeVariable]
1895+
from typing import TypeVar, Generic
1896+
T = TypeVar('T')
1897+
S = TypeVar('S')
1898+
class A(Generic[T]):
1899+
@classmethod
1900+
def f(cls) -> T: ...
1901+
class B(A[S]): pass
1902+
class C(B[str]): pass
1903+
class D(B[int]): pass
1904+
1905+
reveal_type(C.f()) # E: Revealed type is 'builtins.str*'
1906+
reveal_type(D.f()) # E: Revealed type is 'builtins.int*'
1907+
[builtins fixtures/classmethod.pyi]
1908+
1909+
[case testClassMethodOrderOfMultipleGenericClass]
1910+
from typing import TypeVar, Generic
1911+
T = TypeVar('T')
1912+
class A(Generic[T]):
1913+
@classmethod
1914+
def f(cls) -> T: ...
1915+
class B(Generic[T]):
1916+
@classmethod
1917+
def f(cls) -> T: ...
1918+
class C(A[str], B[int]): pass
1919+
class D(A[int], B[str]): pass
1920+
1921+
reveal_type(C.f) # E: Revealed type is 'def () -> builtins.str*'
1922+
reveal_type(C.f()) # E: Revealed type is 'builtins.str*'
1923+
reveal_type(D.f) # E: Revealed type is 'def () -> builtins.int*'
1924+
reveal_type(D.f()) # E: Revealed type is 'builtins.int*'
1925+
[builtins fixtures/classmethod.pyi]
1926+
1927+
[case testClassMethodOfMultipleGenericClass]
1928+
from typing import Tuple, TypeVar, Generic
1929+
T = TypeVar('T')
1930+
S = TypeVar('S')
1931+
class A(Generic[T]): pass
1932+
class B(Generic[T]): pass
1933+
class C(A[T], B[S]):
1934+
@classmethod
1935+
def h(cls) -> Tuple[T, S]: ...
1936+
1937+
class D(C[str, int]): pass
1938+
class E(C[int, str]): pass
1939+
1940+
reveal_type(D.h()) # E: Revealed type is 'Tuple[builtins.str*, builtins.int*]'
1941+
reveal_type(E.h()) # E: Revealed type is 'Tuple[builtins.int*, builtins.str*]'
1942+
[builtins fixtures/classmethod.pyi]
1943+
1944+
[case testClassMethodOfGenericClassOverriding]
1945+
from typing import Generic, TypeVar
1946+
T = TypeVar('T')
1947+
class A(Generic[T]):
1948+
@classmethod
1949+
def f(cls) -> T: pass
1950+
class B(A[str]):
1951+
@classmethod
1952+
def f(cls) -> str: pass
1953+
class C(A[str]):
1954+
@classmethod
1955+
def f(cls) -> int: pass # E: Return type of "f" incompatible with supertype "A"
1956+
[builtins fixtures/classmethod.pyi]
1957+
1958+
[case testClassMethodOfInheritingGenericTypeFromGenericType]
1959+
from typing import Generic, TypeVar
1960+
T = TypeVar('T')
1961+
S = TypeVar('S')
1962+
class A(Generic[T]):
1963+
@classmethod
1964+
def f(cls) -> T: pass
1965+
class B(A[S]): pass
1966+
1967+
A.f # E: Access to generic classmethods via class is ambiguous.
1968+
B.f # E: Access to generic classmethods via class is ambiguous.
1969+
[builtins fixtures/classmethod.pyi]
1970+
1971+
[case testClassMethodOfGenericClassComplexType]
1972+
from typing import Generic, TypeVar, Iterable, Tuple
1973+
T = TypeVar('T')
1974+
class A(Generic[T]):
1975+
@classmethod
1976+
def f(cls) -> T: pass
1977+
1978+
class B(A[Iterable[str]]): pass
1979+
1980+
reveal_type(B.f()) # E: Revealed type is 'typing.Iterable*[builtins.str]'
1981+
[builtins fixtures/classmethod.pyi]
1982+
1983+
[case testClassMethodOfGenericClassComplexReturnType]
1984+
from typing import Generic, TypeVar, Iterable, Tuple
1985+
T = TypeVar('T')
1986+
class A(Generic[T]):
1987+
@classmethod
1988+
def f(cls) -> Iterable[Tuple[T, T]]: pass
1989+
1990+
class B(A[str]): pass
1991+
1992+
reveal_type(B.f()) # E: Revealed type is 'typing.Iterable[Tuple[builtins.str*, builtins.str*]]'
1993+
[builtins fixtures/classmethod.pyi]
1994+
1995+
[case testClassMethodOfGenericClassComplexArgumentType]
1996+
from typing import Generic, TypeVar, Iterable, Tuple
1997+
T = TypeVar('T')
1998+
class A(Generic[T]):
1999+
@classmethod
2000+
def f(cls, arg: Iterable[Tuple[T, T]]) -> T: pass
2001+
2002+
class B(A[str]): pass
2003+
2004+
reveal_type(B.f((('x', 'y'),))) # E: Revealed type is 'builtins.str*'
2005+
[builtins fixtures/classmethod.pyi]
2006+
2007+
[case testClassMethodOfGenericClassMoreComplexTypeAndReturnTypeAndArgumentType]
2008+
from typing import Generic, TypeVar, Iterable, Tuple
2009+
T = TypeVar('T')
2010+
class A(Generic[T]):
2011+
@classmethod
2012+
def f(cls, arg: Iterable[Tuple[T, T]]) -> Iterable[Tuple[T, T, T]]: pass
2013+
2014+
class B(A[Iterable[str]]): pass
2015+
2016+
arg = ((('x',), ('y',),),)
2017+
reveal_type(B.f(arg)) # E: Revealed type is 'typing.Iterable[Tuple[typing.Iterable*[builtins.str], typing.Iterable*[builtins.str], typing.Iterable*[builtins.str]]]'
2018+
[builtins fixtures/classmethod.pyi]
2019+
2020+
[case testClassMethodOfGenericClassMultipleTypeVariable]
2021+
from typing import Generic, TypeVar, Tuple
2022+
T = TypeVar('T')
2023+
S = TypeVar('S')
2024+
class A(Generic[T, S]):
2025+
@classmethod
2026+
def f(cls) -> Tuple[T, S]: pass
2027+
2028+
class B(A[str, int]): pass
2029+
class C(A[int, str]): pass
2030+
2031+
reveal_type(B.f()) # E: Revealed type is 'Tuple[builtins.str*, builtins.int*]'
2032+
reveal_type(C.f()) # E: Revealed type is 'Tuple[builtins.int*, builtins.str*]'
2033+
[builtins fixtures/classmethod.pyi]
2034+
2035+
[case testClassMethodOverloadingGenericClass]
2036+
from typing import overload, TypeVar, Generic, Tuple
2037+
T = TypeVar('T')
2038+
class A(Generic[T]):
2039+
@overload
2040+
@classmethod
2041+
def f(cls, s: str) -> Tuple[str, T]: pass
2042+
@overload
2043+
@classmethod
2044+
def f(cls, s: int) -> Tuple[T, int]: pass
2045+
@classmethod
2046+
def f(cls, s): pass
2047+
class B(A[str]): pass
2048+
class C(A[int]): pass
2049+
2050+
reveal_type(B.f(1)) # E: Revealed type is 'Tuple[builtins.str*, builtins.int]'
2051+
reveal_type(B.f('a')) # E: Revealed type is 'Tuple[builtins.str, builtins.str*]'
2052+
reveal_type(C.f(1)) # E: Revealed type is 'Tuple[builtins.int*, builtins.int]'
2053+
reveal_type(C.f('a')) # E: Revealed type is 'Tuple[builtins.str, builtins.int*]'
2054+
[builtins fixtures/classmethod.pyi]
2055+
2056+
[case testCallGenericClassMethodFromAnotherGenericClassMethodOfGenericClass]
2057+
from typing import Generic, TypeVar
2058+
T = TypeVar('T')
2059+
class A(Generic[T]):
2060+
@classmethod
2061+
def f(cls) -> T: pass
2062+
@classmethod
2063+
def g(cls) -> T:
2064+
return cls.f()
2065+
2066+
class B(A[str]): pass
2067+
2068+
A.f # E: Access to generic classmethods via class is ambiguous.
2069+
A[str].f # E: Access to generic classmethods via class is ambiguous.
2070+
reveal_type(B.g()) # E: Revealed type is 'builtins.str*'
2071+
[builtins fixtures/classmethod.pyi]
2072+
2073+
[case testGenericClassComplexTypeMultiLevelInheritance]
2074+
from typing import Generic, TypeVar, Tuple
2075+
T = TypeVar('T')
2076+
class A(Generic[T]):
2077+
@classmethod
2078+
def f(cls) -> T: ...
2079+
class B(A[Tuple[T, T]]): pass
2080+
class C(B[str]): pass
2081+
2082+
reveal_type(C.f()) # E: Revealed type is 'Tuple[builtins.str*, builtins.str*]'
2083+
[builtins fixtures/classmethod.pyi]

0 commit comments

Comments
 (0)