mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-02 18:02:23 +00:00

Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
We already had support for homogeneous tuples (`tuple[int, ...]`). This PR extends this to also support mixed tuples (`tuple[str, str, *tuple[int, ...], str str]`). A mixed tuple consists of a fixed-length (possibly empty) prefix and suffix, and a variable-length portion in the middle. Every element of the variable-length portion must be of the same type. A homogeneous tuple is then just a mixed tuple with an empty prefix and suffix. The new data representation uses different Rust types for a fixed-length (aka heterogeneous) tuple. Another option would have been to use the `VariableLengthTuple` representation for all tuples, and to wrap the "variable + suffix" portion in an `Option`. I don't think that would simplify the method implementations much, though, since we would still have a 2×2 case analysis for most of them. One wrinkle is that the definition of the `tuple` class in the typeshed has a single typevar, and canonically represents a homogeneous tuple. When getting the class of a tuple instance, that means that we have to summarize our detailed mixed tuple type information into its "homogeneous supertype". (We were already doing this for heterogeneous types.) A similar thing happens when concatenating two mixed tuples: the variable-length portion and suffix of the LHS, and the prefix and variable-length portion of the RHS, all get unioned into the variable-length portion of the result. The LHS prefix and RHS suffix carry through unchanged. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
6.6 KiB
6.6 KiB
Tuple subscripts
Indexing
t = (1, "a", "b")
reveal_type(t[0]) # revealed: Literal[1]
reveal_type(t[1]) # revealed: Literal["a"]
reveal_type(t[-1]) # revealed: Literal["b"]
reveal_type(t[-2]) # revealed: Literal["a"]
reveal_type(t[False]) # revealed: Literal[1]
reveal_type(t[True]) # revealed: Literal["a"]
a = t[4] # error: [index-out-of-bounds]
reveal_type(a) # revealed: Unknown
b = t[-4] # error: [index-out-of-bounds]
reveal_type(b) # revealed: Unknown
Slices
def _(m: int, n: int):
t = (1, "a", None, b"b")
reveal_type(t[0:0]) # revealed: tuple[()]
reveal_type(t[0:1]) # revealed: tuple[Literal[1]]
reveal_type(t[0:2]) # revealed: tuple[Literal[1], Literal["a"]]
reveal_type(t[0:4]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
reveal_type(t[0:5]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
reveal_type(t[1:3]) # revealed: tuple[Literal["a"], None]
reveal_type(t[-2:4]) # revealed: tuple[None, Literal[b"b"]]
reveal_type(t[-3:-1]) # revealed: tuple[Literal["a"], None]
reveal_type(t[-10:10]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
reveal_type(t[0:]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
reveal_type(t[2:]) # revealed: tuple[None, Literal[b"b"]]
reveal_type(t[4:]) # revealed: tuple[()]
reveal_type(t[:0]) # revealed: tuple[()]
reveal_type(t[:2]) # revealed: tuple[Literal[1], Literal["a"]]
reveal_type(t[:10]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
reveal_type(t[:]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
reveal_type(t[::-1]) # revealed: tuple[Literal[b"b"], None, Literal["a"], Literal[1]]
reveal_type(t[::2]) # revealed: tuple[Literal[1], None]
reveal_type(t[-2:-5:-1]) # revealed: tuple[None, Literal["a"], Literal[1]]
reveal_type(t[::-2]) # revealed: tuple[Literal[b"b"], Literal["a"]]
reveal_type(t[-1::-3]) # revealed: tuple[Literal[b"b"], Literal[1]]
reveal_type(t[None:2:None]) # revealed: tuple[Literal[1], Literal["a"]]
reveal_type(t[1:None:1]) # revealed: tuple[Literal["a"], None, Literal[b"b"]]
reveal_type(t[None:None:None]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
start = 1
stop = None
step = 2
reveal_type(t[start:stop:step]) # revealed: tuple[Literal["a"], Literal[b"b"]]
reveal_type(t[False:True]) # revealed: tuple[Literal[1]]
reveal_type(t[True:3]) # revealed: tuple[Literal["a"], None]
t[0:4:0] # error: [zero-stepsize-in-slice]
t[:4:0] # error: [zero-stepsize-in-slice]
t[0::0] # error: [zero-stepsize-in-slice]
t[::0] # error: [zero-stepsize-in-slice]
tuple_slice = t[m:n]
reveal_type(tuple_slice) # revealed: tuple[Literal[1, "a", b"b"] | None, ...]
Slices of homogeneous and mixed tuples
[environment]
python-version = "3.11"
from typing import Literal
def homogeneous(t: tuple[str, ...]) -> None:
reveal_type(t[0]) # revealed: str
reveal_type(t[1]) # revealed: str
reveal_type(t[2]) # revealed: str
reveal_type(t[3]) # revealed: str
reveal_type(t[-1]) # revealed: str
reveal_type(t[-2]) # revealed: str
reveal_type(t[-3]) # revealed: str
reveal_type(t[-4]) # revealed: str
def mixed(s: tuple[str, ...]) -> None:
t = (1, 2, 3) + s + (8, 9, 10)
reveal_type(t[0]) # revealed: Literal[1]
reveal_type(t[1]) # revealed: Literal[2]
reveal_type(t[2]) # revealed: Literal[3]
reveal_type(t[3]) # revealed: str | Literal[8]
reveal_type(t[4]) # revealed: str | Literal[8, 9]
reveal_type(t[5]) # revealed: str | Literal[8, 9, 10]
reveal_type(t[-1]) # revealed: Literal[10]
reveal_type(t[-2]) # revealed: Literal[9]
reveal_type(t[-3]) # revealed: Literal[8]
reveal_type(t[-4]) # revealed: Literal[3] | str
reveal_type(t[-5]) # revealed: Literal[2, 3] | str
reveal_type(t[-6]) # revealed: Literal[1, 2, 3] | str
tuple
as generic alias
For tuple instances, we can track more detailed information about the length and element types of the tuple. This information carries over to the generic alias that the tuple is an instance of.
def _(a: tuple, b: tuple[int], c: tuple[int, str], d: tuple[int, ...]) -> None:
reveal_type(a) # revealed: tuple[Unknown, ...]
reveal_type(b) # revealed: tuple[int]
reveal_type(c) # revealed: tuple[int, str]
reveal_type(d) # revealed: tuple[int, ...]
reveal_type(tuple) # revealed: <class 'tuple'>
reveal_type(tuple[int]) # revealed: <class 'tuple[int]'>
reveal_type(tuple[int, str]) # revealed: <class 'tuple[int, str]'>
reveal_type(tuple[int, ...]) # revealed: <class 'tuple[int, ...]'>
Inheritance
[environment]
python-version = "3.9"
class A(tuple[int, str]): ...
# revealed: tuple[<class 'A'>, <class 'tuple[int, str]'>, <class 'Sequence[int | str]'>, <class 'Reversible[int | str]'>, <class 'Collection[int | str]'>, <class 'Iterable[int | str]'>, <class 'Container[int | str]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(A.__mro__)
class C(tuple): ...
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(C.__mro__)
typing.Tuple
Correspondence with tuple
typing.Tuple
can be used interchangeably with tuple
:
from typing import Any, Tuple
class A: ...
def _(c: Tuple, d: Tuple[int, A], e: Tuple[Any, ...]):
reveal_type(c) # revealed: tuple[Unknown, ...]
reveal_type(d) # revealed: tuple[int, A]
reveal_type(e) # revealed: tuple[Any, ...]
Inheritance
Inheriting from Tuple
results in a MRO with builtins.tuple
and typing.Generic
. Tuple
itself
is not a class.
[environment]
python-version = "3.9"
from typing import Tuple
class A(Tuple[int, str]): ...
# revealed: tuple[<class 'A'>, <class 'tuple[int, str]'>, <class 'Sequence[int | str]'>, <class 'Reversible[int | str]'>, <class 'Collection[int | str]'>, <class 'Iterable[int | str]'>, <class 'Container[int | str]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(A.__mro__)
class C(Tuple): ...
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
reveal_type(C.__mro__)