From bbfd94c22cf4a61b53ef1ad1df37dfcdbe66a423 Mon Sep 17 00:00:00 2001 From: kitsuyui Date: Sat, 6 Oct 2018 15:32:50 +0900 Subject: [PATCH] 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. --- mypy/checkmember.py | 12 ++ mypy/messages.py | 2 + mypy/nodes.py | 13 ++ test-data/unit/check-generics.test | 266 +++++++++++++++++++++++++++++ 4 files changed, 293 insertions(+) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 2fa016e165fb..e77960e776ad 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -559,7 +559,19 @@ def analyze_class_attribute_access(itype: Instance, msg.fail(messages.GENERIC_INSTANCE_VAR_CLASS_ACCESS, context) is_classmethod = ((is_decorated and cast(Decorator, node.node).func.is_class) or (isinstance(node.node, FuncBase) and node.node.is_class)) + if is_classmethod and not isinstance(original_type, TypeType) and itype.args: + msg.fail(messages.GENERIC_CLASSMETHOD_CLASS_ACCESS, context) + result = add_class_tvars(t, itype, is_classmethod, builtin_type, original_type) + + method = itype.type.get_classmethod(name) + if method is not None: + signature = function_type(method, builtin_type('builtins.function')) + signature = freshen_function_type_vars(signature) + signature = bind_self(signature, original_type, is_classmethod=True) + itype = map_instance_to_supertype(itype, method.info) + result = expand_type_by_instance(signature, itype) + if not is_lvalue: result = analyze_descriptor_access(original_type, result, builtin_type, msg, context, chk=chk) diff --git a/mypy/messages.py b/mypy/messages.py index 2162c06acbf0..4dbe40a011e6 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -104,6 +104,8 @@ DUPLICATE_TYPE_SIGNATURES = 'Function has duplicate type signatures' # type: Final GENERIC_INSTANCE_VAR_CLASS_ACCESS = \ 'Access to generic instance variables via class is ambiguous' # type: Final +GENERIC_CLASSMETHOD_CLASS_ACCESS = \ + 'Access to generic classmethods via class is ambiguous.' # type: Final CANNOT_ISINSTANCE_TYPEDDICT = 'Cannot use isinstance() with a TypedDict type' # type: Final CANNOT_ISINSTANCE_NEWTYPE = 'Cannot use isinstance() with a NewType type' # type: Final BARE_GENERIC = 'Missing type parameters for generic type' # type: Final diff --git a/mypy/nodes.py b/mypy/nodes.py index a6c59470037c..e0df68d7b54c 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2315,6 +2315,19 @@ def get_method(self, name: str) -> Optional[FuncBase]: return None return None + def get_classmethod(self, name: str) -> Optional[FuncBase]: + for cls in self.mro: + if name in cls.names: + node = cls.names[name].node + is_decorated = isinstance(node, Decorator) + is_classmethod = ((is_decorated and cast(Decorator, node).func.is_class) + or (isinstance(node, FuncBase) and node.is_class)) + if is_classmethod: + return cast(FuncBase, node) + else: + return None + return None + def calculate_metaclass_type(self) -> 'Optional[mypy.types.Instance]': declared = self.declared_metaclass if declared is not None and not declared.type.has_base('builtins.type'): diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index aaa9283cd0d7..924d532cfe63 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1815,3 +1815,269 @@ def g(x: T) -> T: return x [out] main:3: error: Revealed type is 'def [b.T] (x: b.T`-1) -> b.T`-1' main:4: error: Revealed type is 'def [T] (x: T`-1) -> T`-1' + +[case testClassMethodOfGenericClass] +from typing import TypeVar, Generic +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls) -> T: ... +class B(A[str]): pass +class C(A[int]): pass + +reveal_type(B.f) # E: Revealed type is 'def () -> builtins.str*' +reveal_type(B.f()) # E: Revealed type is 'builtins.str*' +reveal_type(C.f) # E: Revealed type is 'def () -> builtins.int*' +reveal_type(C.f()) # E: Revealed type is 'builtins.int*' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfGenericClassMultipleInheritance] +from typing import TypeVar, Generic +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls) -> T: pass + +class B(Generic[T]): + @classmethod + def g(cls) -> T: pass + +class C(A[str], B[int]): + @classmethod + def f(cls) -> str: pass + @classmethod + def g(cls) -> int: pass + +reveal_type(C.f()) # E: Revealed type is 'builtins.str' +reveal_type(C.g()) # E: Revealed type is 'builtins.int' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfSubcClassOfSubClassOfGenericClass] +from typing import TypeVar, Generic +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls) -> T: ... + +class B(A[str]): pass +class C(B): pass +class D(C): pass + +reveal_type(B.f) # E: Revealed type is 'def () -> builtins.str*' +reveal_type(B.f()) # E: Revealed type is 'builtins.str*' +reveal_type(C.f) # E: Revealed type is 'def () -> builtins.str*' +reveal_type(C.f()) # E: Revealed type is 'builtins.str*' +reveal_type(D.f) # E: Revealed type is 'def () -> builtins.str*' +reveal_type(D.f()) # E: Revealed type is 'builtins.str*' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfGenericClassMultipleLevelInheritance] +from typing import TypeVar, Generic +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls) -> T: ... + +class B(A[str]): pass +class C(B): pass +class D(C): pass +class E(D): pass + +reveal_type(C.f) # E: Revealed type is 'def () -> builtins.str*' +reveal_type(C.f()) # E: Revealed type is 'builtins.str*' +reveal_type(D.f) # E: Revealed type is 'def () -> builtins.str*' +reveal_type(D.f()) # E: Revealed type is 'builtins.str*' +reveal_type(E.f) # E: Revealed type is 'def () -> builtins.str*' +reveal_type(E.f()) # E: Revealed type is 'builtins.str*' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfGenericClassMultipleLevelInheritanceWithChangingTypeVariable] +from typing import TypeVar, Generic +T = TypeVar('T') +S = TypeVar('S') +class A(Generic[T]): + @classmethod + def f(cls) -> T: ... +class B(A[S]): pass +class C(B[str]): pass +class D(B[int]): pass + +reveal_type(C.f()) # E: Revealed type is 'builtins.str*' +reveal_type(D.f()) # E: Revealed type is 'builtins.int*' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOrderOfMultipleGenericClass] +from typing import TypeVar, Generic +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls) -> T: ... +class B(Generic[T]): + @classmethod + def f(cls) -> T: ... +class C(A[str], B[int]): pass +class D(A[int], B[str]): pass + +reveal_type(C.f) # E: Revealed type is 'def () -> builtins.str*' +reveal_type(C.f()) # E: Revealed type is 'builtins.str*' +reveal_type(D.f) # E: Revealed type is 'def () -> builtins.int*' +reveal_type(D.f()) # E: Revealed type is 'builtins.int*' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfMultipleGenericClass] +from typing import Tuple, TypeVar, Generic +T = TypeVar('T') +S = TypeVar('S') +class A(Generic[T]): pass +class B(Generic[T]): pass +class C(A[T], B[S]): + @classmethod + def h(cls) -> Tuple[T, S]: ... + +class D(C[str, int]): pass +class E(C[int, str]): pass + +reveal_type(D.h()) # E: Revealed type is 'Tuple[builtins.str*, builtins.int*]' +reveal_type(E.h()) # E: Revealed type is 'Tuple[builtins.int*, builtins.str*]' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfGenericClassOverriding] +from typing import Generic, TypeVar +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls) -> T: pass +class B(A[str]): + @classmethod + def f(cls) -> str: pass +class C(A[str]): + @classmethod + def f(cls) -> int: pass # E: Return type of "f" incompatible with supertype "A" +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfInheritingGenericTypeFromGenericType] +from typing import Generic, TypeVar +T = TypeVar('T') +S = TypeVar('S') +class A(Generic[T]): + @classmethod + def f(cls) -> T: pass +class B(A[S]): pass + +A.f # E: Access to generic classmethods via class is ambiguous. +B.f # E: Access to generic classmethods via class is ambiguous. +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfGenericClassComplexType] +from typing import Generic, TypeVar, Iterable, Tuple +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls) -> T: pass + +class B(A[Iterable[str]]): pass + +reveal_type(B.f()) # E: Revealed type is 'typing.Iterable*[builtins.str]' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfGenericClassComplexReturnType] +from typing import Generic, TypeVar, Iterable, Tuple +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls) -> Iterable[Tuple[T, T]]: pass + +class B(A[str]): pass + +reveal_type(B.f()) # E: Revealed type is 'typing.Iterable[Tuple[builtins.str*, builtins.str*]]' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfGenericClassComplexArgumentType] +from typing import Generic, TypeVar, Iterable, Tuple +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls, arg: Iterable[Tuple[T, T]]) -> T: pass + +class B(A[str]): pass + +reveal_type(B.f((('x', 'y'),))) # E: Revealed type is 'builtins.str*' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfGenericClassMoreComplexTypeAndReturnTypeAndArgumentType] +from typing import Generic, TypeVar, Iterable, Tuple +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls, arg: Iterable[Tuple[T, T]]) -> Iterable[Tuple[T, T, T]]: pass + +class B(A[Iterable[str]]): pass + +arg = ((('x',), ('y',),),) +reveal_type(B.f(arg)) # E: Revealed type is 'typing.Iterable[Tuple[typing.Iterable*[builtins.str], typing.Iterable*[builtins.str], typing.Iterable*[builtins.str]]]' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOfGenericClassMultipleTypeVariable] +from typing import Generic, TypeVar, Tuple +T = TypeVar('T') +S = TypeVar('S') +class A(Generic[T, S]): + @classmethod + def f(cls) -> Tuple[T, S]: pass + +class B(A[str, int]): pass +class C(A[int, str]): pass + +reveal_type(B.f()) # E: Revealed type is 'Tuple[builtins.str*, builtins.int*]' +reveal_type(C.f()) # E: Revealed type is 'Tuple[builtins.int*, builtins.str*]' +[builtins fixtures/classmethod.pyi] + +[case testClassMethodOverloadingGenericClass] +from typing import overload, TypeVar, Generic, Tuple +T = TypeVar('T') +class A(Generic[T]): + @overload + @classmethod + def f(cls, s: str) -> Tuple[str, T]: pass + @overload + @classmethod + def f(cls, s: int) -> Tuple[T, int]: pass + @classmethod + def f(cls, s): pass +class B(A[str]): pass +class C(A[int]): pass + +reveal_type(B.f(1)) # E: Revealed type is 'Tuple[builtins.str*, builtins.int]' +reveal_type(B.f('a')) # E: Revealed type is 'Tuple[builtins.str, builtins.str*]' +reveal_type(C.f(1)) # E: Revealed type is 'Tuple[builtins.int*, builtins.int]' +reveal_type(C.f('a')) # E: Revealed type is 'Tuple[builtins.str, builtins.int*]' +[builtins fixtures/classmethod.pyi] + +[case testCallGenericClassMethodFromAnotherGenericClassMethodOfGenericClass] +from typing import Generic, TypeVar +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls) -> T: pass + @classmethod + def g(cls) -> T: + return cls.f() + +class B(A[str]): pass + +A.f # E: Access to generic classmethods via class is ambiguous. +A[str].f # E: Access to generic classmethods via class is ambiguous. +reveal_type(B.g()) # E: Revealed type is 'builtins.str*' +[builtins fixtures/classmethod.pyi] + +[case testGenericClassComplexTypeMultiLevelInheritance] +from typing import Generic, TypeVar, Tuple +T = TypeVar('T') +class A(Generic[T]): + @classmethod + def f(cls) -> T: ... +class B(A[Tuple[T, T]]): pass +class C(B[str]): pass + +reveal_type(C.f()) # E: Revealed type is 'Tuple[builtins.str*, builtins.str*]' +[builtins fixtures/classmethod.pyi]