tuple type literals¶
a parenthesized tuple in an annotation position is rewritten to tuple[...]:
point: (int, int)
record: (int, str, float)
nested: (int, (str, float))
maybe: (int, str) | None
def origin() -> (int, int):
return (0, 0)
transpiles to:
point: tuple[int, int]
record: tuple[int, str, float]
nested: tuple[int, tuple[str, float]]
maybe: tuple[int, str] | None
def origin() -> tuple[int, int]:
return (0, 0)
unpacking¶
a * element splices the tuple it names in, rather than nesting it as a single
field:
type Pair = (int, str)
type Triple = (bool, *Pair) # (bool, int, str)
type Same = (*Pair) # (int, str)
the star has already made this a tuple, so the lone-element form needs no
trailing comma. a type alias is resolved first, so a named tuple type unpacks
exactly as the tuple it stands for — that holds for python's tuple[*Pair] and
Unpack[Pair] spellings too
*A and the variadic *: T parse to the same shape but mean
different things: *: T annotates every field, while *A splices one tuple in.
what tells them apart is the :
unpacking lowers to python's own * spelling, so it inherits python's runtime
rule: * on a type alias is only evaluatable lazily. that covers the type
positions the form is written in — another type alias, or any annotation once
from __future__ import annotations is on — but an eagerly evaluated annotation
that unpacks an alias raises at import, exactly as the same annotation would in
python. unpacking a tuple type or a TypeVarTuple directly has no such limit
syntax¶
a parenthesized list of one or more elements — trailing comma allowed. a
single-element form requires the trailing comma to disambiguate from a
parenthesized expression: (int,) is tuple[int], while (int) is
just int. an unpacked element is already unambiguous, so (*A) needs none
scope¶
rewriting fires in syntactic type contexts only: parameter annotations,
return-type annotations, AnnAssign targets, type aliases, and subscript
slices that are themselves type contexts. value-context tuples (x = (1, 2))
are untouched
composition¶
the rule recurses into surrounding type forms — unions, callables, generics, intersections — so any tuple type expression nested inside is also rewritten:
relation to anonymous named tuples¶
if any element in the parenthesized list uses name : type form, the
expression is recognised as an anonymous named tuple
instead. tuple-type rewrite and anon-NT rewrite are exclusive: a tuple
either has all-positional fields (becomes tuple[...]) or contains at
least one named field (becomes a synthesized NamedTuple class)