order specialized types consistently

This commit is contained in:
Douglas Creager 2025-12-12 21:38:47 -05:00
parent 7d5b1e59d2
commit 0d90fe7a72
3 changed files with 34 additions and 12 deletions

View File

@ -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]] # 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]))) 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]] # 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]))) reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.range(Never, T, list[Any])))
# revealed: None # revealed: None
reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.never())) reveal_type(generic_context(constrained_by_gradual_list_reverse).specialize_constrained(ConstraintSet.never()))

View File

@ -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 // but if a union is added to the intersection, we'll distribute ourselves over that union and
// create a union of intersections. // create a union of intersections.
intersections: Vec<InnerIntersectionBuilder<'db>>, intersections: Vec<InnerIntersectionBuilder<'db>>,
order_elements: bool,
db: &'db dyn Db, db: &'db dyn Db,
} }
@ -675,6 +676,7 @@ impl<'db> IntersectionBuilder<'db> {
pub(crate) fn new(db: &'db dyn Db) -> Self { pub(crate) fn new(db: &'db dyn Db) -> Self {
Self { Self {
db, db,
order_elements: false,
intersections: vec![InnerIntersectionBuilder::default()], intersections: vec![InnerIntersectionBuilder::default()],
} }
} }
@ -682,14 +684,25 @@ impl<'db> IntersectionBuilder<'db> {
fn empty(db: &'db dyn Db) -> Self { fn empty(db: &'db dyn Db) -> Self {
Self { Self {
db, db,
order_elements: false,
intersections: vec![], 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 { pub(crate) fn add_positive(self, ty: Type<'db>) -> Self {
self.add_positive_impl(ty, &mut vec![]) 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( pub(crate) fn add_positive_impl(
mut self, mut self,
ty: Type<'db>, ty: Type<'db>,
@ -898,13 +911,16 @@ impl<'db> IntersectionBuilder<'db> {
pub(crate) fn build(mut self) -> Type<'db> { pub(crate) fn build(mut self) -> Type<'db> {
// Avoid allocating the UnionBuilder unnecessarily if we have just one intersection: // Avoid allocating the UnionBuilder unnecessarily if we have just one intersection:
if self.intersections.len() == 1 { if self.intersections.len() == 1 {
self.intersections.pop().unwrap().build(self.db) self.intersections
.pop()
.unwrap()
.build(self.db, self.order_elements)
} else { } else {
UnionType::from_elements( UnionType::from_elements(
self.db, self.db,
self.intersections self.intersections
.into_iter() .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); self.simplify_constrained_typevars(db);
// If any typevars are in `self.positive`, speculatively solve all bounded type variables // 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.positive.shrink_to_fit();
self.negative.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)) Type::Intersection(IntersectionType::new(db, self.positive, self.negative))
} }
} }

View File

@ -78,8 +78,8 @@ use salsa::plumbing::AsId;
use crate::types::generics::{GenericContext, InferableTypeVars, Specialization}; use crate::types::generics::{GenericContext, InferableTypeVars, Specialization};
use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard}; use crate::types::visitor::{TypeCollector, TypeVisitor, walk_type_with_recursion_guard};
use crate::types::{ use crate::types::{
BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionType, Type, TypeVarBoundOrConstraints, BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionBuilder, IntersectionType, Type,
UnionType, walk_bound_type_var_type, TypeVarBoundOrConstraints, UnionBuilder, UnionType, walk_bound_type_var_type,
}; };
use crate::{Db, FxOrderSet}; 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. // do with that, so instead we just report the ambiguity as a specialization failure.
let mut satisfied = false; let mut satisfied = false;
let mut unconstrained = false; let mut unconstrained = false;
let mut greatest_lower_bound = Type::Never; let mut greatest_lower_bound = UnionBuilder::new(db).order_elements(true);
let mut least_upper_bound = Type::object(); let mut least_upper_bound = IntersectionBuilder::new(db).order_elements(true);
let identity = bound_typevar.identity(db); let identity = bound_typevar.identity(db);
tracing::trace!( tracing::trace!(
target: "ty_python_semantic::types::constraints::specialize_constrained", target: "ty_python_semantic::types::constraints::specialize_constrained",
@ -3400,10 +3400,8 @@ impl<'db> GenericContext<'db> {
upper_bound = %upper_bound.display(db), upper_bound = %upper_bound.display(db),
"found representative type", "found representative type",
); );
greatest_lower_bound = greatest_lower_bound.add_in_place(lower_bound);
UnionType::from_elements(db, [greatest_lower_bound, lower_bound]); least_upper_bound.add_positive_in_place(upper_bound);
least_upper_bound =
IntersectionType::from_elements(db, [least_upper_bound, upper_bound]);
} }
None => { None => {
unconstrained = true; 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 // 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. // 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) { if !greatest_lower_bound.is_assignable_to(db, least_upper_bound) {
tracing::debug!( tracing::debug!(
target: "ty_python_semantic::types::constraints::specialize_constrained", target: "ty_python_semantic::types::constraints::specialize_constrained",