diff --git a/mypy/checker.py b/mypy/checker.py index 0949e35b7cd97..82a2d95cc4222 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -39,7 +39,7 @@ from mypy import messages from mypy.subtypes import ( is_subtype, is_equivalent, is_proper_subtype, is_more_precise, - restrict_subtype_away, is_subtype_ignoring_tvars, is_callable_subtype, + restrict_subtype_away, is_subtype_ignoring_tvars, is_callable_compatible, unify_generic_callable, find_member ) from mypy.maptype import map_instance_to_supertype @@ -407,22 +407,32 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: if defn.info: self.check_method_override(defn) self.check_inplace_operator_method(defn) - self.check_overlapping_overloads(defn) + if not defn.is_property: + self.check_overlapping_overloads(defn) return None def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: # At this point we should have set the impl already, and all remaining # items are decorators for i, item in enumerate(defn.items): + # TODO overloads involving decorators assert isinstance(item, Decorator) sig1 = self.function_type(item.func) + for j, item2 in enumerate(defn.items[i + 1:]): - # TODO overloads involving decorators assert isinstance(item2, Decorator) sig2 = self.function_type(item2.func) - if is_unsafe_overlapping_signatures(sig1, sig2): - self.msg.overloaded_signatures_overlap(i + 1, i + j + 2, - item.func) + + assert isinstance(sig1, CallableType) + assert isinstance(sig2, CallableType) + + if not are_argument_counts_overlapping(sig1, sig2): + continue + + if if_overload_can_never_match(sig1, sig2): + self.msg.overloaded_signature_will_never_match(i + 1, i + j + 2, item2.func) + elif is_unsafe_overlapping_overload_signatures(sig1, sig2): + self.msg.overloaded_signatures_overlap(i + 1, i + j + 2, item.func) if defn.impl: if isinstance(defn.impl, FuncDef): impl_type = defn.impl.type @@ -437,7 +447,8 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: assert isinstance(impl_type, CallableType) assert isinstance(sig1, CallableType) - if not is_callable_subtype(impl_type, sig1, ignore_return=True): + if not is_callable_compatible(impl_type, sig1, + is_compat=is_subtype, ignore_return=True): self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl) impl_type_subst = impl_type if impl_type.variables: @@ -1038,8 +1049,8 @@ def check_overlapping_op_methods(self, fallback=self.named_type('builtins.function'), name=reverse_type.name) - if is_unsafe_overlapping_signatures(forward_tweaked, - reverse_tweaked): + if is_unsafe_overlapping_operator_signatures( + forward_tweaked, reverse_tweaked): self.msg.operator_method_signatures_overlap( reverse_class, reverse_name, forward_base, forward_name, context) @@ -1812,10 +1823,18 @@ def check_multi_assignment_from_union(self, lvalues: List[Expression], rvalue: E # Bind a union of types collected in 'assignments' to every expression. if isinstance(expr, StarExpr): expr = expr.expr - types, declared_types = zip(*items) + + # TODO: See todo in binder.py, ConditionalTypeBinder.assign_type + # It's unclear why the 'declared_type' param is sometimes 'None' + clean_items = [] # type: List[Tuple[Type, Type]] + for type, declared_type in items: + assert declared_type is not None + clean_items.append((type, declared_type)) + + types, declared_types = zip(*clean_items) self.binder.assign_type(expr, - UnionType.make_simplified_union(types), - UnionType.make_simplified_union(declared_types), + UnionType.make_simplified_union(list(types)), + UnionType.make_simplified_union(list(declared_types)), False) for union, lv in zip(union_types, self.flatten_lvalues(lvalues)): # Properly store the inferred types. @@ -3527,18 +3546,96 @@ def type(self, type: Type) -> Type: return expand_type(type, self.map) -def is_unsafe_overlapping_signatures(signature: Type, other: Type) -> bool: - """Check if two signatures may be unsafely overlapping. +def are_argument_counts_overlapping(t: CallableType, s: CallableType) -> bool: + """Can a single call match both t and s, based just on positional argument counts? + """ + min_args = max(t.min_args, s.min_args) + max_args = min(t.max_possible_positional_args(), s.max_possible_positional_args()) + return min_args <= max_args - Two signatures s and t are overlapping if both can be valid for the same + +def is_unsafe_overlapping_overload_signatures(signature: CallableType, + other: CallableType) -> bool: + """Check if two overloaded function signatures may be unsafely overlapping. + + We consider two functions 's' and 't' to be unsafely overlapping both + of the following are true: + + 1. s's parameters are all more precise or partially overlapping with t's + 1. s's return type is NOT a subtype of t's. + + both can be valid for the same statically typed values and the return types are incompatible. + 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 + # + # 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. + # + # To fix this, we need to either modify meet.is_overlapping_types or add a new + # function and use "is_more_precise(...) or is_partially_overlapping(...)" for the is_compat + # checks. + # + # 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. + + # 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, + 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, + is_compat_return=lambda l, r: not is_subtype(r, l))) + + +def if_overload_can_never_match(signature: CallableType, other: CallableType) -> bool: + """Check if the 'other' method can never be matched due to 'signature'. + + This can happen if signature's parameters are all strictly broader then + other's parameters. + + Assumes that both signatures have overlapping argument counts. + """ + return is_callable_compatible(signature, other, + is_compat=is_more_precise, + ignore_return=True) + + +def is_unsafe_overlapping_operator_signatures(signature: Type, other: Type) -> bool: + """Check if two operator method signatures may be unsafely overlapping. + + Two signatures s and t are overlapping if both can be valid for the same + statically typed values and the return types are incompatible. + Assume calls are first checked against 'signature', then against 'other'. Thus if 'signature' is more general than 'other', there is no unsafe overlapping. - TODO If argument types vary covariantly, the return type may vary - covariantly as well. + TODO: Clean up this function and make it not perform type erasure. + + Context: This function was previously used to make sure both overloaded + functions and operator methods were not unsafely overlapping. + + We changed the semantics for we should handle overloaded definitions, + but not operator functions. (We can't reuse the same semantics for both: + the overload semantics are too restrictive here). + + We should rewrite this method so that: + + 1. It uses many of the improvements made to overloads: in particular, + eliminating type erasure. + + 2. It contains just the logic necessary for operator methods. """ if isinstance(signature, CallableType): if isinstance(other, CallableType): @@ -3581,12 +3678,11 @@ def is_more_general_arg_prefix(t: FunctionLike, s: FunctionLike) -> bool: """Does t have wider arguments than s?""" # TODO should an overload with additional items be allowed to be more # general than one with fewer items (or just one item)? - # TODO check argument kinds and otherwise make more general if isinstance(t, CallableType): if isinstance(s, CallableType): - t, s = unify_generic_callables(t, s) - return all(is_proper_subtype(args, argt) - for argt, args in zip(t.arg_types, s.arg_types)) + return is_callable_compatible(t, s, + is_compat=is_proper_subtype, + ignore_return=True) elif isinstance(t, FunctionLike): if isinstance(s, FunctionLike): if len(t.items()) == len(s.items()): @@ -3595,29 +3691,6 @@ def is_more_general_arg_prefix(t: FunctionLike, s: FunctionLike) -> bool: return False -def unify_generic_callables(t: CallableType, - s: CallableType) -> Tuple[CallableType, - CallableType]: - """Make type variables in generic callables the same if possible. - - Return updated callables. If we can't unify the type variables, - return the unmodified arguments. - """ - # TODO: Use this elsewhere when comparing generic callables. - if t.is_generic() and s.is_generic(): - t_substitutions = {} - s_substitutions = {} - for tv1, tv2 in zip(t.variables, s.variables): - # Are these something we can unify? - if tv1.id != tv2.id and is_equivalent_type_var_def(tv1, tv2): - newdef = TypeVarDef.new_unification_variable(tv2) - t_substitutions[tv1.id] = TypeVarType(newdef) - s_substitutions[tv2.id] = TypeVarType(newdef) - return (cast(CallableType, expand_type(t, t_substitutions)), - cast(CallableType, expand_type(s, s_substitutions))) - return t, s - - def is_equivalent_type_var_def(tv1: TypeVarDef, tv2: TypeVarDef) -> bool: """Are type variable definitions equivalent? @@ -3633,17 +3706,17 @@ def is_equivalent_type_var_def(tv1: TypeVarDef, tv2: TypeVarDef) -> bool: def is_same_arg_prefix(t: CallableType, s: CallableType) -> bool: - # TODO check argument kinds - return all(is_same_type(argt, args) - for argt, args in zip(t.arg_types, s.arg_types)) + return is_callable_compatible(t, s, + is_compat=is_same_type, + ignore_return=True, + check_args_covariantly=True, + ignore_pos_arg_names=True) def is_more_precise_signature(t: CallableType, s: CallableType) -> bool: """Is t more precise than s? - A signature t is more precise than s if all argument types and the return type of t are more precise than the corresponding types in s. - Assume that the argument kinds and names are compatible, and that the argument counts are overlapping. """ diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0e349a436dab9..c6577d7ac048b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -611,13 +611,15 @@ def check_call(self, callee: Type, args: List[Expression], arg_types = self.infer_arg_types_in_context(None, args) self.msg.enable_errors() - target = self.overload_call_target(arg_types, arg_kinds, arg_names, - callee, context, - messages=arg_messages) - return self.check_call(target, args, arg_kinds, context, arg_names, - arg_messages=arg_messages, - callable_name=callable_name, - object_type=object_type) + return self.check_overload_call(callee=callee, + args=args, + arg_types=arg_types, + arg_kinds=arg_kinds, + arg_names=arg_names, + callable_name=callable_name, + object_type=object_type, + context=context, + arg_messages=arg_messages) elif isinstance(callee, AnyType) or not self.chk.in_checked_function(): self.infer_arg_types_in_context(None, args) if isinstance(callee, AnyType): @@ -1104,68 +1106,246 @@ def check_arg(self, caller_type: Type, original_caller_type: Type, if call: self.msg.note_call(original_caller_type, call, context) - def overload_call_target(self, arg_types: List[Type], arg_kinds: List[int], - arg_names: Optional[Sequence[Optional[str]]], - overload: Overloaded, context: Context, - messages: Optional[MessageBuilder] = None) -> Type: - """Infer the correct overload item to call with given argument types. + def check_overload_call(self, + callee: Overloaded, + args: List[Expression], + arg_types: List[Type], + arg_kinds: List[int], + arg_names: Optional[Sequence[Optional[str]]], + callable_name: Optional[str], + object_type: Optional[Type], + context: Context, + arg_messages: MessageBuilder) -> Tuple[Type, Type]: + """Checks a call to an overloaded function.""" + # Step 1: Filter call targets to remove ones where the argument counts don't match + plausible_targets = self.plausible_overload_call_targets(arg_types, arg_kinds, + arg_names, callee) + + # Step 2: Attempt to find a matching overload + inferred_result = self.infer_overload_return_type(plausible_targets, args, arg_types, + arg_kinds, arg_names, callable_name, + object_type, context, arg_messages) + if inferred_result is not None: + # Success! Stop early. + return inferred_result + + # Step 3: At this point, we know none of the overload alternatives exactly match. + # We fall back to using the erased types to help with union math/help us + # produce a better error message. + erased_targets = self.overload_erased_call_targets(plausible_targets, arg_types, + arg_kinds, arg_names, context) + + # Step 4: Try and infer a second-best alternative. + if len(erased_targets) == 0: + # Step 4a: There are no viable targets, even if we relax our constraints. Give up. + if not self.chk.should_suppress_optional_error(arg_types): + arg_messages.no_variant_matches_arguments(callee, arg_types, context) + target = AnyType(TypeOfAny.from_error) # type: Type + elif any(isinstance(arg, UnionType) for arg in arg_types): + # Step 4b: Try performing union math + unioned_callable = self.union_overload_matches(erased_targets, args, arg_kinds, + arg_names, context) + target = unioned_callable if unioned_callable is not None else erased_targets[0] + else: + # Step 4c: Use the first matching erased target: it won't match, but at + # least we can have a nicer error message. + # TODO: Adjust the error message here to make it clear there was no match. + target = erased_targets[0] + + '''target = self.overload_call_target(args, arg_types, arg_kinds, arg_names, + callee, context, + messages=arg_messages)''' + return self.check_call(target, args, arg_kinds, context, arg_names, + arg_messages=arg_messages, + callable_name=callable_name, + object_type=object_type) + + def plausible_overload_call_targets(self, + arg_types: List[Type], + arg_kinds: List[int], + arg_names: Optional[Sequence[Optional[str]]], + overload: Overloaded) -> List[CallableType]: + """Returns all overload call targets that having matching argument counts.""" + matches = [] # type: List[CallableType] + for typ in overload.items(): + formal_to_actual = map_actuals_to_formals(arg_kinds, arg_names, + typ.arg_kinds, typ.arg_names, + lambda i: arg_types[i]) - The return value may be CallableType or AnyType (if an unique item - could not be determined). + if self.check_argument_count(typ, arg_types, arg_kinds, arg_names, + formal_to_actual, None, None): + matches.append(typ) + + return matches + + def infer_overload_return_type(self, + plausible_targets: List[CallableType], + args: List[Expression], + arg_types: List[Type], + arg_kinds: List[int], + arg_names: Optional[Sequence[Optional[str]]], + callable_name: Optional[str], + object_type: Optional[Type], + context: Context, + arg_messages: Optional[MessageBuilder] = None, + ) -> Optional[Tuple[Type, Type]]: + """Attempts to find the first matching callable from the given list. + + If multiple targets match due to ambiguous Any parameters, returns (AnyType, AnyType). + If no targets match, returns None. + + Assumes all of the given targets have argument counts compatible with the caller. """ - messages = messages or self.msg - # TODO: For overlapping signatures we should try to get a more precise - # result than 'Any'. - match = [] # type: List[CallableType] - best_match = 0 - for typ in overload.items(): - similarity = self.erased_signature_similarity(arg_types, arg_kinds, arg_names, - typ, context=context) - if similarity > 0 and similarity >= best_match: - if (match and not is_same_type(match[-1].ret_type, - typ.ret_type) and - (not mypy.checker.is_more_precise_signature(match[-1], typ) - or (any(isinstance(arg, AnyType) for arg in arg_types) - and any_arg_causes_overload_ambiguity( - match + [typ], arg_types, arg_kinds, arg_names)))): - # Ambiguous return type. Either the function overload is - # overlapping (which we don't handle very well here) or the - # caller has provided some Any argument types; in either - # case we'll fall back to Any. It's okay to use Any types - # in calls. - # - # Overlapping overload items are generally fine if the - # overlapping is only possible when there is multiple - # inheritance, as this is rare. See docstring of - # mypy.meet.is_overlapping_types for more about this. - # - # Note that there is no ambiguity if the items are - # covariant in both argument types and return types with - # respect to type precision. We'll pick the best/closest - # match. - # - # TODO: Consider returning a union type instead if the - # overlapping is NOT due to Any types? - return AnyType(TypeOfAny.special_form) + + arg_messages = self.msg if arg_messages is None else arg_messages + matches = [] # type: List[CallableType] + inferred = [] # type: List[Tuple[Type, Type]] + args_contain_any = any(isinstance(arg, AnyType) for arg in arg_types) + + for typ in plausible_targets: + overload_messages = self.msg.clean_copy() + prev_messages = self.msg + self.msg = overload_messages + try: + # Passing `overload_messages` as the `arg_messages` parameter doesn't + # seem to reliably catch all possible errors. + # + # TODO: Figure out why + result = self.check_call( + callee=typ, + args=args, + arg_kinds=arg_kinds, + arg_names=arg_names, + context=context, + arg_messages=overload_messages, + callable_name=callable_name, + object_type=object_type) + finally: + self.msg = prev_messages + + is_match = not overload_messages.is_errors() + if is_match: + if not args_contain_any: + # There is no possibility of ambiguity due to 'Any', so we can + # just end right away: + return result + elif (args_contain_any and matches + and not is_same_type(matches[-1].ret_type, typ.ret_type) + and any_arg_causes_overload_ambiguity( + matches + [typ], arg_types, arg_kinds, arg_names)): + # Ambiguous return type. The caller has provided some + # Any argument types (which are okay to use in calls), + # so we fall back to returning 'Any'. + source = AnyType(TypeOfAny.special_form) + return self.check_call(callee=source, + 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: - match.append(typ) - best_match = max(best_match, similarity) - if not match: - if not self.chk.should_suppress_optional_error(arg_types): - messages.no_variant_matches_arguments(overload, arg_types, context) - return AnyType(TypeOfAny.from_error) - else: - if len(match) == 1: - return match[0] - else: - # More than one signature matches. Pick the first *non-erased* - # matching signature, or default to the first one if none - # match. - for m in match: - if self.match_signature_types(arg_types, arg_kinds, arg_names, m, - context=context): - return m - return match[0] + matches.append(typ) + inferred.append(result) + + return inferred[0] if len(inferred) > 0 else None + + def overload_erased_call_targets(self, + plausible_targets: List[CallableType], + arg_types: List[Type], + arg_kinds: List[int], + arg_names: Optional[Sequence[Optional[str]]], + context: Context) -> List[CallableType]: + """Returns a list of all targets that match the caller after erasing types. + + Assumes all of the given targets have argument counts compatible with the caller. + """ + matches = [] # type: List[CallableType] + for typ in plausible_targets: + if self.erased_signature_similarity(arg_types, arg_kinds, arg_names, typ, context): + matches.append(typ) + return matches + + def union_overload_matches(self, + callables: List[CallableType], + args: List[Expression], + arg_kinds: List[int], + arg_names: Optional[Sequence[Optional[str]]], + context: Context) -> Optional[CallableType]: + """Accepts a list of overload signatures and attempts to combine them together into a + new CallableType consisting of the union of all of the given arguments and return types. + + Returns None if it is not possible to combine the different callables together in a + sound manner. + + Assumes all of the given callables have argument counts compatible with the caller. + """ + assert len(callables) > 0 + if len(callables) == 1: + return callables[0] + + new_args = [[] for _ in range(len(callables[0].arg_types))] # type: List[List[Type]] + new_returns = [] # type: List[Type] + + expected_names = callables[0].arg_names + expected_kinds = callables[0].arg_kinds + + for target in callables: + if target.arg_names != expected_names or target.arg_kinds != expected_kinds: + # We conservatively end if the overloads do not have the exact same signature. + # TODO: Enhance the union overload logic to handle a wider variety of signatures. + return None + + if target.is_generic(): + formal_to_actual = map_actuals_to_formals( + arg_kinds, arg_names, + target.arg_kinds, target.arg_names, + lambda i: self.accept(args[i])) + + target = freshen_function_type_vars(target) + target = self.infer_function_type_arguments_using_context(target, context) + target = self.infer_function_type_arguments( + target, args, arg_kinds, formal_to_actual, context) + + for i, arg in enumerate(target.arg_types): + new_args[i].append(arg) + new_returns.append(target.ret_type) + + union_count = 0 + final_args = [] + for args_list in new_args: + new_type = UnionType.make_simplified_union(args_list) + union_count += 1 if isinstance(new_type, UnionType) else 0 + final_args.append(new_type) + + # TODO: Modify this check to be less conservative. + # + # Currently, we permit only one union union in the arguments because if we allow + # multiple, we can't always guarantee the synthesized callable will be correct. + # + # For example, suppose we had the following two overloads: + # + # @overload + # def f(x: A, y: B) -> None: ... + # @overload + # def f(x: B, y: A) -> None: ... + # + # If we continued and synthesize "def f(x: Union[A,B], y: Union[A,B]) -> None: ...", + # then we'd incorrectly accept calls like "f(A(), A())" when they really ought to + # be rejected. + # + # However, that means we'll also give up if the original overloads contained + # any unions. This is likely unnecessary -- we only really need to give up if + # there are more then one *synthesized* union arguments. + if union_count >= 2: + return None + + return callables[0].copy_modified( + arg_types=final_args, + ret_type=UnionType.make_simplified_union(new_returns), + implicit=True, + from_overloads=True) def erased_signature_similarity(self, arg_types: List[Type], arg_kinds: List[int], arg_names: Optional[Sequence[Optional[str]]], diff --git a/mypy/constraints.py b/mypy/constraints.py index 92a1f35b999b8..67d42cf8c9237 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -525,7 +525,9 @@ def find_matching_overload_item(overloaded: Overloaded, template: CallableType) for item in items: # Return type may be indeterminate in the template, so ignore it when performing a # subtype check. - if mypy.subtypes.is_callable_subtype(item, template, ignore_return=True): + if mypy.subtypes.is_callable_compatible(item, template, + is_compat=mypy.subtypes.is_subtype, + ignore_return=True): return item # Fall back to the first item if we can't find a match. This is totally arbitrary -- # maybe we should just bail out at this point. diff --git a/mypy/messages.py b/mypy/messages.py index 50c30d4d12e41..d54289d76327b 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -148,6 +148,11 @@ def copy(self) -> 'MessageBuilder': new.disable_type_names = self.disable_type_names return new + def clean_copy(self) -> 'MessageBuilder': + errors = self.errors.copy() + errors.error_info_map = OrderedDict() + return MessageBuilder(errors, self.modules) + def add_errors(self, messages: 'MessageBuilder') -> None: """Add errors in messages to this builder.""" if self.disable_count <= 0: @@ -937,11 +942,19 @@ def incompatible_typevar_value(self, self.format(typ)), context) - def overloaded_signatures_overlap(self, index1: int, index2: int, - context: Context) -> None: + def overloaded_signatures_overlap(self, index1: int, index2: int, context: Context) -> None: self.fail('Overloaded function signatures {} and {} overlap with ' 'incompatible return types'.format(index1, index2), context) + def overloaded_signature_will_never_match(self, index1: int, index2: int, + context: Context) -> None: + self.fail( + 'Overloaded function signature {index2} will never be matched: ' + 'function {index1}\'s parameter type(s) are the same or broader'.format( + index1=index1, + index2=index2), + context) + def overloaded_signatures_arg_specific(self, index1: int, context: Context) -> None: self.fail('Overloaded function implementation does not accept all possible arguments ' 'of signature {}'.format(index1), context) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index ecbcff4f292b6..eb8b7fe92c081 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -203,8 +203,9 @@ def visit_type_var(self, left: TypeVarType) -> bool: def visit_callable_type(self, left: CallableType) -> bool: right = self.right if isinstance(right, CallableType): - return is_callable_subtype( + return is_callable_compatible( left, right, + is_compat=is_subtype, ignore_pos_arg_names=self.ignore_pos_arg_names) elif isinstance(right, Overloaded): return all(is_subtype(left, item, self.check_type_parameter, @@ -310,10 +311,12 @@ def visit_overloaded(self, left: Overloaded) -> bool: else: # If this one overlaps with the supertype in any way, but it wasn't # an exact match, then it's a potential error. - if (is_callable_subtype(left_item, right_item, ignore_return=True, - ignore_pos_arg_names=self.ignore_pos_arg_names) or - is_callable_subtype(right_item, left_item, ignore_return=True, - ignore_pos_arg_names=self.ignore_pos_arg_names)): + if (is_callable_compatible(left_item, right_item, + is_compat=is_subtype, ignore_return=True, + ignore_pos_arg_names=self.ignore_pos_arg_names) or + is_callable_compatible(right_item, left_item, + is_compat=is_subtype, ignore_return=True, + ignore_pos_arg_names=self.ignore_pos_arg_names)): # If this is an overload that's already been matched, there's no # problem. if left_item not in matched_overloads: @@ -568,16 +571,33 @@ def non_method_protocol_members(tp: TypeInfo) -> List[str]: return result -def is_callable_subtype(left: CallableType, right: CallableType, - ignore_return: bool = False, - ignore_pos_arg_names: bool = False, - use_proper_subtype: bool = False) -> bool: - """Is left a subtype of right?""" +def is_callable_compatible(left: CallableType, right: CallableType, + *, + is_compat: Callable[[Type, Type], bool], + 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: + """Is the left compatible with the right, using the provided compatibility check? - if use_proper_subtype: - is_compat = is_proper_subtype - else: - is_compat = is_subtype + is_compat: + The check we want to run against the parameters. + + is_compat_return: + The check we want to run against the return type. + If None, use the 'is_compat' check. + + check_args_covariantly: + If true, check if the left's args is compatible with the right's + instead of the other way around (contravariantly). + + This function is mostly used to check if the left is a subtype of the right which + is why the default is to check the args contravariantly. However, it's occasionally + useful to check the args using some other check, so we leave the variance + configurable. + """ + if is_compat_return is None: + is_compat_return = is_compat # If either function is implicitly typed, ignore positional arg names too if left.implicit or right.implicit: @@ -607,9 +627,12 @@ def is_callable_subtype(left: CallableType, right: CallableType, left = unified # Check return types. - if not ignore_return and not is_compat(left.ret_type, right.ret_type): + if not ignore_return and not is_compat_return(left.ret_type, right.ret_type): return False + if check_args_covariantly: + is_compat = flip_compat_check(is_compat) + if right.is_ellipsis_args: return True @@ -652,7 +675,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, # 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 inifite series of + # series. Also make sure left has its own infinite series of # optional positional arguments. if not left.is_var_arg: return False @@ -664,7 +687,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, 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, use_proper_subtype): + ignore_pos_arg_names, is_compat): return False j += 1 continue @@ -687,7 +710,7 @@ def is_callable_subtype(left: CallableType, right: CallableType, 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, use_proper_subtype): + ignore_pos_arg_names, is_compat): return False continue @@ -696,7 +719,8 @@ def is_callable_subtype(left: CallableType, right: CallableType, if left_arg is None: return False - if not are_args_compatible(left_arg, right_arg, ignore_pos_arg_names, use_proper_subtype): + if not are_args_compatible(left_arg, right_arg, + ignore_pos_arg_names, is_compat): return False done_with_positional = False @@ -748,7 +772,7 @@ def are_args_compatible( left: FormalArgument, right: FormalArgument, ignore_pos_arg_names: bool, - use_proper_subtype: bool) -> bool: + is_compat: Callable[[Type, Type], bool]) -> bool: # 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: @@ -759,18 +783,20 @@ def are_args_compatible( if right.pos is not None and left.pos != right.pos: return False # Left must have a more general type - if use_proper_subtype: - if not is_proper_subtype(right.typ, left.typ): - return False - else: - if not is_subtype(right.typ, left.typ): - return False + 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: return False return True +def flip_compat_check(is_compat: Callable[[Type, Type], bool]) -> Callable[[Type, Type], bool]: + def new_is_compat(left: Type, right: Type) -> bool: + return is_compat(right, left) + return new_is_compat + + def unify_generic_callable(type: CallableType, target: CallableType, ignore_return: bool) -> Optional[CallableType]: """Try to unify a generic callable type with another callable type. @@ -913,10 +939,7 @@ def visit_type_var(self, left: TypeVarType) -> bool: def visit_callable_type(self, left: CallableType) -> bool: right = self.right if isinstance(right, CallableType): - return is_callable_subtype( - left, right, - ignore_pos_arg_names=False, - use_proper_subtype=True) + return is_callable_compatible(left, right, is_compat=is_proper_subtype) elif isinstance(right, Overloaded): return all(is_proper_subtype(left, item) for item in right.items()) diff --git a/mypy/types.py b/mypy/types.py index 43015fbfe11a6..89965eb995834 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1,5 +1,6 @@ """Classes for representing mypy types.""" +import sys import copy from abc import abstractmethod from collections import OrderedDict @@ -674,6 +675,8 @@ class CallableType(FunctionLike): # 'dict') 'from_type_type', # Was this callable generated by analyzing Type[...] # instantiation? + 'from_overloads', # Was this callable generated by synthesizing + # multiple overloads? 'bound_args', # Bound type args, mostly unused but may be useful for # tools that consume mypy ASTs ) @@ -694,6 +697,7 @@ def __init__(self, is_classmethod_class: bool = False, special_sig: Optional[str] = None, from_type_type: bool = False, + from_overloads: bool = False, bound_args: Sequence[Optional[Type]] = (), ) -> None: super().__init__(line, column) @@ -718,6 +722,7 @@ def __init__(self, self.is_classmethod_class = is_classmethod_class self.special_sig = special_sig self.from_type_type = from_type_type + self.from_overloads = from_overloads if not bound_args: bound_args = () self.bound_args = bound_args @@ -734,8 +739,10 @@ def copy_modified(self, line: int = _dummy, column: int = _dummy, is_ellipsis_args: bool = _dummy, + implicit: bool = _dummy, special_sig: Optional[str] = _dummy, from_type_type: bool = _dummy, + from_overloads: bool = _dummy, bound_args: List[Optional[Type]] = _dummy) -> 'CallableType': return CallableType( arg_types=arg_types if arg_types is not _dummy else self.arg_types, @@ -750,10 +757,11 @@ def copy_modified(self, column=column if column is not _dummy else self.column, is_ellipsis_args=( is_ellipsis_args if is_ellipsis_args is not _dummy else self.is_ellipsis_args), - implicit=self.implicit, + implicit=implicit if implicit is not _dummy else self.implicit, is_classmethod_class=self.is_classmethod_class, special_sig=special_sig if special_sig is not _dummy else self.special_sig, from_type_type=from_type_type if from_type_type is not _dummy else self.from_type_type, + from_overloads=from_overloads if from_overloads is not _dummy else self.from_overloads, bound_args=bound_args if bound_args is not _dummy else self.bound_args, ) @@ -789,6 +797,15 @@ def max_fixed_args(self) -> int: n -= 1 return n + def max_possible_positional_args(self) -> int: + """Returns maximum number of positional arguments this method could possibly accept. + + This takes into acount *arg and **kwargs but excludes keyword-only args.""" + if self.is_var_arg or self.is_kw_arg: + return sys.maxsize + blacklist = (ARG_NAMED, ARG_NAMED_OPT) + return len([kind not in blacklist for kind in self.arg_kinds]) + def corresponding_argument(self, model: FormalArgument) -> Optional[FormalArgument]: """Return the argument in this function that corresponds to `model`""" @@ -905,6 +922,7 @@ def serialize(self) -> JsonDict: 'is_ellipsis_args': self.is_ellipsis_args, 'implicit': self.implicit, 'is_classmethod_class': self.is_classmethod_class, + 'from_overloads': self.from_overloads, 'bound_args': [(None if t is None else t.serialize()) for t in self.bound_args], } @@ -923,6 +941,7 @@ def deserialize(cls, data: JsonDict) -> 'CallableType': is_ellipsis_args=data['is_ellipsis_args'], implicit=data['implicit'], is_classmethod_class=data['is_classmethod_class'], + from_overloads=data['from_overloads'], bound_args=[(None if t is None else deserialize_type(t)) for t in data['bound_args']], ) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index b9c598b550d03..4fec0a91909fa 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1649,8 +1649,14 @@ class B(A): def __add__(self, x: 'A') -> 'A': pass @overload def __add__(self, x: 'B') -> 'B': pass +class C(A): + @overload + def __add__(self, x: 'B') -> 'B': pass + @overload + def __add__(self, x: 'A') -> 'A': pass [out] tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A" +tmp/foo.pyi:11: error: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader [case testReverseOperatorMethodArgumentType] from typing import Any @@ -1717,9 +1723,9 @@ from foo import * from typing import overload, Any class A: @overload - def __radd__(self, x: 'A') -> str: pass # Error + def __radd__(self, x: 'A') -> str: pass @overload - def __radd__(self, x: 'A') -> Any: pass + def __radd__(self, x: 'A') -> Any: pass # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader [out] [case testReverseOperatorMethodArgumentTypeAndOverloadedMethod] @@ -2609,15 +2615,15 @@ class Mbad(type): [case testTypeMatchesOverloadedFunctions] from foo import * [file foo.pyi] -from typing import Type, overload, Union +from typing import Type, overload, Any class User: pass UserType = User # type: Type[User] @overload -def f(a: object) -> int: pass +def f(a: int) -> Any: pass @overload -def f(a: int) -> str: pass +def f(a: object) -> int: pass reveal_type(f(User)) # E: Revealed type is 'builtins.int' reveal_type(f(UserType)) # E: Revealed type is 'builtins.int' @@ -2697,10 +2703,10 @@ reveal_type(f("hi")) # E: Revealed type is 'foo.User' [builtins fixtures/classmethod.pyi] [out] -[case testGeneralTypeDoesNotMatchSpecificTypeInOverloadedFunctions] +[case testGeneralTypeMatchesSpecificTypeInOverloadedFunctions] from foo import * [file foo.pyi] -from typing import Type, overload +from typing import Type, Any, overload class User: pass @@ -2709,10 +2715,12 @@ def f(a: Type[User]) -> None: pass @overload def f(a: int) -> None: pass -def mock() -> type: return User +def mock_1() -> type: return User +def mock_2() -> Type[Any]: return User f(User) -f(mock()) # E: No overload variant of "f" matches argument type "type" +f(mock_1()) +f(mock_2()) [builtins fixtures/classmethod.pyi] [out] @@ -2898,7 +2906,7 @@ class Sub(Super): @overload # E: Signature of "foo" incompatible with supertype "Super" def foo(self, a: A) -> A: pass @overload - def foo(self, a: B) -> C: pass + def foo(self, a: B) -> C: pass # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader @overload def foo(self, a: C) -> C: pass [builtins fixtures/classmethod.pyi] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 3fa332c9193e9..5b8f85f9dba7e 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -655,8 +655,7 @@ class B(A): pass @overload def f(x: A) -> A: pass @overload -def f(x: B) -> B: pass # This is more specific than the first item, and thus - # will never be called. +def f(x: B) -> B: pass # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader [case testPartiallyCovariantOverlappingOverloadSignatures] from foo import * @@ -676,9 +675,9 @@ from typing import overload class A: pass class B(A): pass @overload -def g(x: A) -> int: pass # Fine, since A us supertype of B. +def g(x: A) -> int: pass @overload -def g(x: B) -> str: pass +def g(x: B) -> str: pass # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader [case testCovariantOverlappingOverloadSignatures] from foo import * @@ -716,9 +715,9 @@ from foo import * [file foo.pyi] from typing import Any, overload @overload -def g(x: Any) -> Any: pass # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +def g(x: Any) -> Any: pass @overload -def g(x: int) -> int: pass +def g(x: int) -> int: pass # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader [case testOverloadedLtAndGtMethods] from foo import * @@ -924,7 +923,7 @@ def f(x: int, y: List[str] = None) -> int: pass f(y=[1], x=0)() # E: "int" not callable f(y=[''], x=0)() # E: "int" not callable a = f(y=[['']], x=0) # E: List item 0 has incompatible type "List[str]"; expected "int" -a() # E: "int" not callable +reveal_type(a) # E: Revealed type is 'builtins.int' [builtins fixtures/list.pyi] [case testOverloadWithDerivedFromAny] @@ -987,15 +986,29 @@ def g(x: U, y: V) -> None: [case testOverlapWithTypeVars] from foo import * [file foo.pyi] -from typing import overload, TypeVar, Sequence +from typing import overload, TypeVar, Sequence, List T = TypeVar('T', bound=str) @overload def f(x: Sequence[T]) -> None: pass @overload def f(x: Sequence[int]) -> int: pass -# These are considered overlapping despite the bound on T due to runtime type erasure. -[out] -tmp/foo.pyi:4: error: Overloaded function signatures 1 and 2 overlap with incompatible return types + +@overload +def g(x: Sequence[T]) -> None: pass +@overload +def g(x: Sequence[str]) -> int: pass # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader + +@overload +def h(x: Sequence[str]) -> int: pass +@overload +def h(x: Sequence[T]) -> None: pass # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader + +@overload +def i(x: List[str]) -> int: pass # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def i(x: List[T]) -> None: pass +[builtins fixtures/list.pyi] + [case testOverlapWithTypeVarsWithValues] from foo import * @@ -1026,16 +1039,21 @@ g(1, 'foo') g(1, 'foo', b'bar') # E: Value of type variable "AnyStr" of "g" cannot be "object" [builtins fixtures/primitives.pyi] -[case testBadOverlapWithTypeVarsWithValues] +[case testOverlapWithTypeVarsWithValuesOrdering] from foo import * [file foo.pyi] from typing import overload, TypeVar AnyStr = TypeVar('AnyStr', bytes, str) @overload -def f(x: AnyStr) -> None: pass # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +def f(x: AnyStr) -> AnyStr: pass +@overload +def f(x: str) -> str: pass # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader + +@overload +def g(x: str) -> str: pass @overload -def f(x: str) -> bool: pass +def g(x: AnyStr) -> AnyStr: pass [builtins fixtures/primitives.pyi] [case testOverlappingOverloadCounting] @@ -1362,11 +1380,11 @@ def r1(x: Any) -> Any: ... @overload def r2(x: Tuple[A, ...]) -> A: ... @overload -def r2(x: Tuple[A, A]) -> B: ... +def r2(x: Tuple[A, A]) -> B: ... # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader @overload -def r2(x: Tuple[A]) -> B: ... +def r2(x: Tuple[A]) -> B: ... # E: Overloaded function signature 3 will never be matched: function 1's parameter type(s) are the same or broader @overload -def r2(x: Tuple[()]) -> B: ... +def r2(x: Tuple[()]) -> B: ... # E: Overloaded function signature 4 will never be matched: function 1's parameter type(s) are the same or broader def r2(x: Any) -> Any: ... [builtins fixtures/tuple.pyi] @@ -1391,7 +1409,7 @@ def r(x: Any) -> Any:... @overload def g(x: A) -> A: ... @overload -def g(x: Tuple[A1, ...]) -> B: ... # E: Overloaded function signatures 2 and 3 overlap with incompatible return types +def g(x: Tuple[A1, ...]) -> B: ... # TODO: This should be an error @overload def g(x: Tuple[A, A]) -> C: ... @overload @@ -1504,3 +1522,329 @@ def bar(x: None) -> object: ... # E: Overloaded function signatures 1 and 2 ove @overload def bar(x: Optional[str]) -> str: ... def bar(x): pass + +[case testOverloadWithNonPositionalArgs] +from typing import overload + +class A: ... +class B: ... +class C: ... + +@overload +def foo(*, p1: A, p2: B = B()) -> A: ... +@overload +def foo(*, p2: B = B()) -> B: ... +def foo(p1, p2=None): ... + +reveal_type(foo()) # E: Revealed type is '__main__.B' +reveal_type(foo(p2=B())) # E: Revealed type is '__main__.B' +reveal_type(foo(p1=A())) # E: Revealed type is '__main__.A' + +[case testOverloadWithNonPositionalArgsIgnoresOrder] +from typing import overload + +class A: ... +class B(A): ... +class X: ... +class Y: ... + +@overload +def f(*, p1: X, p2: A) -> X: ... +@overload +def f(*, p2: B, p1: X) -> Y: ... # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader +def f(*, p1, p2): ... + +@overload +def g(*, p1: X, p2: B) -> X: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +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 +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(*x: int) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo1(x: int, y: int, z: int) -> str: ... + +@overload +def foo2(*x: int) -> int: ... +@overload +def foo2(x: int, y: str, z: int) -> str: ... + +@overload +def bar1(x: int, y: int, z: int) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def bar1(*x: int) -> int: ... + +@overload +def bar2(x: int, y: str, z: int) -> str: ... +@overload +def bar2(*x: int) -> int: ... + +[case testOverloadDetectsPossibleMatchesWithGenerics] +from typing import overload, TypeVar, Generic + +T = TypeVar('T') + +@overload +def foo(x: None, y: None) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo(x: T, y: T) -> int: ... +def foo(x): ... + +# TODO: We should allow this; T can't be bound to two distinct types +@overload +def bar(x: None, y: int) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def bar(x: T, y: T) -> int: ... +def bar(x, y): ... + +class Wrapper(Generic[T]): + # TODO: This should be an error + @overload + def foo(self, x: None, y: None) -> str: ... + @overload + def foo(self, x: T, y: None) -> str: ... + def foo(self, x): ... + + @overload + def bar(self, x: None, y: int) -> str: ... + @overload + def bar(self, x: T, y: T) -> str: ... + def bar(self, x, y): ... + +[case testOverloadFlagsPossibleMatches] +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(x: str) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo1(x: str, y: str = ...) -> int: ... + +@overload +def foo2(x: str, y: str = ...) -> int: ... +@overload +def foo2(x: str) -> str: ... # E: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader + +@overload +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 +from wrapper import * +[file wrapper.pyi] +from typing import overload + +@overload +def foo1(*args: int) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def foo1(**kwargs: int) -> str: ... + +@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 +[builtins fixtures/dict.pyi] + +[case testOverloadWithPartiallyOverlappingUnions] +from typing import overload, Union + +class A: ... +class B: ... +class C: ... + +@overload +def f(x: Union[A, B]) -> int: ... # TODO: This should be an error +@overload +def f(x: Union[B, C]) -> str: ... +def f(x): ... + +[case testOverloadNotConfusedForProperty] +from typing import overload + +class PropertyClass: + @property + def foo(self) -> str: return "..." + @foo.setter + def foo(self, value: str) -> None: pass + @foo.deleter + def foo(self) -> None: pass + +class OverloadClass: + @overload + def foo(self) -> str: pass + @overload + def foo(self, value: str) -> None: pass + @overload + def foo(self) -> None: pass # E: Overloaded function signature 3 will never be matched: function 1's parameter type(s) are the same or broader + def foo(self, *args): pass + +[builtins fixtures/property.pyi] + +[case testOverloadInferUnionReturnBasic] +from typing import overload, Union + +class A: ... +class B: ... +class C: ... +class D: ... + +@overload +def f1(x: A) -> B: ... +@overload +def f1(x: C) -> D: ... +def f1(x): ... + +arg1: Union[A, C] +reveal_type(f1(arg1)) # E: Revealed type is 'Union[__main__.B, __main__.D]' + +arg2: Union[A, B] +f1(arg2) # E: Argument 1 to "f1" has incompatible type "Union[A, B]"; expected "A" + +@overload +def f2(x: A) -> B: ... +@overload +def f2(x: C) -> B: ... +def f2(x): ... + +reveal_type(f2(arg1)) # E: Revealed type is '__main__.B' + +[case testOverloadInferUnionReturnMultipleArguments] +from typing import overload, Union + +class A: ... +class B: ... +class C: ... +class D: ... + +@overload +def f1(x: A, y: C) -> B: ... +@overload +def f1(x: C, y: A) -> D: ... +def f1(x, y): ... + +arg1: Union[A, C] +reveal_type(f1(arg1, arg1)) + +@overload +def f2(x: A, y: C) -> B: ... +@overload +def f2(x: C, y: C) -> D: ... +def f2(x, y): ... + +reveal_type(f2(arg1, arg1)) +reveal_type(f2(arg1, C())) + +[out] +main:15: error: Revealed type is '__main__.B' +main:15: error: Argument 1 to "f1" has incompatible type "Union[A, C]"; expected "A" +main:15: error: Argument 2 to "f1" has incompatible type "Union[A, C]"; expected "C" +main:23: error: Revealed type is 'Union[__main__.B, __main__.D]' +main:23: error: Argument 2 to "f2" has incompatible type "Union[A, C]"; expected "C" +main:24: error: Revealed type is 'Union[__main__.B, __main__.D]' + +[case testOverloadInferUnionSkipIfParameterNamesAreDifferent] +from typing import overload, Union + +class A: ... +class B: ... +class C: ... + +@overload +def f(x: A) -> B: ... +@overload +def f(y: B) -> C: ... +def f(x): ... + +x: Union[A, B] +reveal_type(f(A())) # E: Revealed type is '__main__.B' +reveal_type(f(B())) # E: Revealed type is '__main__.C' +f(x) # E: Argument 1 to "f" has incompatible type "Union[A, B]"; expected "A" + +[case testOverloadInferUnionReturnFunctionsWithKwargs] +from typing import overload, Union, Optional + +class A: ... +class B: ... +class C: ... +class D(B, C): ... + +@overload +def f(x: A) -> D: ... +@overload +def f(x: A, y: Optional[B] = None) -> C: ... +@overload +def f(x: A, z: Optional[C] = None) -> B: ... +def f(x, y=None, z=None): ... + +reveal_type(f(A(), B())) +reveal_type(f(A(), C())) + +arg: Union[B, C] +reveal_type(f(A(), arg)) +reveal_type(f(A())) + +[builtins fixtures/tuple.pyi] +[out] +main:16: error: Revealed type is '__main__.C' +main:17: error: Revealed type is '__main__.B' +main:20: error: Revealed type is '__main__.C' +main:20: error: Argument 2 to "f" has incompatible type "Union[B, C]"; expected "Optional[B]" +main:21: error: Revealed type is '__main__.D' + +[case testOverloadingInferUnionReturnWithTypevarWithValueRestriction] +from typing import overload, Union, TypeVar, Generic + +class A: pass +class B: pass +class C: pass + +T = TypeVar('T', B, C) + +class Wrapper(Generic[T]): + @overload + def f(self, x: T) -> B: ... + + @overload + def f(self, x: A) -> C: ... + + def f(self, x): ... + +obj: Wrapper[B] = Wrapper() +x: Union[A, B] + +reveal_type(obj.f(A())) # E: Revealed type is '__main__.C' +reveal_type(obj.f(B())) # E: Revealed type is '__main__.B' +reveal_type(obj.f(x)) # E: Revealed type is 'Union[__main__.B, __main__.C]' + +[case testOverloadingInferUnionReturnWithTypevarReturn] +from typing import overload, Union, TypeVar, Generic + +T = TypeVar('T') + +class Wrapper1(Generic[T]): pass +class Wrapper2(Generic[T]): pass +class A: pass +class B: pass + +@overload +def f(x: Wrapper1[T]) -> T: ... +@overload +def f(x: Wrapper2[T]) -> T: ... +def f(x): ... + +obj1: Union[Wrapper1[A], Wrapper2[A]] +reveal_type(f(obj1)) # E: Revealed type is '__main__.A' + +obj2: Union[Wrapper1[A], Wrapper2[B]] +reveal_type(f(obj2)) # E: Revealed type is 'Union[__main__.A, __main__.B]' + diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index d731a406da446..14e1129cc78a5 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -1301,8 +1301,8 @@ def f(x): reveal_type(f(C1())) # E: Revealed type is 'builtins.int' reveal_type(f(C2())) # E: Revealed type is 'builtins.str' class D(C1, C2): pass # Compatible with both P1 and P2 -# FIXME: the below is not right, see #1322 -reveal_type(f(D())) # E: Revealed type is 'Any' +# TODO: Should this return a union instead? +reveal_type(f(D())) # E: Revealed type is 'builtins.int' f(C()) # E: No overload variant of "f" matches argument type "C" [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index ae769e9a933d7..2619e9c8f8ab9 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -1261,15 +1261,15 @@ A = TypedDict('A', {'x': int}) B = TypedDict('B', {'y': str}) @overload -def f(x: A) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +def f(x: A) -> int: ... @overload def f(x: B) -> str: ... def f(x): pass a: A b: B -reveal_type(f(a)) # E: Revealed type is 'Any' -reveal_type(f(b)) # E: Revealed type is 'Any' +reveal_type(f(a)) # E: Revealed type is 'builtins.int' +reveal_type(f(b)) # E: Revealed type is 'builtins.str' [builtins fixtures/dict.pyi] [typing fixtures/typing-full.pyi] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 954c1fd62ec31..58196ee63c8d0 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -2055,6 +2055,7 @@ def g() -> None: pass == main:2: error: Cannot find module named 'm' main:2: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help) +main:7: error: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader main:9: error: Cannot find module named 'n' [case testOverloadSpecialCase] @@ -2083,6 +2084,7 @@ def g() -> None: pass == main:2: error: Cannot find module named 'm' main:2: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help) +main:12: error: Overloaded function signature 2 will never be matched: function 1's parameter type(s) are the same or broader main:14: error: Cannot find module named 'n' [case testRefreshGenericClass]