[ty] Reduce monomorphization (#22195)

This commit is contained in:
Micha Reiser
2025-12-26 10:02:20 +01:00
committed by GitHub
parent 1ec3503cc3
commit 9693375e10
7 changed files with 147 additions and 100 deletions

View File

@@ -10889,25 +10889,37 @@ impl<'db> UnionTypeInstance<'db> {
scope_id: ScopeId<'db>,
typevar_binding_context: Option<Definition<'db>>,
) -> Type<'db> {
let value_expr_types = value_expr_types.into_iter().collect::<Box<_>>();
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<Definition<'db>>,
) -> 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

View File

@@ -5124,27 +5124,46 @@ impl KnownClass {
db: &'db dyn Db,
specialization: impl IntoIterator<Item = Type<'db>>,
) -> Option<ClassType<'db>> {
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<Mutex<FxHashSet<KnownClass>>> =
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::<Box<[_]>>();
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<Mutex<FxHashSet<KnownClass>>> = 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`]

View File

@@ -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<Item = Node<'db>>) {
fn required_specializations(self, db: &'db dyn Db) -> (Node<'db>, Vec<Node<'db>>) {
// 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

View File

@@ -93,25 +93,31 @@ impl<'db> DisplaySettings<'db> {
I: IntoIterator<Item = T>,
T: Into<Type<'db>>,
{
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)
}
}

View File

@@ -563,38 +563,46 @@ impl<'db> GenericContext<'db> {
I: IntoIterator<Item = Option<Type<'db>>>,
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.
//

View File

@@ -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?,

View File

@@ -1841,32 +1841,37 @@ impl<'db> Parameters<'db> {
db: &'db dyn Db,
parameters: impl IntoIterator<Item = Parameter<'db>>,
) -> Self {
let value: Vec<Parameter<'db>> = 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<Parameter<'db>>) -> 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<Parameter<'db>> = parameters.into_iter().collect();
new_impl(db, value)
}
/// Create an empty parameter list.