diff --git a/crates/ty_python_semantic/resources/mdtest/expression/len.md b/crates/ty_python_semantic/resources/mdtest/expression/len.md index fa566c1d79..30b81ca0e4 100644 --- a/crates/ty_python_semantic/resources/mdtest/expression/len.md +++ b/crates/ty_python_semantic/resources/mdtest/expression/len.md @@ -42,14 +42,11 @@ reveal_type(len(())) # revealed: Literal[0] reveal_type(len((1,))) # revealed: Literal[1] reveal_type(len((1, 2))) # revealed: Literal[2] reveal_type(len(tuple())) # revealed: Literal[0] - -# could also be `Literal[0]`, but `int` is accurate -reveal_type(len((*[],))) # revealed: int +reveal_type(len((*[],))) # revealed: Literal[0] # fmt: off -# could also be `Literal[1]`, but `int` is accurate -reveal_type(len( # revealed: int +reveal_type(len( # revealed: Literal[1] ( *[], 1, @@ -58,11 +55,8 @@ reveal_type(len( # revealed: int # fmt: on -# Could also be `Literal[2]`, but `int` is accurate -reveal_type(len((*[], 1, 2))) # revealed: int - -# Could also be `Literal[0]`, but `int` is accurate -reveal_type(len((*[], *{}))) # revealed: int +reveal_type(len((*[], 1, 2))) # revealed: Literal[2] +reveal_type(len((*[], *{}))) # revealed: Literal[0] ``` Tuple subclasses: diff --git a/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md b/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md index 285f4abe79..a36d6fe0ae 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md @@ -534,6 +534,8 @@ reveal_type(x) # revealed: list[Literal[1, 2, 3]] ## Tuples with starred elements ```py +from typing import Literal, Sequence + x = (1, *range(3), 3) reveal_type(x) # revealed: tuple[Literal[1], *tuple[int, ...], Literal[3]] @@ -542,7 +544,21 @@ y = 1, 2 reveal_type(("foo", *y)) # revealed: tuple[Literal["foo"], Literal[1], Literal[2]] aa: tuple[list[int], ...] = ([42], *{[56], [78]}, [100]) -reveal_type(aa) # revealed: tuple[list[int], *tuple[list[int], ...], list[int]] +reveal_type(aa) # revealed: tuple[list[int], list[int], list[int], list[int]] + +bb: tuple[list[Literal[42, 56]], ...] = ([42], *{[56, 42], [42]}, [42, 42, 56]) +reveal_type(bb) # revealed: tuple[list[Literal[42, 56]], list[Literal[42, 56]], list[Literal[42, 56]], list[Literal[42, 56]]] + +reveal_type((*[],)) # revealed: tuple[()] +reveal_type((42, *[], 56, *[])) # revealed: tuple[Literal[42], Literal[56]] + +tup: Sequence[str] = (*{"foo": 42, "bar": 56},) + +# TODO: `tuple[str, str]` would be better, given the type annotation +reveal_type(tup) # revealed: tuple[Unknown | str, Unknown | str] + +def f(x: list[int]): + reveal_type((42, 56, *x, 97)) # revealed: tuple[Literal[42], Literal[56], *tuple[int, ...], Literal[97]] ``` [not a singleton type]: https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957 diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 325de5528e..fd931e9a4f 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -7334,12 +7334,33 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let mut builder = TupleSpecBuilder::with_capacity(elts.len()); for element in elts { - if element.is_starred_expr() { + if let ast::Expr::Starred(starred) = element { let element_type = infer_element(element); // Fine to use `iterate` rather than `try_iterate` here: // errors from iterating over something not iterable will have been // emitted in the `infer_element` call above. - builder = builder.concat(db, &element_type.iterate(db)); + let mut spec = element_type.iterate(db).into_owned(); + + let known_length = match &*starred.value { + ast::Expr::List(ast::ExprList { elts, .. }) + | ast::Expr::Set(ast::ExprSet { elts, .. }) => elts + .iter() + .all(|elt| !elt.is_starred_expr()) + .then_some(elts.len()), + ast::Expr::Dict(ast::ExprDict { items, .. }) => items + .iter() + .all(|item| item.key.is_some()) + .then_some(items.len()), + _ => None, + }; + + if let Some(known_length) = known_length { + spec = spec + .resize(db, TupleLength::Fixed(known_length)) + .unwrap_or(spec); + } + + builder = builder.concat(db, &spec); } else { builder.push(infer_element(element)); }