[ty] Add failing tests for tuple subclasses (#19803)

This commit is contained in:
Alex Waygood 2025-08-07 14:11:15 +01:00 committed by GitHub
parent 7b6abfb030
commit c401a6d86e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 636 additions and 1 deletions

View file

@ -164,6 +164,101 @@ def _(args: tuple[int, str]) -> None:
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type] takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
``` ```
### Subclass of fixed-length tuple argument
```py
def takes_zero() -> None: ...
def takes_one(x: int) -> None: ...
def takes_two(x: int, y: int) -> None: ...
def takes_two_positional_only(x: int, y: int, /) -> None: ...
def takes_two_different(x: int, y: str) -> None: ...
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
def takes_at_least_zero(*args) -> None: ...
def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
class SingleElementTuple(tuple[int]): ...
def _(args: SingleElementTuple) -> None:
# TODO: we should emit `[too-many-positional-arguments]` here
takes_zero(*args)
takes_one(*args)
# TODO: we should emit `[missing-argument]` on both of these
takes_two(*args)
takes_two_positional_only(*args)
# TODO: these should both be `[missing-argument]`, not `[invalid-argument-type]`
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
# TODO: we should emit `[missing-argument]` on both of these
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class TwoElementIntTuple(tuple[int, int]): ...
def _(args: TwoElementIntTuple) -> None:
# TODO: we should emit `[too-many-positional-arguments]` on both of these
takes_zero(*args)
takes_one(*args)
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntStrTuple(tuple[int, str]): ...
def _(args: IntStrTuple) -> None:
# TODO: we should emit `[too-many-positional-arguments]` here
takes_zero(*args)
# TODO: this should be `[too-many-positional-arguments]`, not `[invalid-argument-type]`
takes_one(*args) # error: [invalid-argument-type]
# TODO: we should have one diagnostic for each of these, not two
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two(*args)
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_positional_only(*args)
# TODO: these are all false positives
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_different(*args)
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
# TODO: false positive
# error: [invalid-argument-type]
takes_at_least_one(*args)
# TODO: we should only emit one diagnostic for each of these, not two
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_at_least_two(*args)
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_at_least_two_positional_only(*args)
```
### Mixed tuple argument ### Mixed tuple argument
```toml ```toml
@ -258,6 +353,197 @@ def _(args: tuple[int, *tuple[str, ...], int]) -> None:
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type] takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
``` ```
### Subclass of mixed tuple argument
```toml
[environment]
python-version = "3.11"
```
```py
def takes_zero() -> None: ...
def takes_one(x: int) -> None: ...
def takes_two(x: int, y: int) -> None: ...
def takes_two_positional_only(x: int, y: int, /) -> None: ...
def takes_two_different(x: int, y: str) -> None: ...
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
def takes_at_least_zero(*args) -> None: ...
def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
class IntStarInt(tuple[int, *tuple[int, ...]]): ...
def _(args: IntStarInt) -> None:
# TODO: we should emit `[too-many-positional-arguments]` here
takes_zero(*args)
takes_one(*args)
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntStarStr(tuple[int, *tuple[str, ...]]): ...
def _(args: IntStarStr) -> None:
# TODO: we should emit `[too-many-positional-arguments]` here
takes_zero(*args)
# TODO: false positive
# error: [invalid-argument-type]
takes_one(*args)
# TODO: we should only emit one diagnostic for each of these, not two
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two(*args)
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_positional_only(*args)
# TODO: false positives
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_different(*args)
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
# TODO: false positive
# error: [invalid-argument-type]
takes_at_least_one(*args)
# TODO: we should only have one diagnostic for each of these, not two
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_at_least_two(*args)
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_at_least_two_positional_only(*args)
class IntIntStarInt(tuple[int, int, *tuple[int, ...]]): ...
def _(args: IntIntStarInt) -> None:
# TODO: we should emit `[too-many-positional-arguments]` on both of these
takes_zero(*args)
takes_one(*args)
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntIntStarStr(tuple[int, int, *tuple[str, ...]]): ...
def _(args: IntIntStarStr) -> None:
# TODO: we should emit `[too-many-positional-arguments]` here
takes_zero(*args)
# TODO: this should be `[too-many-positional-arguments]`, not `invalid-argument-type`
takes_one(*args) # error: [invalid-argument-type]
# TODO: these are all false positives
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two(*args)
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_positional_only(*args)
# TODO: each of these should only have one diagnostic, not two
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_different(*args)
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
# TODO: false positive
# error: [invalid-argument-type]
takes_at_least_one(*args)
# TODO: these are both false positives
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_at_least_two(*args)
# TODO: these are both false positives
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_at_least_two_positional_only(*args)
class IntStarIntInt(tuple[int, *tuple[int, ...], int]): ...
def _(args: IntStarIntInt) -> None:
# TODO: we should emit `[too-many-positional-arguments]` on both of these
takes_zero(*args)
takes_one(*args)
takes_two(*args)
takes_two_positional_only(*args)
takes_two_different(*args) # error: [invalid-argument-type]
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
takes_at_least_zero(*args)
takes_at_least_one(*args)
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntStarStrInt(tuple[int, *tuple[str, ...], int]): ...
def _(args: IntStarStrInt) -> None:
# TODO: we should emit `too-many-positional-arguments` here
takes_zero(*args)
# TODO: this should be `too-many-positional-arguments`, not `invalid-argument-type`
takes_one(*args) # error: [invalid-argument-type]
# TODO: we should only emit one diagnostic for each of these
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two(*args)
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_positional_only(*args)
# TODO: we should not emit diagnostics for these
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_different(*args)
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_two_different_positional_only(*args)
takes_at_least_zero(*args)
# TODO: false positive
takes_at_least_one(*args) # error: [invalid-argument-type]
# TODO: should only have one diagnostic here
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_at_least_two(*args)
# TODO: should only have one diagnostic here
# error: [invalid-argument-type]
# error: [invalid-argument-type]
takes_at_least_two_positional_only(*args)
```
### String argument ### String argument
```py ```py

