mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
[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.
This commit is contained in:
parent
a50a993b9c
commit
c60e590b4c
11 changed files with 779 additions and 423 deletions
|
@ -24,7 +24,7 @@ error[invalid-assignment]: Not enough values to unpack
|
|||
1 | [a, *b, c, d] = (1, 2) # error: [invalid-assignment]
|
||||
| ^^^^^^^^^^^^^ ------ Got 2
|
||||
| |
|
||||
| Expected 3 or more
|
||||
| Expected at least 3
|
||||
|
|
||||
info: rule `invalid-assignment` is enabled by default
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ reveal_type(d) # revealed: Literal[5]
|
|||
### Starred expression (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3 or more"
|
||||
# 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]
|
||||
|
@ -119,7 +119,7 @@ reveal_type(d) # revealed: Unknown
|
|||
```py
|
||||
[a, *b, c] = (1, 2)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: list[Unknown]
|
||||
reveal_type(b) # revealed: list[Never]
|
||||
reveal_type(c) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
|
@ -154,7 +154,7 @@ reveal_type(c) # revealed: list[Literal[3, 4]]
|
|||
### Starred expression (6)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 5 or more"
|
||||
# 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
|
||||
|
@ -258,6 +258,155 @@ def _(value: list[int]):
|
|||
reveal_type(c) # revealed: int
|
||||
```
|
||||
|
||||
## Homogeneous tuples
|
||||
|
||||
### Simple unpacking
|
||||
|
||||
```py
|
||||
def _(value: tuple[int, ...]):
|
||||
a, b = value
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int
|
||||
```
|
||||
|
||||
### Nested unpacking
|
||||
|
||||
```py
|
||||
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
|
||||
|
||||
```py
|
||||
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
|
||||
|
||||
```py
|
||||
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
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
### Simple unpacking (1)
|
||||
|
||||
```py
|
||||
def _(value: tuple[int, *tuple[str, ...]]):
|
||||
a, b = value
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: str
|
||||
```
|
||||
|
||||
### Simple unpacking (2)
|
||||
|
||||
```py
|
||||
def _(value: tuple[int, int, *tuple[str, ...]]):
|
||||
a, b = value
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int
|
||||
```
|
||||
|
||||
### Simple unpacking (3)
|
||||
|
||||
```py
|
||||
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
|
||||
|
||||
```py
|
||||
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
|
||||
|
||||
```py
|
||||
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
|
||||
|
||||
```py
|
||||
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)
|
||||
|
||||
```py
|
||||
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)
|
||||
|
||||
```py
|
||||
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)
|
||||
|
||||
```py
|
||||
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)
|
||||
|
||||
```py
|
||||
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
|
||||
|
@ -290,7 +439,7 @@ reveal_type(b) # revealed: Unknown
|
|||
### Starred expression (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3 or more"
|
||||
# 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]
|
||||
|
@ -299,7 +448,7 @@ reveal_type(d) # revealed: Unknown
|
|||
```
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3 or more"
|
||||
# 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
|
||||
|
@ -312,7 +461,7 @@ reveal_type(d) # revealed: Unknown
|
|||
```py
|
||||
(a, *b, c) = "ab"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(b) # revealed: list[Unknown]
|
||||
reveal_type(b) # revealed: list[Never]
|
||||
reveal_type(c) # revealed: LiteralString
|
||||
```
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue