[ty] Evaluate single-predicate narrowing directly

This commit is contained in:
Charlie Marsh committed 2026-10-27 07:02:03 -07:00
1 parent fbf83e2268
commit 319a6193ca
3 files changed
+62 -7

No files matched your search

+5
View File
@@ -1447,6 +1447,11 @@ pub struct BindingWithConstraintsIterator<'map, 'db> {
}
impl<'map, 'db> BindingWithConstraintsIterator<'map, 'db> {
/// Whether only one live binding remains, apart from an initial unbound sentinel.
pub fn has_single_live_binding(&self) -> bool {
self.inner.len() == 1
}
pub const fn predicates(&self) -> &'map Predicates<'db> {
&self.constraint_tables.predicates
}
+2 -1
View File
@@ -1830,6 +1830,7 @@ fn place_from_bindings_impl<'db>(
let predicates = bindings_with_constraints.predicates();
let reachability_constraints = bindings_with_constraints.reachability_constraints();
let boundness_analysis = bindings_with_constraints.boundness_analysis();
let single_binding = bindings_with_constraints.has_single_live_binding();
let mut bindings_with_constraints = bindings_with_constraints.peekable();
let is_non_exported = |binding: Definition<'db>| {
@@ -2006,7 +2007,7 @@ fn place_from_bindings_impl<'db>(
binding_ty,
)
})
.narrow(constraint, binding_ty),
.narrow(constraint, binding_ty, single_binding),
};
Some((narrowed, static_reachability))
},
+55 -6
View File
@@ -592,6 +592,13 @@ const NON_TERMINAL_CALL_CHUNK_SIZE: usize = 16;
const REACHABILITY_EVALUATION_CHUNK_SIZE: usize = 256;
const CONTROL_FLOW_REACHABILITY_CHECKPOINT_INTERVAL: usize = 16;
const NARROWING_EVALUATION_CHECKPOINT_INTERVAL: usize = 8;
fn is_narrowing_checkpoint_position(predicate: ScopedPredicateId) -> bool {
let index = predicate.index();
let position = index ^ (index / NARROWING_EVALUATION_CHECKPOINT_INTERVAL);
(position + 1).is_multiple_of(NARROWING_EVALUATION_CHECKPOINT_INTERVAL)
}
fn predicate_scope<'db>(db: &'db dyn Db, predicate: &Predicate<'db>) -> ScopeId<'db> {
match predicate.node {
PredicateNode::Expression(expression)
@@ -978,7 +985,7 @@ pub(crate) fn narrow_type_by_constraint<'db>(
place,
base_ty,
)
.narrow(id, base_ty)
.narrow(id, base_ty, true)
}
fn apply_accumulated_narrowing<'db>(
@@ -1173,6 +1180,7 @@ impl<'a, 'db> NarrowingProjector<'a, 'db> {
&mut self,
constraint: ScopedNarrowingConstraint,
base_ty: Type<'db>,
single_binding: bool,
) -> Type<'db> {
self.base_ty = base_ty;
match constraint {
@@ -1187,10 +1195,55 @@ impl<'a, 'db> NarrowingProjector<'a, 'db> {
return base_ty;
}
// With multiple bindings, registering each root can turn a suffix of another
// binding's graph into a join. Replacement narrowing depends on those boundaries.
if single_binding && let Some(ty) = self.narrow_single_predicate(constraint) {
return ty;
}
let root = self.project(constraint, true);
self.narrow_projected(root, base_ty)
}
/// Evaluates a single positive or negative expression predicate without projecting a graph.
fn narrow_single_predicate(&self, root: ScopedNarrowingConstraint) -> Option<Type<'db>> {
type Id = ScopedNarrowingConstraint;
let node = self.constraints.get_interior_node(root);
let positive = match (node.if_true, node.if_uncertain, node.if_false) {
(Id::ALWAYS_TRUE, Id::ALWAYS_FALSE, Id::ALWAYS_FALSE) => true,
(Id::ALWAYS_FALSE, Id::ALWAYS_FALSE, Id::ALWAYS_TRUE) => false,
_ => return None,
};
let predicate = self.predicates[node.atom];
if !matches!(predicate.node, PredicateNode::Expression(_)) {
return None;
}
if self
.predicate_narrowing_targets
.contains(node.atom, self.place)
{
if is_narrowing_checkpoint_position(node.atom) {
return None;
}
let (pos, neg) = infer_narrowing_constraints(self.db, predicate, self.place);
if pos.is_some() || neg.is_some() {
return Some(apply_accumulated_narrowing(
self.db,
self.env,
self.base_ty,
if positive { pos } else { neg },
));
}
}
let reachable = analyze_single(self.db, self.env, &predicate)
.negate_if(!positive)
.may_be_true();
Some(if reachable { self.base_ty } else { Type::Never })
}
/// Narrows a projected constraint while reusing suffix results for its original binding type.
///
/// Registering each root lets the graph recognize shared joins incrementally.
@@ -1437,12 +1490,8 @@ impl<'a, 'db> NarrowingProjector<'a, 'db> {
let node = self.constraints.get_interior_node(id);
let predicate = self.predicates[node.atom];
let index = node.atom.index();
let checkpoint_position =
index ^ (index / NARROWING_EVALUATION_CHECKPOINT_INTERVAL);
if (id != root || use_root_checkpoint)
&& (checkpoint_position + 1)
.is_multiple_of(NARROWING_EVALUATION_CHECKPOINT_INTERVAL)
&& is_narrowing_checkpoint_position(node.atom)
&& (self
.predicate_narrowing_targets
.contains(node.atom, self.place)