Skip to content

Commit b5f4df9

Browse files
authored
Apply generic class fix also to non-callable types (python#8030)
This is a follow up for python#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, python#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

Lines changed: 17 additions & 10 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 54 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy