Skip to content

Commit a75fa88

Browse files
Michael0x2ailevkivskyi
authored andcommitted
Make overload checks more strict when there are multiple 'Any's (#5254)
* Make overload checks more strict when there are multiple 'Any's Resolves #5250 This makes the "multiple overload matches due to Any" even more strict: we now return a non-Any type only if all of the return types are the same. * Make overlaps due to Any revert to using erased types if possible This change also modifies how mypy erases callables. Previously, callables of type 'Callable[[A, B, ...], R]' were erased to 'Callable[[], None]'. This change will now make the erasure be 'Callable[..., Any]', largely on the grounds that it seems more useful.
1 parent 01adcf4 commit a75fa88

File tree

4 files changed

+111
-17
lines changed

4 files changed

+111
-17
lines changed

mypy/checkexpr.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import mypy.checker
3030
from mypy import types
3131
from mypy.sametypes import is_same_type
32-
from mypy.erasetype import replace_meta_vars
32+
from mypy.erasetype import replace_meta_vars, erase_type
3333
from mypy.messages import MessageBuilder
3434
from mypy import messages
3535
from mypy.infer import infer_type_arguments, infer_function_type_arguments
@@ -1314,14 +1314,12 @@ def infer_overload_return_type(self,
13141314
return None
13151315
elif any_causes_overload_ambiguity(matches, return_types, arg_types, arg_kinds, arg_names):
13161316
# An argument of type or containing the type 'Any' caused ambiguity.
1317-
if all(is_subtype(ret_type, return_types[-1]) and
1318-
is_subtype(return_types[-1], ret_type)
1319-
for ret_type in return_types[:-1]):
1320-
# The last match is mutually compatible with all previous ones, so it's safe
1321-
# to return that inferred type.
1322-
return return_types[-1], inferred_types[-1]
1317+
# We try returning a precise type if we can. If not, we give up and just return 'Any'.
1318+
if all_same_types(return_types):
1319+
return return_types[0], inferred_types[0]
1320+
elif all_same_types(erase_type(typ) for typ in return_types):
1321+
return erase_type(return_types[0]), erase_type(inferred_types[0])
13231322
else:
1324-
# We give up and return 'Any'.
13251323
return self.check_call(callee=AnyType(TypeOfAny.special_form),
13261324
args=args,
13271325
arg_kinds=arg_kinds,

mypy/erasetype.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55
CallableType, TupleType, TypedDictType, UnionType, Overloaded, ErasedType, PartialType,
66
DeletedType, TypeTranslator, UninhabitedType, TypeType, TypeOfAny
77
)
8+
from mypy.nodes import ARG_STAR, ARG_STAR2
89

910

1011
def erase_type(typ: Type) -> Type:
1112
"""Erase any type variables from a type.
1213
13-
Also replace tuple types with the corresponding concrete types. Replace
14-
callable types with empty callable types.
14+
Also replace tuple types with the corresponding concrete types.
1515
1616
Examples:
1717
A -> A
1818
B[X] -> B[Any]
1919
Tuple[A, B] -> tuple
20-
Callable[...] -> Callable[[], None]
20+
Callable[[A1, A2, ...], R] -> Callable[..., Any]
2121
Type[X] -> Type[Any]
2222
"""
2323

@@ -57,11 +57,19 @@ def visit_type_var(self, t: TypeVarType) -> Type:
5757

5858
def visit_callable_type(self, t: CallableType) -> Type:
5959
# We must preserve the fallback type for overload resolution to work.
60-
ret_type = NoneTyp() # type: Type
61-
return CallableType([], [], [], ret_type, t.fallback)
60+
any_type = AnyType(TypeOfAny.special_form)
61+
return CallableType(
62+
arg_types=[any_type, any_type],
63+
arg_kinds=[ARG_STAR, ARG_STAR2],
64+
arg_names=[None, None],
65+
ret_type=any_type,
66+
fallback=t.fallback,
67+
is_ellipsis_args=True,
68+
implicit=True,
69+
)
6270

6371
def visit_overloaded(self, t: Overloaded) -> Type:
64-
return t.items()[0].accept(self)
72+
return t.fallback.accept(self)
6573

6674
def visit_tuple_type(self, t: TupleType) -> Type:
6775
return t.fallback.accept(self)

mypy/test/testtypes.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
UnboundType, AnyType, CallableType, TupleType, TypeVarDef, Type, Instance, NoneTyp, Overloaded,
1212
TypeType, UnionType, UninhabitedType, true_only, false_only, TypeVarId, TypeOfAny
1313
)
14-
from mypy.nodes import ARG_POS, ARG_OPT, ARG_STAR, CONTRAVARIANT, INVARIANT, COVARIANT
14+
from mypy.nodes import ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2, CONTRAVARIANT, INVARIANT, COVARIANT
1515
from mypy.subtypes import is_subtype, is_more_precise, is_proper_subtype
1616
from mypy.test.typefixture import TypeFixture, InterfaceTypeFixture
1717

@@ -150,11 +150,19 @@ def test_erase_with_tuple_type(self) -> None:
150150

151151
def test_erase_with_function_type(self) -> None:
152152
self.assert_erase(self.fx.callable(self.fx.a, self.fx.b),
153-
self.fx.callable_type(self.fx.nonet))
153+
CallableType(arg_types=[self.fx.anyt, self.fx.anyt],
154+
arg_kinds=[ARG_STAR, ARG_STAR2],
155+
arg_names=[None, None],
156+
ret_type=self.fx.anyt,
157+
fallback=self.fx.function))
154158

