diff --git a/mypy/checker.py b/mypy/checker.py index 4b57be30d2514..feedc5a820a47 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3621,7 +3621,7 @@ def is_unsafe_overlapping_overload_signatures(signature: CallableType, Assumes that 'signature' appears earlier in the list of overload alternatives then 'other' and that their argument counts are overlapping. """ - # TODO: Handle partially overlapping parameter types and argument counts + # TODO: Handle partially overlapping parameter types # # For example, the signatures "f(x: Union[A, B]) -> int" and "f(x: Union[B, C]) -> str" # is unsafe: the parameter types are partially overlapping. @@ -3632,27 +3632,15 @@ def is_unsafe_overlapping_overload_signatures(signature: CallableType, # # (We already have a rudimentary implementation of 'is_partially_overlapping', but it only # attempts to handle the obvious cases -- see its docstring for more info.) - # - # Similarly, the signatures "f(x: A, y: A) -> str" and "f(*x: A) -> int" are also unsafe: - # the parameter *counts* or arity are partially overlapping. - # - # To fix this, we need to modify is_callable_compatible so it can optionally detect - # functions that are *potentially* compatible rather then *definitely* compatible. def is_more_precise_or_partially_overlapping(t: Type, s: Type) -> bool: return is_more_precise(t, s) or is_partially_overlapping_types(t, s) - # The reason we repeat this check twice is so we can do a slightly better job of - # checking for potentially overlapping param counts. Both calls will actually check - # the param and return types in the same "direction" -- the only thing that differs - # is how is_callable_compatible checks non-positional arguments. - return (is_callable_compatible(signature, other, - is_compat=is_more_precise_or_partially_overlapping, - is_compat_return=lambda l, r: not is_subtype(l, r), - check_args_covariantly=True) or - is_callable_compatible(other, signature, - is_compat=is_more_precise_or_partially_overlapping, - is_compat_return=lambda l, r: not is_subtype(r, l))) + return is_callable_compatible(signature, other, + is_compat=is_more_precise_or_partially_overlapping, + is_compat_return=lambda l, r: not is_subtype(l, r), + check_args_covariantly=True, + allow_partial_overlap=True) def overload_can_never_match(signature: CallableType, other: CallableType) -> bool: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 13fe3b3c4c849..8ef43883a9a58 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -577,7 +577,8 @@ def is_callable_compatible(left: CallableType, right: CallableType, is_compat_return: Optional[Callable[[Type, Type], bool]] = None, ignore_return: bool = False, ignore_pos_arg_names: bool = False, - check_args_covariantly: bool = False) -> bool: + check_args_covariantly: bool = False, + allow_partial_overlap: bool = False) -> bool: """Is the left compatible with the right, using the provided compatibility check? is_compat: @@ -616,6 +617,55 @@ def g(x: int) -> int: ... In this case, the first call will succeed and the second will fail: f is a valid stand-in for g but not vice-versa. + + allow_partial_overlap: + By default this function returns True if and only if *all* calls to left are + also calls to right (with respect to the provided 'is_compat' function). + + If this parameter is set to 'True', we return True if *there exists at least one* + call to left that's also a call to right. + + In other words, we perform an existential check instead of a universal one; + we require left to only overlap with right instead of being a subset. + + For example, suppose we set 'is_compat' to some subtype check and compare following: + + f(x: float, y: str = "...", *args: bool) -> str + g(*args: int) -> str + + This function would normally return 'False': f is not a subtype of g. + However, we would return True if this parameter is set to 'True': the two + calls are compatible if the user runs "f_or_g(3)". In the context of that + specific call, the two functions effectively have signatures of: + + f2(float) -> str + g2(int) -> str + + Here, f2 is a valid subtype of g2 so we return True. + + Specifically, if this parameter is set this function will: + + - Ignore optional arguments on either the left or right that have no + corresponding match. + - No longer mandate optional arguments on either side are also optional + on the other. + - No longer mandate that if right has a *arg or **kwarg that left must also + have the same. + + Note: when this argument is set to True, this function becomes "symmetric" -- + the following calls are equivalent: + + is_callable_compatible(f, g, + is_compat=some_check, + check_args_covariantly=False, + allow_partial_overlap=True) + is_callable_compatible(g, f, + is_compat=some_check, + check_args_covariantly=True, + allow_partial_overlap=True) + + If the 'some_check' function is also symmetric, the two calls would be equivalent + whether or not we check the args covariantly. """ if is_compat_return is None: is_compat_return = is_compat @@ -638,7 +688,6 @@ def g(x: int) -> int: ... # type variables of L, because generating and solving # constraints for the variables of L to make L a subtype of R # (below) treats type variables on the two sides as independent. - if left.variables: # Apply generic type variables away in left via type inference. unified = unify_generic_callable(left, right, ignore_return=ignore_return) @@ -647,6 +696,17 @@ def g(x: int) -> int: ... else: left = unified + # If we allow partial overlaps, we don't need to leave R generic: + # if we can find even just a single typevar assignment which + # would make these callables compatible, we should return True. + + # So, we repeat the above checks in the opposite direction. This also + # lets us preserve the 'symmetry' property of allow_partial_overlap. + if allow_partial_overlap and right.variables: + unified = unify_generic_callable(right, left, ignore_return=ignore_return) + if unified is not None: + right = unified + # Check return types. if not ignore_return and not is_compat_return(left.ret_type, right.ret_type): return False @@ -657,16 +717,17 @@ def g(x: int) -> int: ... if right.is_ellipsis_args: return True - right_star_type = None # type: Optional[Type] - right_star2_type = None # type: Optional[Type] + left_star = left.var_arg + left_star2 = left.kw_arg + right_star = right.var_arg + right_star2 = right.kw_arg # Match up corresponding arguments and check them for compatibility. In # every pair (argL, argR) of corresponding arguments from L and R, argL must # be "more general" than argR if L is to be a subtype of R. # Arguments are corresponding if they either share a name, share a position, - # or both. If L's corresponding argument is ambiguous, L is not a subtype of - # R. + # or both. If L's corresponding argument is ambiguous, L is not a subtype of R. # If left has one corresponding argument by name and another by position, # consider them to be one "merged" argument (and not ambiguous) if they're @@ -677,94 +738,92 @@ def g(x: int) -> int: ... # Every argument in R must have a corresponding argument in L, and every # required argument in L must have a corresponding argument in R. - done_with_positional = False - for i in range(len(right.arg_types)): - right_kind = right.arg_kinds[i] - if right_kind in (ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT): - done_with_positional = True - right_required = right_kind in (ARG_POS, ARG_NAMED) - right_pos = None if done_with_positional else i - - right_arg = FormalArgument( - right.arg_names[i], - right_pos, - right.arg_types[i], - right_required) - - if right_kind == ARG_STAR: - right_star_type = right_arg.typ - # Right has an infinite series of optional positional arguments - # here. Get all further positional arguments of left, and make sure - # they're more general than their corresponding member in this - # series. Also make sure left has its own infinite series of - # optional positional arguments. - if not left.is_var_arg: - return False - j = i - while j < len(left.arg_kinds) and left.arg_kinds[j] in (ARG_POS, ARG_OPT): - left_by_position = left.argument_by_position(j) - assert left_by_position is not None - # This fetches the synthetic argument that's from the *args - right_by_position = right.argument_by_position(j) - assert right_by_position is not None - if not are_args_compatible(left_by_position, right_by_position, - ignore_pos_arg_names, is_compat): - return False - j += 1 - continue - - if right_kind == ARG_STAR2: - right_star2_type = right_arg.typ - # Right has an infinite set of optional named arguments here. Get - # all further named arguments of left and make sure they're more - # general than their corresponding member in this set. Also make - # sure left has its own infinite set of optional named arguments. - if not left.is_kw_arg: - return False - left_names = {name for name in left.arg_names if name is not None} - right_names = {name for name in right.arg_names if name is not None} - left_only_names = left_names - right_names - for name in left_only_names: - left_by_name = left.argument_by_name(name) - assert left_by_name is not None - # This fetches the synthetic argument that's from the **kwargs - right_by_name = right.argument_by_name(name) - assert right_by_name is not None - if not are_args_compatible(left_by_name, right_by_name, - ignore_pos_arg_names, is_compat): - return False - continue - # Left must have some kind of corresponding argument. + # Phase 1: Confirm every argument in R has a corresponding argument in L. + + # Phase 1a: If left and right can both accept an infinite number of args, + # their types must be compatible. + # + # Furthermore, if we're checking for compatibility in all cases, + # we confirm that if R accepts an infinite number of arguments, + # L must accept the same. + def _incompatible(left_arg: Optional[FormalArgument], + right_arg: Optional[FormalArgument]) -> bool: + if right_arg is None: + return False + if left_arg is None: + return not allow_partial_overlap + return not is_compat(right_arg.typ, left_arg.typ) + + if _incompatible(left_star, right_star) or _incompatible(left_star2, right_star2): + return False + + # Phase 1b: Check non-star args: for every arg right can accept, left must + # also accept. The only exception is if we are allowing partial + # partial overlaps: in that case, we ignore optional args on the right. + for right_arg in right.formal_arguments(): left_arg = left.corresponding_argument(right_arg) if left_arg is None: + if allow_partial_overlap and not right_arg.required: + continue return False - - if not are_args_compatible(left_arg, right_arg, - ignore_pos_arg_names, is_compat): + if not are_args_compatible(left_arg, right_arg, ignore_pos_arg_names, + allow_partial_overlap, is_compat): return False - done_with_positional = False - for i in range(len(left.arg_types)): - left_kind = left.arg_kinds[i] - if left_kind in (ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT): - done_with_positional = True - left_arg = FormalArgument( - left.arg_names[i], - None if done_with_positional else i, - left.arg_types[i], - left_kind in (ARG_POS, ARG_NAMED)) - - # Check that *args and **kwargs types match in this loop - if left_kind == ARG_STAR: - if right_star_type is not None and not is_compat(right_star_type, left_arg.typ): + # Phase 1c: Check var args. Right has an infinite series of optional positional + # arguments. Get all further positional args of left, and make sure + # they're more general then the corresponding member in right. + if right_star is not None: + # Synthesize an anonymous formal argument for the right + right_by_position = right.try_synthesizing_arg_from_vararg(None) + assert right_by_position is not None + + i = right_star.pos + assert i is not None + while i < len(left.arg_kinds) and left.arg_kinds[i] in (ARG_POS, ARG_OPT): + if allow_partial_overlap and left.arg_kinds[i] == ARG_OPT: + break + + left_by_position = left.argument_by_position(i) + assert left_by_position is not None + + if not are_args_compatible(left_by_position, right_by_position, + ignore_pos_arg_names, allow_partial_overlap, + is_compat): return False - continue - elif left_kind == ARG_STAR2: - if right_star2_type is not None and not is_compat(right_star2_type, left_arg.typ): + i += 1 + + # Phase 1d: Check kw args. Right has an infinite series of optional named + # arguments. Get all further named args of left, and make sure + # they're more general then the corresponding member in right. + if right_star2 is not None: + right_names = {name for name in right.arg_names if name is not None} + left_only_names = set() + for name, kind in zip(left.arg_names, left.arg_kinds): + if name is None or kind in (ARG_STAR, ARG_STAR2) or name in right_names: + continue + left_only_names.add(name) + + # Synthesize an anonymous formal argument for the right + right_by_name = right.try_synthesizing_arg_from_kwarg(None) + assert right_by_name is not None + + for name in left_only_names: + left_by_name = left.argument_by_name(name) + assert left_by_name is not None + + if allow_partial_overlap and not left_by_name.required: + continue + + if not are_args_compatible(left_by_name, right_by_name, ignore_pos_arg_names, + allow_partial_overlap, is_compat): return False - continue + # Phase 2: Left must not impose additional restrictions. + # (Every required argument in L must have a corresponding argument in R) + # Note: we already checked the *arg and **kwarg arguments in phase 1a. + for left_arg in left.formal_arguments(): right_by_name = (right.argument_by_name(left_arg.name) if left_arg.name is not None else None) @@ -782,7 +841,7 @@ def g(x: int) -> int: ... return False # All *required* left-hand arguments must have a corresponding - # right-hand argument. Optional args it does not matter. + # right-hand argument. Optional args do not matter. if left_arg.required and right_by_pos is None and right_by_name is None: return False @@ -793,23 +852,46 @@ def are_args_compatible( left: FormalArgument, right: FormalArgument, ignore_pos_arg_names: bool, + allow_partial_overlap: bool, is_compat: Callable[[Type, Type], bool]) -> bool: + def is_different(left_item: Optional[object], right_item: Optional[object]) -> bool: + """Checks if the left and right items are different. + + If the right item is unspecified (e.g. if the right callable doesn't care + about what name or position its arg has), we default to returning False. + + If we're allowing partial overlap, we also default to returning False + if the left callable also doesn't care.""" + if right_item is None: + return False + if allow_partial_overlap and left_item is None: + return False + return left_item != right_item + # If right has a specific name it wants this argument to be, left must # have the same. - if right.name is not None and left.name != right.name: + if is_different(left.name, right.name): # But pay attention to whether we're ignoring positional arg names if not ignore_pos_arg_names or right.pos is None: return False + # If right is at a specific position, left must have the same: - if right.pos is not None and left.pos != right.pos: + if is_different(left.pos, right.pos): return False - # Left must have a more general type - if not is_compat(right.typ, left.typ): - return False - # If right's argument is optional, left's must also be. - if not right.required and left.required: + + # If right's argument is optional, left's must also be + # (unless we're relaxing the checks to allow potential + # rather then definite compatibility). + if not allow_partial_overlap and not right.required and left.required: return False - return True + + # If we're allowing partial overlaps and neither arg is required, + # the types don't actually need to be the same + if allow_partial_overlap and not left.required and not right.required: + return True + + # Left must have a more general type + return is_compat(right.typ, left.typ) def flip_compat_check(is_compat: Callable[[Type, Type], bool]) -> Callable[[Type, Type], bool]: diff --git a/mypy/types.py b/mypy/types.py index 209cb860aecbb..040ac50e816ca 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -7,7 +7,7 @@ from enum import Enum from typing import ( Any, TypeVar, Dict, List, Tuple, cast, Generic, Set, Optional, Union, Iterable, NamedTuple, - Callable, Sequence + Callable, Sequence, Iterator ) import mypy.nodes @@ -660,8 +660,8 @@ class CallableType(FunctionLike): 'arg_kinds', # ARG_ constants 'arg_names', # Argument names; None if not a keyword argument 'min_args', # Minimum number of arguments; derived from arg_kinds - 'is_var_arg', # Is it a varargs function? Derived from arg_kinds - 'is_kw_arg', # Is it a **kwargs function? Derived from arg_kinds + 'var_arg', # The formal argument for *args. Derived from arg kinds and types + 'kw_arg', # The formal argument for **kwargs. Derived from arg kinds and types 'ret_type', # Return value type 'name', # Name (may be None; for error messages and plugins) 'definition', # For error messages. May be None. @@ -710,8 +710,7 @@ def __init__(self, self.arg_kinds = arg_kinds self.arg_names = list(arg_names) self.min_args = arg_kinds.count(ARG_POS) - self.is_var_arg = ARG_STAR in arg_kinds - self.is_kw_arg = ARG_STAR2 in arg_kinds + self.var_arg, self.kw_arg = self._lookup_star_args(self.arg_types, self.arg_kinds) self.ret_type = ret_type self.fallback = fallback assert not name or ' Tuple[Optional[FormalArgument], Optional[FormalArgument]]: + """Returns the formal arguments for *args and **kwargs, if they exist. + + This helper method is used only in the constructor.""" + star_arg = None + kwarg_arg = None + for position, (type, kind) in enumerate(zip(arg_types, arg_kinds)): + if kind == ARG_STAR: + star_arg = FormalArgument(None, position, type, False) + elif kind == ARG_STAR2: + kwarg_arg = FormalArgument(None, position, type, False) + return star_arg, kwarg_arg + + @property + def is_var_arg(self) -> bool: + """Does this callable have a *args argument?""" + return self.var_arg is not None + + @property + def is_kw_arg(self) -> bool: + """Does this callable have a **kwargs argument?""" + return self.kw_arg is not None + def is_type_obj(self) -> bool: return self.fallback.type.is_metaclass() @@ -818,6 +843,30 @@ def max_possible_positional_args(self) -> int: blacklist = (ARG_NAMED, ARG_NAMED_OPT) return len([kind not in blacklist for kind in self.arg_kinds]) + def formal_arguments(self, include_star_args: bool = False) -> Iterator[FormalArgument]: + """Yields the formal arguments corresponding to this callable, ignoring *arg and **kwargs. + + To handle *args and **kwargs, use the 'callable.var_args' and 'callable.kw_args' fields, + if they are not None. + + If you really want to include star args in the yielded output, set the + 'include_star_args' parameter to 'True'.""" + done_with_positional = False + for i in range(len(self.arg_types)): + kind = self.arg_kinds[i] + if kind in (ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT): + done_with_positional = True + if not include_star_args and kind in (ARG_STAR, ARG_STAR2): + continue + + required = kind in (ARG_POS, ARG_NAMED) + pos = None if done_with_positional else i + yield FormalArgument( + self.arg_names[i], + pos, + self.arg_types[i], + required) + def corresponding_argument(self, model: FormalArgument) -> Optional[FormalArgument]: """Return the argument in this function that corresponds to `model`""" @@ -846,37 +895,23 @@ def argument_by_name(self, name: Optional[str]) -> Optional[FormalArgument]: if name is None: return None seen_star = False - star2_type = None # type: Optional[Type] for i, (arg_name, kind, typ) in enumerate( zip(self.arg_names, self.arg_kinds, self.arg_types)): # No more positional arguments after these. if kind in (ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT): seen_star = True - if kind == ARG_STAR: - continue - if kind == ARG_STAR2: - star2_type = typ + if kind == ARG_STAR or kind == ARG_STAR2: continue if arg_name == name: position = None if seen_star else i return FormalArgument(name, position, typ, kind in (ARG_POS, ARG_NAMED)) - if star2_type is not None: - return FormalArgument(name, None, star2_type, False) - return None + return self.try_synthesizing_arg_from_kwarg(name) def argument_by_position(self, position: Optional[int]) -> Optional[FormalArgument]: if position is None: return None - if self.is_var_arg: - for kind, typ in zip(self.arg_kinds, self.arg_types): - if kind == ARG_STAR: - star_type = typ - break if position >= len(self.arg_names): - if self.is_var_arg: - return FormalArgument(None, position, star_type, False) - else: - return None + return self.try_synthesizing_arg_from_vararg(position) name, kind, typ = ( self.arg_names[position], self.arg_kinds[position], @@ -885,10 +920,21 @@ def argument_by_position(self, position: Optional[int]) -> Optional[FormalArgume if kind in (ARG_POS, ARG_OPT): return FormalArgument(name, position, typ, kind == ARG_POS) else: - if self.is_var_arg: - return FormalArgument(None, position, star_type, False) - else: - return None + return self.try_synthesizing_arg_from_vararg(position) + + def try_synthesizing_arg_from_kwarg(self, + name: Optional[str]) -> Optional[FormalArgument]: + if self.kw_arg is not None: + return FormalArgument(name, None, self.kw_arg.typ, False) + else: + return None + + def try_synthesizing_arg_from_vararg(self, + position: Optional[int]) -> Optional[FormalArgument]: + if self.var_arg is not None: + return FormalArgument(None, position, self.var_arg.typ, False) + else: + return None def items(self) -> List['CallableType']: return [self] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index cf722b2f63d05..589b04b49e097 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -1412,15 +1412,19 @@ reveal_type(f(d)) # E: Revealed type is 'builtins.list[builtins.int]' from typing import overload, Any @overload -def f(*, x: int = 3, y: int = 3) -> int: ... +def f(*, x: int = 3, y: int = 3) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types @overload def f(**kwargs: str) -> str: ... def f(*args, **kwargs): pass +# Checking an overload flagged as unsafe is a bit weird, but this is the +# cleanest way to make sure 'Any' ambiguity checks work correctly with +# keyword arguments. 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' +reveal_type(f(x=a, y=a)) # E: Revealed type is 'Any' [builtins fixtures/dict.pyi] @@ -1687,8 +1691,7 @@ def g(*, p1: X, p2: B) -> X: ... # E: Overloaded function signatures 1 and 2 ov def g(*, p2: A, p1: X) -> Y: ... def g(*, p1, p2): ... -[case testOverloadWithVariableArgsAreOverlapping-skip] -# TODO: Re-enable this after adding support for partially overlapping arg counts +[case testOverloadWithVariableArgsAreOverlapping] from wrapper import * [file wrapper.pyi] from typing import overload @@ -1765,8 +1768,7 @@ def foo3(x: str) -> str: ... @overload def foo3(x: str, y: str) -> int: ... -[case testOverloadPossibleOverlapWithArgsAndKwargs-skip] -# TODO: Re-enable this after adding support for partially overlapping arg counts +[case testOverloadPossibleOverlapWithArgsAndKwargs] from wrapper import * [file wrapper.pyi] from typing import overload @@ -1776,10 +1778,233 @@ def foo1(*args: int) -> int: ... # E: Overloaded function signatures 1 and 2 ov @overload def foo1(**kwargs: int) -> str: ... +@overload +def foo2(**kwargs: int) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo2(*args: int) -> int: ... +[builtins fixtures/dict.pyi] + +[case testOverloadPossibleOverlapWithVarargs] +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(*args: int) -> int: ... +@overload +def foo1(*args2: int) -> str: ... # E: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader + +@overload +def foo2(*args: int) -> str: ... +@overload +def foo2(*args2: str) -> int: ... + +@overload +def foo3(*args: int) -> str: ... +@overload +def foo3(*args: str) -> int: ... + +[case testOverloadPossibleOverlapWithVarargs2] +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(*args: str) -> int: ... +@overload +def foo1(x: int, *args2: int) -> str: ... + +@overload +def foo2(x: int, *args: int) -> str: ... +@overload +def foo2(*args2: str) -> int: ... + +@overload +def foo3(*args: int) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo3(x: int, *args2: int) -> str: ... + +@overload +def foo4(x: int, *args: int) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo4(*args2: int) -> int: ... + +[case testOverloadPossibleOverlapWithVarargs3] +from wrapper import * +[file wrapper.pyi] +from typing import overload + +class Other: ... + +@overload +def foo1(x: Other, *args: int) -> str: ... +@overload +def foo1(*args: str) -> int: ... + +@overload +def foo2(*args: int) -> str: ... +@overload +def foo2(x: Other, *args: str) -> int: ... + +@overload +def foo3(x: Other = ..., *args: int) -> str: ... +@overload +def foo3(*args: str) -> int: ... + +@overload +def foo4(*args: int) -> str: ... +@overload +def foo4(x: Other = ..., *args: str) -> int: ... + +[case testOverloadPossibleOverlapWithVarargs4] +from typing import overload + +@overload +def foo1(x: int = 0, y: int = 0) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo1(*xs: int) -> str: ... +def foo1(*args): pass + +@overload +def foo2(*xs: int) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo2(x: int = 0, y: int = 0) -> int: ... +def foo2(*args): pass + +[case testOverloadPossibleOverlapWithKwargs] +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(**kwargs: int) -> int: ... +@overload +def foo1(**kwargs2: int) -> str: ... # E: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader + @overload def foo2(**kwargs: int) -> str: ... @overload -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 +def foo2(**kwargs2: str) -> int: ... + +@overload +def foo(**kwargs: int) -> str: ... +@overload +def foo(**kwargs: str) -> int: ... +[builtins fixtures/dict.pyi] + +[case testOverloadPossibleOverlapMixingNamedArgsWithVarargs] +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(x: str, *, y: str) -> str: ... +@overload +def foo1(*x: str) -> int: ... + +@overload +def foo2(*x: str) -> int: ... +@overload +def foo2(x: str, *, y: str) -> str: ... + +[case testOverloadPossibleOverlapMixingOptionalArgsWithVarargs] +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(x: str, y: str = ..., z: str = ...) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo1(*x: str) -> int: ... + +@overload +def foo2(*x: str) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo2(x: str, y: str = ..., z: str = ...) -> str: ... + +@overload +def foo3(x: int, y: str = ..., z: str = ...) -> str: ... +@overload +def foo3(*x: str) -> int: ... + +[case testOverloadPossibleOverlapMixingOptionalArgsWithVarargs2] +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(x: str, y: str = ..., z: int = ...) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo1(*x: str) -> int: ... + +@overload +def foo2(x: str, y: str = ..., z: int = ...) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo2(*x: str) -> int: ... + +[case testOverloadPossibleOverlapMixingNamedArgsWithKwargs] +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(*, x: str, y: str, z: str) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo1(**x: str) -> int: ... + +@overload +def foo2(**x: str) -> int: ... +@overload +def foo2(*, x: str, y: str, z: str) -> str: ... # E: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader + +@overload +def foo3(*, x: int, y: str, z: str) -> str: ... +@overload +def foo3(*x: str) -> int: ... + +[builtins fixtures/dict.pyi] + +[case testOverloadPossibleOverlapMixingNamedArgsWithKwargs2] +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(*, x: str, y: str, z: int) -> str: ... +@overload +def foo1(**x: str) -> int: ... + +@overload +def foo2(**x: str) -> int: ... +@overload +def foo2(*, x: str, y: str, z: int) -> str: ... + +@overload +def foo3(*, x: str, y: str, z: int = ...) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo3(**x: str) -> int: ... + +@overload +def foo4(**x: str) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo4(*, x: str, y: str, z: int = ...) -> str: ... +[builtins fixtures/dict.pyi] + +[case testOverloadPossibleOverlapMixingNamedArgsWithKwargs3] +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(x: str, *, y: str, z: str) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo1(**x: str) -> int: ... + +@overload +def foo2(**x: str) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo2(x: str, *, y: str, z: str) -> str: ... + [builtins fixtures/dict.pyi] [case testOverloadVarargInputAndVarargDefinition]