diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index 77b96bd74b..bd78d94c9b 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -83,7 +83,7 @@ use crate::types::{ BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionType, Type, TypeVarBoundOrConstraints, UnionType, walk_bound_type_var_type, }; -use crate::{Db, FxOrderMap}; +use crate::{Db, FxOrderMap, FxOrderSet}; /// An extension trait for building constraint sets from [`Option`] values. pub(crate) trait OptionConstraintsExtension { @@ -2222,10 +2222,21 @@ impl<'db> InteriorNode<'db> { constraints = %Node::Interior(self).display(db), "create sequent map", ); - let mut map = SequentMap::default(); - Node::Interior(self).for_each_constraint(db, &mut |constraint, _| { - map.add(db, constraint); + + // Sort the constraints in this BDD by their `source_order`s before adding them to the + // sequent map. This ensures that constraints appear in the sequent map in a stable order. + // The constraints mentioned in a BDD should all have distinct `source_order`s, so an + // unstable sort is fine. + let mut constraints = Vec::new(); + Node::Interior(self).for_each_constraint(db, &mut |constraint, source_order| { + constraints.push((constraint, source_order)); }); + constraints.sort_unstable_by_key(|(_, source_order)| *source_order); + + let mut map = SequentMap::default(); + for (constraint, _) in constraints { + map.add(db, constraint); + } map } @@ -2781,10 +2792,10 @@ struct SequentMap<'db> { /// Sequents of the form `C₁ ∧ C₂ → D` pair_implications: FxHashMap< (ConstrainedTypeVar<'db>, ConstrainedTypeVar<'db>), - FxHashSet>, + FxOrderSet>, >, /// Sequents of the form `C → D` - single_implications: FxHashMap, FxHashSet>>, + single_implications: FxHashMap, FxOrderSet>>, /// Constraints that we have already processed processed: FxHashSet>, /// Constraints that enqueued to be processed diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index ca6a700bd2..36e89553a0 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -1590,14 +1590,19 @@ impl<'db> SpecializationBuilder<'db> { upper: FxOrderSet>, } + // Sort the constraints in each path by their `source_order`s, to ensure that we construct + // any unions or intersections in our type mappings in a stable order. Constraints might + // come out of `PathAssignment`s with identical `source_order`s, but if they do, those + // "tied" constraints will still be ordered in a stable way. So we need a stable sort to + // retain that stable per-tie ordering. let constraints = constraints.limit_to_valid_specializations(self.db); let mut sorted_paths = Vec::new(); constraints.for_each_path(self.db, |path| { let mut path: Vec<_> = path.positive_constraints().collect(); - path.sort_unstable_by_key(|(_, source_order)| *source_order); + path.sort_by_key(|(_, source_order)| *source_order); sorted_paths.push(path); }); - sorted_paths.sort_unstable_by(|path1, path2| { + sorted_paths.sort_by(|path1, path2| { let source_orders1 = path1.iter().map(|(_, source_order)| *source_order); let source_orders2 = path2.iter().map(|(_, source_order)| *source_order); source_orders1.cmp(source_orders2)