Skip to content

Commit 06ca8f5

Browse files
committed
[ty] Add precise tuple specs when iterating over other kinds of types
1 parent c5e05df commit 06ca8f5

File tree

2 files changed

+49
-30
lines changed

2 files changed

+49
-30
lines changed

crates/ty_python_semantic/resources/mdtest/unpacking.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -523,8 +523,8 @@ def f(x: MixedTupleSubclass):
523523

524524
```py
525525
a, b = "ab"
526-
reveal_type(a) # revealed: LiteralString
527-
reveal_type(b) # revealed: LiteralString
526+
reveal_type(a) # revealed: Literal["a"]
527+
reveal_type(b) # revealed: Literal["b"]
528528
```
529529

530530
### Uneven unpacking (1)
@@ -570,37 +570,37 @@ reveal_type(d) # revealed: Unknown
570570

571571
```py
572572
(a, *b, c) = "ab"
573-
reveal_type(a) # revealed: LiteralString
573+
reveal_type(a) # revealed: Literal["a"]
574574
reveal_type(b) # revealed: list[Never]
575-
reveal_type(c) # revealed: LiteralString
575+
reveal_type(c) # revealed: Literal["b"]
576576
```
577577

578578
### Starred expression (3)
579579

580580
```py
581581
(a, *b, c) = "abc"
582-
reveal_type(a) # revealed: LiteralString
583-
reveal_type(b) # revealed: list[LiteralString]
584-
reveal_type(c) # revealed: LiteralString
582+
reveal_type(a) # revealed: Literal["a"]
583+
reveal_type(b) # revealed: list[Literal["b"]]
584+
reveal_type(c) # revealed: Literal["c"]
585585
```
586586

587587
### Starred expression (4)
588588

589589
```py
590590
(a, *b, c, d) = "abcdef"
591-
reveal_type(a) # revealed: LiteralString
592-
reveal_type(b) # revealed: list[LiteralString]
593-
reveal_type(c) # revealed: LiteralString
594-
reveal_type(d) # revealed: LiteralString
591+
reveal_type(a) # revealed: Literal["a"]
592+
reveal_type(b) # revealed: list[Literal["b", "c", "d"]]
593+
reveal_type(c) # revealed: Literal["e"]
594+
reveal_type(d) # revealed: Literal["f"]
595595
```
596596

597597
### Starred expression (5)
598598

599599
```py
600600
(a, b, *c) = "abcd"
601-
reveal_type(a) # revealed: LiteralString
602-
reveal_type(b) # revealed: LiteralString
603-
reveal_type(c) # revealed: list[LiteralString]
601+
reveal_type(a) # revealed: Literal["a"]
602+
reveal_type(b) # revealed: Literal["b"]
603+
reveal_type(c) # revealed: list[Literal["c", "d"]]
604604
```
605605

606606
### Starred expression (6)
@@ -650,8 +650,8 @@ reveal_type(b) # revealed: Unknown
650650
```py
651651
(a, b) = "\ud800\udfff"
652652

653-
reveal_type(a) # revealed: LiteralString
654-
reveal_type(b) # revealed: LiteralString
653+
reveal_type(a) # revealed: Literal["�"]
654+
reveal_type(b) # revealed: Literal["�"]
655655
```
656656

657657
## Union
@@ -714,7 +714,7 @@ def _(arg: tuple[int, tuple[str, bytes]] | tuple[tuple[int, bytes], Literal["ab"
714714
a, (b, c) = arg
715715
reveal_type(a) # revealed: int | tuple[int, bytes]
716716
reveal_type(b) # revealed: str
717-
reveal_type(c) # revealed: bytes | LiteralString
717+
reveal_type(c) # revealed: bytes | Literal["b"]
718718
```
719719

720720
### Starred expression
@@ -785,8 +785,8 @@ from typing import Literal
785785

786786
def _(arg: tuple[int, int] | Literal["ab"]):
787787
a, b = arg
788-
reveal_type(a) # revealed: int | LiteralString
789-
reveal_type(b) # revealed: int | LiteralString
788+
reveal_type(a) # revealed: int | Literal["a"]
789+
reveal_type(b) # revealed: int | Literal["b"]
790790
```
791791

792792
### Custom iterator (1)

crates/ty_python_semantic/src/types.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ use crate::types::constraints::{
4545
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
4646
use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
4747
pub use crate::types::display::DisplaySettings;
48-
use crate::types::enums::{enum_metadata, is_single_member_enum};
48+
use crate::types::enums::{enum_member_literals, enum_metadata, is_single_member_enum};
4949
use crate::types::function::{
5050
DataclassTransformerParams, FunctionDecorators, FunctionSpans, FunctionType, KnownFunction,
5151
};
@@ -4917,6 +4917,8 @@ impl<'db> Type<'db> {
49174917
db: &'db dyn Db,
49184918
mode: EvaluationMode,
49194919
) -> Result<Cow<'db, TupleSpec<'db>>, IterationError<'db>> {
4920+
const MAX_TUPLE_LENGTH: usize = 128;
4921+
49204922
if mode.is_async() {
49214923
let try_call_dunder_anext_on_iterator = |iterator: Type<'db>| -> Result<
49224924
Result<Type<'db>, AwaitError<'db>>,
@@ -4984,14 +4986,24 @@ impl<'db> Type<'db> {
49844986
))));
49854987
}
49864988
Type::StringLiteral(string_literal_ty) => {
4987-
// We could go further and deconstruct to an array of `StringLiteral`
4988-
// with each individual character, instead of just an array of
4989-
// `LiteralString`, but there would be a cost and it's not clear that
4990-
// it's worth it.
4991-
return Ok(Cow::Owned(TupleSpec::heterogeneous(std::iter::repeat_n(
4992-
Type::LiteralString,
4993-
string_literal_ty.python_len(db),
4994-
))));
4989+
let string_literal = string_literal_ty.value(db);
4990+
if string_literal.len() < MAX_TUPLE_LENGTH {
4991+
return Ok(Cow::Owned(TupleSpec::heterogeneous(
4992+
string_literal
4993+
.chars()
4994+
.map(|c| Type::string_literal(db, &c.to_string())),
4995+
)));
4996+
}
4997+
}
4998+
Type::BytesLiteral(bytes) => {
4999+
let bytes_literal = bytes.value(db);
5000+
if bytes_literal.len() < MAX_TUPLE_LENGTH {
5001+
return Ok(Cow::Owned(TupleSpec::heterogeneous(
5002+
bytes_literal
5003+
.iter()
5004+
.map(|b| Type::IntLiteral(i64::from(*b))),
5005+
)));
5006+
}
49955007
}
49965008
Type::Never => {
49975009
// The dunder logic below would have us return `tuple[Never, ...]`, which eagerly
@@ -5016,6 +5028,15 @@ impl<'db> Type<'db> {
50165028
"should not be able to iterate over type variable {} in inferable position",
50175029
self.display(db)
50185030
),
5031+
Type::ClassLiteral(class) => {
5032+
if let Some(enum_members) = enum_member_literals(db, class, None) {
5033+
if enum_metadata(db, class)
5034+
.is_some_and(|metadata| metadata.members.len() < MAX_TUPLE_LENGTH)
5035+
{
5036+
return Ok(Cow::Owned(TupleSpec::heterogeneous(enum_members)));
5037+
}
5038+
}
5039+
}
50195040
Type::Dynamic(_)
50205041
| Type::FunctionLiteral(_)
50215042
| Type::GenericAlias(_)
@@ -5026,7 +5047,6 @@ impl<'db> Type<'db> {
50265047
| Type::DataclassTransformer(_)
50275048
| Type::Callable(_)
50285049
| Type::ModuleLiteral(_)
5029-
| Type::ClassLiteral(_)
50305050
| Type::SubclassOf(_)
50315051
| Type::ProtocolInstance(_)
50325052
| Type::SpecialForm(_)
@@ -5040,7 +5060,6 @@ impl<'db> Type<'db> {
50405060
| Type::BooleanLiteral(_)
50415061
| Type::EnumLiteral(_)
50425062
| Type::LiteralString
5043-
| Type::BytesLiteral(_)
50445063
| Type::BoundSuper(_)
50455064
| Type::TypeIs(_)
50465065
| Type::TypedDict(_) => {}

0 commit comments

Comments
 (0)