Skip to content

Commit 1b798e4

Browse files
ilevkivskyimsullivan
authored andcommitted
Make constraint inference in bind_self() more principled (#7938)
* Moves TypeVarExtractor to typeops.py * Infers and applies all and only type variables that appear in an explicit self type (instead of just always the first one as currently) Fixes #7925
1 parent 5c25cec commit 1b798e4

File tree

3 files changed

+78
-29
lines changed

3 files changed

+78
-29
lines changed

mypy/checker.py

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
from contextlib import contextmanager
66

77
from typing import (
8-
Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, Iterable,
9-
Sequence
8+
Dict, Set, List, cast, Tuple, TypeVar, Union, Optional, NamedTuple, Iterator, Sequence
109
)
1110
from typing_extensions import Final
1211

@@ -48,7 +47,7 @@
4847
from mypy.typeops import (
4948
map_type_from_supertype, bind_self, erase_to_bound, make_simplified_union,
5049
erase_def_to_union_or_bound, erase_to_union_or_bound,
51-
true_only, false_only, function_type,
50+
true_only, false_only, function_type, TypeVarExtractor
5251
)
5352
from mypy import message_registry
5453
from mypy.subtypes import (
@@ -4527,20 +4526,6 @@ def detach_callable(typ: CallableType) -> CallableType:
45274526
return out
45284527

45294528

4530-
class TypeVarExtractor(TypeQuery[List[TypeVarType]]):
4531-
def __init__(self) -> None:
4532-
super().__init__(self._merge)
4533-
4534-
def _merge(self, iter: Iterable[List[TypeVarType]]) -> List[TypeVarType]:
4535-
out = []
4536-
for item in iter:
4537-
out.extend(item)
4538-
return out
4539-
4540-
def visit_type_var(self, t: TypeVarType) -> List[TypeVarType]:
4541-
return [t]
4542-
4543-
45444529
def overload_can_never_match(signature: CallableType, other: CallableType) -> bool:
45454530
"""Check if the 'other' method can never be matched due to 'signature'.
45464531

mypy/typeops.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
since these may assume that MROs are ready.
66
"""
77

8-
from typing import cast, Optional, List, Sequence, Set
8+
from typing import cast, Optional, List, Sequence, Set, Iterable
99
import sys
1010

1111
from mypy.types import (
1212
TupleType, Instance, FunctionLike, Type, CallableType, TypeVarDef, Overloaded,
1313
TypeVarType, UninhabitedType, FormalArgument, UnionType, NoneType,
1414
AnyType, TypeOfAny, TypeType, ProperType, LiteralType, get_proper_type, get_proper_types,
15-
copy_type, TypeAliasType
15+
copy_type, TypeAliasType, TypeQuery
1616
)
1717
from mypy.nodes import (
1818
FuncBase, FuncItem, OverloadedFuncDef, TypeInfo, TypeVar, ARG_STAR, ARG_STAR2, ARG_POS,
@@ -215,23 +215,29 @@ class B(A): pass
215215
original_type = erase_to_bound(self_param_type)
216216
original_type = get_proper_type(original_type)
217217

218-
ids = [x.id for x in func.variables]
219-
typearg = get_proper_type(infer_type_arguments(ids, self_param_type,
220-
original_type, is_supertype=True)[0])
221-
if (is_classmethod and isinstance(typearg, UninhabitedType)
218+
all_ids = [x.id for x in func.variables]
219+
typeargs = infer_type_arguments(all_ids, self_param_type, original_type,
220+
is_supertype=True)
221+
if (is_classmethod
222+
# TODO: why do we need the extra guards here?
223+
and any(isinstance(get_proper_type(t), UninhabitedType) for t in typeargs)
222224
and isinstance(original_type, (Instance, TypeVarType, TupleType))):
223225
# In case we call a classmethod through an instance x, fallback to type(x)
224-
typearg = get_proper_type(infer_type_arguments(ids, self_param_type,
225-
TypeType(original_type),
226-
is_supertype=True)[0])
226+
typeargs = infer_type_arguments(all_ids, self_param_type, TypeType(original_type),
227+
is_supertype=True)
228+
229+
ids = [tid for tid in all_ids
230+
if any(tid == t.id for t in get_type_vars(self_param_type))]
231+
232+
# Technically, some constrains might be unsolvable, make them <nothing>.
233+
to_apply = [t if t is not None else UninhabitedType() for t in typeargs]
227234

228235
def expand(target: Type) -> Type:
229-
assert typearg is not None
230-
return expand_type(target, {func.variables[0].id: typearg})
236+
return expand_type(target, {id: to_apply[all_ids.index(id)] for id in ids})
231237

232238
arg_types = [expand(x) for x in func.arg_types[1:]]
233239
ret_type = expand(func.ret_type)
234-
variables = func.variables[1:]
240+
variables = [v for v in func.variables if v.id not in ids]
235241
else:
236242
arg_types = func.arg_types[1:]
237243
ret_type = func.ret_type
@@ -587,3 +593,21 @@ def coerce_to_literal(typ: Type) -> ProperType:
587593
if len(enum_values) == 1:
588594
return LiteralType(value=enum_values[0], fallback=typ)
589595
return typ
596+
597+
598+
def get_type_vars(tp: Type) -> List[TypeVarType]:
599+
return tp.accept(TypeVarExtractor())
600+
601+
602+
class TypeVarExtractor(TypeQuery[List[TypeVarType]]):
603+
def __init__(self) -> None:
604+
super().__init__(self._merge)
605+
606+
def _merge(self, iter: Iterable[List[TypeVarType]]) -> List[TypeVarType]:
607+
out = []
608+
for item in iter:
609+
out.extend(item)
610+
return out
611+
612+
def visit_type_var(self, t: TypeVarType) -> List[TypeVarType]:
613+
return [t]

test-data/unit/check-selftype.test

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,46 @@ ab: Union[A, B, C]
824824
reveal_type(ab.x) # N: Revealed type is 'builtins.int'
825825
[builtins fixtures/property.pyi]
826826

827+
[case testSelfTypeNoTypeVars]
828+
from typing import Generic, List, Optional, TypeVar, Any
829+
830+
Q = TypeVar("Q")
831+
T = TypeVar("T", bound=Super[Any])
832+
833+
class Super(Generic[Q]):
834+
@classmethod
835+
def meth(cls, arg: List[T]) -> List[T]:
836+
pass
837+
838+
class Sub(Super[int]): ...
839+
840+
def test(x: List[Sub]) -> None:
841+
reveal_type(Sub.meth(x)) # N: Revealed type is 'builtins.list[__main__.Sub*]'
842+
[builtins fixtures/isinstancelist.pyi]
843+
844+
[case testSelfTypeNoTypeVarsRestrict]
845+
from typing import Generic, TypeVar
846+
847+
T = TypeVar('T')
848+
S = TypeVar('S')
849+
850+
class C(Generic[T]):
851+
def limited(self: C[str], arg: S) -> S: ...
852+
853+
reveal_type(C[str]().limited(0)) # N: Revealed type is 'builtins.int*'
854+
855+
[case testSelfTypeMultipleTypeVars]
856+
from typing import Generic, TypeVar, Tuple
857+
858+
T = TypeVar('T')
859+
S = TypeVar('S')
860+
U = TypeVar('U')
861+
862+
class C(Generic[T]):
863+
def magic(self: C[Tuple[S, U]]) -> Tuple[T, S, U]: ...
864+
865+
reveal_type(C[Tuple[int, str]]().magic()) # N: Revealed type is 'Tuple[Tuple[builtins.int, builtins.str], builtins.int, builtins.str]'
866+
827867
[case testSelfTypeOnUnion]
828868
from typing import TypeVar, Union
829869

0 commit comments

Comments
 (0)