Skip to content

Commit 2ba6451

Browse files
authored
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 9ba4491 commit 2ba6451

File tree

4 files changed

+99
-27
lines changed

4 files changed

+99
-27
lines changed

mypy/checker.py

+11-7
Original file line numberDiff line numberDiff line change
@@ -748,14 +748,14 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
748748

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

755755
# Is the overload alternative's return type a subtype of the implementation's?
756756
if not (
757-
is_subtype_no_promote(sig1.ret_type, impl.ret_type)
758-
or is_subtype_no_promote(impl.ret_type, sig1.ret_type)
757+
is_subtype(sig1.ret_type, impl.ret_type)
758+
or is_subtype(impl.ret_type, sig1.ret_type)
759759
):
760760
self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl)
761761

@@ -6485,15 +6485,15 @@ def is_unsafe_overlapping_overload_signatures(
64856485
return is_callable_compatible(
64866486
signature,
64876487
other,
6488-
is_compat=is_overlapping_types_no_promote,
6488+
is_compat=is_overlapping_types_no_promote_no_uninhabited,
64896489
is_compat_return=lambda l, r: not is_subtype_no_promote(l, r),
64906490
ignore_return=False,
64916491
check_args_covariantly=True,
64926492
allow_partial_overlap=True,
64936493
) or is_callable_compatible(
64946494
other,
64956495
signature,
6496-
is_compat=is_overlapping_types_no_promote,
6496+
is_compat=is_overlapping_types_no_promote_no_uninhabited,
64976497
is_compat_return=lambda l, r: not is_subtype_no_promote(r, l),
64986498
ignore_return=False,
64996499
check_args_covariantly=False,
@@ -6977,8 +6977,12 @@ def is_subtype_no_promote(left: Type, right: Type) -> bool:
69776977
return is_subtype(left, right, ignore_promotions=True)
69786978

69796979

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

69836987

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

mypy/meet.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ def is_overlapping_types(
211211
right: Type,
212212
ignore_promotions: bool = False,
213213
prohibit_none_typevar_overlap: bool = False,
214+
ignore_uninhabited: bool = False,
214215
) -> bool:
215216
"""Can a value of type 'left' also be of type 'right' or vice-versa?
216217
@@ -235,6 +236,7 @@ def _is_overlapping_types(left: Type, right: Type) -> bool:
235236
right,
236237
ignore_promotions=ignore_promotions,
237238
prohibit_none_typevar_overlap=prohibit_none_typevar_overlap,
239+
ignore_uninhabited=ignore_uninhabited,
238240
)
239241

240242
# We should never encounter this type.
@@ -282,8 +284,10 @@ def _is_overlapping_types(left: Type, right: Type) -> bool:
282284
):
283285
return True
284286

285-
if is_proper_subtype(left, right, ignore_promotions=ignore_promotions) or is_proper_subtype(
286-
right, left, ignore_promotions=ignore_promotions
287+
if is_proper_subtype(
288+
left, right, ignore_promotions=ignore_promotions, ignore_uninhabited=ignore_uninhabited
289+
) or is_proper_subtype(
290+
right, left, ignore_promotions=ignore_promotions, ignore_uninhabited=ignore_uninhabited
287291
):
288292
return True
289293

@@ -425,8 +429,10 @@ def _callable_overlap(left: CallableType, right: CallableType) -> bool:
425429
if isinstance(left, Instance) and isinstance(right, Instance):
426430
# First we need to handle promotions and structural compatibility for instances
427431
# that came as fallbacks, so simply call is_subtype() to avoid code duplication.
428-
if is_subtype(left, right, ignore_promotions=ignore_promotions) or is_subtype(
429-
right, left, ignore_promotions=ignore_promotions
432+
if is_subtype(
433+
left, right, ignore_promotions=ignore_promotions, ignore_uninhabited=ignore_uninhabited
434+
) or is_subtype(
435+
right, left, ignore_promotions=ignore_promotions, ignore_uninhabited=ignore_uninhabited
430436
):
431437
return True
432438

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

470-
assert type(left) != type(right)
476+
assert type(left) != type(right), f"{type(left)} vs {type(right)}"
471477
return False
472478

473479

mypy/subtypes.py

+51-15
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
IS_CLASSVAR: Final = 2
6868
IS_CLASS_OR_STATIC: Final = 3
6969

70-
TypeParameterChecker: _TypeAlias = Callable[[Type, Type, int, bool], bool]
70+
TypeParameterChecker: _TypeAlias = Callable[[Type, Type, int, bool, "SubtypeContext"], bool]
7171

7272

7373
class SubtypeContext:
@@ -80,6 +80,7 @@ def __init__(
8080
ignore_declared_variance: bool = False,
8181
# Supported for both proper and non-proper
8282
ignore_promotions: bool = False,
83+
ignore_uninhabited: bool = False,
8384
# Proper subtype flags
8485
erase_instances: bool = False,
8586
keep_erased_types: bool = False,
@@ -89,6 +90,7 @@ def __init__(
8990
self.ignore_pos_arg_names = ignore_pos_arg_names
9091
self.ignore_declared_variance = ignore_declared_variance
9192
self.ignore_promotions = ignore_promotions
93+
self.ignore_uninhabited = ignore_uninhabited
9294
self.erase_instances = erase_instances
9395
self.keep_erased_types = keep_erased_types
9496
self.options = options
@@ -115,6 +117,7 @@ def is_subtype(
115117
ignore_pos_arg_names: bool = False,
116118
ignore_declared_variance: bool = False,
117119
ignore_promotions: bool = False,
120+
ignore_uninhabited: bool = False,
118121
options: Options | None = None,
119122
) -> bool:
120123
"""Is 'left' subtype of 'right'?
@@ -134,6 +137,7 @@ def is_subtype(
134137
ignore_pos_arg_names=ignore_pos_arg_names,
135138
ignore_declared_variance=ignore_declared_variance,
136139
ignore_promotions=ignore_promotions,
140+
ignore_uninhabited=ignore_uninhabited,
137141
options=options,
138142
)
139143
else:
@@ -143,6 +147,7 @@ def is_subtype(
143147
ignore_pos_arg_names,
144148
ignore_declared_variance,
145149
ignore_promotions,
150+
ignore_uninhabited,
146151
options,
147152
}
148153
), "Don't pass both context and individual flags"
@@ -177,6 +182,7 @@ def is_proper_subtype(
177182
*,
178183
subtype_context: SubtypeContext | None = None,
179184
ignore_promotions: bool = False,
185+
ignore_uninhabited: bool = False,
180186
erase_instances: bool = False,
181187
keep_erased_types: bool = False,
182188
) -> bool:
@@ -192,12 +198,19 @@ def is_proper_subtype(
192198
if subtype_context is None:
193199
subtype_context = SubtypeContext(
194200
ignore_promotions=ignore_promotions,
201+
ignore_uninhabited=ignore_uninhabited,
195202
erase_instances=erase_instances,
196203
keep_erased_types=keep_erased_types,
197204
)
198205
else:
199206
assert not any(
200-
{ignore_promotions, erase_instances, keep_erased_types}
207+
{
208+
ignore_promotions,
209+
ignore_uninhabited,
210+
erase_instances,
211+
keep_erased_types,
212+
ignore_uninhabited,
213+
}
201214
), "Don't pass both context and individual flags"
202215
if TypeState.is_assumed_proper_subtype(left, right):
203216
return True
@@ -215,23 +228,28 @@ def is_equivalent(
215228
ignore_type_params: bool = False,
216229
ignore_pos_arg_names: bool = False,
217230
options: Options | None = None,
231+
subtype_context: SubtypeContext | None = None,
218232
) -> bool:
219233
return is_subtype(
220234
a,
221235
b,
222236
ignore_type_params=ignore_type_params,
223237
ignore_pos_arg_names=ignore_pos_arg_names,
224238
options=options,
239+
subtype_context=subtype_context,
225240
) and is_subtype(
226241
b,
227242
a,
228243
ignore_type_params=ignore_type_params,
229244
ignore_pos_arg_names=ignore_pos_arg_names,
230245
options=options,
246+
subtype_context=subtype_context,
231247
)
232248

233249

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

248268

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

308328

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

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

324353

325-
def ignore_type_parameter(lefta: Type, righta: Type, variance: int, proper_subtype: bool) -> bool:
354+
def ignore_type_parameter(
355+
lefta: Type, righta: Type, variance: int, proper_subtype: bool, subtype_context: SubtypeContext
356+
) -> bool:
326357
return True
327358

328359

@@ -385,7 +416,11 @@ def visit_none_type(self, left: NoneType) -> bool:
385416
return True
386417

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

390425
def visit_erased_type(self, left: ErasedType) -> bool:
391426
# This may be encountered during type inference. The result probably doesn't
@@ -521,12 +556,12 @@ def check_mixed(
521556
for lefta, righta, tvar in type_params:
522557
if isinstance(tvar, TypeVarType):
523558
if not self.check_type_parameter(
524-
lefta, righta, tvar.variance, self.proper_subtype
559+
lefta, righta, tvar.variance, self.proper_subtype, self.subtype_context
525560
):
526561
nominal = False
527562
else:
528563
if not self.check_type_parameter(
529-
lefta, righta, COVARIANT, self.proper_subtype
564+
lefta, righta, COVARIANT, self.proper_subtype, self.subtype_context
530565
):
531566
nominal = False
532567
if nominal:
@@ -694,6 +729,7 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool:
694729
if not left.names_are_wider_than(right):
695730
return False
696731
for name, l, r in left.zip(right):
732+
# TODO: should we pass on the full subtype_context here and below?
697733
if self.proper_subtype:
698734
check = is_same_type(l, r)
699735
else:

test-data/unit/check-overloading.test

+26
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)