Skip to content

Commit 536225f

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 536225f

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)