Skip to content

Commit 5b17cc6

Browse files
committed
Fix overload overlap check for UninhabitedType (#13461)
The issue was exposed by merge of subtype visitors. Fix is actually trivial, but the diff is big because I need to add and pass the new flag everywhere (`is_subtype()`, `is_proper_subtype()`, `is_equivalent()`, `is_same_type()` can call each other).
1 parent c7b4714 commit 5b17cc6

File tree

4 files changed

+99
-27
lines changed

4 files changed

+99
-27
lines changed

mypy/checker.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -752,14 +752,14 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
752752

753753
# Is the overload alternative's arguments subtypes of the implementation's?
754754
if not is_callable_compatible(
755-
impl, sig1, is_compat=is_subtype_no_promote, ignore_return=True
755+
impl, sig1, is_compat=is_subtype, ignore_return=True
756756
):
757757
self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl)
758758

759759
# Is the overload alternative's return type a subtype of the implementation's?
760760
if not (
761-
is_subtype_no_promote(sig1.ret_type, impl.ret_type)
762-
or is_subtype_no_promote(impl.ret_type, sig1.ret_type)
761+
is_subtype(sig1.ret_type, impl.ret_type)
762+
or is_subtype(impl.ret_type, sig1.ret_type)
763763
):
764764
self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl)
765765

