Repository: python/mypy

Issue:
Signatures of overloaded methods are incompatible with supertypes, even if they really are
e.g.

```python
from typing import overload

class A:
    @overload
    def f(self, x: str) -> str: ...
    @overload
    def f(self, y: int) -> int: ...

class B(A):
    @overload
    def f(self, x: str) -> str: ...
    @overload
    def f(self, y: int) -> int: ...
    @overload
    def f(self, z: bytes) -> bytes: ...
```

IMO in this case, it should still be compatible, since `B` supports `A`'s full API, just with another extra function.
Signatures of overloaded methods are incompatible with supertypes, even if they really are
e.g.

```python
from typing import overload

class A:
    @overload
    def f(self, x: str) -> str: ...
    @overload
    def f(self, y: int) -> int: ...

class B(A):
    @overload
    def f(self, x: str) -> str: ...
    @overload
    def f(self, y: int) -> int: ...
    @overload
    def f(self, z: bytes) -> bytes: ...
```

IMO in this case, it should still be compatible, since `B` supports `A`'s full API, just with another extra function.


Discussion:
This looks like me, if somebody else doesn't get it first. 

> On Apr 26, 2017, at 9:49 AM, Ryan Gonzalez <notifications@github.com> wrote:
> 
> e.g.
> 
> from typing import overload
> 
> class A:
>     @overload
>     def f(self, x: str) -> str: ...
>     @overload
>     def f(self, y: int) -> int: ...
> 
> class B(A):
>     @overload
>     def f(self, x: str) -> str: ...
>     @overload
>     def f(self, y: int) -> int: ...
>     @overload
>     def f(self, z: bytes) -> bytes: ...
> IMO in this case, it should still be compatible, since B supports A's full API, just with another extra function.
> 
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub, or mute the thread.
> 

@sixolet I think you might be around 10 seconds too late... ;)
This looks like me, if somebody else doesn't get it first. 

> On Apr 26, 2017, at 9:49 AM, Ryan Gonzalez <notifications@github.com> wrote:
> 
> e.g.
> 
> from typing import overload
> 
> class A:
>     @overload
>     def f(self, x: str) -> str: ...
>     @overload
>     def f(self, y: int) -> int: ...
> 
> class B(A):
>     @overload
>     def f(self, x: str) -> str: ...
>     @overload
>     def f(self, y: int) -> int: ...
>     @overload
>     def f(self, z: bytes) -> bytes: ...
> IMO in this case, it should still be compatible, since B supports A's full API, just with another extra function.
> 
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub, or mute the thread.
> 

@sixolet I think you might be around 10 seconds too late... ;)

Resolving patch:
diff --git a/mypy/subtypes.py b/mypy/subtypes.py
--- 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


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- 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]