diff --git a/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md b/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md index 20e04ab24b..0cf656ccc3 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/specialize_constrained.md @@ -316,7 +316,7 @@ def constrained_by_gradual_list_reverse[T: (list[Any], list[Base])](): # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[object]] reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.range(Never, T, list[object]))) # TODO: revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[Any]] - # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[Any] & list[Base]] + # revealed: ty_extensions.Specialization[T@constrained_by_gradual_list_reverse = list[Base] & list[Any]] reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.range(Never, T, list[Any]))) # revealed: None reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.never())) diff --git a/crates/ty_python_semantic/src/types/builder.rs b/crates/ty_python_semantic/src/types/builder.rs index 36977078e9..ae2c21759b 100644 --- a/crates/ty_python_semantic/src/types/builder.rs +++ b/crates/ty_python_semantic/src/types/builder.rs @@ -668,6 +668,7 @@ pub(crate) struct IntersectionBuilder<'db> { // but if a union is added to the intersection, we'll distribute ourselves over that union and // create a union of intersections. intersections: Vec>, + order_elements: bool, db: &'db dyn Db, } @@ -675,6 +676,7 @@ impl<'db> IntersectionBuilder<'db> { pub(crate) fn new(db: &'db dyn Db) -> Self { Self { db, + order_elements: false, intersections: vec![InnerIntersectionBuilder::default()], } } @@ -682,14 +684,25 @@ impl<'db> IntersectionBuilder<'db> { fn empty(db: &'db dyn Db) -> Self { Self { db, + order_elements: false, intersections: vec![], } } + pub(crate) fn order_elements(mut self, val: bool) -> Self { + self.order_elements = val; + self + } + pub(crate) fn add_positive(self, ty: Type<'db>) -> Self { self.add_positive_impl(ty, &mut vec![]) } + pub(crate) fn add_positive_in_place(&mut self, ty: Type<'db>) { + let updated = std::mem::replace(self, Self::empty(self.db)).add_positive(ty); + *self = updated; + } + pub(crate) fn add_positive_impl( mut self, ty: Type<'db>, @@ -898,13 +911,16 @@ impl<'db> IntersectionBuilder<'db> { pub(crate) fn build(mut self) -> Type<'db> { // Avoid allocating the UnionBuilder unnecessarily if we have just one intersection: if self.intersections.len() == 1 { - self.intersections.pop().unwrap().build(self.db) + self.intersections + .pop() + .unwrap() + .build(self.db, self.order_elements) } else { UnionType::from_elements( self.db, self.intersections .into_iter() - .map(|inner| inner.build(self.db)), + .map(|inner| inner.build(self.db, self.order_elements)), ) } } @@ -1238,7 +1254,7 @@ impl<'db> InnerIntersectionBuilder<'db> { } } - fn build(mut self, db: &'db dyn Db) -> Type<'db> { + fn build(mut self, db: &'db dyn Db, order_elements: bool) -> Type<'db> { self.simplify_constrained_typevars(db); // If any typevars are in `self.positive`, speculatively solve all bounded type variables @@ -1279,6 +1295,12 @@ impl<'db> InnerIntersectionBuilder<'db> { _ => { self.positive.shrink_to_fit(); self.negative.shrink_to_fit(); + if order_elements { + self.positive + .sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r)); + self.negative + .sort_unstable_by(|l, r| union_or_intersection_elements_ordering(db, l, r)); + } Type::Intersection(IntersectionType::new(db, self.positive, self.negative)) } } diff --git a/crates/ty_python_semantic/src/types/constraints.rs b/crates/ty_python_semantic/src/types/constraints.rs index 780a1a9deb..d4f3e436f6 100644 --- a/crates/ty_python_semantic/src/types/constraints.rs +++ b/crates/ty_python_semantic/src/types/constraints.rs @@ -78,8 +78,8 @@ use salsa::plumbing::AsId; use crate::types::generics::{GenericContext, InferableTypeVars, Specialization}; use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard}; use crate::types::{ - BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionType, Type, TypeVarBoundOrConstraints, - UnionType, walk_bound_type_var_type, + BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionBuilder, IntersectionType, Type, + TypeVarBoundOrConstraints, UnionBuilder, UnionType, walk_bound_type_var_type, }; use crate::{Db, FxOrderSet}; @@ -3380,8 +3380,8 @@ impl<'db> GenericContext<'db> { // do with that, so instead we just report the ambiguity as a specialization failure. let mut satisfied = false; let mut unconstrained = false; - let mut greatest_lower_bound = Type::Never; - let mut least_upper_bound = Type::object(); + let mut greatest_lower_bound = UnionBuilder::new(db).order_elements(true); + let mut least_upper_bound = IntersectionBuilder::new(db).order_elements(true); let identity = bound_typevar.identity(db); tracing::trace!( target: "ty_python_semantic::types::constraints::specialize_constrained", @@ -3400,10 +3400,8 @@ impl<'db> GenericContext<'db> { upper_bound = %upper_bound.display(db), "found representative type", ); - greatest_lower_bound = - UnionType::from_elements(db, [greatest_lower_bound, lower_bound]); - least_upper_bound = - IntersectionType::from_elements(db, [least_upper_bound, upper_bound]); + greatest_lower_bound.add_in_place(lower_bound); + least_upper_bound.add_positive_in_place(upper_bound); } None => { unconstrained = true; @@ -3437,6 +3435,8 @@ impl<'db> GenericContext<'db> { // If `lower ≰ upper`, then there is no type that satisfies all of the paths in the // BDD. That's an ambiguous specialization, as described above. + let greatest_lower_bound = greatest_lower_bound.build(); + let least_upper_bound = least_upper_bound.build(); if !greatest_lower_bound.is_assignable_to(db, least_upper_bound) { tracing::debug!( target: "ty_python_semantic::types::constraints::specialize_constrained",