@@ -6496,15 +6496,15 @@ def is_unsafe_overlapping_overload_signatures(
64966496
return is_callable_compatible(
64976497
signature,
64986498
other,
6499-
is_compat=is_overlapping_types_no_promote,
6499+
is_compat=is_overlapping_types_no_promote_no_uninhabited,
65006500
is_compat_return=lambda l, r: not is_subtype_no_promote(l, r),
65016501
ignore_return=False,
65026502
check_args_covariantly=True,
65036503
allow_partial_overlap=True,
65046504
) or is_callable_compatible(
65056505
other,
65066506
signature,
6507-
is_compat=is_overlapping_types_no_promote,
6507+
is_compat=is_overlapping_types_no_promote_no_uninhabited,
65086508
is_compat_return=lambda l, r: not is_subtype_no_promote(r, l),
65096509
ignore_return=False,
65106510
check_args_covariantly=False,
@@ -6988,8 +6988,12 @@ def is_subtype_no_promote(left: Type, right: Type) -> bool:
69886988
return is_subtype(left, right, ignore_promotions=True)
69896989

69906990

6991-
def is_overlapping_types_no_promote(left: Type, right: Type) -> bool:
6992-
return is_overlapping_types(left, right, ignore_promotions=True)
6991+
def is_overlapping_types_no_promote_no_uninhabited(left: Type, right: Type) -> bool:
6992+
# For the purpose of unsafe overload checks we consider list[<nothing>] and list[int]
6993+
# non-overlapping. This is consistent with how we treat list[int] and list[str] as
6994+
# non-overlapping, despite [] belongs to both. Also this will prevent false positives
6995+
# for failed type inference during unification.
6996+
return is_overlapping_types(left, right, ignore_promotions=True, ignore_uninhabited=True)
69936997

69946998

69956999
def is_private(node_name: str) -> bool:

mypy/meet.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ def is_overlapping_types(
215215
right: Type,
216216
ignore_promotions: bool = False,
217217
prohibit_none_typevar_overlap: bool = False,
218+
ignore_uninhabited: bool = False,
218219
) -> bool:
219220
"""Can a value of type 'left' also be of type 'right' or vice-versa?
220221
@@ -239,6 +240,7 @@ def _is_overlapping_types(left: Type, right: Type) -> bool:
239240
right,
240241
ignore_promotions=ignore_promotions,
241242
prohibit_none_typevar_overlap=prohibit_none_typevar_overlap,
243+
ignore_uninhabited=ignore_uninhabited,
242244
)
243245

244246
# We should never encounter this type.
@@ -286,8 +288,10 @@ def _is_overlapping_types(left: Type, right: Type) -> bool:
286288
):
287289
return True
288290

289-
if is_proper_subtype(left, right, ignore_promotions=ignore_promotions) or is_proper_subtype(
290-
right, left, ignore_promotions=ignore_promotions
291+
if is_proper_subtype(
292+
left, right, ignore_promotions=ignore_promotions, ignore_uninhabited=ignore_uninhabited
293+
) or is_proper_subtype(
294+
right, left, ignore_promotions=ignore_promotions, ignore_uninhabited=ignore_uninhabited
291295
):
292296
return True
293297

@@ -429,8 +433,10 @@ def _callable_overlap(left: CallableType, right: CallableType) -> bool:
429433
if isinstance(left, Instance) and isinstance(right, Instance):
430434
# First we need to handle promotions and structural compatibility for instances
431435
# that came as fallbacks, so simply call is_subtype() to avoid code duplication.
432-
if is_subtype(left, right, ignore_promotions=ignore_promotions) or is_subtype(
433-
right, left, ignore_promotions=ignore_promotions
436+
if is_subtype(
437+
left, right, ignore_promotions=ignore_promotions, ignore_uninhabited=ignore_uninhabited
438+
) or is_subtype(
439+
right, left, ignore_promotions=ignore_promotions, ignore_uninhabited=ignore_uninhabited
434440
):
435441
return True
436442

@@ -471,7 +477,7 @@ def _callable_overlap(left: CallableType, right: CallableType) -> bool:
471477
# Note: it's unclear however, whether returning False is the right thing
472478
# to do when inferring reachability -- see https://github.com/python/mypy/issues/5529
473479

474-
assert type(left) != type(right)
480+
assert type(left) != type(right), f"{type(left)} vs {type(right)}"
475481
return False
476482

477483

mypy/subtypes.py

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
IS_CLASSVAR: Final = 2
6969
IS_CLASS_OR_STATIC: Final = 3
7070

71-
TypeParameterChecker: _TypeAlias = Callable[[Type, Type, int, bool], bool]
71+
TypeParameterChecker: _TypeAlias = Callable[[Type, Type, int, bool, "SubtypeContext"], bool]
7272

7373

7474
class SubtypeContext:
@@ -81,6 +81,7 @@ def __init__(
8181
ignore_declared_variance: bool = False,
8282
# Supported for both proper and non-proper
8383
ignore_promotions: bool = False,
84+
ignore_uninhabited: bool = False,
8485
# Proper subtype flags
8586
erase_instances: bool = False,
8687
keep_erased_types: bool = False,
@@ -90,6 +91,7 @@ def __init__(
9091
self.ignore_pos_arg_names = ignore_pos_arg_names
9192
self.ignore_declared_variance = ignore_declared_variance
9293
self.ignore_promotions = ignore_promotions
94+
self.ignore_uninhabited = ignore_uninhabited
9395
self.erase_instances = erase_instances
9496
self.keep_erased_types = keep_erased_types
9597
self.options = options
@@ -116,6 +118,7 @@ def is_subtype(
116118
ignore_pos_arg_names: bool = False,
117119
ignore_declared_variance: bool = False,
118120
ignore_promotions: bool = False,
121+
ignore_uninhabited: bool = False,
119122
options: Options | None = None,
120123
) -> bool:
121124
"""Is 'left' subtype of 'right'?
@@ -135,6 +138,7 @@ def is_subtype(
135138
ignore_pos_arg_names=ignore_pos_arg_names,
136139
ignore_declared_variance=ignore_declared_variance,
137140
ignore_promotions=ignore_promotions,
141+
ignore_uninhabited=ignore_uninhabited,
138142
options=options,
139143
)
140144
else:
@@ -144,6 +148,7 @@ def is_subtype(
144148
ignore_pos_arg_names,
145149
ignore_declared_variance,
146150
ignore_promotions,
151+
ignore_uninhabited,
147152
options,
148153
}
149154
), "Don't pass both context and individual flags"
@@ -178,6 +183,7 @@ def is_proper_subtype(
178183
*,
179184
subtype_context: SubtypeContext | None = None,
180185
ignore_promotions: bool = False,
186+
ignore_uninhabited: bool = False,
181187
erase_instances: bool = False,
182188
keep_erased_types: bool = False,
183189
) -> bool:
@@ -193,12 +199,19 @@ def is_proper_subtype(
193199
if subtype_context is None:
194200
subtype_context = SubtypeContext(
195201
ignore_promotions=ignore_promotions,
202+
ignore_uninhabited=ignore_uninhabited,
196203
erase_instances=erase_instances,
197204
keep_erased_types=keep_erased_types,
198205
)
199206
else:
200207
assert not any(
201-
{ignore_promotions, erase_instances, keep_erased_types}
208+
{
209+
ignore_promotions,
210+
ignore_uninhabited,
211+
erase_instances,
212+
keep_erased_types,
213+
ignore_uninhabited,
214+
}
202215
), "Don't pass both context and individual flags"
203216
if TypeState.is_assumed_proper_subtype(left, right):
204217
return True
@@ -216,23 +229,28 @@ def is_equivalent(
216229
ignore_type_params: bool = False,
217230
ignore_pos_arg_names: bool = False,
218231
options: Options | None = None,
232+
subtype_context: SubtypeContext | None = None,
219233
) -> bool:
220234
return is_subtype(
221235
a,
222236
b,
223237
ignore_type_params=ignore_type_params,
224238
ignore_pos_arg_names=ignore_pos_arg_names,
225239
options=options,
240+
subtype_context=subtype_context,
226241
) and is_subtype(
227242
b,
228243
a,
229244
ignore_type_params=ignore_type_params,
230245
ignore_pos_arg_names=ignore_pos_arg_names,
231246
options=options,
247+
subtype_context=subtype_context,
232248
)
233249

234250

235-
def is_same_type(a: Type, b: Type, ignore_promotions: bool = True) -> bool:
251+
def is_same_type(
252+
a: Type, b: Type, ignore_promotions: bool = True, subtype_context: SubtypeContext | None = None
253+
) -> bool:
236254
"""Are these types proper subtypes of each other?
237255
238256
This means types may have different representation (e.g. an alias, or
@@ -242,8 +260,10 @@ def is_same_type(a: Type, b: Type, ignore_promotions: bool = True) -> bool:
242260
# considered not the same type (which is the case at runtime).
243261
# Also Union[bool, int] (if it wasn't simplified before) will be different
244262
# from plain int, etc.
245-
return is_proper_subtype(a, b, ignore_promotions=ignore_promotions) and is_proper_subtype(
246-
b, a, ignore_promotions=ignore_promotions
263+
return is_proper_subtype(
264+
a, b, ignore_promotions=ignore_promotions, subtype_context=subtype_context
265+
) and is_proper_subtype(
266+
b, a, ignore_promotions=ignore_promotions, subtype_context=subtype_context
247267
)
248268

249269

@@ -307,23 +327,34 @@ def check_item(left: Type, right: Type, subtype_context: SubtypeContext) -> bool
307327
return left.accept(SubtypeVisitor(orig_right, subtype_context, proper_subtype))
308328

309329

310-
# TODO: should we pass on the original flags here and in couple other places?
311-
# This seems logical but was never done in the past for some reasons.
312-
def check_type_parameter(lefta: Type, righta: Type, variance: int, proper_subtype: bool) -> bool:
330+
def check_type_parameter(
331+
lefta: Type, righta: Type, variance: int, proper_subtype: bool, subtype_context: SubtypeContext
332+
) -> bool:
313333
def check(left: Type, right: Type) -> bool:
314-
return is_proper_subtype(left, right) if proper_subtype else is_subtype(left, right)
334+
return (
335+
is_proper_subtype(left, right, subtype_context=subtype_context)
336+
if proper_subtype
337+
else is_subtype(left, right, subtype_context=subtype_context)
338+
)
315339

316340
if variance == COVARIANT:
317341
return check(lefta, righta)
318342
elif variance == CONTRAVARIANT:
319343
return check(righta, lefta)
320344
else:
321345
if proper_subtype:
322-
return is_same_type(lefta, righta)
323-
return is_equivalent(lefta, righta)
346+
# We pass ignore_promotions=False because it is a default for subtype checks.
347+
# The actual value will be taken from the subtype_context, and it is whatever
348+
# the original caller passed.
349+
return is_same_type(
350+
lefta, righta, ignore_promotions=False, subtype_context=subtype_context
351+
)
352+
return is_equivalent(lefta, righta, subtype_context=subtype_context)
324353

325354

326-
def ignore_type_parameter(lefta: Type, righta: Type, variance: int, proper_subtype: bool) -> bool:
355+
def ignore_type_parameter(
356+
lefta: Type, righta: Type, variance: int, proper_subtype: bool, subtype_context: SubtypeContext
357+
) -> bool:
327358
return True
328359

329360

@@ -386,7 +417,11 @@ def visit_none_type(self, left: NoneType) -> bool:
386417
return True
387418

388419
def visit_uninhabited_type(self, left: UninhabitedType) -> bool:
389-
return True
420+
# We ignore this for unsafe overload checks, so that and empty list and
421+
# a list of int will be considered non-overlapping.
422+
if isinstance(self.right, UninhabitedType):
423+
return True
424+
return not self.subtype_context.ignore_uninhabited
390425

391426
def visit_erased_type(self, left: ErasedType) -> bool:
392427
# This may be encountered during type inference. The result probably doesn't
@@ -522,12 +557,12 @@ def check_mixed(
522557
for lefta, righta, tvar in type_params:
523558
if isinstance(tvar, TypeVarType):
524559
if not self.check_type_parameter(
525-
lefta, righta, tvar.variance, self.proper_subtype
560+
lefta, righta, tvar.variance, self.proper_subtype, self.subtype_context
526561
):
527562
nominal = False
528563
else:
529564
if not self.check_type_parameter(
530-
lefta, righta, COVARIANT, self.proper_subtype
565+
lefta, righta, COVARIANT, self.proper_subtype, self.subtype_context
531566
):
532567
nominal = False
533568
if nominal:
@@ -697,6 +732,7 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool:
697732
if not left.names_are_wider_than(right):
698733
return False
699734
for name, l, r in left.zip(right):
735+
# TODO: should we pass on the full subtype_context here and below?
700736
if self.proper_subtype:
701737
check = is_same_type(l, r)
702738
else:

test-data/unit/check-overloading.test

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6467,3 +6467,29 @@ spam: Callable[..., str] = lambda x, y: 'baz'
64676467
reveal_type(func(spam)) # N: Revealed type is "def (*Any, **Any) -> builtins.str"
64686468

64696469
[builtins fixtures/paramspec.pyi]
6470+
6471+
[case testGenericOverloadOverlapWithType]
6472+
import m
6473+
6474+
[file m.pyi]
6475+
from typing import TypeVar, Type, overload, Callable
6476+
6477+
T = TypeVar("T", bound=str)
6478+
@overload
6479+
def foo(x: Type[T] | int) -> int: ...
6480+
@overload
6481+
def foo(x: Callable[[int], bool]) -> str: ...
6482+
6483+
[case testGenericOverloadOverlapWithCollection]
6484+
import m
6485+
6486+
[file m.pyi]
6487+
from typing import TypeVar, Sequence, overload, List
6488+
6489+
T = TypeVar("T", bound=str)
6490+
6491+
@overload
6492+
def foo(x: List[T]) -> str: ...
6493+
@overload
6494+
def foo(x: Sequence[int]) -> int: ...
6495+
[builtins fixtures/list.pyi]

0 commit comments

Comments
 (0)