diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 69c72198e32d..a2723511397e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1195,8 +1195,34 @@ def plausible_overload_call_targets(self, arg_kinds: List[int], arg_names: Optional[Sequence[Optional[str]]], overload: Overloaded) -> List[CallableType]: - """Returns all overload call targets that having matching argument counts.""" + """Returns all overload call targets that having matching argument counts. + + If the given args contains a star-arg (*arg or **kwarg argument), this method + will ensure all star-arg overloads appear at the start of the list, instead + of their usual location. + + The only exception is if the starred argument is something like a Tuple or a + NamedTuple, which has a definitive "shape". If so, we don't move the corresponding + alternative to the front since we can infer a more precise match using the original + order.""" + + def has_shape(typ: Type) -> bool: + # TODO: Once https://github.com/python/mypy/issues/5198 is fixed, + # add 'isinstance(typ, TypedDictType)' somewhere below. + return (isinstance(typ, TupleType) + or (isinstance(typ, Instance) and typ.type.is_named_tuple)) + matches = [] # type: List[CallableType] + star_matches = [] # type: List[CallableType] + + args_have_var_arg = False + args_have_kw_arg = False + for kind, typ in zip(arg_kinds, arg_types): + if kind == ARG_STAR and not has_shape(typ): + args_have_var_arg = True + if kind == ARG_STAR2 and not has_shape(typ): + args_have_kw_arg = True + for typ in overload.items(): formal_to_actual = map_actuals_to_formals(arg_kinds, arg_names, typ.arg_kinds, typ.arg_names, @@ -1204,9 +1230,14 @@ def plausible_overload_call_targets(self, if self.check_argument_count(typ, arg_types, arg_kinds, arg_names, formal_to_actual, None, None): - matches.append(typ) + if args_have_var_arg and typ.is_var_arg: + star_matches.append(typ) + elif args_have_kw_arg and typ.is_kw_arg: + star_matches.append(typ) + else: + matches.append(typ) - return matches + return star_matches + matches def infer_overload_return_type(self, plausible_targets: List[CallableType], @@ -1270,15 +1301,20 @@ def infer_overload_return_type(self, return None elif any_causes_overload_ambiguity(matches, return_types, arg_types, arg_kinds, arg_names): # An argument of type or containing the type 'Any' caused ambiguity. - # We infer a type of 'Any' - return self.check_call(callee=AnyType(TypeOfAny.special_form), - args=args, - arg_kinds=arg_kinds, - arg_names=arg_names, - context=context, - arg_messages=arg_messages, - callable_name=callable_name, - object_type=object_type) + if all(is_subtype(ret_type, return_types[-1]) for ret_type in return_types[:-1]): + # The last match is a supertype of all the previous ones, so it's safe + # to return that inferred type. + return return_types[-1], inferred_types[-1] + else: + # We give up and return 'Any'. + return self.check_call(callee=AnyType(TypeOfAny.special_form), + args=args, + arg_kinds=arg_kinds, + arg_names=arg_names, + context=context, + arg_messages=arg_messages, + callable_name=callable_name, + object_type=object_type) else: # Success! No ambiguity; return the first match. return return_types[0], inferred_types[0] @@ -3174,16 +3210,20 @@ def any_causes_overload_ambiguity(items: List[CallableType], matching_formals_unfiltered = [(item_idx, lookup[arg_idx]) for item_idx, lookup in enumerate(actual_to_formal) if lookup[arg_idx]] + + matching_returns = [] matching_formals = [] for item_idx, formals in matching_formals_unfiltered: - if len(formals) > 1: - # An actual maps to multiple formals -- give up as too - # complex, just assume it overlaps. - return True - matching_formals.append((item_idx, items[item_idx].arg_types[formals[0]])) - if (not all_same_types(t for _, t in matching_formals) and - not all_same_types(items[idx].ret_type - for idx, _ in matching_formals)): + matched_callable = items[item_idx] + matching_returns.append(matched_callable.ret_type) + + # Note: if an actual maps to multiple formals of differing types within + # a single callable, then we know at least one of those formals must be + # a different type then the formal(s) in some other callable. + # So it's safe to just append everything to the same list. + for formal in formals: + matching_formals.append(matched_callable.arg_types[formal]) + if not all_same_types(matching_formals) and not all_same_types(matching_returns): # Any maps to multiple different types, and the return types of these items differ. return True return False diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 3208036afe36..b4e9cd08b63f 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -1276,7 +1276,7 @@ def f(x: object) -> object: ... def f(x): pass a: Any -reveal_type(f(a)) # E: Revealed type is 'Any' +reveal_type(f(a)) # E: Revealed type is 'builtins.object' [case testOverloadWithOverlappingItemsAndAnyArgument2] from typing import overload, Any @@ -1288,7 +1288,7 @@ def f(x: float) -> float: ... def f(x): pass a: Any -reveal_type(f(a)) # E: Revealed type is 'Any' +reveal_type(f(a)) # E: Revealed type is 'builtins.float' [case testOverloadWithOverlappingItemsAndAnyArgument3] from typing import overload, Any @@ -1312,16 +1312,17 @@ def f(x: object, y: int, z: str) -> object: ... def f(x): pass a: Any -# Any causes ambiguity -reveal_type(f(a, 1, '')) # E: Revealed type is 'Any' +# Any causes ambiguity; we fall back to returning object since it's a +# supertype of int +reveal_type(f(a, 1, '')) # E: Revealed type is 'builtins.object' # Any causes no ambiguity reveal_type(f(1, a, a)) # E: Revealed type is 'builtins.int' reveal_type(f('', a, a)) # E: Revealed type is 'builtins.object' # Like above, but use keyword arguments. -reveal_type(f(y=1, z='', x=a)) # E: Revealed type is 'Any' +reveal_type(f(y=1, z='', x=a)) # E: Revealed type is 'builtins.object' reveal_type(f(y=a, z='', x=1)) # E: Revealed type is 'builtins.int' reveal_type(f(z='', x=1, y=a)) # E: Revealed type is 'builtins.int' -reveal_type(f(z='', x=a, y=1)) # E: Revealed type is 'Any' +reveal_type(f(z='', x=a, y=1)) # E: Revealed type is 'builtins.object' [case testOverloadWithOverlappingItemsAndAnyArgument5] from typing import overload, Any, Union @@ -1333,7 +1334,7 @@ def f(x: Union[int, float]) -> float: ... def f(x): pass a: Any -reveal_type(f(a)) # E: Revealed type is 'Any' +reveal_type(f(a)) # E: Revealed type is 'builtins.float' [case testOverloadWithOverlappingItemsAndAnyArgument6] from typing import overload, Any @@ -1343,7 +1344,7 @@ def f(x: int, y: int) -> int: ... @overload def f(x: float, y: int, z: str) -> float: ... @overload -def f(x: object, y: int, z: str, a: None) -> object: ... +def f(x: object, y: int, z: str, a: None) -> str: ... def f(x): pass a: Any @@ -1352,7 +1353,7 @@ reveal_type(f(*a)) # E: Revealed type is 'Any' reveal_type(f(a, *a)) # E: Revealed type is 'Any' reveal_type(f(1, *a)) # E: Revealed type is 'Any' reveal_type(f(1.1, *a)) # E: Revealed type is 'Any' -reveal_type(f('', *a)) # E: Revealed type is 'builtins.object' +reveal_type(f('', *a)) # E: Revealed type is 'builtins.str' [case testOverloadWithOverlappingItemsAndAnyArgument7] from typing import overload, Any @@ -1363,9 +1364,15 @@ def f(x: int, y: int, z: int) -> int: ... def f(x: object, y: int, z: int) -> object: ... def f(x): pass +@overload +def g(x: int, y: int, z: int) -> int: ... +@overload +def g(x: object, y: int, z: str) -> object: ... +def g(x): pass + a: Any -# TODO: We could infer 'int' here -reveal_type(f(1, *a)) # E: Revealed type is 'Any' +reveal_type(f(1, *a)) # E: Revealed type is 'builtins.int' +reveal_type(g(1, *a)) # E: Revealed type is 'builtins.object' [case testOverloadWithOverlappingItemsAndAnyArgument8] from typing import overload, Any @@ -1381,6 +1388,58 @@ a: Any reveal_type(f(a, 1, 1)) # E: Revealed type is 'builtins.str' reveal_type(f(1, *a)) # E: Revealed type is 'builtins.str' +[case testOverloadWithOverlappingItemsAndAnyArgument9] +from typing import overload, Any, List + +@overload +def f(x: List[int]) -> List[int]: ... +@overload +def f(x: List[Any]) -> List[Any]: ... +def f(x): pass + +a: Any +b: List[Any] +c: List[str] +d: List[int] +reveal_type(f(a)) # E: Revealed type is 'builtins.list[Any]' +reveal_type(f(b)) # E: Revealed type is 'builtins.list[Any]' +reveal_type(f(c)) # E: Revealed type is 'builtins.list[Any]' +reveal_type(f(d)) # E: Revealed type is 'builtins.list[builtins.int]' + +[builtins fixtures/list.pyi] + +[case testOverloadWithOverlappingItemsAndAnyArgument10] +from typing import overload, Any + +@overload +def f(*, x: int = 3, y: int = 3) -> int: ... +@overload +def f(**kwargs: str) -> str: ... +def f(*args, **kwargs): pass + +a: Any +i: int +reveal_type(f(x=a, y=i)) # E: Revealed type is 'builtins.int' +reveal_type(f(y=a)) # E: Revealed type is 'Any' + +[builtins fixtures/dict.pyi] + +[case testOverloadWithOverlappingItemsAndAnyArgument11] +from typing import overload, Any, Dict + +@overload +def f(x: int = 3, **kwargs: int) -> int: ... +@overload +def f(**kwargs: str) -> str: ... +def f(*args, **kwargs): pass + +a: Dict[str, Any] +i: int +reveal_type(f(x=i, **a)) # E: Revealed type is 'builtins.int' +reveal_type(f(**a)) # E: Revealed type is 'Any' + +[builtins fixtures/dict.pyi] + [case testOverloadOnOverloadWithType] from typing import Any, Type, TypeVar, overload from mod import MyInt @@ -1723,6 +1782,235 @@ def foo2(**kwargs: int) -> str: ... def foo2(*args: int) -> int: ... # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader [builtins fixtures/dict.pyi] +[case testOverloadVarargInputAndVarargDefinition] +from typing import overload, List + +class A: ... +class B: ... +class C: ... + +@overload +def foo(x: int) -> A: ... +@overload +def foo(x: int, y: int) -> B: ... +@overload +def foo(x: int, y: int, z: int, *args: int) -> C: ... +def foo(*args): pass + +reveal_type(foo(1)) # E: Revealed type is '__main__.A' +reveal_type(foo(1, 2)) # E: Revealed type is '__main__.B' +reveal_type(foo(1, 2, 3)) # E: Revealed type is '__main__.C' + +reveal_type(foo(*[1])) # E: Revealed type is '__main__.C' +reveal_type(foo(*[1, 2])) # E: Revealed type is '__main__.C' +reveal_type(foo(*[1, 2, 3])) # E: Revealed type is '__main__.C' + +x: List[int] +reveal_type(foo(*x)) # E: Revealed type is '__main__.C' + +y: List[str] +foo(*y) # E: No overload variant of "foo" matches argument type "List[str]" +[builtins fixtures/list.pyi] + +[case testOverloadMultipleVarargDefinition] +from typing import overload, List, Any + +class A: ... +class B: ... +class C: ... +class D: ... + +@overload +def foo(x: int) -> A: ... +@overload +def foo(x: int, y: int) -> B: ... +@overload +def foo(x: int, y: int, z: int, *args: int) -> C: ... +@overload +def foo(*x: str) -> D: ... +def foo(*args): pass + +reveal_type(foo(*[1, 2])) # E: Revealed type is '__main__.C' +reveal_type(foo(*["a", "b"])) # E: Revealed type is '__main__.D' + +x: List[Any] +reveal_type(foo(*x)) # E: Revealed type is 'Any' +[builtins fixtures/list.pyi] + +[case testOverloadMultipleVarargDefinitionComplex] +from typing import TypeVar, overload, Any, Callable + +T1 = TypeVar('T1') +T2 = TypeVar('T2') +T3 = TypeVar('T3') + +@overload +def chain_call(input_value: T1, + f1: Callable[[T1], T2]) -> T2: ... +@overload +def chain_call(input_value: T1, + f1: Callable[[T1], T2], + f2: Callable[[T2], T3]) -> T3: ... +@overload +def chain_call(input_value: T1, + *f_rest: Callable[[T1], T1]) -> T1: ... +@overload +def chain_call(input_value: T1, + f1: Callable[[T1], T2], + f2: Callable[[T2], T3], + f3: Callable[[T3], Any], + *f_rest: Callable[[Any], Any]) -> Any: ... +def chain_call(input_value, *f_rest): + for function in f_rest: + input_value = function(input_value) + return input_value + + +class A: ... +class B: ... +class C: ... +class D: ... + +def f(x: A) -> A: ... +def f1(x: A) -> B: ... +def f2(x: B) -> C: ... +def f3(x: C) -> D: ... + +reveal_type(chain_call(A(), f1, f2)) # E: Revealed type is '__main__.C*' +reveal_type(chain_call(A(), f1, f2, f3)) # E: Revealed type is 'Any' +reveal_type(chain_call(A(), f, f, f, f)) # E: Revealed type is '__main__.A' +[builtins fixtures/list.pyi] + +[case testOverloadVarargsSelection] +from typing import overload, Tuple +@overload +def f(x: int) -> Tuple[int]: ... +@overload +def f(x: int, y: int) -> Tuple[int, int]: ... +@overload +def f(*xs: int) -> Tuple[int, ...]: ... +def f(*args): pass + +i: int +reveal_type(f(i)) # E: Revealed type is 'Tuple[builtins.int]' +reveal_type(f(i, i)) # E: Revealed type is 'Tuple[builtins.int, builtins.int]' +reveal_type(f(i, i, i)) # E: Revealed type is 'builtins.tuple[builtins.int]' + +reveal_type(f(*[])) # E: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(f(*[i])) # E: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(f(*[i, i])) # E: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(f(*[i, i, i])) # E: Revealed type is 'builtins.tuple[builtins.int]' +[builtins fixtures/list.pyi] + +[case testOverloadVarargsSelectionWithTuples] +from typing import overload, Tuple +@overload +def f(x: int) -> Tuple[int]: ... +@overload +def f(x: int, y: int) -> Tuple[int, int]: ... +@overload +def f(*xs: int) -> Tuple[int, ...]: ... +def f(*args): pass + +i: int +reveal_type(f(*())) # E: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(f(*(i,))) # E: Revealed type is 'Tuple[builtins.int]' +reveal_type(f(*(i, i))) # E: Revealed type is 'Tuple[builtins.int, builtins.int]' +reveal_type(f(*(i, i, i))) # E: Revealed type is 'builtins.tuple[builtins.int]' +[builtins fixtures/tuple.pyi] + +[case testOverloadVarargsSelectionWithNamedTuples] +from typing import overload, Tuple, NamedTuple +@overload +def f(x: int, y: int) -> Tuple[int, int]: ... +@overload +def f(*xs: int) -> Tuple[int, ...]: ... +def f(*args): pass + +A = NamedTuple('A', [('x', int), ('y', int)]) +B = NamedTuple('B', [('a', int), ('b', int)]) +C = NamedTuple('C', [('a', int), ('b', int), ('c', int)]) + +a: A +b: B +c: C +reveal_type(f(*a)) # E: Revealed type is 'Tuple[builtins.int, builtins.int]' +reveal_type(f(*b)) # E: Revealed type is 'Tuple[builtins.int, builtins.int]' +reveal_type(f(*c)) # E: Revealed type is 'builtins.tuple[builtins.int]' +[builtins fixtures/tuple.pyi] + +[case testOverloadKwargsSelectionWithDict] +from typing import overload, Tuple, Dict +@overload +def f(*, x: int) -> Tuple[int]: ... +@overload +def f(*, x: int, y: int) -> Tuple[int, int]: ... +@overload +def f(**xs: int) -> Tuple[int, ...]: ... +def f(**kwargs): pass + +empty: Dict[str, int] +reveal_type(f(**empty)) # E: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(f(**{'x': 4})) # E: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(f(**{'x': 4, 'y': 4})) # E: Revealed type is 'builtins.tuple[builtins.int]' +reveal_type(f(**{'a': 4, 'b': 4, 'c': 4})) # E: Revealed type is 'builtins.tuple[builtins.int]' +[builtins fixtures/dict.pyi] + +[case testOverloadKwargsSelectionWithTypedDict-skip] +# TODO: Mypy doesn't seem to correctly destructure typed dicts in general. +# We should re-enable this once https://github.com/python/mypy/issues/5198 is resolved +from typing import overload, Tuple +from mypy_extensions import TypedDict +@overload +def f(*, x: int) -> Tuple[int]: ... +@overload +def f(*, x: int, y: int) -> Tuple[int, int]: ... +@overload +def f(**xs: int) -> Tuple[int, ...]: ... +def f(**args): pass + +A = TypedDict('A', {'x': int}) +B = TypedDict('B', {'x': int, 'y': int}) +C = TypedDict('C', {'x': int, 'y': int, 'z': int}) + +a: A +b: B +c: C + +reveal_type(f(**a)) # E: Revealed type is 'Tuple[builtins.int]' +reveal_type(f(**b)) # E: Revealed type is 'Tuple[builtins.int, builtins.int]' +reveal_type(f(**c)) # E: Revealed type is 'builtins.tuple[builtins.int]' +[builtins fixtures/dict.pyi] + +[case testOverloadVarargsAndKwargsSelection] +from typing import overload, Any, Tuple, Dict + +class A: pass +class B(A): pass + +@overload +def f(x: int, y: int) -> B: pass +@overload +def f(x: int, y: int, **kwargs: int) -> A: pass +@overload +def f(*args: int, **kwargs: int) -> Any: pass +def f(*args, **kwargs): pass + +a: Tuple[int, int] +b: Tuple[int, ...] +c: Dict[str, int] + +reveal_type(f(*a, **c)) # E: Revealed type is '__main__.A' +reveal_type(f(*b, **c)) # E: Revealed type is '__main__.A' +reveal_type(f(*a)) # E: Revealed type is '__main__.B' +reveal_type(f(*b)) # E: Revealed type is 'Any' + +# TODO: Should this be 'Any' instead? +# The first matching overload with a kwarg is f(int, int, **int) -> A, +# but f(*int, **int) -> Any feels like a better fit. +reveal_type(f(**c)) # E: Revealed type is '__main__.A' +[builtins fixtures/args.pyi] + [case testOverloadWithPartiallyOverlappingUnions] from typing import overload, Union