diff --git a/mypy/subtypes.py b/mypy/subtypes.py index a280c217cd8a..3a4c7fdc0fd5 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -249,13 +249,46 @@ def visit_overloaded(self, left: Overloaded) -> bool: return True return False elif isinstance(right, Overloaded): - # TODO: this may be too restrictive - if len(left.items()) != len(right.items()): - return False - for i in range(len(left.items())): - if not is_subtype(left.items()[i], right.items()[i], self.check_type_parameter, - ignore_pos_arg_names=self.ignore_pos_arg_names): + # Ensure each overload in the right side (the supertype) is accounted for. + previous_match_left_index = -1 + matched_overloads = set() + possible_invalid_overloads = set() + + for right_index, right_item in enumerate(right.items()): + found_match = False + + for left_index, left_item in enumerate(left.items()): + subtype_match = is_subtype(left_item, right_item, self.check_type_parameter, + ignore_pos_arg_names=self.ignore_pos_arg_names) + + # Order matters: we need to make sure that the index of + # this item is at least the index of the previous one. + if subtype_match and previous_match_left_index <= left_index: + if not found_match: + # Update the index of the previous match. + previous_match_left_index = left_index + found_match = True + matched_overloads.add(left_item) + possible_invalid_overloads.discard(left_item) + 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 this is an overload that's already been matched, there's no + # problem. + if left_item not in matched_overloads: + possible_invalid_overloads.add(left_item) + + if not found_match: return False + + if possible_invalid_overloads: + # There were potentially invalid overloads that were never matched to the + # supertype. + return False return True elif isinstance(right, UnboundType): return True diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 1dd3353ec903..94a74ba2fa69 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1416,8 +1416,6 @@ class B(A): def __add__(self, x: str) -> A: pass @overload def __add__(self, x: type) -> A: pass -[out] -tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A" [case testOverloadedOperatorMethodOverrideWithSwitchedItemOrder] from foo import * @@ -2494,6 +2492,88 @@ reveal_type(f(BChild())) # E: Revealed type is 'foo.B' [builtins fixtures/classmethod.pyi] [out] +[case testSubtypeWithMoreOverloadsThanSupertypeSucceeds] +from foo import * +[file foo.pyi] +from typing import overload + + +class X: pass +class Y: pass +class Z: pass + + +class A: + @overload + def f(self, x: X) -> X: pass + @overload + def f(self, y: Y) -> Y: pass + +class B(A): + @overload + def f(self, x: X) -> X: pass + @overload + def f(self, y: Y) -> Y: pass + @overload + def f(self, z: Z) -> Z: pass +[builtins fixtures/classmethod.pyi] +[out] + +[case testSubtypeOverloadCoveringMultipleSupertypeOverloadsSucceeds] +from foo import * +[file foo.pyi] +from typing import overload + + +class A: pass +class B(A): pass +class C(A): pass +class D: pass + + +class Super: + @overload + def foo(self, a: B) -> C: pass + @overload + def foo(self, a: C) -> A: pass + @overload + def foo(self, a: D) -> D: pass + +class Sub(Super): + @overload + def foo(self, a: A) -> C: pass + @overload + def foo(self, a: D) -> D: pass +[builtins fixtures/classmethod.pyi] +[out] + +[case testSubtypeOverloadWithOverlappingArgumentsButWrongReturnType] +from foo import * +[file foo.pyi] +from typing import overload + + +class A: pass +class B(A): pass +class C: pass + + +class Super: + @overload + def foo(self, a: A) -> A: pass + @overload + def foo(self, a: C) -> C: pass + +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 + @overload + def foo(self, a: C) -> C: pass +[builtins fixtures/classmethod.pyi] +[out] + [case testTypeTypeOverlapsWithObjectAndType] from foo import * [file foo.pyi]