ruff/crates/ty_python_semantic/resources/mdtest/unpacking.md
Douglas Creager c60e590b4c
[ty] Support variable-length tuples in unpacking assignments (#18948)
This PR updates our unpacking assignment logic to use the new tuple
machinery. As a result, we can now unpack variable-length tuples
correctly.

As part of this, the `TupleSpec` classes have been renamed to `Tuple`,
and can now contain any element (Rust) type, not just `Type<'db>`. The
unpacker uses a tuple of `UnionBuilder`s to maintain the types that will
be assigned to each target, as we iterate through potentially many union
elements on the rhs. We also add a new consuming iterator for tuples,
and update the `all_elements` methods to wrap the result in an enum
(similar to `itertools::Position`) letting you know which part of the
tuple each element appears in. I also added a new
`UnionBuilder::try_build`, which lets you specify a different fallback
type if the union contains no elements.
2025-06-27 15:29:04 -04:00

22 KiB

Unpacking

If there are not enough or too many values when unpacking, an error will occur and the types of all variables (if nested tuple unpacking fails, only the variables within the failed tuples) is inferred to be Unknown.

Tuple

Simple tuple

(a, b, c) = (1, 2, 3)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: Literal[2]
reveal_type(c)  # revealed: Literal[3]

Simple list

[a, b, c] = (1, 2, 3)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: Literal[2]
reveal_type(c)  # revealed: Literal[3]

Simple mixed

[a, (b, c), d] = (1, (2, 3), 4)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: Literal[2]
reveal_type(c)  # revealed: Literal[3]
reveal_type(d)  # revealed: Literal[4]

Multiple assignment

a, b = c = 1, 2
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: Literal[2]
reveal_type(c)  # revealed: tuple[Literal[1], Literal[2]]

Nested tuple with unpacking

(a, (b, c), d) = (1, (2, 3), 4)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: Literal[2]
reveal_type(c)  # revealed: Literal[3]
reveal_type(d)  # revealed: Literal[4]

Nested tuple without unpacking

(a, b, c) = (1, (2, 3), 4)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: tuple[Literal[2], Literal[3]]
reveal_type(c)  # revealed: Literal[4]

Uneven unpacking (1)

# error: [invalid-assignment] "Not enough values to unpack: Expected 3"
(a, b, c) = (1, 2)
reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: Unknown
reveal_type(c)  # revealed: Unknown

Uneven unpacking (2)

# error: [invalid-assignment] "Too many values to unpack: Expected 2"
(a, b) = (1, 2, 3)
reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: Unknown

Nested uneven unpacking (1)

# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
(a, (b, c), d) = (1, (2,), 3)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: Unknown
reveal_type(c)  # revealed: Unknown
reveal_type(d)  # revealed: Literal[3]

Nested uneven unpacking (2)

# error: [invalid-assignment] "Too many values to unpack: Expected 2"
(a, (b, c), d) = (1, (2, 3, 4), 5)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: Unknown
reveal_type(c)  # revealed: Unknown
reveal_type(d)  # revealed: Literal[5]

Starred expression (1)

# error: [invalid-assignment] "Not enough values to unpack: Expected at least 3"
[a, *b, c, d] = (1, 2)
reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: list[Unknown]
reveal_type(c)  # revealed: Unknown
reveal_type(d)  # revealed: Unknown

Starred expression (2)

[a, *b, c] = (1, 2)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: list[Never]
reveal_type(c)  # revealed: Literal[2]

Starred expression (3)

[a, *b, c] = (1, 2, 3)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: list[Literal[2]]
reveal_type(c)  # revealed: Literal[3]

Starred expression (4)

[a, *b, c, d] = (1, 2, 3, 4, 5, 6)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: list[Literal[2, 3, 4]]
reveal_type(c)  # revealed: Literal[5]
reveal_type(d)  # revealed: Literal[6]

Starred expression (5)

[a, b, *c] = (1, 2, 3, 4)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: Literal[2]
reveal_type(c)  # revealed: list[Literal[3, 4]]

Starred expression (6)

# error: [invalid-assignment] "Not enough values to unpack: Expected at least 5"
(a, b, c, *d, e, f) = (1,)
reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: Unknown
reveal_type(c)  # revealed: Unknown
reveal_type(d)  # revealed: list[Unknown]
reveal_type(e)  # revealed: Unknown
reveal_type(f)  # revealed: Unknown

Non-iterable unpacking

# error: "Object of type `Literal[1]` is not iterable"
a, b = 1
reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: Unknown

Custom iterator unpacking

class Iterator:
    def __next__(self) -> int:
        return 42

class Iterable:
    def __iter__(self) -> Iterator:
        return Iterator()

(a, b) = Iterable()
reveal_type(a)  # revealed: int
reveal_type(b)  # revealed: int

Custom iterator unpacking nested

class Iterator:
    def __next__(self) -> int:
        return 42

class Iterable:
    def __iter__(self) -> Iterator:
        return Iterator()

(a, (b, c), d) = (1, Iterable(), 2)
reveal_type(a)  # revealed: Literal[1]
reveal_type(b)  # revealed: int
reveal_type(c)  # revealed: int
reveal_type(d)  # revealed: Literal[2]

List

Literal unpacking

a, b = [1, 2]
# TODO: should be `int` for both `a` and `b`
reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: Unknown

Simple unpacking

def _(value: list[int]):
    a, b = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: int

Nested unpacking

def _(value: list[list[int]]):
    a, (b, c) = value
    reveal_type(a)  # revealed: list[int]
    reveal_type(b)  # revealed: int
    reveal_type(c)  # revealed: int

Invalid nested unpacking

def _(value: list[int]):
    # error: [not-iterable] "Object of type `int` is not iterable"
    a, (b, c) = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: Unknown
    reveal_type(c)  # revealed: Unknown

Starred expression

def _(value: list[int]):
    a, *b, c = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: list[int]
    reveal_type(c)  # revealed: int

Homogeneous tuples

Simple unpacking

def _(value: tuple[int, ...]):
    a, b = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: int

Nested unpacking

def _(value: tuple[tuple[int, ...], ...]):
    a, (b, c) = value
    reveal_type(a)  # revealed: tuple[int, ...]
    reveal_type(b)  # revealed: int
    reveal_type(c)  # revealed: int

Invalid nested unpacking

def _(value: tuple[int, ...]):
    # error: [not-iterable] "Object of type `int` is not iterable"
    a, (b, c) = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: Unknown
    reveal_type(c)  # revealed: Unknown

Starred expression

def _(value: tuple[int, ...]):
    a, *b, c = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: list[int]
    reveal_type(c)  # revealed: int

Mixed tuples

[environment]
python-version = "3.11"

Simple unpacking (1)

def _(value: tuple[int, *tuple[str, ...]]):
    a, b = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: str

Simple unpacking (2)

def _(value: tuple[int, int, *tuple[str, ...]]):
    a, b = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: int

Simple unpacking (3)

def _(value: tuple[int, *tuple[str, ...], int]):
    a, b, c = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: str
    reveal_type(c)  # revealed: int

Invalid unpacked

def _(value: tuple[int, int, int, *tuple[str, ...]]):
    # error: [invalid-assignment] "Too many values to unpack: Expected 2"
    a, b = value
    reveal_type(a)  # revealed: Unknown
    reveal_type(b)  # revealed: Unknown

Nested unpacking

def _(value: tuple[str, *tuple[tuple[int, ...], ...]]):
    a, (b, c) = value
    reveal_type(a)  # revealed: str
    reveal_type(b)  # revealed: int
    reveal_type(c)  # revealed: int

Invalid nested unpacking

def _(value: tuple[str, *tuple[int, ...]]):
    # error: [not-iterable] "Object of type `int` is not iterable"
    a, (b, c) = value
    reveal_type(a)  # revealed: str
    reveal_type(b)  # revealed: Unknown
    reveal_type(c)  # revealed: Unknown

Starred expression (1)

def _(value: tuple[int, *tuple[str, ...]]):
    a, *b, c = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: list[str]
    reveal_type(c)  # revealed: str

Starred expression (2)

def _(value: tuple[int, *tuple[str, ...], int]):
    a, *b, c = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: list[str]
    reveal_type(c)  # revealed: int

Starred expression (3)

def _(value: tuple[int, *tuple[str, ...], int]):
    a, *b, c, d = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: list[str]
    reveal_type(c)  # revealed: str
    reveal_type(d)  # revealed: int

Starred expression (4)

def _(value: tuple[int, int, *tuple[str, ...], int]):
    a, *b, c = value
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: list[int | str]
    reveal_type(c)  # revealed: int

String

Simple unpacking

a, b = "ab"
reveal_type(a)  # revealed: LiteralString
reveal_type(b)  # revealed: LiteralString

Uneven unpacking (1)

# error: [invalid-assignment] "Not enough values to unpack: Expected 3"
a, b, c = "ab"
reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: Unknown
reveal_type(c)  # revealed: Unknown

Uneven unpacking (2)

# error: [invalid-assignment] "Too many values to unpack: Expected 2"
a, b = "abc"
reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: Unknown

Starred expression (1)

# error: [invalid-assignment] "Not enough values to unpack: Expected at least 3"
(a, *b, c, d) = "ab"
reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: list[Unknown]
reveal_type(c)  # revealed: Unknown
reveal_type(d)  # revealed: Unknown
# error: [invalid-assignment] "Not enough values to unpack: Expected at least 3"
(a, b, *c, d) = "a"
reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: Unknown
reveal_type(c)  # revealed: list[Unknown]
reveal_type(d)  # revealed: Unknown

Starred expression (2)

(a, *b, c) = "ab"
reveal_type(a)  # revealed: LiteralString
reveal_type(b)  # revealed: list[Never]
reveal_type(c)  # revealed: LiteralString

Starred expression (3)

(a, *b, c) = "abc"
reveal_type(a)  # revealed: LiteralString
reveal_type(b)  # revealed: list[LiteralString]
reveal_type(c)  # revealed: LiteralString

Starred expression (4)

(a, *b, c, d) = "abcdef"
reveal_type(a)  # revealed: LiteralString
reveal_type(b)  # revealed: list[LiteralString]
reveal_type(c)  # revealed: LiteralString
reveal_type(d)  # revealed: LiteralString

Starred expression (5)

(a, b, *c) = "abcd"
reveal_type(a)  # revealed: LiteralString
reveal_type(b)  # revealed: LiteralString
reveal_type(c)  # revealed: list[LiteralString]

Starred expression (6)

from typing_extensions import LiteralString

def _(s: LiteralString):
    a, b, *c = s
    reveal_type(a)  # revealed: LiteralString
    reveal_type(b)  # revealed: LiteralString
    reveal_type(c)  # revealed: list[LiteralString]

Unicode

# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
(a, b) = "é"

reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: Unknown

Unicode escape (1)

# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
(a, b) = "\u9e6c"

reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: Unknown

Unicode escape (2)

# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
(a, b) = "\U0010ffff"

reveal_type(a)  # revealed: Unknown
reveal_type(b)  # revealed: Unknown

Surrogates

(a, b) = "\ud800\udfff"

reveal_type(a)  # revealed: LiteralString
reveal_type(b)  # revealed: LiteralString

Union

Same types

Union of two tuples of equal length and each element is of the same type.

def _(arg: tuple[int, int] | tuple[int, int]):
    (a, b) = arg
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: int

Mixed types (1)

Union of two tuples of equal length and one element differs in its type.

def _(arg: tuple[int, int] | tuple[int, str]):
    a, b = arg
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: int | str

Mixed types (2)

Union of two tuples of equal length and both the element types are different.

def _(arg: tuple[int, str] | tuple[str, int]):
    a, b = arg
    reveal_type(a)  # revealed: int | str
    reveal_type(b)  # revealed: str | int

Mixed types (3)

Union of three tuples of equal length and various combination of element types:

  1. All same types
  2. One different type
  3. All different types
def _(arg: tuple[int, int, int] | tuple[int, str, bytes] | tuple[int, int, str]):
    a, b, c = arg
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: int | str
    reveal_type(c)  # revealed: int | bytes | str

Nested

from typing import Literal

def _(arg: tuple[int, tuple[str, bytes]] | tuple[tuple[int, bytes], Literal["ab"]]):
    a, (b, c) = arg
    reveal_type(a)  # revealed: int | tuple[int, bytes]
    reveal_type(b)  # revealed: str
    reveal_type(c)  # revealed: bytes | LiteralString

Starred expression

def _(arg: tuple[int, bytes, int] | tuple[int, int, str, int, bytes]):
    a, *b, c = arg
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: list[bytes] | list[int | str]
    reveal_type(c)  # revealed: int | bytes

Size mismatch (1)

def _(arg: tuple[int, bytes, int] | tuple[int, int, str, int, bytes]):
    # error: [invalid-assignment] "Too many values to unpack: Expected 2"
    # error: [invalid-assignment] "Too many values to unpack: Expected 2"
    a, b = arg
    reveal_type(a)  # revealed: Unknown
    reveal_type(b)  # revealed: Unknown

Size mismatch (2)

def _(arg: tuple[int, bytes] | tuple[int, str]):
    # error: [invalid-assignment] "Not enough values to unpack: Expected 3"
    # error: [invalid-assignment] "Not enough values to unpack: Expected 3"
    a, b, c = arg
    reveal_type(a)  # revealed: Unknown
    reveal_type(b)  # revealed: Unknown
    reveal_type(c)  # revealed: Unknown

Same literal types

def _(flag: bool):
    if flag:
        value = (1, 2)
    else:
        value = (3, 4)

    a, b = value
    reveal_type(a)  # revealed: Literal[1, 3]
    reveal_type(b)  # revealed: Literal[2, 4]

Mixed literal types

def _(flag: bool):
    if flag:
        value = (1, 2)
    else:
        value = ("a", "b")

    a, b = value
    reveal_type(a)  # revealed: Literal[1, "a"]
    reveal_type(b)  # revealed: Literal[2, "b"]

Typing literal

from typing import Literal

def _(arg: tuple[int, int] | Literal["ab"]):
    a, b = arg
    reveal_type(a)  # revealed: int | LiteralString
    reveal_type(b)  # revealed: int | LiteralString

Custom iterator (1)

class Iterator:
    def __next__(self) -> tuple[int, int] | tuple[int, str]:
        return (1, 2)

class Iterable:
    def __iter__(self) -> Iterator:
        return Iterator()

((a, b), c) = Iterable()
reveal_type(a)  # revealed: int
reveal_type(b)  # revealed: int | str
reveal_type(c)  # revealed: tuple[int, int] | tuple[int, str]

Custom iterator (2)

class Iterator:
    def __next__(self) -> bytes:
        return b""

class Iterable:
    def __iter__(self) -> Iterator:
        return Iterator()

def _(arg: tuple[int, str] | Iterable):
    a, b = arg
    reveal_type(a)  # revealed: int | bytes
    reveal_type(b)  # revealed: str | bytes

For statement

Unpacking in a for statement.

Same types

def _(arg: tuple[tuple[int, int], tuple[int, int]]):
    for a, b in arg:
        reveal_type(a)  # revealed: int
        reveal_type(b)  # revealed: int

Mixed types (1)

def _(arg: tuple[tuple[int, int], tuple[int, str]]):
    for a, b in arg:
        reveal_type(a)  # revealed: int
        reveal_type(b)  # revealed: int | str

Mixed types (2)

def _(arg: tuple[tuple[int, str], tuple[str, int]]):
    for a, b in arg:
        reveal_type(a)  # revealed: int | str
        reveal_type(b)  # revealed: str | int

Mixed types (3)

def _(arg: tuple[tuple[int, int, int], tuple[int, str, bytes], tuple[int, int, str]]):
    for a, b, c in arg:
        reveal_type(a)  # revealed: int
        reveal_type(b)  # revealed: int | str
        reveal_type(c)  # revealed: int | bytes | str

Same literal values

for a, b in ((1, 2), (3, 4)):
    reveal_type(a)  # revealed: Literal[1, 3]
    reveal_type(b)  # revealed: Literal[2, 4]

Mixed literal values (1)

for a, b in ((1, 2), ("a", "b")):
    reveal_type(a)  # revealed: Literal[1, "a"]
    reveal_type(b)  # revealed: Literal[2, "b"]

Mixed literals values (2)

# error: "Object of type `Literal[1]` is not iterable"
# error: "Object of type `Literal[2]` is not iterable"
# error: "Object of type `Literal[4]` is not iterable"
# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
for a, b in (1, 2, (3, "a"), 4, (5, "b"), "c"):
    reveal_type(a)  # revealed: Unknown | Literal[3, 5]
    reveal_type(b)  # revealed: Unknown | Literal["a", "b"]

Custom iterator (1)

class Iterator:
    def __next__(self) -> tuple[int, int]:
        return (1, 2)

class Iterable:
    def __iter__(self) -> Iterator:
        return Iterator()

for a, b in Iterable():
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: int

Custom iterator (2)

class Iterator:
    def __next__(self) -> bytes:
        return b""

class Iterable:
    def __iter__(self) -> Iterator:
        return Iterator()

def _(arg: tuple[tuple[int, str], Iterable]):
    for a, b in arg:
        reveal_type(a)  # revealed: int | bytes
        reveal_type(b)  # revealed: str | bytes

With statement

Unpacking in a with statement.

Same types

class ContextManager:
    def __enter__(self) -> tuple[int, int]:
        return (1, 2)

    def __exit__(self, exc_type, exc_value, traceback) -> None:
        pass

with ContextManager() as (a, b):
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: int

Mixed types

class ContextManager:
    def __enter__(self) -> tuple[int, str]:
        return (1, "a")

    def __exit__(self, exc_type, exc_value, traceback) -> None:
        pass

with ContextManager() as (a, b):
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: str

Nested

class ContextManager:
    def __enter__(self) -> tuple[int, tuple[str, bytes]]:
        return (1, ("a", b"bytes"))

    def __exit__(self, exc_type, exc_value, traceback) -> None:
        pass

with ContextManager() as (a, (b, c)):
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: str
    reveal_type(c)  # revealed: bytes

Starred expression

class ContextManager:
    def __enter__(self) -> tuple[int, int, int]:
        return (1, 2, 3)

    def __exit__(self, exc_type, exc_value, traceback) -> None:
        pass

with ContextManager() as (a, *b):
    reveal_type(a)  # revealed: int
    reveal_type(b)  # revealed: list[int]

Unbound context manager expression

# error: [unresolved-reference] "Name `nonexistant` used when not defined"
with nonexistant as (x, y):
    reveal_type(x)  # revealed: Unknown
    reveal_type(y)  # revealed: Unknown

Invalid unpacking

class ContextManager:
    def __enter__(self) -> tuple[int, str]:
        return (1, "a")

    def __exit__(self, *args) -> None:
        pass

# error: [invalid-assignment] "Not enough values to unpack: Expected 3"
with ContextManager() as (a, b, c):
    reveal_type(a)  # revealed: Unknown
    reveal_type(b)  # revealed: Unknown
    reveal_type(c)  # revealed: Unknown

Comprehension

Unpacking in a comprehension.

Same types

def _(arg: tuple[tuple[int, int], tuple[int, int]]):
    # revealed: tuple[int, int]
    [reveal_type((a, b)) for a, b in arg]

Mixed types (1)

def _(arg: tuple[tuple[int, int], tuple[int, str]]):
    # revealed: tuple[int, int | str]
    [reveal_type((a, b)) for a, b in arg]

Mixed types (2)

def _(arg: tuple[tuple[int, str], tuple[str, int]]):
    # revealed: tuple[int | str, str | int]
    [reveal_type((a, b)) for a, b in arg]

Mixed types (3)

def _(arg: tuple[tuple[int, int, int], tuple[int, str, bytes], tuple[int, int, str]]):
    # revealed: tuple[int, int | str, int | bytes | str]
    [reveal_type((a, b, c)) for a, b, c in arg]

Same literal values

# revealed: tuple[Literal[1, 3], Literal[2, 4]]
[reveal_type((a, b)) for a, b in ((1, 2), (3, 4))]

Mixed literal values (1)

# revealed: tuple[Literal[1, "a"], Literal[2, "b"]]
[reveal_type((a, b)) for a, b in ((1, 2), ("a", "b"))]

Mixed literals values (2)

# error: "Object of type `Literal[1]` is not iterable"
# error: "Object of type `Literal[2]` is not iterable"
# error: "Object of type `Literal[4]` is not iterable"
# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
# revealed: tuple[Unknown | Literal[3, 5], Unknown | Literal["a", "b"]]
[reveal_type((a, b)) for a, b in (1, 2, (3, "a"), 4, (5, "b"), "c")]

Custom iterator (1)

class Iterator:
    def __next__(self) -> tuple[int, int]:
        return (1, 2)

class Iterable:
    def __iter__(self) -> Iterator:
        return Iterator()

# revealed: tuple[int, int]
[reveal_type((a, b)) for a, b in Iterable()]

Custom iterator (2)

class Iterator:
    def __next__(self) -> bytes:
        return b""

class Iterable:
    def __iter__(self) -> Iterator:
        return Iterator()

def _(arg: tuple[tuple[int, str], Iterable]):
    # revealed: tuple[int | bytes, str | bytes]
    [reveal_type((a, b)) for a, b in arg]

Empty

Unpacking an empty tuple or list shouldn't raise any diagnostics.

[] = []
() = ()
[] = ()
() = []