[ty] Infer list[T] when unpacking non-tuple type (#18438)

## Summary

Follow-up from #18401, I was looking at whether that would fix the issue
at https://github.com/astral-sh/ty/issues/247#issuecomment-2917656676
and it didn't, which made me realize that the PR only inferred `list[T]`
when the value type was tuple but it could be other types as well.

This PR fixes the actual issue by inferring `list[T]` for the non-tuple
type case.

## Test Plan

Add test cases for starred expression involved with non-tuple type. I
also added a few test cases for list type and list literal.

I also verified that the example in the linked issue comment works:
```py
def _(line: str):
    a, b, *c = line.split(maxsplit=2)
    c.pop()
```
This commit is contained in:
Dhruv Manilawala 2025-06-03 19:17:47 +05:30 committed by GitHub
parent 0986edf427
commit 8d98c601d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 75 additions and 5 deletions

View file

@ -1,8 +1,8 @@
# 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`.
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
@ -207,6 +207,57 @@ reveal_type(c) # revealed: int
reveal_type(d) # revealed: Literal[2]
```
## List
### Literal unpacking
```py
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
```py
def _(value: list[int]):
a, b = value
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int
```
### Nested unpacking
```py
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
```py
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
```py
def _(value: list[int]):
a, *b, c = value
reveal_type(a) # revealed: int
reveal_type(b) # revealed: list[int]
reveal_type(c) # revealed: int
```
## String
### Simple unpacking
@ -293,6 +344,18 @@ reveal_type(b) # revealed: LiteralString
reveal_type(c) # revealed: list[LiteralString]
```
### Starred expression (6)
```py
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
```py

View file

@ -192,8 +192,15 @@ impl<'db> Unpacker<'db> {
err.fallback_element_type(self.db())
})
};
for target_type in &mut target_types {
target_type.push(ty);
// Both `elts` and `target_types` are guaranteed to have the same length.
for (element, target_type) in elts.iter().zip(&mut target_types) {
if element.is_starred_expr() {
target_type.push(
KnownClass::List.to_specialized_instance(self.db(), [ty]),
);
} else {
target_type.push(ty);
}
}
}
}