Repository: python/mypy

Issue:
"Overloaded ... incompatible return types" error when one overloaded argument is a Tuple
Running mypy (0.550) on the following code:

```python
from typing import overload, Tuple, Any

class A: ...
class B: ...
class C: ...
class D: ...

@overload
def f(x: A) -> B: ...

@overload
def f(x: Tuple[C]) -> D: ...

def f(x: Any) -> Any:...
```

reports:

`error: Overloaded function signatures 1 and 2 overlap with incompatible return types`

I've tried a couple different types for the second overload and so far the error only seems to occur with `Tuple[...]` arguments.  For instance, I don't see the issue if I substitute `def f(x:List[C]) -> D: ...` as the 2nd overload.


Discussion:
Yeah, this looks like a bug. I'm not sure where to look. Strangely it perseveres if I use `Tuple[C, C]` or `Tuple[Any]` but it is gone with `Tuple` or `tuple`.

Resolving patch:
diff --git a/mypy/meet.py b/mypy/meet.py
--- a/mypy/meet.py
+++ b/mypy/meet.py
@@ -73,7 +73,6 @@ class C(A, B): ...
     multiple inheritance actually occurs somewhere in a program, due to
     stub files hiding implementation details, dynamic loading etc.
 
-    TODO: Don't consider tuples always overlapping.
     TODO: Don't consider callables always overlapping.
     TODO: Don't consider type variables with values always overlapping.
     """
@@ -95,6 +94,20 @@ class C(A, B): ...
         t = t.as_anonymous().fallback
     if isinstance(s, TypedDictType):
         s = s.as_anonymous().fallback
+
+    if isinstance(t, UnionType):
+        return any(is_overlapping_types(item, s)
+                   for item in t.relevant_items())
+    if isinstance(s, UnionType):
+        return any(is_overlapping_types(t, item)
+                   for item in s.relevant_items())
+
+    # We must check for TupleTypes before Instances, since Tuple[A, ...]
+    # is an Instance
+    tup_overlap = is_overlapping_tuples(t, s, use_promotions)
+    if tup_overlap is not None:
+        return tup_overlap
+
     if isinstance(t, Instance):
         if isinstance(s, Instance):
             # Consider two classes non-disjoint if one is included in the mro
@@ -114,12 +127,6 @@ class C(A, B): ...
             if s.type.is_protocol and is_protocol_implementation(t, s):
                 return True
             return False
-    if isinstance(t, UnionType):
-        return any(is_overlapping_types(item, s)
-                   for item in t.relevant_items())
-    if isinstance(s, UnionType):
-        return any(is_overlapping_types(t, item)
-                   for item in s.relevant_items())
     if isinstance(t, TypeType) and isinstance(s, TypeType):
         # If both types are TypeType, compare their inner types.
         return is_overlapping_types(t.item, s.item, use_promotions)
@@ -136,11 +143,35 @@ class C(A, B): ...
         if isinstance(t, NoneTyp) != isinstance(s, NoneTyp):
             # NoneTyp does not overlap with other non-Union types under strict Optional checking
             return False
-    # We conservatively assume that non-instance, non-union, and non-TypeType types can overlap
-    # any other types.
+    # We conservatively assume that non-instance, non-union, non-TupleType and non-TypeType types
+    # can overlap any other types.
     return True
 
 
+def is_overlapping_tuples(t: Type, s: Type, use_promotions: bool) -> Optional[bool]:
+    """Part of is_overlapping_types(), for tuples only"""
+    t = adjust_tuple(t, s) or t
+    s = adjust_tuple(s, t) or s
+    if isinstance(t, TupleType) or isinstance(s, TupleType):
+        if isinstance(t, TupleType) and isinstance(s, TupleType):
+            if t.length() == s.length():
+                if all(is_overlapping_types(ti, si, use_promotions)
+                       for ti, si in zip(t.items, s.items)):
+                    return True
+        # TupleType and non-tuples do not overlap
+        return False
+    # No tuples are involved here
+    return None
+
+
+def adjust_tuple(left: Type, r: Type) -> Optional[TupleType]:
+    """Find out if `left` is a Tuple[A, ...], and adjust its length to `right`"""
+    if isinstance(left, Instance) and left.type.fullname() == 'builtins.tuple':
+        n = r.length() if isinstance(r, TupleType) else 1
+        return TupleType([left.args[0]] * n, left)
+    return None
+
+
 class TypeMeetVisitor(TypeVisitor[Type]):
     def __init__(self, s: Type) -> None:
         self.s = s


Test patch:
diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test
--- a/test-data/unit/check-overloading.test
+++ b/test-data/unit/check-overloading.test
@@ -1319,3 +1319,83 @@ class MyInt:
     @overload
     def __init__(self, x: str, y: int) -> None: pass
 [out]
+
+[case testOverloadTupleInstance]
+from typing import overload, Tuple, Any
+
+class A: ...
+class A1(A): ...
+class B: ...
+class C: ...
+class D: ...
+
+@overload
+def f(x: A) -> A: ...
+@overload
+def f(x: Tuple[C]) -> B: ...
+@overload
+def f(x: Tuple[A1, int]) -> C: ...  # E: Overloaded function signatures 3 and 5 overlap with incompatible return types
+@overload
+def f(x: Tuple[A, str]) -> D: ...
+@overload
+def f(x: Tuple[A, int]) -> D: ...
+@overload
+def f(x: Tuple[()]) -> D: ...
+def f(x: Any) -> Any:...
+
+[case testOverloadTupleEllipsisNumargs]
+from typing import overload, Tuple, Any
+
+class A: ...
+class B: ...
+
+@overload
+def r1(x: Tuple[()]) -> B: ...  # E: Overloaded function signatures 1 and 4 overlap with incompatible return types
+@overload
+def r1(x: Tuple[A]) -> B: ...  # E: Overloaded function signatures 2 and 4 overlap with incompatible return types
+@overload
+def r1(x: Tuple[A, A]) -> B: ...  # E: Overloaded function signatures 3 and 4 overlap with incompatible return types
+@overload
+def r1(x: Tuple[A, ...]) -> A: ...
+def r1(x: Any) -> Any: ...
+
+@overload
+def r2(x: Tuple[A, ...]) -> A: ...
+@overload
+def r2(x: Tuple[A, A]) -> B: ...
+@overload
+def r2(x: Tuple[A]) -> B: ...
+@overload
+def r2(x: Tuple[()]) -> B: ...
+def r2(x: Any) -> Any: ...
+
+[builtins fixtures/tuple.pyi]
+
+[case testOverloadTupleEllipsisVariance]
+from typing import overload, Tuple, Any
+
+class A: ...
+class A1(A): ...
+class B: ...
+class C: ...
+class D: ...
+
+@overload
+def r(x: Tuple[A1, ...]) -> A: ...  # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
+@overload
+def r(x: Tuple[A, ...]) -> B: ...
+@overload
+def r(x: Tuple[B, ...]) -> C: ...
+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
+@overload
+def g(x: Tuple[A, A]) -> C: ...
+@overload
+def g(x: Tuple[A, B]) -> D: ...
+def g(x: Any) -> Any:...
+
+[builtins fixtures/tuple.pyi]