Repository: python/mypy

Issue:
Strange error for overloaded get() method and Tuple key value
This came up when I merged https://github.com/python/typeshed/pull/223.
I think I have a complete repo that doesn't depend on the stubs. Consider this:

``` python
from typing import TypeVar, Generic, Tuple, overload

KT = TypeVar('KT')
VT = TypeVar('VT')

class A(Generic[KT, VT]):
    def get(self, arg: KT) -> VT:
        pass

a = A()  # type: A[Tuple[int, int], int]
a.get((0, 0))  # OK

class B(Generic[KT, VT]):
    @overload
    def get(self, arg: KT) -> VT:
        pass
    @overload
    def get(self, arg: KT, default: VT) -> VT:
        pass

b1 = B()  # type: B[int, int]
b1.get(0)  # OK
b2 = B()  # type: B[Tuple[int, int], int]
b2.get((0, 0))  # error here
```

I get an error on the very last line (`b2.get((0, 0)`):

```
x.py:24: error: No overload variant of "get" of "B" matches argument types [Tuple[builtins.int, builtins.int]]
```

But there's no error two lines above, on `b1.get(0)`. Also note that the same code with class A which does not overload `get()`) has no errors. I've got a feeling that there's a subtle bug in the handling of type variables that only shows up when a Tuple is matched against an overloaded method.

@rwbarton Any thoughts? Or @JukkaL ?



Discussion:
I looked at this and noticed that the `fallback` of tuple types are inconsistent. One of the tuple types it is `builtins.tuple[builtins.int]`, and the other it's just `builtins.tuple`. We should always have the first form. In addition, erasing tuple types was broken, as the fallback didn't get erased.


Resolving patch:
diff --git a/mypy/erasetype.py b/mypy/erasetype.py
--- a/mypy/erasetype.py
+++ b/mypy/erasetype.py
@@ -67,7 +67,7 @@ def visit_overloaded(self, t: Overloaded) -> Type:
         return t.items()[0].accept(self)
 
     def visit_tuple_type(self, t: TupleType) -> Type:
-        return t.fallback
+        return t.fallback.accept(self)
 
     def visit_union_type(self, t: UnionType) -> Type:
         return AnyType()        # XXX: return underlying type if only one?
diff --git a/mypy/typeanal.py b/mypy/typeanal.py
--- a/mypy/typeanal.py
+++ b/mypy/typeanal.py
@@ -100,8 +100,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
                     node = self.lookup_fqn_func('builtins.tuple')
                     info = cast(TypeInfo, node.node)
                     return Instance(info, [t.args[0].accept(self)], t.line)
-                return TupleType(self.anal_array(t.args),
-                                 self.builtin_type('builtins.tuple'))
+                return self.tuple_type(self.anal_array(t.args))
             elif fullname == 'typing.Union':
                 items = self.anal_array(t.args)
                 items = [item for item in items if not isinstance(item, Void)]
@@ -191,7 +190,7 @@ def visit_tuple_type(self, t: TupleType) -> Type:
         if star_count > 1:
             self.fail('At most one star type allowed in a tuple', t)
             return AnyType()
-        fallback = t.fallback if t.fallback else self.builtin_type('builtins.tuple')
+        fallback = t.fallback if t.fallback else self.builtin_type('builtins.tuple', [AnyType()])
         return TupleType(self.anal_array(t.items), fallback, t.line)
 
     def visit_star_type(self, t: StarType) -> Type:
@@ -257,10 +256,13 @@ def anal_var_defs(self, var_defs: List[TypeVarDef]) -> List[TypeVarDef]:
                                 vd.line))
         return a
 
-    def builtin_type(self, fully_qualified_name: str) -> Instance:
+    def builtin_type(self, fully_qualified_name: str, args: List[Type] = None) -> Instance:
         node = self.lookup_fqn_func(fully_qualified_name)
         info = cast(TypeInfo, node.node)
-        return Instance(info, [])
+        return Instance(info, args or [])
+
+    def tuple_type(self, items: List[Type]) -> TupleType:
+        return TupleType(items, fallback=self.builtin_type('builtins.tuple', [AnyType()]))
 
 
 class TypeAnalyserPass3(TypeVisitor[None]):


