From d1ec354516a812b86e97f5a1aeb36dc1bff904af Mon Sep 17 00:00:00 2001
From: Ryan Gonzalez <rymg19@gmail.com>
Date: Wed, 26 Apr 2017 15:19:33 -0500
Subject: [PATCH 1/5] Fix #3262: allow subtypes to define more overloads than
 their supertype

---
 mypy/subtypes.py                  | 16 +++++++----
 test-data/unit/check-classes.test | 44 +++++++++++++++++++------------
 2 files changed, 38 insertions(+), 22 deletions(-)

diff --git a/mypy/subtypes.py b/mypy/subtypes.py
index a280c217cd8a..9848cb45154b 100644
--- a/mypy/subtypes.py
+++ b/mypy/subtypes.py
@@ -249,13 +249,19 @@ 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,
+            # Ensure each overload in the left side is accounted for.
+            sub_overloads = left.items()[:]
+            while sub_overloads:
+                left_item = sub_overloads[-1]
+                for right_item in right.items():
+                    if is_subtype(left_item, right_item, self.check_type_parameter,
                                   ignore_pos_arg_names=self.ignore_pos_arg_names):
+                        sub_overloads.pop()
+                        break
+                else:
+                    # One of the overloads was not present in the right side.
                     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..36c66a906001 100644
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -1419,23 +1419,6 @@ class B(A):
 [out]
 tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A"
 
-[case testOverloadedOperatorMethodOverrideWithSwitchedItemOrder]
-from foo import *
-[file foo.pyi]
-from typing import overload, Any
-class A:
-    @overload
-    def __add__(self, x: 'B') -> 'B': pass
-    @overload
-    def __add__(self, x: 'A') -> 'A': pass
-class B(A):
-    @overload
-    def __add__(self, x: 'A') -> 'A': pass
-    @overload
-    def __add__(self, x: 'B') -> 'B': pass
-[out]
-tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A"
-
 [case testReverseOperatorMethodArgumentType]
 from typing import Any
 class A: pass
@@ -2494,6 +2477,33 @@ 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 testTypeTypeOverlapsWithObjectAndType]
 from foo import *
 [file foo.pyi]

From 49e7b229fab278b872dea8c0caa4ff05f2013bb2 Mon Sep 17 00:00:00 2001
From: Ryan Gonzalez <rymg19@gmail.com>
Date: Fri, 15 Sep 2017 15:30:13 -0500
Subject: [PATCH 2/5] Swap subtype check

---
 mypy/subtypes.py | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/mypy/subtypes.py b/mypy/subtypes.py
index 9848cb45154b..948df497baf3 100644
--- a/mypy/subtypes.py
+++ b/mypy/subtypes.py
@@ -249,17 +249,17 @@ def visit_overloaded(self, left: Overloaded) -> bool:
                     return True
             return False
         elif isinstance(right, Overloaded):
-            # Ensure each overload in the left side is accounted for.
-            sub_overloads = left.items()[:]
-            while sub_overloads:
-                left_item = sub_overloads[-1]
-                for right_item in right.items():
+            # Ensure each overload in the right side is accounted for.
+            super_overloads = right.items()[:]
+            while super_overloads:
+                right_item = super_overloads[-1]
+                for left_item in left.items():
                     if is_subtype(left_item, right_item, self.check_type_parameter,
                                   ignore_pos_arg_names=self.ignore_pos_arg_names):
-                        sub_overloads.pop()
+                        super_overloads.pop()
                         break
                 else:
-                    # One of the overloads was not present in the right side.
+                    # One of the overloads was not present in the left side.
                     return False
 
             return True

From b8a83043ce8cf9631b4b9177f076887dadf8a773 Mon Sep 17 00:00:00 2001
From: Ryan Gonzalez <rymg19@gmail.com>
Date: Sat, 30 Sep 2017 17:19:25 -0500
Subject: [PATCH 3/5] Fix an old test

---
 test-data/unit/check-classes.test | 2 --
 1 file changed, 2 deletions(-)

diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
index 36c66a906001..95c840ad4ff8 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 testReverseOperatorMethodArgumentType]
 from typing import Any

From 2ca49fcf12eba664da15364626aba32eabbbd34e Mon Sep 17 00:00:00 2001
From: Ryan Gonzalez <rymg19@gmail.com>
Date: Tue, 7 Nov 2017 14:06:46 -0600
Subject: [PATCH 4/5] Fix several overload supertype/subtype cases

---
 mypy/subtypes.py                  | 38 +++++++++++-----
 test-data/unit/check-classes.test | 72 +++++++++++++++++++++++++++++++
 2 files changed, 99 insertions(+), 11 deletions(-)

diff --git a/mypy/subtypes.py b/mypy/subtypes.py
index 948df497baf3..0dcbf9fa07c5 100644
--- a/mypy/subtypes.py
+++ b/mypy/subtypes.py
@@ -249,17 +249,33 @@ def visit_overloaded(self, left: Overloaded) -> bool:
                     return True
             return False
         elif isinstance(right, Overloaded):
-            # Ensure each overload in the right side is accounted for.
-            super_overloads = right.items()[:]
-            while super_overloads:
-                right_item = super_overloads[-1]
-                for left_item in left.items():
-                    if is_subtype(left_item, right_item, self.check_type_parameter,
-                                  ignore_pos_arg_names=self.ignore_pos_arg_names):
-                        super_overloads.pop()
-                        break
-                else:
-                    # One of the overloads was not present in the left side.
+            # Ensure each overload in the right side (the supertype) is accounted for.
+            previous_match_left_index = -1
+
+            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
+                    else:
+                        # If this one overlaps with the supertype in any way, but it wasn't
+                        # an exact match, then it's a type 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)):
+                            return False
+
+                if not found_match:
                     return False
 
             return True
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
index 95c840ad4ff8..94a74ba2fa69 100644
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -1417,6 +1417,23 @@ class B(A):
     @overload
     def __add__(self, x: type) -> A: pass
 
+[case testOverloadedOperatorMethodOverrideWithSwitchedItemOrder]
+from foo import *
+[file foo.pyi]
+from typing import overload, Any
+class A:
+    @overload
+    def __add__(self, x: 'B') -> 'B': pass
+    @overload
+    def __add__(self, x: 'A') -> 'A': pass
+class B(A):
+    @overload
+    def __add__(self, x: 'A') -> 'A': pass
+    @overload
+    def __add__(self, x: 'B') -> 'B': pass
+[out]
+tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A"
+
 [case testReverseOperatorMethodArgumentType]
 from typing import Any
 class A: pass
@@ -2502,6 +2519,61 @@ class B(A):
 [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]

From eb36c415d2608299159405e4e3a607fd2bb56801 Mon Sep 17 00:00:00 2001
From: Ryan Gonzalez <rymg19@gmail.com>
Date: Tue, 7 Nov 2017 14:20:27 -0600
Subject: [PATCH 5/5] Be more lenient with detecting invalid subtype overloads

---
 mypy/subtypes.py | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/mypy/subtypes.py b/mypy/subtypes.py
index 0dcbf9fa07c5..3a4c7fdc0fd5 100644
--- a/mypy/subtypes.py
+++ b/mypy/subtypes.py
@@ -251,6 +251,8 @@ def visit_overloaded(self, left: Overloaded) -> bool:
         elif isinstance(right, Overloaded):
             # 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
@@ -266,18 +268,27 @@ def visit_overloaded(self, left: Overloaded) -> bool:
                             # 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 type error.
+                        # 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)):
-                            return False
+                            # 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