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]]
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()))

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
// create a union of intersections.
intersections: Vec<InnerIntersectionBuilder<'db>>,
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))
}
}

View File

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