Test patch:
diff --git a/mypy/test/data/check-overloading.test b/mypy/test/data/check-overloading.test
--- a/mypy/test/data/check-overloading.test
+++ b/mypy/test/data/check-overloading.test
@@ -678,3 +678,20 @@ def f(x: int) -> None: pass
 def f(x: B) -> str: pass # E: Overloaded function signatures 2 and 3 overlap with incompatible return types
 @overload
 def f(x: A) -> int: pass
+
+[case testOverloadWithTupleMatchingTypeVar]
+from typing import TypeVar, Generic, Tuple, overload
+
+T = TypeVar('T')
+
+class A(Generic[T]):
+    @overload
+    def f(self, arg: T) -> None:
+        pass
+    @overload
+    def f(self, arg: T, default: int) -> None:
+        pass
+
+b = A()  # type: A[Tuple[int, int]]
+b.f((0, 0))
+b.f((0, '')) # E: Argument 1 to "f" of "A" has incompatible type "Tuple[int, str]"; expected "Tuple[int, int]"
diff --git a/mypy/test/data/check-tuples.test b/mypy/test/data/check-tuples.test
--- a/mypy/test/data/check-tuples.test
+++ b/mypy/test/data/check-tuples.test
@@ -594,16 +594,18 @@ b = bool()
 s = t.__len__()  # E: Incompatible types in assignment (expression has type "int", variable has type "str")
 i = t.__str__()  # E: Incompatible types in assignment (expression has type "str", variable has type "int")
 i = s in t       # E: Incompatible types in assignment (expression has type "bool", variable has type "int")
-t.foo            # E: "tuple" has no attribute "foo"
+t.foo            # E: Tuple[Any, ...] has no attribute "foo"
 
 i = t.__len__()
 s = t.__str__()
 b = s in t
 
 [file builtins.py]
+from typing import TypeVar, Generic
+_T = TypeVar('_T')
 class object:
     def __init__(self) -> None: pass
-class tuple:
+class tuple(Generic[_T]):
     def __len__(self) -> int: pass
     def __str__(self) -> str: pass
     def __contains__(self, o: object) -> bool: pass
@@ -669,6 +671,7 @@ class A(Tuple[int, str]):
         a, b = self
         b, a = self  # Error
         self.f('')   # Error
+[builtins fixtures/tuple.py]
 [out]
 main:1: note: In module imported here:
 tmp/m.pyi: note: In member "f" of class "A":
@@ -679,6 +682,7 @@ tmp/m.pyi:7: error: Argument 1 to "f" of "A" has incompatible type "str"; expect
 [case testInvalidTupleBaseClass]
 from typing import Tuple
 class A(Tuple[int, str]): pass # E: Tuple[...] not supported as a base class outside a stub file
+[builtins fixtures/tuple.py]
 [out]
 
 [case testValidTupleBaseClass]
@@ -704,6 +708,7 @@ from typing import TypeVar, Generic, Tuple
 T = TypeVar('T')
 class Test(Generic[T], Tuple[T]): pass
 x = Test() # type: Test[int]
+[builtins fixtures/tuple.py]
 [out]
 main:3: error: Tuple[...] not supported as a base class outside a stub file
 main:4: error: Generic tuple types not supported
diff --git a/mypy/test/data/semanal-classes.test b/mypy/test/data/semanal-classes.test
--- a/mypy/test/data/semanal-classes.test
+++ b/mypy/test/data/semanal-classes.test
@@ -572,6 +572,7 @@ import m
 [file m.pyi]
 from typing import Tuple
 class A(Tuple[int, str]): pass
+[builtins fixtures/tuple.py]
 [out]
 MypyFile:1(
   Import:1(m))
@@ -583,7 +584,7 @@ MypyFile:1(
     TupleType(
       Tuple[builtins.int, builtins.str])
     BaseType(
-      builtins.tuple)
+      builtins.tuple[Any])
     PassStmt:2()))
 
 [case testBaseClassFromIgnoredModule]