Skip to content

Commit 1122fc6

Browse files
authored
Make logic around bind_self() consistent in different code paths (#8021)
Fixes #8020 There is a bunch of code/logic duplication around `bind_self()`, mostly because of #7724. This PR makes all three main code paths consistently follow the same structure: 1. `freshen_function_type_vars()` 2. `bind_self()` 3. `expand_type_by_instance(..., map_instance_to_supertype())` (a.k.a `map_type_from_supertype()`) I briefly scrolled through other code paths, and it looks like this was last major/obvious inconsistency (although code around `__getattr__`/`__setattr__`/`__get__`/`__set__` looks a bit suspicious).
1 parent 596cde6 commit 1122fc6

File tree

3 files changed

+87
-10
lines changed

3 files changed

+87
-10
lines changed

mypy/checkmember.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -829,9 +829,7 @@ class B(A[str]): pass
829829
variables of the class callable on which the method was accessed.
830830
"""
831831
# TODO: verify consistency between Q and T
832-
if is_classmethod:
833-
assert isuper is not None
834-
t = get_proper_type(expand_type_by_instance(t, isuper))
832+
835833
# We add class type variables if the class method is accessed on class object
836834
# without applied type arguments, this matches the behavior of __init__().
837835
# For example (continuing the example in docstring):
@@ -847,7 +845,10 @@ class B(A[str]): pass
847845
if isinstance(t, CallableType):
848846
tvars = original_vars if original_vars is not None else []
849847
if is_classmethod:
848+
t = freshen_function_type_vars(t)
850849
t = bind_self(t, original_type, is_classmethod=True)
850+
assert isuper is not None
851+
t = cast(CallableType, expand_type_by_instance(t, isuper))
851852
return t.copy_modified(variables=tvars + t.variables)
852853
elif isinstance(t, Overloaded):
853854
return Overloaded([cast(CallableType, add_class_tvars(item, itype, isuper, is_classmethod,

test-data/unit/check-generics.test

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2062,7 +2062,7 @@ class Base(Generic[T]):
20622062
return (cls(item),)
20632063
return cls(item)
20642064

2065-
reveal_type(Base.make_some) # N: Revealed type is 'Overload(def [T] (item: T`1) -> __main__.Base*[T`1], def [T] (item: T`1, n: builtins.int) -> builtins.tuple[__main__.Base*[T`1]])'
2065+
reveal_type(Base.make_some) # N: Revealed type is 'Overload(def [T] (item: T`1) -> __main__.Base[T`1], def [T] (item: T`1, n: builtins.int) -> builtins.tuple[__main__.Base[T`1]])'
20662066
reveal_type(Base.make_some(1)) # N: Revealed type is '__main__.Base[builtins.int*]'
20672067
reveal_type(Base.make_some(1, 1)) # N: Revealed type is 'builtins.tuple[__main__.Base[builtins.int*]]'
20682068

@@ -2100,11 +2100,11 @@ class A(Generic[T]):
21002100

21012101
class B(A[T], Generic[T, S]):
21022102
def meth(self) -> None:
2103-
reveal_type(A[T].foo) # N: Revealed type is 'def () -> Tuple[T`1, __main__.A*[T`1]]'
2103+
reveal_type(A[T].foo) # N: Revealed type is 'def () -> Tuple[T`1, __main__.A[T`1]]'
21042104
@classmethod
21052105
def other(cls) -> None:
2106-
reveal_type(cls.foo) # N: Revealed type is 'def () -> Tuple[T`1, __main__.B*[T`1, S`2]]'
2107-
reveal_type(B.foo) # N: Revealed type is 'def [T, S] () -> Tuple[T`1, __main__.B*[T`1, S`2]]'
2106+
reveal_type(cls.foo) # N: Revealed type is 'def () -> Tuple[T`1, __main__.B[T`1, S`2]]'
2107+
reveal_type(B.foo) # N: Revealed type is 'def [T, S] () -> Tuple[T`1, __main__.B[T`1, S`2]]'
21082108
[builtins fixtures/classmethod.pyi]
21092109

21102110
[case testGenericClassAlternativeConstructorPrecise]
@@ -2171,8 +2171,8 @@ class C(Generic[T]):
21712171

21722172
class D(C[str]): ...
21732173

2174-
reveal_type(D.get()) # N: Revealed type is 'builtins.str'
2175-
reveal_type(D.get(42)) # N: Revealed type is 'builtins.tuple[builtins.str]'
2174+
reveal_type(D.get()) # N: Revealed type is 'builtins.str*'
2175+
reveal_type(D.get(42)) # N: Revealed type is 'builtins.tuple[builtins.str*]'
21762176
[builtins fixtures/classmethod.pyi]
21772177

21782178
[case testGenericClassMethodAnnotation]

test-data/unit/check-selftype.test

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,7 @@ class A(Generic[T]):
959959

960960
t: Type[Union[A[int], A[str]]]
961961
x = t.meth()
962-
reveal_type(x) # N: Revealed type is 'Union[__main__.A*[builtins.int], __main__.A*[builtins.str]]'
962+
reveal_type(x) # N: Revealed type is 'Union[__main__.A[builtins.int], __main__.A[builtins.str]]'
963963
[builtins fixtures/classmethod.pyi]
964964

965965
[case testSelfTypeClassMethodOnUnionList]
@@ -1055,3 +1055,79 @@ class Concrete(Blah):
10551055
def something(self) -> None: ...
10561056

10571057
Concrete() # OK
1058+
1059+
[case testSelfTypeGenericClassNoClashInstanceMethod]
1060+
from typing import TypeVar, Generic
1061+
1062+
M = TypeVar("M")
1063+
T = TypeVar("T")
1064+
S = TypeVar("S")
1065+
1066+
class Descriptor(Generic[M]): ...
1067+
1068+
class BaseWrapper(Generic[M]):
1069+
def create_wrapper(self: T, metric_descriptor: Descriptor[M]) -> T: ...
1070+
class SubWrapper(BaseWrapper[M]): ...
1071+
1072+
def build_wrapper(descriptor: Descriptor[M]) -> BaseWrapper[M]:
1073+
wrapper: BaseWrapper[M]
1074+
return wrapper.create_wrapper(descriptor)
1075+
1076+
def build_sub_wrapper(descriptor: Descriptor[S]) -> SubWrapper[S]:
1077+
wrapper: SubWrapper[S]
1078+
x = wrapper.create_wrapper(descriptor)
1079+
reveal_type(x) # N: Revealed type is '__main__.SubWrapper[S`-1]'
1080+
return x
1081+
1082+
[case testSelfTypeGenericClassNoClashClassMethod]
1083+
from typing import TypeVar, Generic, Type
1084+
1085+
M = TypeVar("M")
1086+
T = TypeVar("T")
1087+
S = TypeVar("S")
1088+
1089+
class Descriptor(Generic[M]): ...
1090+
1091+
class BaseWrapper(Generic[M]):
1092+
@classmethod
1093+
def create_wrapper(cls: Type[T], metric_descriptor: Descriptor[M]) -> T: ...
1094+
class SubWrapper(BaseWrapper[M]): ...
1095+
1096+
def build_wrapper(descriptor: Descriptor[M]) -> BaseWrapper[M]:
1097+
wrapper_cls: Type[BaseWrapper[M]]
1098+
return wrapper_cls.create_wrapper(descriptor)
1099+
1100+
def build_sub_wrapper(descriptor: Descriptor[S]) -> SubWrapper[S]:
1101+
wrapper_cls: Type[SubWrapper[S]]
1102+
x = wrapper_cls.create_wrapper(descriptor)
1103+
reveal_type(x) # N: Revealed type is '__main__.SubWrapper[S`-1]'
1104+
return x
1105+
[builtins fixtures/classmethod.pyi]
1106+
1107+
[case testSelfTypeGenericClassNoClashClassMethodClassObject]
1108+
from typing import TypeVar, Generic, Type
1109+
1110+
M = TypeVar("M")
1111+
T = TypeVar("T")
1112+
1113+
class Descriptor(Generic[M]): ...
1114+
1115+
class BaseWrapper(Generic[M]):
1116+
@classmethod
1117+
def create_wrapper(cls: Type[T], metric_descriptor: Descriptor[M]) -> T: ...
1118+
class SubWrapper(BaseWrapper[M]): ...
1119+
1120+
def build_wrapper(descriptor: Descriptor[M]) -> BaseWrapper[M]:
1121+
return BaseWrapper.create_wrapper(descriptor)
1122+
1123+
def build_sub_wrapper(descriptor: Descriptor[M]) -> SubWrapper[M]:
1124+
x = SubWrapper.create_wrapper(descriptor)
1125+
reveal_type(x) # N: Revealed type is '__main__.SubWrapper[M`-1]'
1126+
return x
1127+
1128+
def build_wrapper_non_gen(descriptor: Descriptor[int]) -> BaseWrapper[str]:
1129+
return BaseWrapper.create_wrapper(descriptor) # E: Argument 1 to "create_wrapper" of "BaseWrapper" has incompatible type "Descriptor[int]"; expected "Descriptor[str]"
1130+
1131+
def build_sub_wrapper_non_gen(descriptor: Descriptor[int]) -> SubWrapper[str]:
1132+
return SubWrapper.create_wrapper(descriptor) # E: Argument 1 to "create_wrapper" of "BaseWrapper" has incompatible type "Descriptor[int]"; expected "Descriptor[str]"
1133+
[builtins fixtures/classmethod.pyi]

0 commit comments

Comments
 (0)