use source order in specialize_constrained too

This commit is contained in:
Douglas Creager 2025-12-15 08:35:50 -05:00
parent 1f34f43745
commit 7e2ea8bd69
1 changed files with 44 additions and 40 deletions

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, IntersectionBuilder, IntersectionType, Type, BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionType, Type, TypeVarBoundOrConstraints,
TypeVarBoundOrConstraints, UnionBuilder, UnionType, walk_bound_type_var_type, UnionType, walk_bound_type_var_type,
}; };
use crate::{Db, FxOrderSet}; use crate::{Db, FxOrderSet};
@ -1318,7 +1318,7 @@ impl<'db> Node<'db> {
self, self,
db: &'db dyn Db, db: &'db dyn Db,
bound_typevar: BoundTypeVarIdentity<'db>, bound_typevar: BoundTypeVarIdentity<'db>,
mut f: impl FnMut(Option<(Type<'db>, Type<'db>)>), mut f: impl FnMut(Option<(Type<'db>, Type<'db>, usize)>),
) { ) {
self.retain_one(db, bound_typevar) self.retain_one(db, bound_typevar)
.find_representative_types_inner(db, &mut Vec::default(), &mut f); .find_representative_types_inner(db, &mut Vec::default(), &mut f);
@ -1328,7 +1328,7 @@ impl<'db> Node<'db> {
self, self,
db: &'db dyn Db, db: &'db dyn Db,
current_bounds: &mut Vec<RepresentativeBounds<'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>, usize)>),
) { ) {
match self { match self {
Node::AlwaysTrue => { Node::AlwaysTrue => {
@ -1354,10 +1354,17 @@ impl<'db> Node<'db> {
// process. // process.
debug_assert!(greatest_lower_bound.is_assignable_to(db, least_upper_bound)); debug_assert!(greatest_lower_bound.is_assignable_to(db, least_upper_bound));
// SAFETY: Checked that current_bounds is non-empty above.
let minimum_source_order = current_bounds[0].source_order;
// 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(Some((greatest_lower_bound, least_upper_bound))); f(Some((
greatest_lower_bound,
least_upper_bound,
minimum_source_order,
)));
} }
Node::AlwaysFalse => { Node::AlwaysFalse => {
@ -3617,6 +3624,7 @@ impl<'db> GenericContext<'db> {
// Then we find all of the "representative types" for each typevar in the constraint set. // Then we find all of the "representative types" for each typevar in the constraint set.
let mut error_occurred = false; let mut error_occurred = false;
let mut constraints = Vec::new();
let types = self.variables(db).map(|bound_typevar| { let types = self.variables(db).map(|bound_typevar| {
// Each representative type represents one of the ways that the typevar can satisfy the // Each representative type represents one of the ways that the typevar can satisfy the
// constraint, expressed as a lower/upper bound on the types that the typevar can // constraint, expressed as a lower/upper bound on the types that the typevar can
@ -3628,10 +3636,7 @@ impl<'db> GenericContext<'db> {
// (This happens most often with constrained typevars.) We could in the future turn // (This happens most often with constrained typevars.) We could in the future turn
// _each_ of the paths into separate specializations, but it's not clear what we would // _each_ of the paths into separate specializations, but it's not clear what we would
// 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 unconstrained = false; let mut unconstrained = false;
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); 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",
@ -3639,10 +3644,9 @@ impl<'db> GenericContext<'db> {
abstracted = %abstracted.retain_one(db, identity).display(db), abstracted = %abstracted.retain_one(db, identity).display(db),
"find specialization for typevar", "find specialization for typevar",
); );
abstracted.find_representative_types(db, identity, |bounds| { constraints.clear();
satisfied = true; abstracted.find_representative_types(db, identity, |bounds| match bounds {
match bounds { Some(bounds @ (lower_bound, upper_bound, _)) => {
Some((lower_bound, upper_bound)) => {
tracing::trace!( tracing::trace!(
target: "ty_python_semantic::types::constraints::specialize_constrained", target: "ty_python_semantic::types::constraints::specialize_constrained",
bound_typevar = %identity.display(db), bound_typevar = %identity.display(db),
@ -3650,28 +3654,13 @@ 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.add_in_place(lower_bound); constraints.push(bounds);
least_upper_bound.add_positive_in_place(upper_bound);
} }
None => { None => {
unconstrained = true; unconstrained = true;
} }
}
}); });
// If there are no satisfiable paths in the BDD, then there is no valid specialization
// for this constraint set.
if !satisfied {
// TODO: Construct a useful error here
tracing::debug!(
target: "ty_python_semantic::types::constraints::specialize_constrained",
bound_typevar = %identity.display(db),
"typevar cannot be satisfied",
);
error_occurred = true;
return None;
}
// The BDD is satisfiable, but the typevar is unconstrained, then we use `None` to tell // The BDD is satisfiable, but the typevar is unconstrained, then we use `None` to tell
// specialize_recursive to fall back on the typevar's default. // specialize_recursive to fall back on the typevar's default.
if unconstrained { if unconstrained {
@ -3683,10 +3672,25 @@ impl<'db> GenericContext<'db> {
return None; return None;
} }
// If there are no satisfiable paths in the BDD, then there is no valid specialization
// for this constraint set.
if constraints.is_empty() {
// TODO: Construct a useful error here
tracing::debug!(
target: "ty_python_semantic::types::constraints::specialize_constrained",
bound_typevar = %identity.display(db),
"typevar cannot be satisfied",
);
error_occurred = true;
return None;
}
// 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 greatest_lower_bound =
let least_upper_bound = least_upper_bound.build(); UnionType::from_elements(db, constraints.iter().map(|(lower, _, _)| *lower));
let least_upper_bound =
IntersectionType::from_elements(db, constraints.iter().map(|(_, upper, _)| *upper));
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",