exist away, intersect/union not commutative

This commit is contained in:
Douglas Creager 2025-11-11 16:41:22 -05:00
parent 3140140763
commit f20368fadf
1 changed files with 59 additions and 6 deletions

View File

@ -66,8 +66,8 @@ use salsa::plumbing::AsId;
use crate::Db; use crate::Db;
use crate::types::generics::InferableTypeVars; use crate::types::generics::InferableTypeVars;
use crate::types::{ use crate::types::{
BoundTypeVarInstance, IntersectionType, Type, TypeRelation, TypeVarBoundOrConstraints, BoundTypeVarIdentity, BoundTypeVarInstance, IntersectionType, Type, TypeRelation,
UnionType, TypeVarBoundOrConstraints, UnionType,
}; };
/// An extension trait for building constraint sets from [`Option`] values. /// An extension trait for building constraint sets from [`Option`] values.
@ -294,16 +294,20 @@ impl<'db> ConstraintSet<'db> {
} }
/// Updates this constraint set to hold the union of itself and another constraint set. /// Updates this constraint set to hold the union of itself and another constraint set.
/// XXX: Document not commutative
pub(crate) fn union(&mut self, db: &'db dyn Db, other: Self) -> Self { pub(crate) fn union(&mut self, db: &'db dyn Db, other: Self) -> Self {
self.node = self.node.or(db, other.node); let to_abstract = other.inferable.subtract(db, self.inferable);
self.inferable = self.inferable.merge(db, other.inferable); let other = other.node.exists(db, to_abstract);
self.node = self.node.or(db, other);
*self *self
} }
/// Updates this constraint set to hold the intersection of itself and another constraint set. /// Updates this constraint set to hold the intersection of itself and another constraint set.
/// XXX: Document not commutative
pub(crate) fn intersect(&mut self, db: &'db dyn Db, other: Self) -> Self { pub(crate) fn intersect(&mut self, db: &'db dyn Db, other: Self) -> Self {
self.node = self.node.and(db, other.node); let to_abstract = other.inferable.subtract(db, self.inferable);
self.inferable = self.inferable.merge(db, other.inferable); let other = other.node.exists(db, to_abstract);
self.node = self.node.and(db, other);
*self *self
} }
@ -932,6 +936,29 @@ impl<'db> Node<'db> {
true true
} }
/// Returns a new BDD that is the _existential abstraction_ of `self` for a set of typevars.
/// The result will return true whenever `self` returns true for _any_ assignment of those
/// typevars. The result will not contain any constraints that mention those typevars.
fn exists(
self,
db: &'db dyn Db,
bound_typevars: impl IntoIterator<Item = BoundTypeVarIdentity<'db>>,
) -> Self {
bound_typevars
.into_iter()
.fold(self.simplify(db), |abstracted, bound_typevar| {
abstracted.exists_one(db, bound_typevar)
})
}
fn exists_one(self, db: &'db dyn Db, bound_typevar: BoundTypeVarIdentity<'db>) -> Self {
match self {
Node::AlwaysTrue => Node::AlwaysTrue,
Node::AlwaysFalse => Node::AlwaysFalse,
Node::Interior(interior) => interior.exists_one(db, bound_typevar),
}
}
/// Returns a new BDD that returns the same results as `self`, but with some inputs fixed to /// Returns a new BDD that returns the same results as `self`, but with some inputs fixed to
/// particular values. (Those variables will not be checked when evaluating the result, and /// particular values. (Those variables will not be checked when evaluating the result, and
/// will not be present in the result.) /// will not be present in the result.)
@ -1345,6 +1372,32 @@ impl<'db> InteriorNode<'db> {
} }
} }
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
fn exists_one(self, db: &'db dyn Db, bound_typevar: BoundTypeVarIdentity<'db>) -> Node<'db> {
let self_constraint = self.constraint(db);
let self_typevar = self_constraint.typevar(db).identity(db);
match bound_typevar.cmp(&self_typevar) {
// If the typevar that this node checks is "later" than the typevar we're abstracting
// over, then we have reached a point in the BDD where the abstraction can no longer
// affect the result, and we can return early.
Ordering::Less => Node::Interior(self),
// If the typevar that this node checks _is_ the typevar we're abstracting over, then
// we replace this node with the OR of its if_false/if_true edges. That is, the result
// is true if there's any assignment of this node's constraint that is true.
Ordering::Equal => {
let if_true = self.if_true(db).exists_one(db, bound_typevar);
let if_false = self.if_false(db).exists_one(db, bound_typevar);
if_true.or(db, if_false)
}
// Otherwise, we abstract the if_false/if_true edges recursively.
Ordering::Greater => {
let if_true = self.if_true(db).exists_one(db, bound_typevar);
let if_false = self.if_false(db).exists_one(db, bound_typevar);
Node::new(db, self_constraint, if_true, if_false)
}
}
}
#[salsa::tracked(heap_size=ruff_memory_usage::heap_size)] #[salsa::tracked(heap_size=ruff_memory_usage::heap_size)]
fn restrict_one( fn restrict_one(
self, self,