sort specialize_constrained by source_order

This commit is contained in:
Douglas Creager 2025-12-14 19:38:07 -05:00
parent 49ca97a20e
commit 5a8a9500b9
1 changed files with 45 additions and 25 deletions

View File

@ -1312,33 +1312,41 @@ impl<'db> Node<'db> {
mut f: impl FnMut(Option<(Type<'db>, Type<'db>)>), mut f: impl FnMut(Option<(Type<'db>, Type<'db>)>),
) { ) {
self.retain_one(db, bound_typevar) self.retain_one(db, bound_typevar)
.find_representative_types_inner(db, None, &mut f); .find_representative_types_inner(db, &mut Vec::default(), &mut f);
} }
fn find_representative_types_inner( fn find_representative_types_inner(
self, self,
db: &'db dyn Db, db: &'db dyn Db,
current_bounds: Option<(Type<'db>, Type<'db>)>, current_bounds: &mut Vec<RepresentativeBounds<'db>>,
f: &mut dyn FnMut(Option<(Type<'db>, Type<'db>)>), f: &mut dyn FnMut(Option<(Type<'db>, Type<'db>)>),
) { ) {
match self { match self {
Node::AlwaysTrue => { Node::AlwaysTrue => {
if current_bounds.is_empty() {
f(None);
return;
}
// If we reach the `true` terminal, the path we've been following represents one // If we reach the `true` terminal, the path we've been following represents one
// representative type. // representative type.
current_bounds.sort_unstable_by_key(|bounds| bounds.source_order);
let greatest_lower_bound =
UnionType::from_elements(db, current_bounds.iter().map(|bounds| bounds.lower));
let least_upper_bound = IntersectionType::from_elements(
db,
current_bounds.iter().map(|bounds| bounds.upper),
);
// If `lower ≰ upper`, then this path somehow represents in invalid specialization. // If `lower ≰ upper`, then this path somehow represents in invalid specialization.
// That should have been removed from the BDD domain as part of the simplification // That should have been removed from the BDD domain as part of the simplification
// process. // process.
debug_assert!(current_bounds.is_none_or( debug_assert!(greatest_lower_bound.is_assignable_to(db, least_upper_bound));
|(greatest_lower_bound, least_upper_bound)| {
greatest_lower_bound.is_assignable_to(db, least_upper_bound)
}
));
// We've been tracking the lower and upper bound that the types for this path must // We've been tracking the lower and upper bound that the types for this path must
// satisfy. Pass those bounds along and let the caller choose a representative type // satisfy. Pass those bounds along and let the caller choose a representative type
// from within that range. // from within that range.
f(current_bounds); f(Some((greatest_lower_bound, least_upper_bound)));
} }
Node::AlwaysFalse => { Node::AlwaysFalse => {
@ -1347,8 +1355,7 @@ impl<'db> Node<'db> {
} }
Node::Interior(interior) => { Node::Interior(interior) => {
let (greatest_lower_bound, least_upper_bound) = let reset_point = current_bounds.len();
current_bounds.unwrap_or((Type::Never, Type::object()));
// For an interior node, there are two outgoing paths: one for the `if_true` // For an interior node, there are two outgoing paths: one for the `if_true`
// branch, and one for the `if_false` branch. // branch, and one for the `if_false` branch.
@ -1357,16 +1364,11 @@ impl<'db> Node<'db> {
// on the types that satisfy the current path through the BDD. So we intersect the // on the types that satisfy the current path through the BDD. So we intersect the
// current glb/lub with the constraint's bounds to get the new glb/lub for the // current glb/lub with the constraint's bounds to get the new glb/lub for the
// recursive call. // recursive call.
let constraint = interior.constraint(db); current_bounds.push(RepresentativeBounds::from_interior_node(db, interior));
let new_greatest_lower_bound = interior
UnionType::from_elements(db, [greatest_lower_bound, constraint.lower(db)]); .if_true(db)
let new_least_upper_bound = .find_representative_types_inner(db, current_bounds, f);
IntersectionType::from_elements(db, [least_upper_bound, constraint.upper(db)]); current_bounds.truncate(reset_point);
interior.if_true(db).find_representative_types_inner(
db,
Some((new_greatest_lower_bound, new_least_upper_bound)),
f,
);
// For the `if_false` branch, then the types that satisfy the current path through // For the `if_false` branch, then the types that satisfy the current path through
// the BDD do _not_ satisfy the node's constraint. Because we used `retain_one` to // the BDD do _not_ satisfy the node's constraint. Because we used `retain_one` to
@ -1378,11 +1380,9 @@ impl<'db> Node<'db> {
// without updating the lower/upper bounds, relying on the other constraints along // without updating the lower/upper bounds, relying on the other constraints along
// the path to incorporate that negative "hole" in the set of valid types for this // the path to incorporate that negative "hole" in the set of valid types for this
// path. // path.
interior.if_false(db).find_representative_types_inner( interior
db, .if_false(db)
Some((greatest_lower_bound, least_upper_bound)), .find_representative_types_inner(db, current_bounds, f);
f,
);
} }
} }
} }
@ -1714,6 +1714,26 @@ impl<'db> Node<'db> {
} }
} }
struct RepresentativeBounds<'db> {
lower: Type<'db>,
upper: Type<'db>,
source_order: usize,
}
impl<'db> RepresentativeBounds<'db> {
fn from_interior_node(db: &'db dyn Db, interior: InteriorNode<'db>) -> Self {
let constraint = interior.constraint(db);
let lower = constraint.lower(db);
let upper = constraint.upper(db);
let source_order = interior.source_order(db);
Self {
lower,
upper,
source_order,
}
}
}
/// An interior node of a BDD /// An interior node of a BDD
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)] #[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
struct InteriorNode<'db> { struct InteriorNode<'db> {