155159
def test_erase_with_type_object(self) -> None:
156160
self.assert_erase(self.fx.callable_type(self.fx.a, self.fx.b),
157-
self.fx.callable_type(self.fx.nonet))
161+
CallableType(arg_types=[self.fx.anyt, self.fx.anyt],
162+
arg_kinds=[ARG_STAR, ARG_STAR2],
163+
arg_names=[None, None],
164+
ret_type=self.fx.anyt,
165+
fallback=self.fx.type_type))
158166

159167
def test_erase_with_type_type(self) -> None:
160168
self.assert_erase(self.fx.type_a, self.fx.type_a)

test-data/unit/check-overloading.test

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,86 @@ reveal_type(f(**a)) # E: Revealed type is 'Any'
14431443

14441444
[builtins fixtures/dict.pyi]
14451445

1446+
[case testOverloadWithOverlappingItemsAndAnyArgument12]
1447+
from typing import overload, Any
1448+
1449+
@overload
1450+
def f(x: int) -> Any: ...
1451+
@overload
1452+
def f(x: str) -> str: ...
1453+
def f(x): pass
1454+
1455+
a: Any
1456+
reveal_type(f(a)) # E: Revealed type is 'Any'
1457+
1458+
[case testOverloadWithOverlappingItemsAndAnyArgument13]
1459+
from typing import Any, overload, TypeVar, Generic
1460+
1461+
class slice: pass
1462+
1463+
T = TypeVar('T')
1464+
class A(Generic[T]):
1465+
@overload
1466+
def f(self, x: int) -> T: ...
1467+
@overload
1468+
def f(self, x: slice) -> A[T]: ...
1469+
def f(self, x): ...
1470+
1471+
i: Any
1472+
a: A[Any]
1473+
reveal_type(a.f(i)) # E: Revealed type is 'Any'
1474+
1475+
[case testOverloadWithOverlappingItemsAndAnyArgument14]
1476+
from typing import Any, overload, TypeVar, Generic
1477+
1478+
T = TypeVar('T')
1479+
1480+
class Wrapper(Generic[T]): pass
1481+
class slice: pass
1482+
1483+
class A(Generic[T]):
1484+
@overload
1485+
def f(self, x: int) -> Wrapper[T]: ...
1486+
@overload
1487+
def f(self, x: slice) -> Wrapper[A[T]]: ...
1488+
def f(self, x): ...
1489+
1490+
i: Any
1491+
a: A[Any]
1492+
reveal_type(a.f(i)) # E: Revealed type is '__main__.Wrapper[Any]'
1493+
1494+
[case testOverloadWithOverlappingItemsAndAnyArgument15]
1495+
from typing import overload, Any, Union
1496+
1497+
@overload
1498+
def f(x: int) -> str: ...
1499+
@overload
1500+
def f(x: str) -> str: ...
1501+
def f(x): pass
1502+
1503+
@overload
1504+
def g(x: int) -> Union[str, int]: ...
1505+
@overload
1506+
def g(x: str) -> Union[int, str]: ...
1507+
def g(x): pass
1508+
1509+
a: Any
1510+
reveal_type(f(a)) # E: Revealed type is 'builtins.str'
1511+
reveal_type(g(a)) # E: Revealed type is 'Union[builtins.str, builtins.int]'
1512+
1513+
[case testOverloadWithOverlappingItemsAndAnyArgument16]
1514+
from typing import overload, Any, Union, Callable
1515+
1516+
@overload
1517+
def f(x: int) -> Callable[[int, int], int]: ...
1518+
@overload
1519+
def f(x: str) -> Callable[[str], str]: ...
1520+
def f(x): pass
1521+
1522+
a: Any
1523+
reveal_type(f(a)) # E: Revealed type is 'def (*Any, **Any) -> Any'
1524+
reveal_type(f(a)(a)) # E: Revealed type is 'Any'
1525+
14461526
[case testOverloadOnOverloadWithType]
14471527
from typing import Any, Type, TypeVar, overload
14481528
from mod import MyInt

0 commit comments

Comments
 (0)