View file

@ -392,6 +392,33 @@ class C(tuple[T, U]): ...
reveal_type(C((1, 2))) # revealed: C[int, int] reveal_type(C((1, 2))) # revealed: C[int, int]
``` ```
### Upcasting a `tuple` to its `Sequence` supertype
This test is taken from the
[typing spec conformance suite](https://github.com/python/typing/blob/c141cdfb9d7085c1aafa76726c8ce08362837e8b/conformance/tests/tuples_type_compat.py#L133-L153)
```toml
[environment]
python-version = "3.11"
```
```py
from typing import TypeVar, Sequence, Never
T = TypeVar("T")
def test_seq(x: Sequence[T]) -> Sequence[T]:
return x
def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: tuple[()]):
# TODO: should be `Sequence[int | float | complex | list[int]]`
reveal_type(test_seq(t1)) # revealed: Sequence[Unknown]
# TODO: should be `Sequence[int | str]`
reveal_type(test_seq(t2)) # revealed: Sequence[Unknown]
# TODO: this should be `Sequence[Never]`
reveal_type(test_seq(t3)) # revealed: Sequence[Unknown]
```
### `__init__` is itself generic ### `__init__` is itself generic
```py ```py

View file

@ -355,6 +355,26 @@ class C[T, U](tuple[T, U]): ...
reveal_type(C((1, 2))) # revealed: C[int, int] reveal_type(C((1, 2))) # revealed: C[int, int]
``` ```
### Upcasting a `tuple` to its `Sequence` supertype
This test is taken from the
[typing spec conformance suite](https://github.com/python/typing/blob/c141cdfb9d7085c1aafa76726c8ce08362837e8b/conformance/tests/tuples_type_compat.py#L133-L153)
```py
from typing import Sequence, Never
def test_seq[T](x: Sequence[T]) -> Sequence[T]:
return x
def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: tuple[()]):
# TODO: should be `Sequence[int | float | complex | list[int]]`
reveal_type(test_seq(t1)) # revealed: Sequence[Unknown]
# TODO: should be `Sequence[int | str]`
reveal_type(test_seq(t2)) # revealed: Sequence[Unknown]
# TODO: this should be `Sequence[Never]`
reveal_type(test_seq(t3)) # revealed: Sequence[Unknown]
```
### `__init__` is itself generic ### `__init__` is itself generic
```py ```py

View file

@ -217,9 +217,89 @@ def _(m: int, n: int):
tuple_slice = t[m:n] tuple_slice = t[m:n]
reveal_type(tuple_slice) # revealed: tuple[Literal[1, "a", b"b"] | None, ...] reveal_type(tuple_slice) # revealed: tuple[Literal[1, "a", b"b"] | None, ...]
class I0: ...
class I1: ...
class I2: ...
class I3: ...
class HeterogeneousTupleSubclass(tuple[I0, I1, I2, I3]): ...
def __(t: HeterogeneousTupleSubclass, m: int, n: int):
# TODO: should be `tuple[()]`
reveal_type(t[0:0]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0]`
reveal_type(t[0:1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0, I1]`
reveal_type(t[0:2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be tuple[I0, I1, I2, I3]`
reveal_type(t[0:4]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be tuple[I0, I1, I2, I3]`
reveal_type(t[0:5]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0, I1]`
reveal_type(t[1:3]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I2, I3]`
reveal_type(t[-2:4]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I1, I2]`
reveal_type(t[-3:-1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0, I1, I2, I3]`
reveal_type(t[-10:10]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0, I1, I2, I3]`
reveal_type(t[0:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I2, I3]`
reveal_type(t[2:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[()]`
reveal_type(t[4:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[()]`
reveal_type(t[:0]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0, I1]`
reveal_type(t[:2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0, I1, I2, I3]`
reveal_type(t[:10]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0, I1, I2, I3]`
reveal_type(t[:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I3, I2, I1, I0]`
reveal_type(t[::-1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0, I2]`
reveal_type(t[::2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I2, I1, I0]`
reveal_type(t[-2:-5:-1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I3, I1]`
reveal_type(t[::-2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I3, I0]`
reveal_type(t[-1::-3]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0, I1]`
reveal_type(t[None:2:None]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I1, I2, I3]`
reveal_type(t[1:None:1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0, I1, I2, I3]`
reveal_type(t[None:None:None]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
start = 1
stop = None
step = 2
# TODO: should be `tuple[I1, I3]`
reveal_type(t[start:stop:step]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I0]`
reveal_type(t[False:True]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: should be `tuple[I1, I2]`
reveal_type(t[True:3]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
# TODO: we should emit `zero-stepsize-in-slice` on all of these:
t[0:4:0]
t[:4:0]
t[0::0]
t[::0]
tuple_slice = t[m:n]
reveal_type(tuple_slice) # revealed: tuple[I0 | I1 | I2 | I3, ...]
``` ```
## Slices of homogeneous and mixed tuples ## Indexes into homogeneous and mixed tuples
```toml ```toml
[environment] [environment]

View file

@ -477,4 +477,52 @@ class NotAlwaysTruthyTuple(tuple[int]):
t: tuple[int] = NotAlwaysTruthyTuple((1,)) t: tuple[int] = NotAlwaysTruthyTuple((1,))
``` ```
## Unspecialized
An unspecialized tuple is equivalent to `tuple[Any, ...]` and `tuple[Unknown, ...]`.
```py
from typing_extensions import Any, assert_type
from ty_extensions import Unknown, is_equivalent_to, static_assert
static_assert(is_equivalent_to(tuple[Any, ...], tuple[Unknown, ...]))
def f(x: tuple, y: tuple[Unknown, ...]):
reveal_type(x) # revealed: tuple[Unknown, ...]
assert_type(x, tuple[Any, ...])
assert_type(x, tuple[Unknown, ...])
reveal_type(y) # revealed: tuple[Unknown, ...]
assert_type(y, tuple[Any, ...])
assert_type(y, tuple[Unknown, ...])
```
## Converting a `tuple` to another `Sequence` type
For covariant types, such as `frozenset`, the ideal behaviour would be to not promote `Literal`
types to their instance supertypes: doing so causes more false positives than it fixes:
```py
# TODO: should be `frozenset[Literal[1, 2, 3]]`
reveal_type(frozenset((1, 2, 3))) # revealed: frozenset[Unknown]
# TODO: should be `frozenset[tuple[Literal[1], Literal[2], Literal[3]]]`
reveal_type(frozenset(((1, 2, 3),))) # revealed: frozenset[Unknown]
```
Literals are always promoted for invariant containers such as `list`, however, even though this can
in some cases cause false positives:
```py
from typing import Literal
# TODO: should be `list[int]`
reveal_type(list((1, 2, 3))) # revealed: list[Unknown]
# TODO: should be `list[tuple[int, int, int]]`
reveal_type(list(((1, 2, 3),))) # revealed: list[Unknown]
x: list[Literal[1, 2, 3]] = list((1, 2, 3))
# TODO: should be `list[Literal[1, 2, 3]]`
reveal_type(x) # revealed: list[Unknown]
```
[not a singleton type]: https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957 [not a singleton type]: https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957

View file

@ -16,6 +16,21 @@ static_assert(is_single_valued(Literal[b"abc"]))
static_assert(is_single_valued(tuple[()])) static_assert(is_single_valued(tuple[()]))
static_assert(is_single_valued(tuple[Literal[True], Literal[1]])) static_assert(is_single_valued(tuple[Literal[True], Literal[1]]))
class EmptyTupleSubclass(tuple[()]): ...
class HeterogeneousTupleSubclass(tuple[Literal[True], Literal[1]]): ...
# N.B. this follows from the fact that `EmptyTupleSubclass` is a subtype of `tuple[()]`,
# and any property recognised for `tuple[()]` should therefore also be recognised for
# `EmptyTupleSubclass` since an `EmptyTupleSubclass` instance can be used anywhere where
# `tuple[()]` is accepted. This is only sound, however, if we ban `__eq__` and `__ne__`
# from being overridden on a tuple subclass. This is something we plan to do as part of
# our implementation of the Liskov Substitution Principle
# (https://github.com/astral-sh/ty/issues/166)
#
# TODO: these should pass
static_assert(is_single_valued(EmptyTupleSubclass)) # error: [static-assert-error]
static_assert(is_single_valued(HeterogeneousTupleSubclass)) # error: [static-assert-error]
static_assert(not is_single_valued(str)) static_assert(not is_single_valued(str))
static_assert(not is_single_valued(Never)) static_assert(not is_single_valued(Never))
static_assert(not is_single_valued(Any)) static_assert(not is_single_valued(Any))
@ -24,6 +39,10 @@ static_assert(not is_single_valued(Literal[1, 2]))
static_assert(not is_single_valued(tuple[None, int])) static_assert(not is_single_valued(tuple[None, int]))
class MultiValuedHeterogeneousTupleSubclass(tuple[None, int]): ...
static_assert(not is_single_valued(MultiValuedHeterogeneousTupleSubclass))
static_assert(not is_single_valued(Callable[..., None])) static_assert(not is_single_valued(Callable[..., None]))
static_assert(not is_single_valued(Callable[[int, str], None])) static_assert(not is_single_valued(Callable[[int, str], None]))

View file

@ -407,6 +407,161 @@ def _(value: tuple[int, int, *tuple[str, ...], int]):
reveal_type(c) # revealed: int reveal_type(c) # revealed: int
``` ```
## Tuple subclasses
A tuple subclass inherits its heterogeneous unpacking behaviour from its tuple superclass.
```toml
[environment]
python-version = "3.11"
```
```py
class I0: ...
class I1: ...
class I2: ...
class HeterogeneousTupleSubclass(tuple[I0, I1, I2]): ...
def f(x: HeterogeneousTupleSubclass):
a, b, c = x
# TODO: should be `I0`
reveal_type(a) # revealed: I0 | I1 | I2
# TODO: should be `I1`
reveal_type(b) # revealed: I0 | I1 | I2
# TODO: should be `I2`
reveal_type(c) # revealed: I0 | I1 | I2
# TODO: should emit a diagnostic ([invalid-assignment] "Too many values to unpack: Expected 2")
d, e = x
reveal_type(d) # revealed: I0 | I1 | I2
reveal_type(e) # revealed: I0 | I1 | I2
# TODO: should emit a diagnostic ([invalid-assignment] "Not enough values to unpack: Expected 4")
f, g, h, i = x
reveal_type(f) # revealed: I0 | I1 | I2
reveal_type(g) # revealed: I0 | I1 | I2
reveal_type(h) # revealed: I0 | I1 | I2
reveal_type(i) # revealed: I0 | I1 | I2
[j, *k] = x
# TODO: should be `I0`
reveal_type(j) # revealed: I0 | I1 | I2
# TODO: should be `list[I1 | I2]`
reveal_type(k) # revealed: list[I0 | I1 | I2]
[l, m, *n] = x
# TODO: should be `I0`
reveal_type(l) # revealed: I0 | I1 | I2
# TODO: should be `I1`
reveal_type(m) # revealed: I0 | I1 | I2
# TODO: should be `list[I2]`
reveal_type(n) # revealed: list[I0 | I1 | I2]
[o, p, q, *r] = x
# TODO: should be `I0`
reveal_type(o) # revealed: I0 | I1 | I2
# TODO: should be `I1`
reveal_type(p) # revealed: I0 | I1 | I2
# TODO: should be `I2`
reveal_type(q) # revealed: I0 | I1 | I2
# TODO: should be `list[Never]`
reveal_type(r) # revealed: list[I0 | I1 | I2]
# TODO: should emit a diagnostic ([invalid-assignment] "Not enough values to unpack: Expected at least 4")
[s, t, u, v, *w] = x
reveal_type(s) # revealed: I0 | I1 | I2
reveal_type(t) # revealed: I0 | I1 | I2
reveal_type(u) # revealed: I0 | I1 | I2
reveal_type(v) # revealed: I0 | I1 | I2
reveal_type(w) # revealed: list[I0 | I1 | I2]
class MixedTupleSubclass(tuple[I0, *tuple[I1, ...], I2]): ...
def f(x: MixedTupleSubclass):
# TODO: should emit a diagnostic: ([invalid-assignment] "Too many values to unpack: Expected 1"`)
(a,) = x
reveal_type(a) # revealed: I0 | I1 | I2
c, d = x
# TODO: should be `I0`
reveal_type(c) # revealed: I0 | I1 | I2
# TODO: should be `I2`
reveal_type(d) # revealed: I0 | I1 | I2
e, f, g = x
# TODO: should be `I0`
reveal_type(e) # revealed: I0 | I1 | I2
# TODO: should be `I1`
reveal_type(f) # revealed: I0 | I1 | I2
# TODO: should be `I2`
reveal_type(g) # revealed: I0 | I1 | I2
h, i, j, k = x
# TODO: should be `I0`
reveal_type(h) # revealed: I0 | I1 | I2
# TODO: should be `I1`
reveal_type(i) # revealed: I0 | I1 | I2
# TODO: should be `I1`
reveal_type(j) # revealed: I0 | I1 | I2
# TODO: should be `I2`
reveal_type(k) # revealed: I0 | I1 | I2
[l, *m] = x
# TODO: should be `I0`
reveal_type(l) # revealed: I0 | I1 | I2
# TODO: should be `list[I1 | I2]`
reveal_type(m) # revealed: list[I0 | I1 | I2]
[n, o, *p] = x
# TODO: should be `I0`
reveal_type(n) # revealed: I0 | I1 | I2
# TODO: should be `I1 | I2`
reveal_type(o) # revealed: I0 | I1 | I2
# TODO: should be `list[I1 | I2]`
reveal_type(p) # revealed: list[I0 | I1 | I2]
[o, p, q, *r] = x
# TODO: should be `I0`
reveal_type(o) # revealed: I0 | I1 | I2
# TODO: should be `I1 | I2`
reveal_type(p) # revealed: I0 | I1 | I2
# TODO: should be `I1 | I2
reveal_type(q) # revealed: I0 | I1 | I2
# TODO: should be `list[I1 | I2]
reveal_type(r) # revealed: list[I0 | I1 | I2]
s, *t, u = x
# TODO: should be `I0`
reveal_type(s) # revealed: I0 | I1 | I2
# TODO: should be `list[I1]`
reveal_type(t) # revealed: list[I0 | I1 | I2]
# TODO: should be `I2`
reveal_type(u) # revealed: I0 | I1 | I2
aa, bb, *cc, dd = x
# TODO: should be `I0`
reveal_type(aa) # revealed: I0 | I1 | I2
# TODO: should be `I1`
reveal_type(bb) # revealed: I0 | I1 | I2
# TODO: should be `list[I1]`
reveal_type(cc) # revealed: list[I0 | I1 | I2]
# TODO: should be I2
reveal_type(dd) # revealed: I0 | I1 | I2
```
## String ## String
### Simple unpacking ### Simple unpacking