Skip to content

Commit 101070c

Browse files
committed
Fix overload resolution returning Any instead of Never when argument contains Any
When a function has overloads where one returns Never and another returns a concrete type, and the argument has type Any (causing overload ambiguity), mypy was falling back to Any instead of using the Never-returning overload. Never is a subtype of every type, so an overload returning Never is always the most specific possible result. When Any-argument ambiguity fires and exactly one matching overload returns Never, there is no real ambiguity to resolve: the Never overload is the right answer. This was particularly visible with iter() on generic classes that have both __iter__ (returning Never) and __getitem__: iter(x) was returning Any when x had an Any type argument, while x.__iter__() correctly returned Never. Fixes #21027
1 parent f779f77 commit 101070c

2 files changed

Lines changed: 43 additions & 0 deletions

File tree

mypy/checkexpr.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3092,6 +3092,20 @@ def infer_overload_return_type(
30923092
self.chk.store_types(type_maps[0])
30933093
return erase_type(return_types[0]), erase_type(inferred_types[0])
30943094
else:
3095+
# If exactly one match returns Never (uninhabited, non-ambiguous), it is the
3096+
# most specific result possible and there is no real ambiguity: Never is a
3097+
# subtype of every type, so the overload that returns Never is always the
3098+
# most precise answer regardless of which overload would otherwise be chosen.
3099+
never_indices = [
3100+
i
3101+
for i, t in enumerate(return_types)
3102+
if isinstance(get_proper_type(t), UninhabitedType)
3103+
and not get_proper_type(t).ambiguous # type: ignore[union-attr]
3104+
]
3105+
if len(never_indices) == 1:
3106+
idx = never_indices[0]
3107+
self.chk.store_types(type_maps[idx])
3108+
return return_types[idx], inferred_types[idx]
30953109
return self.check_call(
30963110
callee=AnyType(TypeOfAny.special_form),
30973111
args=args,

test-data/unit/check-overloading.test

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,6 +1906,35 @@ a: Any
19061906
reveal_type(f(a)) # N: Revealed type is "def (*Any, **Any) -> Any"
19071907
reveal_type(f(a)(a)) # N: Revealed type is "Any"
19081908

1909+
[case testOverloadWithOverlappingItemsAndAnyArgumentNeverReturn]
1910+
# Regression test for https://github.com/python/mypy/issues/21027
1911+
# When one overload returns Never, it is always the most specific result and
1912+
# should be chosen even when an argument contains Any that would otherwise
1913+
# cause overload ambiguity.
1914+
from typing import Any, Generic, Never, TypeVar, overload
1915+
1916+
T = TypeVar('T')
1917+
1918+
@overload
1919+
def f(x: int) -> Never: ...
1920+
@overload
1921+
def f(x: str) -> int: ...
1922+
def f(x): ...
1923+
1924+
def test_plain(a: Any) -> None:
1925+
reveal_type(f(a)) # N: Revealed type is "Never"
1926+
1927+
T2 = TypeVar('T2')
1928+
class A(Generic[T2]):
1929+
@overload
1930+
def g(self, x: int) -> Never: ...
1931+
@overload
1932+
def g(self, x: str) -> T2: ...
1933+
def g(self, x): ...
1934+
1935+
def test_generic(obj: A[Any], a: Any) -> None:
1936+
reveal_type(obj.g(a)) # N: Revealed type is "Never"
1937+
19091938
[case testOverloadOnOverloadWithType]
19101939
from typing import Any, Type, TypeVar, overload
19111940
from mod import MyInt

0 commit comments

Comments
 (0)