Skip to content

Commit 6b8a7ca

Browse files
committed
Consider Any in selftype when checking overloads
1 parent bed188f commit 6b8a7ca

File tree

8 files changed

+98
-7
lines changed

8 files changed

+98
-7
lines changed

mypy/checkexpr.py

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
validate_instance,
144144
)
145145
from mypy.typeops import (
146+
bind_self,
146147
callable_type,
147148
custom_special_method,
148149
erase_to_union_or_bound,
@@ -1001,6 +1002,7 @@ def typeddict_callable_from_context(
10011002
self.named_type("builtins.type"),
10021003
variables=variables,
10031004
is_bound=True,
1005+
original_self_type=AnyType(TypeOfAny.implementation_artifact),
10041006
)
10051007

10061008
def check_typeddict_call_with_kwargs(
@@ -2911,10 +2913,54 @@ def infer_overload_return_type(
29112913
args_contain_any = any(map(has_any_type, arg_types))
29122914
type_maps: list[dict[Expression, Type]] = []
29132915

2916+
# If we have a selftype overload, it should contribute to `any_causes_overload_ambiguity`
2917+
# check. Pretend that we're checking `Foo.func(instance, ...)` instead of
2918+
# `instance.func(...)`.
2919+
p_object_type = get_proper_type(object_type) if object_type is not None else None
2920+
prepend_self = (
2921+
isinstance(p_object_type, Instance)
2922+
and has_any_type(p_object_type)
2923+
and any(
2924+
typ.is_bound and typ.original_self_type is not None for typ in plausible_targets
2925+
)
2926+
)
2927+
if prepend_self:
2928+
assert object_type is not None
2929+
2930+
args = [TempNode(object_type)] + args
2931+
arg_types = [object_type] + arg_types
2932+
arg_kinds = [ARG_POS] + arg_kinds
2933+
arg_names = [None, *arg_names] if arg_names is not None else None
2934+
2935+
def maybe_bind_self(t: Type) -> Type:
2936+
if prepend_self and isinstance(t, ProperType) and isinstance(t, FunctionLike):
2937+
return bind_self(t, object_type)
2938+
return t
2939+
29142940
for typ in plausible_targets:
29152941
assert self.msg is self.chk.msg
2916-
with self.msg.filter_errors() as w:
2917-
with self.chk.local_type_map as m:
2942+
with self.msg.filter_errors() as w, self.chk.local_type_map as m:
2943+
if prepend_self:
2944+
param = typ.original_self_type
2945+
assert param is not None, "Overload bound only partially?"
2946+
assert isinstance(p_object_type, Instance)
2947+
param = expand_type_by_instance(param, p_object_type)
2948+
ret_type, infer_type = self.check_call(
2949+
callee=typ.copy_modified(
2950+
arg_types=[param] + typ.arg_types,
2951+
arg_kinds=[ARG_POS] + typ.arg_kinds,
2952+
arg_names=[None, *typ.arg_names],
2953+
is_bound=False,
2954+
original_self_type=None,
2955+
),
2956+
args=args,
2957+
arg_kinds=arg_kinds,
2958+
arg_names=arg_names,
2959+
context=context,
2960+
callable_name=callable_name,
2961+
object_type=None,
2962+
)
2963+
else:
29182964
ret_type, infer_type = self.check_call(
29192965
callee=typ,
29202966
args=args,
@@ -2928,9 +2974,9 @@ def infer_overload_return_type(
29282974
if is_match:
29292975
# Return early if possible; otherwise record info, so we can
29302976
# check for ambiguity due to 'Any' below.
2931-
if not args_contain_any:
2977+
if not args_contain_any and not prepend_self:
29322978
self.chk.store_types(m)
2933-
return ret_type, infer_type
2979+
return ret_type, maybe_bind_self(infer_type)
29342980
p_infer_type = get_proper_type(infer_type)
29352981
if isinstance(p_infer_type, CallableType):
29362982
# Prefer inferred types if possible, this will avoid false triggers for
@@ -2949,10 +2995,10 @@ def infer_overload_return_type(
29492995
# We try returning a precise type if we can. If not, we give up and just return 'Any'.
29502996
if all_same_types(return_types):
29512997
self.chk.store_types(type_maps[0])
2952-
return return_types[0], inferred_types[0]
2998+
return return_types[0], maybe_bind_self(inferred_types[0])
29532999
elif all_same_types([erase_type(typ) for typ in return_types]):
29543000
self.chk.store_types(type_maps[0])
2955-
return erase_type(return_types[0]), erase_type(inferred_types[0])
3001+
return erase_type(return_types[0]), maybe_bind_self(erase_type(inferred_types[0]))
29563002
else:
29573003
return self.check_call(
29583004
callee=AnyType(TypeOfAny.special_form),
@@ -2966,7 +3012,7 @@ def infer_overload_return_type(
29663012
else:
29673013
# Success! No ambiguity; return the first match.
29683014
self.chk.store_types(type_maps[0])
2969-
return return_types[0], inferred_types[0]
3015+
return return_types[0], maybe_bind_self(inferred_types[0])
29703016

29713017
def overload_erased_call_targets(
29723018
self,
@@ -5017,6 +5063,7 @@ def apply_type_arguments_to_callable(
50175063
name="tuple",
50185064
definition=tp.definition,
50195065
is_bound=tp.is_bound,
5066+
original_self_type=tp.original_self_type,
50205067
)
50215068
self.msg.incompatible_type_application(
50225069
min_arg_count, len(type_vars), len(args), ctx

mypy/checkmember.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1519,6 +1519,7 @@ def bind_self_fast(method: F, original_type: Type | None = None) -> F:
15191519
arg_kinds=method.arg_kinds[1:],
15201520
arg_names=method.arg_names[1:],
15211521
is_bound=True,
1522+
original_self_type=method.arg_types[0],
15221523
)
15231524

15241525

mypy/exportjson.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,9 @@ def convert_callable_type(self: CallableType) -> Json:
467467
"is_ellipsis_args": self.is_ellipsis_args,
468468
"implicit": self.implicit,
469469
"is_bound": self.is_bound,
470+
"original_self_type": (
471+
convert_type(self.original_self_type) if self.original_self_type is not None else None
472+
),
470473
"type_guard": convert_type(self.type_guard) if self.type_guard is not None else None,
471474
"type_is": convert_type(self.type_is) if self.type_is is not None else None,
472475
"from_concatenate": self.from_concatenate,

mypy/server/astdiff.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,7 @@ def visit_callable_type(self, typ: CallableType) -> SnapshotItem:
471471
typ.is_ellipsis_args,
472472
snapshot_types(typ.variables),
473473
typ.is_bound,
474+
snapshot_types([typ.original_self_type] if typ.original_self_type is not None else []),
474475
)
475476

476477
def normalize_callable_variables(self, typ: CallableType) -> CallableType:

mypy/typeops.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P
205205
arg_names=["_args", "_kwds"],
206206
ret_type=any_type,
207207
is_bound=True,
208+
original_self_type=any_type,
208209
fallback=named_type("builtins.function"),
209210
)
210211
result: FunctionLike = class_callable(sig, info, fallback, None, is_new=False)
@@ -490,6 +491,7 @@ class B(A): pass
490491
arg_names=func.arg_names[1:],
491492
variables=variables,
492493
is_bound=True,
494+
original_self_type=func.arg_types[0],
493495
)
494496
return cast(F, res)
495497

mypy/types.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2169,6 +2169,7 @@ class CallableType(FunctionLike):
21692169
"from_type_type", # Was this callable generated by analyzing Type[...]
21702170
# instantiation?
21712171
"is_bound", # Is this a bound method?
2172+
"original_self_type", # If bound, what was the type of `self` before?
21722173
"type_guard", # T, if -> TypeGuard[T] (ret_type is bool in this case).
21732174
"type_is", # T, if -> TypeIs[T] (ret_type is bool in this case).
21742175
"from_concatenate", # whether this callable is from a concatenate object
@@ -2195,6 +2196,7 @@ def __init__(
21952196
special_sig: str | None = None,
21962197
from_type_type: bool = False,
21972198
is_bound: bool = False,
2199+
original_self_type: Type | None = None,
21982200
type_guard: Type | None = None,
21992201
type_is: Type | None = None,
22002202
from_concatenate: bool = False,
@@ -2232,6 +2234,7 @@ def __init__(
22322234
self.from_concatenate = from_concatenate
22332235
self.imprecise_arg_kinds = imprecise_arg_kinds
22342236
self.is_bound = is_bound
2237+
self.original_self_type = original_self_type
22352238
self.type_guard = type_guard
22362239
self.type_is = type_is
22372240
self.unpack_kwargs = unpack_kwargs
@@ -2253,6 +2256,7 @@ def copy_modified(
22532256
special_sig: Bogus[str | None] = _dummy,
22542257
from_type_type: Bogus[bool] = _dummy,
22552258
is_bound: Bogus[bool] = _dummy,
2259+
original_self_type: Bogus[Type | None] = _dummy,
22562260
type_guard: Bogus[Type | None] = _dummy,
22572261
type_is: Bogus[Type | None] = _dummy,
22582262
from_concatenate: Bogus[bool] = _dummy,
@@ -2277,6 +2281,9 @@ def copy_modified(
22772281
special_sig=special_sig if special_sig is not _dummy else self.special_sig,
22782282
from_type_type=from_type_type if from_type_type is not _dummy else self.from_type_type,
22792283
is_bound=is_bound if is_bound is not _dummy else self.is_bound,
2284+
original_self_type=(
2285+
original_self_type if original_self_type is not _dummy else self.original_self_type
2286+
),
22802287
type_guard=type_guard if type_guard is not _dummy else self.type_guard,
22812288
type_is=type_is if type_is is not _dummy else self.type_is,
22822289
from_concatenate=(
@@ -2598,6 +2605,11 @@ def serialize(self) -> JsonDict:
25982605
"is_ellipsis_args": self.is_ellipsis_args,
25992606
"implicit": self.implicit,
26002607
"is_bound": self.is_bound,
2608+
"original_self_type": (
2609+
self.original_self_type.serialize()
2610+
if self.original_self_type is not None
2611+
else None
2612+
),
26012613
"type_guard": self.type_guard.serialize() if self.type_guard is not None else None,
26022614
"type_is": (self.type_is.serialize() if self.type_is is not None else None),
26032615
"from_concatenate": self.from_concatenate,
@@ -2620,6 +2632,11 @@ def deserialize(cls, data: JsonDict) -> CallableType:
26202632
is_ellipsis_args=data["is_ellipsis_args"],
26212633
implicit=data["implicit"],
26222634
is_bound=data["is_bound"],
2635+
original_self_type=(
2636+
deserialize_type(data["original_self_type"])
2637+
if data["original_self_type"] is not None
2638+
else None
2639+
),
26232640
type_guard=(
26242641
deserialize_type(data["type_guard"]) if data["type_guard"] is not None else None
26252642
),
@@ -2641,6 +2658,7 @@ def write(self, data: Buffer) -> None:
26412658
write_bool(data, self.is_ellipsis_args)
26422659
write_bool(data, self.implicit)
26432660
write_bool(data, self.is_bound)
2661+
write_type_opt(data, self.original_self_type)
26442662
write_type_opt(data, self.type_guard)
26452663
write_type_opt(data, self.type_is)
26462664
write_bool(data, self.from_concatenate)
@@ -2663,6 +2681,7 @@ def read(cls, data: Buffer) -> CallableType:
26632681
is_ellipsis_args=read_bool(data),
26642682
implicit=read_bool(data),
26652683
is_bound=read_bool(data),
2684+
original_self_type=read_type_opt(data),
26662685
type_guard=read_type_opt(data),
26672686
type_is=read_type_opt(data),
26682687
from_concatenate=read_bool(data),

test-data/unit/check-overloading.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6852,3 +6852,20 @@ if isinstance(headers, dict):
68526852

68536853
reveal_type(headers) # N: Revealed type is "Union[__main__.Headers, typing.Iterable[tuple[builtins.bytes, builtins.bytes]]]"
68546854
[builtins fixtures/isinstancelist.pyi]
6855+
6856+
[case testSelfOverloadWithAnySelf]
6857+
from typing import Any, Generic, TypeVar, overload
6858+
6859+
T = TypeVar("T")
6860+
6861+
class A(Generic[T]):
6862+
@overload
6863+
def run(self: A[int]) -> int: ...
6864+
@overload
6865+
def run(self: A[str]) -> str: ...
6866+
def run(self: "A[int] | A[str]") -> "int | str": ...
6867+
6868+
foo: A[Any]
6869+
reveal_type(foo.run()) # N: Revealed type is "Any"
6870+
reveal_type(A.run(foo)) # N: Revealed type is "Any"
6871+
[builtins fixtures/tuple.pyi]

test-data/unit/exportjson.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ def foo(a: int) -> None: ...
170170
"is_ellipsis_args": false,
171171
"implicit": false,
172172
"is_bound": false,
173+
"original_self_type": null,
173174
"type_guard": null,
174175
"type_is": null,
175176
"from_concatenate": false,

0 commit comments

Comments
 (0)