Repository: python/mypy

Issue:
Change meaning of unparameterized Tuple to Tuple[Any, ...] (== tuple)
Currently, `Type` (without parameters) means a 0-length tuple, IOW `Tuple[()]` (which is the PEP 484 way to spell it). Analogy with `List` suggests it should be an unconstrained tuple, i.e. `Tuple` == `tuple` == `Tuple[Any, ...]`. The second half is already true. The `List` analogy is `List` == `list` == `List[Any]`. (The last part of that is spelled differently, but is a closer analogy to `Tuple[Any, ...]` than to `Tuple[Any]`, since the latter is constrained to 1-length tuples.)



Discussion:
See also https://github.com/python/typing/issues/284


Resolving patch:
diff --git a/mypy/fastparse.py b/mypy/fastparse.py
--- a/mypy/fastparse.py
+++ b/mypy/fastparse.py
@@ -833,12 +833,15 @@ def visit_Subscript(self, n: ast35.Subscript) -> Type:
         assert isinstance(value, UnboundType)
         assert not value.args
 
+        empty_tuple_index = False
         if isinstance(n.slice.value, ast35.Tuple):
             params = self.visit_list(n.slice.value.elts)
+            if len(n.slice.value.elts) == 0:
+                empty_tuple_index = True
         else:
             params = [self.visit(n.slice.value)]
 
-        return UnboundType(value.name, params, line=self.line)
+        return UnboundType(value.name, params, line=self.line, empty_tuple_index=empty_tuple_index)
 
     def visit_Tuple(self, n: ast35.Tuple) -> Type:
         return TupleType(self.visit_list(n.elts), None, implicit=True, line=self.line)
diff --git a/mypy/typeanal.py b/mypy/typeanal.py
--- a/mypy/typeanal.py
+++ b/mypy/typeanal.py
@@ -107,6 +107,9 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
             elif fullname == 'typing.Any':
                 return AnyType()
             elif fullname == 'typing.Tuple':
+                if len(t.args) == 0 and not t.empty_tuple_index:
+                    # Bare 'Tuple' is same as 'tuple'
+                    return self.builtin_type('builtins.tuple')
                 if len(t.args) == 2 and isinstance(t.args[1], EllipsisType):
                     # Tuple[T, ...] (uniform, variable-length tuple)
                     node = self.lookup_fqn_func('builtins.tuple')
diff --git a/mypy/types.py b/mypy/types.py
--- a/mypy/types.py
+++ b/mypy/types.py
@@ -181,6 +181,8 @@ class UnboundType(Type):
     optional = False
     # is this type a return type?
     is_ret_type = False
+    # special case for X[()]
+    empty_tuple_index = False
 
     def __init__(self,
                  name: str,
@@ -188,13 +190,15 @@ def __init__(self,
                  line: int = -1,
                  column: int = -1,
                  optional: bool = False,
-                 is_ret_type: bool = False) -> None:
+                 is_ret_type: bool = False,
+                 empty_tuple_index: bool = False) -> None:
         if not args:
             args = []
         self.name = name
         self.args = args
         self.optional = optional
         self.is_ret_type = is_ret_type
+        self.empty_tuple_index = empty_tuple_index
         super().__init__(line, column)
 
     def accept(self, visitor: 'TypeVisitor[T]') -> T:


Test patch:
diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test
--- a/test-data/unit/check-tuples.test
+++ b/test-data/unit/check-tuples.test
@@ -820,3 +820,22 @@ from typing import List, Tuple
 def f() -> Tuple[List[str], ...]:
     return ([],)
 [builtins fixtures/tuple.pyi]
+
+[case testTupleWithoutArgs]
+from typing import Tuple
+def f(a: Tuple) -> None: pass
+f(())
+f((1,))
+f(('', ''))
+f(0)  # E: Argument 1 to "f" has incompatible type "int"; expected Tuple[Any, ...]
+[builtins fixtures/tuple.pyi]
+
+[case testTupleSingleton]
+# flags: --fast-parser
+from typing import Tuple
+def f(a: Tuple[()]) -> None: pass
+f(())
+f((1,))  # E: Argument 1 to "f" has incompatible type "Tuple[int]"; expected "Tuple[]"
+f(('', ''))  # E: Argument 1 to "f" has incompatible type "Tuple[str, str]"; expected "Tuple[]"
+f(0)  # E: Argument 1 to "f" has incompatible type "int"; expected "Tuple[]"
+[builtins fixtures/tuple.pyi]