From 9693375e101ffb7749c160230ca90d820161a5da Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 26 Dec 2025 10:02:20 +0100 Subject: [PATCH] [ty] Reduce monomorphization (#22195) --- crates/ty_python_semantic/src/types.rs | 40 +++++++---- crates/ty_python_semantic/src/types/class.rs | 49 +++++++++---- .../src/types/constraints.rs | 5 +- .../ty_python_semantic/src/types/display.rs | 32 +++++---- .../ty_python_semantic/src/types/generics.rs | 68 +++++++++++-------- .../src/types/ide_support.rs | 6 +- .../src/types/signatures.rs | 47 +++++++------ 7 files changed, 147 insertions(+), 100 deletions(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 7beaa66258..6bcd6756d6 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -10889,25 +10889,37 @@ impl<'db> UnionTypeInstance<'db> { scope_id: ScopeId<'db>, typevar_binding_context: Option>, ) -> Type<'db> { - let value_expr_types = value_expr_types.into_iter().collect::>(); - - let mut builder = UnionBuilder::new(db); - for ty in &value_expr_types { - match ty.in_type_expression(db, scope_id, typevar_binding_context) { - Ok(ty) => builder.add_in_place(ty), - Err(error) => { - return Type::KnownInstance(KnownInstanceType::UnionType( - UnionTypeInstance::new(db, Some(value_expr_types), Err(error)), - )); + fn from_value_expression_types_impl<'db>( + db: &'db dyn Db, + value_expr_types: Box<[Type<'db>]>, + scope_id: ScopeId<'db>, + typevar_binding_context: Option>, + ) -> Type<'db> { + let mut builder = UnionBuilder::new(db); + for ty in &value_expr_types { + match ty.in_type_expression(db, scope_id, typevar_binding_context) { + Ok(ty) => builder.add_in_place(ty), + Err(error) => { + return Type::KnownInstance(KnownInstanceType::UnionType( + UnionTypeInstance::new(db, Some(value_expr_types), Err(error)), + )); + } } } + + Type::KnownInstance(KnownInstanceType::UnionType(UnionTypeInstance::new( + db, + Some(value_expr_types), + Ok(builder.build()), + ))) } - Type::KnownInstance(KnownInstanceType::UnionType(UnionTypeInstance::new( + from_value_expression_types_impl( db, - Some(value_expr_types), - Ok(builder.build()), - ))) + value_expr_types.into_iter().collect(), + scope_id, + typevar_binding_context, + ) } /// Get the types of the elements of this union as they would appear in a value diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 09c003e8a8..0520647d46 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -5124,27 +5124,46 @@ impl KnownClass { db: &'db dyn Db, specialization: impl IntoIterator>, ) -> Option> { + fn to_specialized_class_type_impl<'db>( + db: &'db dyn Db, + class: KnownClass, + class_literal: ClassLiteral<'db>, + specialization: Box<[Type<'db>]>, + generic_context: GenericContext<'db>, + ) -> ClassType<'db> { + if specialization.len() != generic_context.len(db) { + // a cache of the `KnownClass`es that we have already seen mismatched-arity + // specializations for (and therefore that we've already logged a warning for) + static MESSAGES: LazyLock>> = + LazyLock::new(Mutex::default); + if MESSAGES.lock().unwrap().insert(class) { + tracing::info!( + "Wrong number of types when specializing {}. \ + Falling back to default specialization for the symbol instead.", + class.display(db) + ); + } + return class_literal.default_specialization(db); + } + + class_literal + .apply_specialization(db, |_| generic_context.specialize(db, specialization)) + } + let Type::ClassLiteral(class_literal) = self.to_class_literal(db) else { return None; }; + let generic_context = class_literal.generic_context(db)?; - let types = specialization.into_iter().collect::>(); - if types.len() != generic_context.len(db) { - // a cache of the `KnownClass`es that we have already seen mismatched-arity - // specializations for (and therefore that we've already logged a warning for) - static MESSAGES: LazyLock>> = LazyLock::new(Mutex::default); - if MESSAGES.lock().unwrap().insert(self) { - tracing::info!( - "Wrong number of types when specializing {}. \ - Falling back to default specialization for the symbol instead.", - self.display(db) - ); - } - return Some(class_literal.default_specialization(db)); - } - Some(class_literal.apply_specialization(db, |_| generic_context.specialize(db, types))) + Some(to_specialized_class_type_impl( + db, + self, + class_literal, + types, + generic_context, + )) } /// Lookup a [`KnownClass`] in typeshed and return a [`Type`] diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index f5e2b6c98c..e97fbfd030 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -3774,10 +3774,7 @@ impl<'db> BoundTypeVarInstance<'db> { /// specifies the required specializations, and the iterator will be empty. For a constrained /// typevar, the primary result will include the fully static constraints, and the iterator /// will include an entry for each non-fully-static constraint. - fn required_specializations( - self, - db: &'db dyn Db, - ) -> (Node<'db>, impl IntoIterator>) { + fn required_specializations(self, db: &'db dyn Db) -> (Node<'db>, Vec>) { // For upper bounds and constraints, we are free to choose any materialization that makes // the check succeed. In non-inferable positions, it is most helpful to choose a // materialization that is as restrictive as possible, since that minimizes the number of diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 5693df9d1b..1825632f79 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -93,25 +93,31 @@ impl<'db> DisplaySettings<'db> { I: IntoIterator, T: Into>, { + fn build_display_settings<'db>( + collector: &AmbiguousClassCollector<'db>, + ) -> DisplaySettings<'db> { + DisplaySettings { + qualified: Rc::new( + collector + .class_names + .borrow() + .iter() + .filter_map(|(name, ambiguity)| { + Some((*name, QualificationLevel::from_ambiguity_state(ambiguity)?)) + }) + .collect(), + ), + ..DisplaySettings::default() + } + } + let collector = AmbiguousClassCollector::default(); for ty in types { collector.visit_type(db, ty.into()); } - Self { - qualified: Rc::new( - collector - .class_names - .borrow() - .iter() - .filter_map(|(name, ambiguity)| { - Some((*name, QualificationLevel::from_ambiguity_state(ambiguity)?)) - }) - .collect(), - ), - ..Self::default() - } + build_display_settings(&collector) } } diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 3e6364e6d3..8f553ccf91 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -563,38 +563,46 @@ impl<'db> GenericContext<'db> { I: IntoIterator>>, I::IntoIter: ExactSizeIterator, { - let mut types = self.fill_in_defaults(db, types); - let len = types.len(); - loop { - let mut any_changed = false; - for i in 0..len { - let partial = PartialSpecialization { - generic_context: self, - types: &types, - // Don't recursively substitute type[i] in itself. Ideally, we could instead - // check if the result is self-referential after we're done applying the - // partial specialization. But when we apply a paramspec, we don't use the - // callable that it maps to directly; we create a new callable that reuses - // parts of it. That means we can't look for the previous type directly. - // Instead we use this to skip specializing the type in itself in the first - // place. - skip: Some(i), - }; - let updated = types[i].apply_type_mapping( - db, - &TypeMapping::PartialSpecialization(partial), - TypeContext::default(), - ); - if updated != types[i] { - types[i] = updated; - any_changed = true; + fn specialize_recursive_impl<'db>( + db: &'db dyn Db, + context: GenericContext<'db>, + mut types: Box<[Type<'db>]>, + ) -> Specialization<'db> { + let len = types.len(); + loop { + let mut any_changed = false; + for i in 0..len { + let partial = PartialSpecialization { + generic_context: context, + types: &types, + // Don't recursively substitute type[i] in itself. Ideally, we could instead + // check if the result is self-referential after we're done applying the + // partial specialization. But when we apply a paramspec, we don't use the + // callable that it maps to directly; we create a new callable that reuses + // parts of it. That means we can't look for the previous type directly. + // Instead we use this to skip specializing the type in itself in the first + // place. + skip: Some(i), + }; + let updated = types[i].apply_type_mapping( + db, + &TypeMapping::PartialSpecialization(partial), + TypeContext::default(), + ); + if updated != types[i] { + types[i] = updated; + any_changed = true; + } + } + + if !any_changed { + return Specialization::new(db, context, types, None, None); } } - - if !any_changed { - return Specialization::new(db, self, types, None, None); - } } + + let types = self.fill_in_defaults(db, types); + specialize_recursive_impl(db, self, types) } /// Creates a specialization of this generic context for the `tuple` class. @@ -614,7 +622,7 @@ impl<'db> GenericContext<'db> { { let types = types.into_iter(); let variables = self.variables(db); - assert!(self.len(db) == types.len()); + assert_eq!(self.len(db), types.len()); // Typevars can have other typevars as their default values, e.g. // diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index 8f26ab0dec..1e93e9d842 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -514,7 +514,7 @@ pub fn call_signature_details<'db>( // Extract signature details from all callable bindings bindings .into_iter() - .flat_map(std::iter::IntoIterator::into_iter) + .flatten() .map(|binding| { let argument_to_parameter_mapping = binding.argument_matches().to_vec(); let signature = binding.signature; @@ -623,7 +623,7 @@ pub fn definitions_for_bin_op<'db>( let definitions: Vec<_> = bindings .into_iter() - .flat_map(std::iter::IntoIterator::into_iter) + .flatten() .filter_map(|binding| { Some(ResolvedDefinition::Definition( binding.signature.definition?, @@ -681,7 +681,7 @@ pub fn definitions_for_unary_op<'db>( let definitions = bindings .into_iter() - .flat_map(std::iter::IntoIterator::into_iter) + .flatten() .filter_map(|binding| { Some(ResolvedDefinition::Definition( binding.signature.definition?, diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index efb9914bf5..37574df023 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -1841,32 +1841,37 @@ impl<'db> Parameters<'db> { db: &'db dyn Db, parameters: impl IntoIterator>, ) -> Self { - let value: Vec> = parameters.into_iter().collect(); - let mut kind = ParametersKind::Standard; - if let [p1, p2] = value.as_slice() - && p1.is_variadic() - && p2.is_keyword_variadic() - { - match (p1.annotated_type(), p2.annotated_type()) { - (None | Some(Type::Dynamic(_)), None | Some(Type::Dynamic(_))) => { - kind = ParametersKind::Gradual; - } - (Some(Type::TypeVar(args_typevar)), Some(Type::TypeVar(kwargs_typevar))) => { - if let (Some(ParamSpecAttrKind::Args), Some(ParamSpecAttrKind::Kwargs)) = ( - args_typevar.paramspec_attr(db), - kwargs_typevar.paramspec_attr(db), - ) { - let typevar = args_typevar.without_paramspec_attr(db); - if typevar.is_same_typevar_as(db, kwargs_typevar.without_paramspec_attr(db)) - { - kind = ParametersKind::ParamSpec(typevar); + fn new_impl<'db>(db: &'db dyn Db, value: Vec>) -> Parameters<'db> { + let mut kind = ParametersKind::Standard; + if let [p1, p2] = value.as_slice() + && p1.is_variadic() + && p2.is_keyword_variadic() + { + match (p1.annotated_type(), p2.annotated_type()) { + (None | Some(Type::Dynamic(_)), None | Some(Type::Dynamic(_))) => { + kind = ParametersKind::Gradual; + } + (Some(Type::TypeVar(args_typevar)), Some(Type::TypeVar(kwargs_typevar))) => { + if let (Some(ParamSpecAttrKind::Args), Some(ParamSpecAttrKind::Kwargs)) = ( + args_typevar.paramspec_attr(db), + kwargs_typevar.paramspec_attr(db), + ) { + let typevar = args_typevar.without_paramspec_attr(db); + if typevar + .is_same_typevar_as(db, kwargs_typevar.without_paramspec_attr(db)) + { + kind = ParametersKind::ParamSpec(typevar); + } } } + _ => {} } - _ => {} } + Parameters { value, kind } } - Self { value, kind } + + let value: Vec> = parameters.into_iter().collect(); + new_impl(db, value) } /// Create an empty parameter list.