mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 10:23:11 +00:00
[red-knot] avoid inferring types if unpacking fails (#16530)
## Summary This PR closes #15199. The change I just made is to set all variables to type `Unknown` if unpacking fails, but in some cases this may be excessive. For example: ```py a, b, c = "ab" reveal_type(a) # Unknown, but it would be reasonable to think of it as LiteralString reveal_type(c) # Unknown ``` ```py # Failed to unpack before the starred expression (a, b, *c, d, e) = (1,) reveal_type(a) # Unknown reveal_type(b) # Unknown ... # Failed to unpack after the starred expression (a, b, *c, d, e) = (1, 2, 3) reveal_type(a) # Unknown, but should it be Literal[1]? reveal_type(b) # Unknown, but should it be Literal[2]? reveal_type(c) # Todo reveal_type(d) # Unknown reveal_type(e) # Unknown ``` I will modify it if you think it would be better to make it a different type than just `Unknown`. ## Test Plan I have made appropriate modifications to the test cases affected by this change, and also added some more test cases.
This commit is contained in:
parent
6d6e524b90
commit
348c196cb3
2 changed files with 69 additions and 36 deletions
|
@ -1,5 +1,9 @@
|
|||
# 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
|
||||
|
@ -63,8 +67,8 @@ reveal_type(c) # revealed: Literal[4]
|
|||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
(a, b, c) = (1, 2)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: Literal[2]
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
@ -73,8 +77,30 @@ reveal_type(c) # revealed: Unknown
|
|||
```py
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
|
||||
(a, b) = (1, 2, 3)
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
### Nested uneven unpacking (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
(a, (b, c), d) = (1, (2,), 3)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: Literal[2]
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
### Nested uneven unpacking (2)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
|
||||
(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)
|
||||
|
@ -82,10 +108,10 @@ reveal_type(b) # revealed: Literal[2]
|
|||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3 or more, got 2)"
|
||||
[a, *b, c, d] = (1, 2)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(a) # revealed: Unknown
|
||||
# TODO: Should be list[Any] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(c) # revealed: Literal[2]
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
@ -135,10 +161,10 @@ reveal_type(c) # revealed: @Todo(starred unpacking)
|
|||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 5 or more, got 1)"
|
||||
(a, b, c, *d, e, f) = (1,)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(d) # revealed: Unknown
|
||||
reveal_type(e) # revealed: Unknown
|
||||
reveal_type(f) # revealed: Unknown
|
||||
```
|
||||
|
@ -201,8 +227,8 @@ reveal_type(b) # revealed: LiteralString
|
|||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
a, b, c = "ab"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(b) # revealed: LiteralString
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
@ -211,8 +237,8 @@ reveal_type(c) # revealed: Unknown
|
|||
```py
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
|
||||
a, b = "abc"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(b) # revealed: LiteralString
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
### Starred expression (1)
|
||||
|
@ -220,10 +246,19 @@ reveal_type(b) # revealed: LiteralString
|
|||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3 or more, got 2)"
|
||||
(a, *b, c, d) = "ab"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(a) # revealed: Unknown
|
||||
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(c) # revealed: LiteralString
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3 or more, got 1)"
|
||||
(a, b, *c, d) = "a"
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
@ -274,7 +309,7 @@ reveal_type(c) # revealed: @Todo(starred unpacking)
|
|||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
(a, b) = "é"
|
||||
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
@ -284,7 +319,7 @@ reveal_type(b) # revealed: Unknown
|
|||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
(a, b) = "\u9e6c"
|
||||
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
@ -294,7 +329,7 @@ reveal_type(b) # revealed: Unknown
|
|||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
(a, b) = "\U0010ffff"
|
||||
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
@ -388,8 +423,8 @@ def _(arg: tuple[int, bytes, int] | tuple[int, int, str, int, bytes]):
|
|||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 5)"
|
||||
a, b = arg
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: bytes | int
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
### Size mismatch (2)
|
||||
|
@ -399,8 +434,8 @@ def _(arg: tuple[int, bytes] | tuple[int, str]):
|
|||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
a, b, c = arg
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: bytes | str
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
@ -542,7 +577,7 @@ for a, b in ((1, 2), ("a", "b")):
|
|||
# error: "Object of type `Literal[4]` is not iterable"
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
for a, b in (1, 2, (3, "a"), 4, (5, "b"), "c"):
|
||||
reveal_type(a) # revealed: Unknown | Literal[3, 5] | LiteralString
|
||||
reveal_type(a) # revealed: Unknown | Literal[3, 5]
|
||||
reveal_type(b) # revealed: Unknown | Literal["a", "b"]
|
||||
```
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ impl<'db> Unpacker<'db> {
|
|||
if let Some(tuple_ty) = ty.into_tuple() {
|
||||
let tuple_ty_elements = self.tuple_ty_elements(target, elts, tuple_ty);
|
||||
|
||||
match elts.len().cmp(&tuple_ty_elements.len()) {
|
||||
let length_mismatch = match elts.len().cmp(&tuple_ty_elements.len()) {
|
||||
Ordering::Less => {
|
||||
self.context.report_lint(
|
||||
&INVALID_ASSIGNMENT,
|
||||
|
@ -132,6 +132,7 @@ impl<'db> Unpacker<'db> {
|
|||
tuple_ty_elements.len()
|
||||
),
|
||||
);
|
||||
true
|
||||
}
|
||||
Ordering::Greater => {
|
||||
self.context.report_lint(
|
||||
|
@ -143,13 +144,18 @@ impl<'db> Unpacker<'db> {
|
|||
tuple_ty_elements.len()
|
||||
),
|
||||
);
|
||||
true
|
||||
}
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
Ordering::Equal => false,
|
||||
};
|
||||
|
||||
for (index, ty) in tuple_ty_elements.iter().enumerate() {
|
||||
if let Some(element_types) = target_types.get_mut(index) {
|
||||
element_types.push(*ty);
|
||||
if length_mismatch {
|
||||
element_types.push(Type::unknown());
|
||||
} else {
|
||||
element_types.push(*ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -243,15 +249,7 @@ impl<'db> Unpacker<'db> {
|
|||
),
|
||||
);
|
||||
|
||||
let mut element_types = tuple_ty.elements(self.db()).to_vec();
|
||||
|
||||
// Subtract 1 to insert the starred expression type at the correct
|
||||
// index.
|
||||
element_types.resize(targets.len() - 1, Type::unknown());
|
||||
// TODO: This should be `list[Unknown]`
|
||||
element_types.insert(starred_index, todo_type!("starred unpacking"));
|
||||
|
||||
Cow::Owned(element_types)
|
||||
Cow::Owned(vec![Type::unknown(); targets.len()])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue