Repository: python/mypy

Issue:
(🐞) False positive "error: Type application has too many types (1 expected)" on tuple when annotated as a type or in a `TypeAlias` with new union syntax (PEP 604)
mypy 0.910
```py
a: type = tuple[int] # no error
b = tuple[int] # no error
c = tuple[int, int] # no error
error: type = tuple[int, int] # error: Type application has too many types (1 expected)
```

also on functions attributes:
```py
def foo(tp: type): ...

foo(tuple[int, int])
```

also when in a new union syntax type alias:
```py
A = str | tuple[str, str]  # error: Type application has too many types (1 expected)
```

https://mypy-play.net/?mypy=latest&python=3.10&flags=strict%2Ccheck-untyped-defs%2Cdisallow-any-decorated%2Cdisallow-any-expr%2Cdisallow-any-explicit%2Cdisallow-any-generics%2Cdisallow-any-unimported%2Cdisallow-incomplete-defs%2Cdisallow-subclassing-any%2Cdisallow-untyped-calls%2Cdisallow-untyped-decorators%2Cdisallow-untyped-defs%2Cwarn-incomplete-stub%2Cwarn-redundant-casts%2Cwarn-return-any%2Cwarn-unreachable%2Cwarn-unused-configs%2Cwarn-unused-ignores&gist=9604ab8dfb68e31788348b8ea36b54a1


Discussion:
Also `c2 = tuple[int, ...]` shows an incorrect error
Still happening with mypy 0.931 in .pyi files:

```
(env) akuli@akuli-desktop:~/typeshed$ cat a.pyi
_Color = str | tuple[float, float, float]

(env) akuli@akuli-desktop:~/typeshed$ mypy a.pyi
a.pyi:1: error: Type application has too many types (1 expected)
Found 1 error in 1 file (checked 1 source file)
```

Could potentially be fixed by #11915. This too is a blocker for using new union syntax everywhere in typeshed.
Yeah, looks like it could be fixed by #11915. I now have a minimal repro for the case that motivated that one, but I'm still working on incorporating the repro into mypy's test suite.

I'll try to test whether my proposed stub-only fix also fixes this case, and add a regression test for it, if so.
> Could potentially be fixed by #11915. This too is a blocker for using new union syntax everywhere in typeshed.

I have just tested this, and it appears that this is _not_ fixed by #11915, sadly. (@sobolevn, @hauntsaninja, any idea about what the cause of this might be?)
Here is a failing test that is ready to be integrated into mypy's test suite, if it is helpful:

```
[case test604TypeAliasWithParameterisedTuple]
# flags: --python-version 3.10
from color import c  # N: Revealed type is "Union[builtins.str, Tuple[builtins.float, builtins.float, builtins.float]]"
reveal_type(c)
[file color.py]
_Color = str | tuple[float, float, float]
c: _Color
[builtins fixtures/tuple.pyi]
```

This fails with:

```
Expected:
  main:2: note: Revealed type is "Union[builtins.str, Tuple[builtins.float, builtins.float, builtins.float]]" (diff)
Actual:
  tmp/color.pyi:1: error: Type application has too many types (1 expected) (diff)
  main:3: note: Revealed type is "Union[builtins.str, Tuple[builtins.float, builtins.float, builtins.float]]" (diff)

Alignment of first line difference:
  E: main:2: note: Revealed type is "Union[builtins.str, Tuple[builtins.float...
  A: tmp/color.pyi:1: error: Type application has too many types (1 expected)...
     ^
```

So mypy appears able to infer the type just fine, but nonetheless raises the spurious "Type application has too many types" error message.
Error is happening in a different, later place of the type checker. Quickest fix is probably special casing `tuple` in checkexpr's `visit_type_application`
> Here is a failing test that is ready to be integrated into mypy's test suite

Whats the deal with contributing xfail tests?

I could add a whole heap from issues I've raised, is that useful at all?
(just speaking for myself) I think of them as basically a better TODO comment.
E.g., I wouldn't typically write a PR that just adds a TODO, but if I added a PR that fixed some things but left some stuff unresolved or regressed in a minor way I'd leave a TODO. The other use case for xfails I see is if it's in a finnicky part of the type checker where it could be "accidentally" fixed and you'd want to know if so.
So if I made some PRs, only adding xfails from issues I've raised, they would be appreciated?
To be clear, I wasn't suggesting that the test case I was providing above should be integrated into the test suite now, without a fix for the issue; I just thought it might be helpful to someone else, if they wanted to work on a fix. The mypy test suite is a slightly different environment to running mypy normally, so it's not _always_ as easy to reproduce something in the test suite as it is in mypy playground.
I haven't really looked into this, but I imagine xfail tests are prone to failing for the wrong reason. We frequently make big changes to our test suite when some wording changes a little, so chances are xfail tests will rapidly start failing not because of the actual bug but because the test says "builtins.int" instead of "int".
I've submitted https://github.com/python/mypy/pull/12134
Please, feel free to review! 👍 
@KotlinIsland you changed the title of this issue yesterday but the title now refers to the wrong PEP. You want 604, not 406.

Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -3969,6 +3969,9 @@ def apply_type_arguments_to_callable(
 
         if isinstance(tp, CallableType):
             if len(tp.variables) != len(args):
+                if tp.is_type_obj() and tp.type_object().fullname == "builtins.tuple":
+                    # TODO: Specialize the callable for the type arguments
+                    return tp
                 self.msg.incompatible_type_application(len(tp.variables), len(args), ctx)
                 return AnyType(TypeOfAny.from_error)
             return self.apply_generic_arguments(tp, args, ctx)


Test patch:
diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test
--- a/test-data/unit/check-type-aliases.test
+++ b/test-data/unit/check-type-aliases.test
@@ -993,3 +993,35 @@ x_bad: A[bytes]  # E: Value of type variable "S" of "A" cannot be "bytes"
 B = List[C[U]]
 y: B[int]
 y_bad: B[str]  # E: Type argument "str" of "B" must be a subtype of "int"
+
+[case testTupleWithDifferentArgsPy38]
+# flags: --python-version 3.8
+NotYet1 = tuple[float]  # E: "tuple" is not subscriptable
+NotYet2 = tuple[float, float]  # E: "tuple" is not subscriptable
+NotYet3 = tuple[float, ...]  # E: Unexpected "..." \
+                             # E: "tuple" is not subscriptable
+NotYet4 = tuple[float, float, ...]  # E: Unexpected "..." \
+                                    # E: "tuple" is not subscriptable
+[builtins fixtures/tuple.pyi]
+
+[case testTupleWithDifferentArgsStub]
+# https://github.com/python/mypy/issues/11098
+import tup
+
+[file tup.pyi]
+Correct1 = str | tuple[float, float, str]
+Correct2 = tuple[float] | str
+Correct3 = tuple[float, ...] | str
+Correct4 = tuple[float, str] | str
+Correct5 = tuple[int, str]
+Correct6 = tuple[int, ...]
+
+RHSAlias1: type = tuple[int, int]
+RHSAlias2: type = tuple[int]
+RHSAlias3: type = tuple[int, ...]
+
+# Wrong:
+
+WrongTypeElement = str | tuple[float, 1]  # E: Invalid type: try using Literal[1] instead?
+WrongEllipsis = str | tuple[float, float, ...]  # E: Unexpected "..."
+[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
@@ -1816,3 +1816,45 @@ def foo(k: str) -> TD:
     return x.get(k, {})
 [out]
 _testTypedDictUnionGetFull.py:11: note: Revealed type is "TypedDict('_testTypedDictUnionGetFull.TD', {'x'?: builtins.int, 'y'?: builtins.int})"
+
+[case testTupleWithDifferentArgsPy310]
+# https://github.com/python/mypy/issues/11098
+# flags: --python-version 3.10
+Correct1 = str | tuple[float, float, str]
+Correct2 = tuple[float] | str
+Correct3 = tuple[float, ...] | str
+Correct4 = tuple[float, str]
+Correct5 = tuple[float, ...]
+Correct6 = list[tuple[int, str]]
+c1: Correct1
+c2: Correct2
+c3: Correct3
+c4: Correct4
+c5: Correct5
+c6: Correct6
+reveal_type(c1)
+reveal_type(c2)
+reveal_type(c3)
+reveal_type(c4)
+reveal_type(c5)
+reveal_type(c6)
+
+RHSAlias1: type = tuple[int, int]
+RHSAlias2: type = tuple[int]
+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]]"
+_testTupleWithDifferentArgsPy310.py:16: note: Revealed type is "Union[Tuple[builtins.float], builtins.str]"
+_testTupleWithDifferentArgsPy310.py:17: note: Revealed type is "Union[builtins.tuple[builtins.float, ...], builtins.str]"
+_testTupleWithDifferentArgsPy310.py:18: note: Revealed type is "Tuple[builtins.float, builtins.str]"
+_testTupleWithDifferentArgsPy310.py:19: note: Revealed type is "builtins.tuple[builtins.float, ...]"
+_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, ...]"