Skip to content

Commit b5f4df9

Browse files
authored
Apply generic class fix also to non-callable types (#8030)
This is a follow up for #8021, that applies the fix also to non-callable types. Currently non-callable types are still wrong: ```python R = TypeVar('R') T = TypeVar('T') # Can be any decorator that makes type non-callable. def classproperty(f: Callable[..., R]) -> R: ... class C(Generic[T]): @classproperty def test(self) -> T: ... x: C[int] y: Type[C[int]] reveal_type(x.test) # Revealed type is 'int', OK reveal_type(y.test) # Revealed type is 'T' ??? ``` So, #7724 strikes again. It turns out there is not only duplicated logic for attribute kinds (decorators vs normal methods), but also for callable vs non-callable types. In latter case we still need to expand the type (like in other places, e.g., `analyze_var`).
1 parent f6e250d commit b5f4df9

File tree

3 files changed

+72
-10
lines changed

3 files changed

+72
-10
lines changed

mypy/checkmember.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -764,8 +764,8 @@ def analyze_class_attribute_access(itype: Instance,
764764
t = get_proper_type(t)
765765
if isinstance(t, FunctionLike) and is_classmethod:
766766
t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg)
767-
result = add_class_tvars(t, itype, isuper, is_classmethod,
768-
mx.builtin_type, mx.self_type, original_vars=original_vars)
767+
result = add_class_tvars(t, isuper, is_classmethod,
768+
mx.self_type, original_vars=original_vars)
769769
if not mx.is_lvalue:
770770
result = analyze_descriptor_access(mx.original_type, result, mx.builtin_type,
771771
mx.msg, mx.context, chk=mx.chk)
@@ -808,9 +808,8 @@ def analyze_class_attribute_access(itype: Instance,
808808
return typ
809809

810810

811-
def add_class_tvars(t: ProperType, itype: Instance, isuper: Optional[Instance],
811+
def add_class_tvars(t: ProperType, isuper: Optional[Instance],
812812
is_classmethod: bool,
813-
builtin_type: Callable[[str], Instance],
814813
original_type: Type,
815814
original_vars: Optional[List[TypeVarDef]] = None) -> Type:
816815
"""Instantiate type variables during analyze_class_attribute_access,
@@ -821,12 +820,18 @@ class A(Generic[T]):
821820
def foo(cls: Type[Q]) -> Tuple[T, Q]: ...
822821
823822
class B(A[str]): pass
824-
825823
B.foo()
826824
827-
original_type is the value of the type B in the expression B.foo() or the corresponding
828-
component in case if a union (this is used to bind the self-types); original_vars are type
829-
variables of the class callable on which the method was accessed.
825+
Args:
826+
t: Declared type of the method (or property)
827+
isuper: Current instance mapped to the superclass where method was defined, this
828+
is usually done by map_instance_to_supertype()
829+
is_classmethod: True if this method is decorated with @classmethod
830+
original_type: The value of the type B in the expression B.foo() or the corresponding
831+
component in case of a union (this is used to bind the self-types)
832+
original_vars: Type variables of the class callable on which the method was accessed
833+
Returns:
834+
Expanded method type with added type variables (when needed).
830835
"""
831836
# TODO: verify consistency between Q and T
832837

@@ -851,10 +856,12 @@ class B(A[str]): pass
851856
t = cast(CallableType, expand_type_by_instance(t, isuper))
852857
return t.copy_modified(variables=tvars + t.variables)
853858
elif isinstance(t, Overloaded):
854-
return Overloaded([cast(CallableType, add_class_tvars(item, itype, isuper, is_classmethod,
855-
builtin_type, original_type,
859+
return Overloaded([cast(CallableType, add_class_tvars(item, isuper,
860+
is_classmethod, original_type,
856861
original_vars=original_vars))
857862
for item in t.items()])
863+
if isuper is not None:
864+
t = cast(ProperType, expand_type_by_instance(t, isuper))
858865
return t
859866

860867

mypy/expandtype.py

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def expand_type(typ: Type, env: Mapping[TypeVarId, Type]) -> Type:
1919
def expand_type_by_instance(typ: Type, instance: Instance) -> Type:
2020
"""Substitute type variables in type using values from an Instance.
2121
Type variables are considered to be bound by the class declaration."""
22+
# TODO: use an overloaded signature? (ProperType stays proper after expansion.)
2223
if instance.args == []:
2324
return typ
2425
else:

test-data/unit/check-generics.test

+54
Original file line numberDiff line numberDiff line change
@@ -2337,3 +2337,57 @@ class Test():
23372337
reveal_type(MakeTwoAppliedSubAbstract()(2)) # N: Revealed type is '__main__.TwoTypes[builtins.str, builtins.int*]'
23382338
reveal_type(MakeTwoGenericSubAbstract[str]()('foo')) # N: Revealed type is '__main__.TwoTypes[builtins.str, builtins.str*]'
23392339
reveal_type(MakeTwoGenericSubAbstract[str]()(2)) # N: Revealed type is '__main__.TwoTypes[builtins.str, builtins.int*]'
2340+
2341+
[case testGenericClassPropertyBound]
2342+
from typing import Generic, TypeVar, Callable, Type, List, Dict
2343+
2344+
T = TypeVar('T')
2345+
U = TypeVar('U')
2346+
2347+
def classproperty(f: Callable[..., U]) -> U: ...
2348+
2349+
class C(Generic[T]):
2350+
@classproperty
2351+
def test(self) -> T: ...
2352+
2353+
class D(C[str]): ...
2354+
class E1(C[T], Generic[T, U]): ...
2355+
class E2(C[U], Generic[T, U]): ...
2356+
class G(C[List[T]]): ...
2357+
2358+
x: C[int]
2359+
y: Type[C[int]]
2360+
reveal_type(x.test) # N: Revealed type is 'builtins.int*'
2361+
reveal_type(y.test) # N: Revealed type is 'builtins.int*'
2362+
2363+
xd: D
2364+
yd: Type[D]
2365+
reveal_type(xd.test) # N: Revealed type is 'builtins.str*'
2366+
reveal_type(yd.test) # N: Revealed type is 'builtins.str*'
2367+
2368+
ye1: Type[E1[int, str]]
2369+
ye2: Type[E2[int, str]]
2370+
reveal_type(ye1.test) # N: Revealed type is 'builtins.int*'
2371+
reveal_type(ye2.test) # N: Revealed type is 'builtins.str*'
2372+
2373+
xg: G[int]
2374+
yg: Type[G[int]]
2375+
reveal_type(xg.test) # N: Revealed type is 'builtins.list*[builtins.int*]'
2376+
reveal_type(yg.test) # N: Revealed type is 'builtins.list*[builtins.int*]'
2377+
2378+
class Sup:
2379+
attr: int
2380+
S = TypeVar('S', bound=Sup)
2381+
2382+
def func(tp: Type[C[S]]) -> S:
2383+
reveal_type(tp.test.attr) # N: Revealed type is 'builtins.int'
2384+
2385+
reg: Dict[S, G[S]]
2386+
reveal_type(reg[tp.test]) # N: Revealed type is '__main__.G*[S`-1]'
2387+
reveal_type(reg[tp.test].test) # N: Revealed type is 'builtins.list*[S`-1]'
2388+
2389+
if bool():
2390+
return tp.test
2391+
else:
2392+
return reg[tp.test].test[0]
2393+
[builtins fixtures/dict.pyi]

0 commit comments

Comments
 (0)