From a33aebd99247527ff77ccd51bfb46238e98648e0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 28 Nov 2019 13:05:38 +0000 Subject: [PATCH 1/4] Apply generic class fix also to non-callable types --- mypy/checkmember.py | 2 +- test-data/unit/check-generics.test | 47 ++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 7d82fd89e737..d8d729bbc7ed 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -855,7 +855,7 @@ class B(A[str]): pass builtin_type, original_type, original_vars=original_vars)) for item in t.items()]) - return t + return expand_type_by_instance(t, isuper) def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> ProperType: diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 82bbe58463f3..3a1346e31e57 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -2337,3 +2337,50 @@ 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 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*' + +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] From 165b04c14362ba85201e99edaea3be8a02dde170 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 28 Nov 2019 13:19:56 +0000 Subject: [PATCH 2/4] Fix self-check --- mypy/checkmember.py | 4 +++- mypy/expandtype.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index d8d729bbc7ed..5dd1b62de396 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -855,7 +855,9 @@ class B(A[str]): pass builtin_type, original_type, original_vars=original_vars)) for item in t.items()]) - return expand_type_by_instance(t, isuper) + if isuper is not None: + t = cast(ProperType, expand_type_by_instance(t, isuper)) + return t def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> ProperType: 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: From 603c858e1cea8f88fd2640c7832db72789f64485 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 28 Nov 2019 19:21:46 +0000 Subject: [PATCH 3/4] Remove unused arg; improve docstring; add tests --- mypy/checkmember.py | 25 +++++++++++++++---------- test-data/unit/check-generics.test | 13 +++++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 5dd1b62de396..239874f164d2 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,8 +856,8 @@ 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: diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 3a1346e31e57..d9fc52b7a5c3 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -2351,7 +2351,10 @@ class C(Generic[T]): 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]]): ... +class F(G[List[int]]): ... x: C[int] y: Type[C[int]] @@ -2363,11 +2366,21 @@ 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*]' +xf: F +yf: Type[F] +reveal_type(xf.test) # N: Revealed type is 'builtins.list*[builtins.list*[builtins.int]]' +reveal_type(yf.test) # N: Revealed type is 'builtins.list*[builtins.list*[builtins.int]]' + class Sup: attr: int S = TypeVar('S', bound=Sup) From 40b98dbbbd48a73af3d02cac19fe4443a5d94e9b Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 29 Nov 2019 13:15:56 +0000 Subject: [PATCH 4/4] Fix docstring punctuation; remove redundant tests --- mypy/checkmember.py | 10 +++++----- test-data/unit/check-generics.test | 6 ------ 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 239874f164d2..e63dd73335fd 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -823,13 +823,13 @@ class B(A[str]): pass B.foo() Args: - t: Declared type of the method (or property); + 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; + 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. + 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). """ diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index d9fc52b7a5c3..63e3470c8f56 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -2354,7 +2354,6 @@ class D(C[str]): ... class E1(C[T], Generic[T, U]): ... class E2(C[U], Generic[T, U]): ... class G(C[List[T]]): ... -class F(G[List[int]]): ... x: C[int] y: Type[C[int]] @@ -2376,11 +2375,6 @@ 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*]' -xf: F -yf: Type[F] -reveal_type(xf.test) # N: Revealed type is 'builtins.list*[builtins.list*[builtins.int]]' -reveal_type(yf.test) # N: Revealed type is 'builtins.list*[builtins.list*[builtins.int]]' - class Sup: attr: int S = TypeVar('S', bound=Sup)