Skip to content

Commit 5dc7454

Browse files
authored
Choose best type when working with unioned overloads (#5242)
Fixes #5240
1 parent 0f3f09a commit 5dc7454

File tree

2 files changed

+30
-7
lines changed

2 files changed

+30
-7
lines changed

mypy/checkexpr.py

+16-7
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,7 @@ def check_overload_call(self,
11291129
erased_targets = None # type: Optional[List[CallableType]]
11301130
unioned_result = None # type: Optional[Tuple[Type, Type]]
11311131
unioned_errors = None # type: Optional[MessageBuilder]
1132+
union_success = False
11321133
if any(isinstance(arg, UnionType) and len(arg.relevant_items()) > 1 # "real" union
11331134
for arg in arg_types):
11341135
erased_targets = self.overload_erased_call_targets(plausible_targets, arg_types,
@@ -1142,18 +1143,26 @@ def check_overload_call(self,
11421143
arg_messages=unioned_errors,
11431144
callable_name=callable_name,
11441145
object_type=object_type)
1145-
if not unioned_errors.is_errors():
1146-
# Success! Stop early.
1147-
return unioned_result
1146+
# Record if we succeeded. Next we need to see if maybe normal procedure
1147+
# gives a narrower type.
1148+
union_success = unioned_result is not None and not unioned_errors.is_errors()
11481149

1149-
# Step 3: If the union math fails, or if there was no union in the argument types,
1150-
# we fall back to checking each branch one-by-one.
1150+
# Step 3: We try checking each branch one-by-one.
11511151
inferred_result = self.infer_overload_return_type(plausible_targets, args, arg_types,
11521152
arg_kinds, arg_names, callable_name,
11531153
object_type, context, arg_messages)
11541154
if inferred_result is not None:
1155-
# Success! Stop early.
1156-
return inferred_result
1155+
# Success! Stop early by returning the best among normal and unioned.
1156+
if not union_success:
1157+
return inferred_result
1158+
else:
1159+
assert unioned_result is not None
1160+
if is_subtype(inferred_result[0], unioned_result[0]):
1161+
return inferred_result
1162+
return unioned_result
1163+
elif union_success:
1164+
assert unioned_result is not None
1165+
return unioned_result
11571166

11581167
# Step 4: Failure. At this point, we know there is no match. We fall back to trying
11591168
# to find a somewhat plausible overload target using the erased types

test-data/unit/check-overloading.test

+14
Original file line numberDiff line numberDiff line change
@@ -3611,6 +3611,20 @@ class Wrapper:
36113611

36123612
[builtins fixtures/staticmethod.pyi]
36133613

3614+
[case testUnionMathOverloadingReturnsBestType]
3615+
from typing import Union, overload
3616+
3617+
@overload
3618+
def f(x: Union[int, str]) -> int: ...
3619+
@overload
3620+
def f(x: object) -> object: ...
3621+
def f(x):
3622+
pass
3623+
3624+
x: Union[int, str]
3625+
reveal_type(f(x)) # E: Revealed type is 'builtins.int'
3626+
[out]
3627+
36143628
[case testOverloadAndSelfTypes]
36153629
from typing import overload, Union, TypeVar, Type
36163630

0 commit comments

Comments
 (0)