Repository: python/mypy

Issue:
Tuple types don't work with higher level typing
mypy 1.8.0, python 3.10.10

So tuple types get matched and checked normally in simple situations like this:
```python
a: tuple[int, int] = (2, "a")
# Incompatible types in assignment ...
```

But imagine I want to define a function that takes a type and returns a value of that type (e.g. by deserializing something as the given type):
```python
from typing import Tuple, TypeVar, Type, List

_T = TypeVar("_T")

def structure(structure_into: Type[_T], obj: str) -> _T:
    raise NotImplementedError
```

This works with builtin types, or with generics like `List`:
```
reveal_type(structure(int, "a"))
# Revealed type is "builtins.int"
reveal_type(structure(List[int], "a"))
# Revealed type is "builtins.list[builtins.int]"
```

... but not with tuples:
```python
reveal_type(structure(Tuple[int, int], "a"))
# note: Revealed type is "Never"
# error: Argument 1 to "structure" has incompatible type "object"; expected "type[Never]"  [arg-type]
```

Trying to reveal the type of `Tuple` itself leads to strange results too (compared to `List[...]`):
```
reveal_type(Tuple[int, int])
# Revealed type is "builtins.object"
reveal_type(tuple[int, int])
# Revealed type is "def [_T_co] (typing.Iterable[_T_co`1] =) -> builtins.tuple[_T_co`1, ...]"
```

So the question is, why does `Tuple` get a special treatment, and is there a way to make it behave like other generics?
Tuple types don't work with higher level typing
mypy 1.8.0, python 3.10.10

So tuple types get matched and checked normally in simple situations like this:
```python
a: tuple[int, int] = (2, "a")
# Incompatible types in assignment ...
```

But imagine I want to define a function that takes a type and returns a value of that type (e.g. by deserializing something as the given type):
```python
from typing import Tuple, TypeVar, Type, List

_T = TypeVar("_T")

def structure(structure_into: Type[_T], obj: str) -> _T:
    raise NotImplementedError
```

This works with builtin types, or with generics like `List`:
```
reveal_type(structure(int, "a"))
# Revealed type is "builtins.int"
reveal_type(structure(List[int], "a"))
# Revealed type is "builtins.list[builtins.int]"
```

... but not with tuples:
```python
reveal_type(structure(Tuple[int, int], "a"))
# note: Revealed type is "Never"
# error: Argument 1 to "structure" has incompatible type "object"; expected "type[Never]"  [arg-type]
```

Trying to reveal the type of `Tuple` itself leads to strange results too (compared to `List[...]`):
```
reveal_type(Tuple[int, int])
# Revealed type is "builtins.object"
reveal_type(tuple[int, int])
# Revealed type is "def [_T_co] (typing.Iterable[_T_co`1] =) -> builtins.tuple[_T_co`1, ...]"
```

So the question is, why does `Tuple` get a special treatment, and is there a way to make it behave like other generics?


Discussion:
Same issue with `UnionType` types (`T1 | T2 | ...`).
Same issue with `UnionType` types (`T1 | T2 | ...`).

Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -4836,8 +4836,21 @@ def apply_type_arguments_to_callable(
                 len(args) < min_arg_count or len(args) > len(tp.variables)
             ) and not has_type_var_tuple:
                 if tp.is_type_obj() and tp.type_object().fullname == "builtins.tuple":
-                    # TODO: Specialize the callable for the type arguments
-                    return tp
+                    # e.g. expression tuple[X, Y]
+                    # - want the type of the expression i.e. a function with that as its return type
+                    # - tp is type of tuple (note it won't have params as we are only called
+                    #   with generic callable type)
+                    # - tuple[X, Y]() takes a single arg that is a tuple containing an X and a Y
+                    return CallableType(
+                        [TupleType(list(args), self.chk.named_type("tuple"))],
+                        [ARG_POS],
+                        [None],
+                        TupleType(list(args), self.chk.named_type("tuple")),
+                        tp.fallback,
+                        name="tuple",
+                        definition=tp.definition,
+                        bound_args=tp.bound_args,
+                    )
                 self.msg.incompatible_type_application(
                     min_arg_count, len(tp.variables), len(args), ctx
                 )


Test patch:
diff --git a/test-data/unit/check-type-object-type-inference.test b/test-data/unit/check-type-object-type-inference.test
new file mode 100644
--- /dev/null
+++ b/test-data/unit/check-type-object-type-inference.test
@@ -0,0 +1,41 @@
+[case testInferTupleType]
+# flags: --python-version 3.9
+from typing import TypeVar, Generic, Type
+from abc import abstractmethod
+
+T = TypeVar('T')
+class E(Generic[T]):
+    @abstractmethod
+    def e(self, t: T) -> str:
+        ...
+
+class F:
+    @abstractmethod
+    def f(self, tp: Type[T]) -> E[T]:
+        ...
+
+def g(f: F):
+    f.f(int).e(7)
+    f.f(tuple[int,str])
+    f.f(tuple[int,str]).e('x')  # E: Argument 1 to "e" of "E" has incompatible type "str"; expected "Tuple[int, str]"
+    f.f(tuple[int,str]).e( (7,8) )  # E: Argument 1 to "e" of "E" has incompatible type "Tuple[int, int]"; expected "Tuple[int, str]"
+    f.f(tuple[int,str]).e( (7,'x') )  # OK
+    reveal_type(f.f(tuple[int,str]).e)  # N: Revealed type is "def (t: Tuple[builtins.int, builtins.str]) -> builtins.str"
+
+def h(f: F):
+    f.f(int).e(7)
+    f.f(tuple)
+    f.f(tuple).e('y') # E: Argument 1 to "e" of "E" has incompatible type "str"; expected "Tuple[Any, ...]"
+    f.f(tuple).e( (8,'y') )  # OK
+    reveal_type(f.f(tuple).e)  # N: Revealed type is "def (t: builtins.tuple[Any, ...]) -> builtins.str"
+
+def i(f: F):
+    f.f(tuple[int,tuple[int,str]])
+    f.f(tuple[int,tuple[int,str]]).e('z')  # E: Argument 1 to "e" of "E" has incompatible type "str"; expected "Tuple[int, Tuple[int, str]]"
+    f.f(tuple[int,tuple[int,str]]).e( (8,9) )  # E: Argument 1 to "e" of "E" has incompatible type "Tuple[int, int]"; expected "Tuple[int, Tuple[int, str]]"
+    f.f(tuple[int,tuple[int,str]]).e( (17, (28, 29)) )  # E: Argument 1 to "e" of "E" has incompatible type "Tuple[int, Tuple[int, int]]"; expected "Tuple[int, Tuple[int, str]]"
+    f.f(tuple[int,tuple[int,str]]).e( (27,(28,'z')) )  # OK
+    reveal_type(f.f(tuple[int,tuple[int,str]]).e)  # N: Revealed type is "def (t: Tuple[builtins.int, Tuple[builtins.int, builtins.str]]) -> builtins.str"
+
+x = tuple[int,str][str]  # E: The type "Type[Tuple[Any, ...]]" is not generic and not indexable
+[builtins fixtures/tuple.pyi]
diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test
--- a/test-data/unit/pythoneval.test
+++ b/test-data/unit/pythoneval.test
@@ -1828,7 +1828,6 @@ RHSAlias3: type = tuple[int, ...]
 WrongTypeElement = str | tuple[float, 1]  # Error
 WrongEllipsis = tuple[float, float, ...] | str  # Error
 
-# TODO: This should produce a fixed-length tuple
 reveal_type(tuple[int, str]((1, "x")))
 [out]
 _testTupleWithDifferentArgsPy310.py:15: note: Revealed type is "Union[builtins.str, Tuple[builtins.float, builtins.float, builtins.str]]"
@@ -1839,7 +1838,7 @@ _testTupleWithDifferentArgsPy310.py:19: note: Revealed type is "builtins.tuple[b
 _testTupleWithDifferentArgsPy310.py:20: note: Revealed type is "builtins.list[Tuple[builtins.int, builtins.str]]"
 _testTupleWithDifferentArgsPy310.py:26: error: Invalid type: try using Literal[1] instead?
 _testTupleWithDifferentArgsPy310.py:27: error: Unexpected "..."
-_testTupleWithDifferentArgsPy310.py:30: note: Revealed type is "builtins.tuple[builtins.object, ...]"
+_testTupleWithDifferentArgsPy310.py:29: note: Revealed type is "Tuple[builtins.int, builtins.str]"
 
 [case testEnumIterMetaInference]
 import socket