diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 7d82fd89e737..e63dd73335fd 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -764,8 +764,8 @@ def analyze_class_attribute_access(itype: Instance, t = get_proper_type(t) if isinstance(t, FunctionLike) and is_classmethod: t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg) - result = add_class_tvars(t, itype, isuper, is_classmethod, - mx.builtin_type, mx.self_type, original_vars=original_vars) + result = add_class_tvars(t, isuper, is_classmethod, + mx.self_type, original_vars=original_vars) if not mx.is_lvalue: result = analyze_descriptor_access(mx.original_type, result, mx.builtin_type, mx.msg, mx.context, chk=mx.chk) @@ -808,9 +808,8 @@ def analyze_class_attribute_access(itype: Instance, return typ -def add_class_tvars(t: ProperType, itype: Instance, isuper: Optional[Instance], +def add_class_tvars(t: ProperType, isuper: Optional[Instance], is_classmethod: bool, - builtin_type: Callable[[str], Instance], original_type: Type, original_vars: Optional[List[TypeVarDef]] = None) -> Type: """Instantiate type variables during analyze_class_attribute_access, @@ -821,12 +820,18 @@ class A(Generic[T]): def foo(cls: Type[Q]) -> Tuple[T, Q]: ... class B(A[str]): pass - B.foo() - original_type is the value of the type B in the expression B.foo() or the corresponding - component in case if a union (this is used to bind the self-types); original_vars are type - variables of the class callable on which the method was accessed. + Args: + t: Declared type of the method (or property) + isuper: Current instance mapped to the superclass where method was defined, this + is usually done by map_instance_to_supertype() + is_classmethod: True if this method is decorated with @classmethod + original_type: The value of the type B in the expression B.foo() or the corresponding + component in case of a union (this is used to bind the self-types) + original_vars: Type variables of the class callable on which the method was accessed + Returns: + Expanded method type with added type variables (when needed). """ # TODO: verify consistency between Q and T @@ -851,10 +856,12 @@ class B(A[str]): pass t = cast(CallableType, expand_type_by_instance(t, isuper)) return t.copy_modified(variables=tvars + t.variables) elif isinstance(t, Overloaded): - return Overloaded([cast(CallableType, add_class_tvars(item, itype, isuper, is_classmethod, - builtin_type, original_type, + return Overloaded([cast(CallableType, add_class_tvars(item, isuper, + is_classmethod, original_type, original_vars=original_vars)) for item in t.items()]) + if isuper is not None: + t = cast(ProperType, expand_type_by_instance(t, isuper)) return t diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 128f187e3d88..cdcb9c77dec2 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -19,6 +19,7 @@ def expand_type(typ: Type, env: Mapping[TypeVarId, Type]) -> Type: def expand_type_by_instance(typ: Type, instance: Instance) -> Type: """Substitute type variables in type using values from an Instance. Type variables are considered to be bound by the class declaration.""" + # TODO: use an overloaded signature? (ProperType stays proper after expansion.) if instance.args == []: return typ else: diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 82bbe58463f3..63e3470c8f56 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -2337,3 +2337,57 @@ class Test(): reveal_type(MakeTwoAppliedSubAbstract()(2)) # N: Revealed type is '__main__.TwoTypes[builtins.str, builtins.int*]' reveal_type(MakeTwoGenericSubAbstract[str]()('foo')) # N: Revealed type is '__main__.TwoTypes[builtins.str, builtins.str*]' reveal_type(MakeTwoGenericSubAbstract[str]()(2)) # N: Revealed type is '__main__.TwoTypes[builtins.str, builtins.int*]' + +[case testGenericClassPropertyBound] +from typing import Generic, TypeVar, Callable, Type, List, Dict + +T = TypeVar('T') +U = TypeVar('U') + +def classproperty(f: Callable[..., U]) -> U: ... + +class C(Generic[T]): + @classproperty + def test(self) -> T: ... + +class D(C[str]): ... +class E1(C[T], Generic[T, U]): ... +class E2(C[U], Generic[T, U]): ... +class G(C[List[T]]): ... + +x: C[int] +y: Type[C[int]] +reveal_type(x.test) # N: Revealed type is 'builtins.int*' +reveal_type(y.test) # N: Revealed type is 'builtins.int*' + +xd: D +yd: Type[D] +reveal_type(xd.test) # N: Revealed type is 'builtins.str*' +reveal_type(yd.test) # N: Revealed type is 'builtins.str*' + +ye1: Type[E1[int, str]] +ye2: Type[E2[int, str]] +reveal_type(ye1.test) # N: Revealed type is 'builtins.int*' +reveal_type(ye2.test) # N: Revealed type is 'builtins.str*' + +xg: G[int] +yg: Type[G[int]] +reveal_type(xg.test) # N: Revealed type is 'builtins.list*[builtins.int*]' +reveal_type(yg.test) # N: Revealed type is 'builtins.list*[builtins.int*]' + +class Sup: + attr: int +S = TypeVar('S', bound=Sup) + +def func(tp: Type[C[S]]) -> S: + reveal_type(tp.test.attr) # N: Revealed type is 'builtins.int' + + reg: Dict[S, G[S]] + reveal_type(reg[tp.test]) # N: Revealed type is '__main__.G*[S`-1]' + reveal_type(reg[tp.test].test) # N: Revealed type is 'builtins.list*[S`-1]' + + if bool(): + return tp.test + else: + return reg[tp.test].test[0] +[builtins fixtures/dict.pyi]