diff --git a/crates/ty_python_semantic/resources/mdtest/call/function.md b/crates/ty_python_semantic/resources/mdtest/call/function.md index 10fa4c2b85..741f9b89b2 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/function.md +++ b/crates/ty_python_semantic/resources/mdtest/call/function.md @@ -164,6 +164,101 @@ def _(args: tuple[int, str]) -> None: 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 ```toml @@ -258,6 +353,197 @@ def _(args: tuple[int, *tuple[str, ...], int]) -> None: 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 ```py diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 84779ba979..2435f66730 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -392,6 +392,33 @@ class C(tuple[T, U]): ... 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 ```py diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index 0dddaf2df2..bef2ca2ecc 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -355,6 +355,26 @@ class C[T, U](tuple[T, U]): ... 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 ```py diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md index b85065fb06..0dae5486d4 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md @@ -217,9 +217,89 @@ def _(m: int, n: int): tuple_slice = t[m:n] 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 [environment] diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md index 2419eead01..475420431a 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md @@ -477,4 +477,52 @@ class NotAlwaysTruthyTuple(tuple[int]): 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 diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_single_valued.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_single_valued.md index 4724ba7605..6f668a7f88 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_single_valued.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_single_valued.md @@ -16,6 +16,21 @@ static_assert(is_single_valued(Literal[b"abc"])) static_assert(is_single_valued(tuple[()])) 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(Never)) 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])) +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[[int, str], None])) diff --git a/crates/ty_python_semantic/resources/mdtest/unpacking.md b/crates/ty_python_semantic/resources/mdtest/unpacking.md index 02d4952780..fc71ba3449 100644 --- a/crates/ty_python_semantic/resources/mdtest/unpacking.md +++ b/crates/ty_python_semantic/resources/mdtest/unpacking.md @@ -407,6 +407,161 @@ def _(value: tuple[int, int, *tuple[str, ...], 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 ### Simple unpacking