diff --git a/crates/ty_python_semantic/resources/mdtest/bidirectional.md b/crates/ty_python_semantic/resources/mdtest/bidirectional.md index 20f28ef72ad87..d60647fcefbf1 100644 --- a/crates/ty_python_semantic/resources/mdtest/bidirectional.md +++ b/crates/ty_python_semantic/resources/mdtest/bidirectional.md @@ -42,6 +42,12 @@ def f[T](x: T, cond: bool) -> T | list[T]: return x if cond else [x] l5: int | list[int] = f(1, True) + +a: list[int] = [1, 2, *(3, 4, 5)] +reveal_type(a) # revealed: list[int] + +b: list[list[int]] = [[1], [2], *([3], [4])] +reveal_type(b) # revealed: list[list[int]] ``` `typed_dict.py`: diff --git a/crates/ty_python_semantic/resources/mdtest/classes.md b/crates/ty_python_semantic/resources/mdtest/classes.md index 4d92cb9cdf5e0..e4ed634c49e33 100644 --- a/crates/ty_python_semantic/resources/mdtest/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/classes.md @@ -25,3 +25,22 @@ B = bytes reveal_mro(C) # revealed: (, , , typing.Generic, ) ``` + +## Starred bases + +These are currently not supported, but ideally we would support them in some limited situations. + +```py +from ty_extensions import reveal_mro + +class A: ... +class B: ... +class C: ... + +bases = (A, B, C) + +class Foo(*bases): ... + +# revealed: (, @Todo(Starred expressions in class bases), ) +reveal_mro(Foo) +``` diff --git a/crates/ty_python_semantic/resources/mdtest/expression/len.md b/crates/ty_python_semantic/resources/mdtest/expression/len.md index 1db6b5a39a914..30b81ca0e4335 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] - -# TODO: Handle star unpacks; Should be: Literal[0] -reveal_type(len((*[],))) # revealed: Literal[1] +reveal_type(len((*[],))) # revealed: Literal[0] # fmt: off -# TODO: Handle star unpacks; Should be: Literal[1] -reveal_type(len( # revealed: Literal[2] +reveal_type(len( # revealed: Literal[1] ( *[], 1, @@ -58,11 +55,8 @@ reveal_type(len( # revealed: Literal[2] # fmt: on -# TODO: Handle star unpacks; Should be: Literal[2] -reveal_type(len((*[], 1, 2))) # revealed: Literal[3] - -# TODO: Handle star unpacks; Should be: Literal[0] -reveal_type(len((*[], *{}))) # revealed: Literal[2] +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 e323d25a17bea..a36d6fe0ae9af 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md @@ -531,4 +531,34 @@ x: list[Literal[1, 2, 3]] = list((1, 2, 3)) 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]] + +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], 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/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index dce0c5b7eb435..81acb615cd9e6 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -2974,7 +2974,11 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> { ) { let parameters = self.signature.parameters(); let parameter = ¶meters[parameter_index]; - if let Some(mut expected_ty) = parameter.annotated_type() { + + // TODO: handle starred annotations, e.g. `*args: *Ts` or `*args: *tuple[int, *tuple[str, ...]]` + if let Some(mut expected_ty) = parameter.annotated_type() + && !parameter.has_starred_annotation() + { if let Some(specialization) = self.specialization { argument_type = argument_type.apply_specialization(self.db, specialization); expected_ty = expected_ty.apply_specialization(self.db, specialization); diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 033f326c39a7e..4715a5171081c 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -38,6 +38,7 @@ use crate::types::{ ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType, TypeContext, TypeMapping, TypeRelation, TypedDictParams, UnionBuilder, VarianceInferable, binding_type, declaration_type, determine_upper_bound, + todo_type, }; use crate::{ Db, FxIndexMap, FxIndexSet, FxOrderSet, Program, @@ -1664,20 +1665,28 @@ impl<'db> ClassLiteral<'db> { let class_definition = semantic_index(db, self.file(db)).expect_single_definition(class_stmt); - if self.is_known(db, KnownClass::VersionInfo) { - let tuple_type = TupleType::new(db, &TupleSpec::version_info_spec(db)) - .expect("sys.version_info tuple spec should always be a valid tuple"); + if class_stmt.bases().iter().any(ast::Expr::is_starred_expr) { + return Box::new([todo_type!("Starred expressions in class bases")]); + } - Box::new([ - definition_expression_type(db, class_definition, &class_stmt.bases()[0]), - Type::from(tuple_type.to_class_type(db)), - ]) - } else { - class_stmt + match self.known(db) { + Some(KnownClass::VersionInfo) => { + let tuple_type = TupleType::new(db, &TupleSpec::version_info_spec(db)) + .expect("sys.version_info tuple spec should always be a valid tuple"); + + Box::new([ + definition_expression_type(db, class_definition, &class_stmt.bases()[0]), + Type::from(tuple_type.to_class_type(db)), + ]) + } + // Special-case `NotImplementedType`: typeshed says that it inherits from `Any`, + // but this causes more problems than it fixes. + Some(KnownClass::NotImplementedType) => Box::new([]), + _ => class_stmt .bases() .iter() .map(|base_node| definition_expression_type(db, class_definition, base_node)) - .collect() + .collect(), } } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index fe39deb36a593..9bc72c172d52c 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -37,6 +37,7 @@ //! be considered a bug.) use ruff_db::parsed::{ParsedModuleRef, parsed_module}; +use ruff_python_ast as ast; use ruff_text_size::Ranged; use rustc_hash::{FxHashMap, FxHashSet}; use salsa; @@ -384,6 +385,27 @@ impl<'db> TypeContext<'db> { self.annotation .is_some_and(|ty| ty.is_typealias_special_form()) } + + // TODO: we could just always use `Iterable[]` + // as the type context once is fixed. + pub(crate) fn for_starred_expression( + db: &'db dyn Db, + expected_element_type: Type<'db>, + expr: &ast::ExprStarred, + ) -> Self { + match &*expr.value { + ast::Expr::List(_) => Self::new(Some( + KnownClass::List.to_specialized_instance(db, [expected_element_type]), + )), + ast::Expr::Set(_) => Self::new(Some( + KnownClass::Set.to_specialized_instance(db, [expected_element_type]), + )), + ast::Expr::Tuple(_) => { + Self::new(Some(Type::homogeneous_tuple(db, expected_element_type))) + } + _ => Self::default(), + } + } } /// Returns the statically-known truthiness of a given expression. diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index b8005da421cfa..fd931e9a4fc07 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -95,7 +95,9 @@ use crate::types::mro::MroErrorKind; use crate::types::newtype::NewType; use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::subclass_of::SubclassOfInner; -use crate::types::tuple::{Tuple, TupleLength, TupleSpec, TupleType}; +use crate::types::tuple::{ + Tuple, TupleLength, TupleSpec, TupleSpecBuilder, TupleType, VariableLengthTuple, +}; use crate::types::typed_dict::{ TypedDictAssignmentKind, validate_typed_dict_constructor, validate_typed_dict_dict_literal, validate_typed_dict_key_assignment, @@ -7048,7 +7050,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ast::Expr::If(if_expression) => self.infer_if_expression(if_expression, tcx), ast::Expr::Lambda(lambda_expression) => self.infer_lambda_expression(lambda_expression), ast::Expr::Call(call_expression) => self.infer_call_expression(call_expression, tcx), - ast::Expr::Starred(starred) => self.infer_starred_expression(starred), + ast::Expr::Starred(starred) => self.infer_starred_expression(starred, tcx), ast::Expr::Yield(yield_expression) => self.infer_yield_expression(yield_expression), ast::Expr::YieldFrom(yield_from) => self.infer_yield_from_expression(yield_from), ast::Expr::Await(await_expression) => self.infer_await_expression(await_expression), @@ -7284,25 +7286,87 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) }); + let mut is_homogeneous_tuple_annotation = false; + let annotated_tuple = tcx .known_specialization(self.db(), KnownClass::Tuple) .and_then(|specialization| { - specialization + let spec = specialization .tuple(self.db()) - .expect("the specialization of `KnownClass::Tuple` must have a tuple spec") - .resize(self.db(), TupleLength::Fixed(elts.len())) - .ok() + .expect("the specialization of `KnownClass::Tuple` must have a tuple spec"); + + if matches!( + spec, + Tuple::Variable(VariableLengthTuple { prefix, variable: _, suffix}) + if prefix.is_empty() && suffix.is_empty() + ) { + is_homogeneous_tuple_annotation = true; + } + + spec.resize(self.db(), TupleLength::Fixed(elts.len())).ok() }); let mut annotated_elt_tys = annotated_tuple.as_ref().map(Tuple::all_elements); let db = self.db(); - let element_types = elts.iter().map(|element| { - let annotated_elt_ty = annotated_elt_tys.as_mut().and_then(Iterator::next).copied(); - self.infer_expression(element, TypeContext::new(annotated_elt_ty)) - }); - Type::heterogeneous_tuple(db, element_types) + let can_use_type_context = + is_homogeneous_tuple_annotation || elts.iter().all(|elt| !elt.is_starred_expr()); + + let mut infer_element = |elt: &ast::Expr| { + if can_use_type_context { + let annotated_elt_ty = annotated_elt_tys.as_mut().and_then(Iterator::next).copied(); + let context = if let ast::Expr::Starred(starred) = elt { + annotated_elt_ty + .map(|expected_element_type| { + TypeContext::for_starred_expression(db, expected_element_type, starred) + }) + .unwrap_or_default() + } else { + TypeContext::new(annotated_elt_ty) + }; + self.infer_expression(elt, context) + } else { + self.infer_expression(elt, TypeContext::default()) + } + }; + + let mut builder = TupleSpecBuilder::with_capacity(elts.len()); + + for element in elts { + 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. + 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)); + } + } + + Type::tuple(TupleType::new(db, &builder.build())) } fn infer_list_expression(&mut self, list: &ast::ExprList, tcx: TypeContext<'db>) -> Type<'db> { @@ -7459,7 +7523,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let inferable = generic_context.inferable_typevars(self.db()); - // Remove any union elements of that are unrelated to the collection type. + // Remove any union elements of the annotation that are unrelated to the collection type. // // For example, we only want the `list[int]` from `annotation: list[int] | None` if // `collection_ty` is `list`. @@ -7499,8 +7563,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } let elt_tcxs = match annotated_elt_tys { - None => Either::Left(iter::repeat(TypeContext::default())), - Some(tys) => Either::Right(tys.iter().map(|ty| TypeContext::new(Some(*ty)))), + None => Either::Left(iter::repeat(None)), + Some(tys) => Either::Right(tys.iter().copied().map(Some)), }; for elts in elts { @@ -7529,6 +7593,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { { let Some(elt) = elt else { continue }; + let elt_tcx = if let ast::Expr::Starred(starred) = elt { + elt_tcx + .map(|ty| TypeContext::for_starred_expression(self.db(), ty, starred)) + .unwrap_or_default() + } else { + TypeContext::new(elt_tcx) + }; + let inferred_elt_ty = infer_elt_expression(self, elt, elt_tcx); // Simplify the inference based on the declared type of the element. @@ -7542,7 +7614,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // unions for large nested list literals, which the constraint solver struggles with. let inferred_elt_ty = inferred_elt_ty.promote_literals(self.db(), elt_tcx); - builder.infer(Type::TypeVar(elt_ty), inferred_elt_ty).ok()?; + builder + .infer( + Type::TypeVar(elt_ty), + if elt.is_starred_expr() { + inferred_elt_ty + .iterate(self.db()) + .homogeneous_element_type(self.db()) + } else { + inferred_elt_ty + }, + ) + .ok()?; } } @@ -8359,7 +8442,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } - fn infer_starred_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> { + fn infer_starred_expression( + &mut self, + starred: &ast::ExprStarred, + tcx: TypeContext<'db>, + ) -> Type<'db> { let ast::ExprStarred { range: _, node_index: _, @@ -8367,17 +8454,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ctx: _, } = starred; - let iterable_type = self.infer_expression(value, TypeContext::default()); + let db = self.db(); + let iterable_type = self.infer_expression(value, tcx); + iterable_type - .try_iterate(self.db()) - .map(|tuple| tuple.homogeneous_element_type(self.db())) + .try_iterate(db) + .map(|spec| Type::tuple(TupleType::new(db, &spec))) .unwrap_or_else(|err| { err.report_diagnostic(&self.context, iterable_type, value.as_ref().into()); - err.fallback_element_type(self.db()) - }); - - // TODO - todo_type!("starred expression") + Type::homogeneous_tuple(db, err.fallback_element_type(db)) + }) } fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> { diff --git a/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs index 8c211735cde47..de1369571ac03 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs @@ -166,9 +166,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ast::Expr::StringLiteral(string) => self.infer_string_annotation_expression(string), // Annotation expressions also get special handling for `*args` and `**kwargs`. - ast::Expr::Starred(starred) => { - TypeAndQualifiers::declared(self.infer_starred_expression(starred)) - } + ast::Expr::Starred(starred) => TypeAndQualifiers::declared( + self.infer_starred_expression(starred, TypeContext::default()), + ), ast::Expr::BytesLiteral(bytes) => { if let Some(builder) = self diff --git a/crates/ty_python_semantic/src/types/mro.rs b/crates/ty_python_semantic/src/types/mro.rs index 21501060dafd6..dffd166fe09ed 100644 --- a/crates/ty_python_semantic/src/types/mro.rs +++ b/crates/ty_python_semantic/src/types/mro.rs @@ -7,7 +7,7 @@ use rustc_hash::FxBuildHasher; use crate::Db; use crate::types::class_base::ClassBase; use crate::types::generics::Specialization; -use crate::types::{ClassLiteral, ClassType, KnownClass, KnownInstanceType, SpecialFormType, Type}; +use crate::types::{ClassLiteral, ClassType, KnownInstanceType, SpecialFormType, Type}; /// The inferred method resolution order of a given class. /// @@ -52,11 +52,6 @@ impl<'db> Mro<'db> { specialization: Option>, ) -> Result> { let class = class_literal.apply_optional_specialization(db, specialization); - // Special-case `NotImplementedType`: typeshed says that it inherits from `Any`, - // but this causes more problems than it fixes. - if class_literal.is_known(db, KnownClass::NotImplementedType) { - return Ok(Self::from([ClassBase::Class(class), ClassBase::object(db)])); - } Self::of_class_impl(db, class, class_literal.explicit_bases(db), specialization) .map_err(|err| err.into_mro_error(db, class)) } diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index ebe294569383e..f4e1d71b66594 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -1499,6 +1499,7 @@ impl<'db> Parameters<'db> { if let Some(inferred_annotation_type) = inferred_annotation(param) { Parameter { annotated_type: Some(inferred_annotation_type), + has_starred_annotation: false, inferred_annotation: true, kind: ParameterKind::PositionalOnly { name: Some(param.parameter.name.id.clone()), @@ -1543,6 +1544,7 @@ impl<'db> Parameters<'db> { if let Some(inferred_annotation_type) = inferred_annotation(arg) { Parameter { annotated_type: Some(inferred_annotation_type), + has_starred_annotation: false, inferred_annotation: true, kind: ParameterKind::PositionalOrKeyword { name: arg.parameter.name.id.clone(), @@ -1738,6 +1740,15 @@ pub(crate) struct Parameter<'db> { /// the context, like `Self` for the `self` parameter of instance methods. inferred_annotation: bool, + /// Variadic parameters can have starred annotations, e.g. + /// - `*args: *Ts` + /// - `*args: *tuple[int, ...]` + /// - `*args: *tuple[int, *tuple[str, ...], bytes]` + /// + /// The `*` prior to the type gives the annotation a different meaning, + /// so this must be propagated upwards. + has_starred_annotation: bool, + kind: ParameterKind<'db>, pub(crate) form: ParameterForm, } @@ -1746,6 +1757,7 @@ impl<'db> Parameter<'db> { pub(crate) fn positional_only(name: Option) -> Self { Self { annotated_type: None, + has_starred_annotation: false, inferred_annotation: false, kind: ParameterKind::PositionalOnly { name, @@ -1758,6 +1770,7 @@ impl<'db> Parameter<'db> { pub(crate) fn positional_or_keyword(name: Name) -> Self { Self { annotated_type: None, + has_starred_annotation: false, inferred_annotation: false, kind: ParameterKind::PositionalOrKeyword { name, @@ -1770,6 +1783,7 @@ impl<'db> Parameter<'db> { pub(crate) fn variadic(name: Name) -> Self { Self { annotated_type: None, + has_starred_annotation: false, inferred_annotation: false, kind: ParameterKind::Variadic { name }, form: ParameterForm::Value, @@ -1779,6 +1793,7 @@ impl<'db> Parameter<'db> { pub(crate) fn keyword_only(name: Name) -> Self { Self { annotated_type: None, + has_starred_annotation: false, inferred_annotation: false, kind: ParameterKind::KeywordOnly { name, @@ -1791,6 +1806,7 @@ impl<'db> Parameter<'db> { pub(crate) fn keyword_variadic(name: Name) -> Self { Self { annotated_type: None, + has_starred_annotation: false, inferred_annotation: false, kind: ParameterKind::KeywordVariadic { name }, form: ParameterForm::Value, @@ -1830,6 +1846,7 @@ impl<'db> Parameter<'db> { annotated_type: self .annotated_type .map(|ty| ty.apply_type_mapping_impl(db, type_mapping, tcx, visitor)), + has_starred_annotation: self.has_starred_annotation, kind: self .kind .apply_type_mapping_impl(db, type_mapping, tcx, visitor), @@ -1849,6 +1866,7 @@ impl<'db> Parameter<'db> { ) -> Self { let Parameter { annotated_type, + has_starred_annotation, inferred_annotation, kind, form, @@ -1893,6 +1911,7 @@ impl<'db> Parameter<'db> { Self { annotated_type: Some(annotated_type), + has_starred_annotation: *has_starred_annotation, inferred_annotation: *inferred_annotation, kind, form: *form, @@ -1911,6 +1930,7 @@ impl<'db> Parameter<'db> { inferred_annotation, kind, form, + has_starred_annotation, } = self; let annotated_type = match annotated_type { @@ -1975,6 +1995,7 @@ impl<'db> Parameter<'db> { inferred_annotation: *inferred_annotation, kind, form: *form, + has_starred_annotation: *has_starred_annotation, }) } @@ -1984,10 +2005,20 @@ impl<'db> Parameter<'db> { parameter: &ast::Parameter, kind: ParameterKind<'db>, ) -> Self { + let annotation = parameter.annotation(); + + let (annotated_type, is_starred) = annotation + .map(|annotation| { + ( + Some(definition_expression_type(db, definition, annotation)), + annotation.is_starred_expr(), + ) + }) + .unwrap_or((None, false)); + Self { - annotated_type: parameter - .annotation() - .map(|annotation| definition_expression_type(db, definition, annotation)), + annotated_type, + has_starred_annotation: is_starred, kind, form: ParameterForm::Value, inferred_annotation: false, @@ -2040,6 +2071,12 @@ impl<'db> Parameter<'db> { self.annotated_type } + /// Return `true` if this parameter has a starred annotation, + /// e.g. `*args: *Ts` or `*args: *tuple[int, *tuple[str, ...], bytes]` + pub(crate) fn has_starred_annotation(&self) -> bool { + self.has_starred_annotation + } + /// Kind of the parameter. pub(crate) fn kind(&self) -> &ParameterKind<'db> { &self.kind