use itertools::{EitherOrBoth, Itertools};
use ruff_index::newtype_index;
use smallvec::{SmallVec, smallvec};
use crate::ReachabilityConstraintsBuilder;
use crate::narrowing_constraints::{NarrowingConstraintsBuilder, ScopedNarrowingConstraint};
use crate::reachability_constraints::ScopedReachabilityConstraintId;
#[newtype_index]
#[derive(Ord, PartialOrd, get_size2::GetSize)]
pub struct ScopedDefinitionId;
impl ScopedDefinitionId {
pub(crate) const UNBOUND: ScopedDefinitionId = ScopedDefinitionId::from_u32(0);
pub(crate) fn is_unbound(self) -> bool {
self == Self::UNBOUND
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, get_size2::GetSize)]
pub(super) struct Declarations {
live_declarations: SmallVec<[LiveDeclaration; 2]>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
pub(super) struct LiveDeclaration {
pub(super) declaration: ScopedDefinitionId,
pub(super) reachability_constraint: ScopedReachabilityConstraintId,
}
pub(super) type LiveDeclarationsIterator<'a> = std::slice::Iter<'a, LiveDeclaration>;
#[derive(Clone, Copy, Debug)]
pub(crate) enum PreviousDefinitions {
AreShadowed,
AreKept,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
pub(crate) enum FutureDefinitions {
ShadowThisOne,
DontShadowThisOne,
}
impl PreviousDefinitions {
fn are_shadowed(self) -> bool {
matches!(self, PreviousDefinitions::AreShadowed)
}
}
impl Declarations {
pub(super) fn undeclared_reachability_constraint(
&self,
) -> Option<ScopedReachabilityConstraintId> {
let [
LiveDeclaration {
declaration: ScopedDefinitionId::UNBOUND,
reachability_constraint,
},
] = self.live_declarations.as_slice()
else {
return None;
};
Some(*reachability_constraint)
}
pub(super) fn is_always_undeclared(&self) -> bool {
self.undeclared_reachability_constraint()
== Some(ScopedReachabilityConstraintId::ALWAYS_TRUE)
}
pub(super) fn undeclared(reachability_constraint: ScopedReachabilityConstraintId) -> Self {
let initial_declaration = LiveDeclaration {
declaration: ScopedDefinitionId::UNBOUND,
reachability_constraint,
};
Self {
live_declarations: smallvec![initial_declaration],
}
}
pub(super) fn record_declaration(
&mut self,
declaration: ScopedDefinitionId,
reachability_constraint: ScopedReachabilityConstraintId,
previous_definitions: PreviousDefinitions,
) {
if previous_definitions.are_shadowed() {
self.live_declarations.clear();
}
self.live_declarations.push(LiveDeclaration {
declaration,
reachability_constraint,
});
}
fn record_reachability_constraint(
&mut self,
reachability_constraints: &mut ReachabilityConstraintsBuilder,
constraint: ScopedReachabilityConstraintId,
) {
for declaration in &mut self.live_declarations {
declaration.reachability_constraint = reachability_constraints
.add_and_constraint(declaration.reachability_constraint, constraint);
}
}
pub(super) fn iter(&self) -> LiveDeclarationsIterator<'_> {
self.live_declarations.iter()
}
pub(super) fn as_slice(&self) -> &[LiveDeclaration] {
&self.live_declarations
}
fn merge(&mut self, b: Self, reachability_constraints: &mut ReachabilityConstraintsBuilder) {
let a = std::mem::take(self);
let a = a.live_declarations.into_iter();
let b = b.live_declarations.into_iter();
for zipped in a.merge_join_by(b, |a, b| a.declaration.cmp(&b.declaration)) {
match zipped {
EitherOrBoth::Both(a, b) => {
let reachability_constraint = reachability_constraints
.add_or_constraint(a.reachability_constraint, b.reachability_constraint);
self.live_declarations.push(LiveDeclaration {
declaration: a.declaration,
reachability_constraint,
});
}
EitherOrBoth::Left(declaration) | EitherOrBoth::Right(declaration) => {
self.live_declarations.push(declaration);
}
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
pub(super) enum EnclosingSnapshot {
Constraint(ScopedNarrowingConstraint),
Bindings(Bindings),
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, get_size2::GetSize)]
pub(super) struct Bindings {
unbound_narrowing_constraint: Option<ScopedNarrowingConstraint>,
live_bindings: SmallVec<[LiveBinding; 2]>,
}
impl Bindings {
pub(super) fn is_always_unbound(&self) -> bool {
let [binding] = self.live_bindings.as_slice() else {
return false;
};
self.unbound_narrowing_constraint.is_none()
&& binding.binding() == ScopedDefinitionId::UNBOUND
&& binding.narrowing_constraint == ScopedNarrowingConstraint::ALWAYS_TRUE
&& binding.reachability_constraint == ScopedReachabilityConstraintId::ALWAYS_TRUE
&& binding.can_be_shadowed() == FutureDefinitions::ShadowThisOne
}
pub(super) fn unbound_narrowing_constraint(&self) -> ScopedNarrowingConstraint {
self.unbound_narrowing_constraint
.unwrap_or(self.live_bindings[0].narrowing_constraint)
}
pub(super) fn finish(
&mut self,
narrowing_constraints: &mut NarrowingConstraintsBuilder,
reachability_constraints: &mut ReachabilityConstraintsBuilder,
) {
self.live_bindings.shrink_to_fit();
for binding in &self.live_bindings {
reachability_constraints.mark_used(binding.reachability_constraint);
narrowing_constraints.mark_used(binding.narrowing_constraint);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
pub struct LiveBinding {
binding: PackedDefinitionId,
narrowing_constraint: ScopedNarrowingConstraint,
reachability_constraint: ScopedReachabilityConstraintId,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
struct PackedDefinitionId(u32);
impl PackedDefinitionId {
const DONT_SHADOW: u32 = 1 << 31;
const DEFINITION_MASK: u32 = !Self::DONT_SHADOW;
fn new(binding: ScopedDefinitionId, can_be_shadowed: FutureDefinitions) -> Self {
let binding = binding.as_u32();
assert_eq!(
binding & Self::DONT_SHADOW,
0,
"scopes cannot contain more than 2^31 definitions"
);
Self(
binding
| match can_be_shadowed {
FutureDefinitions::ShadowThisOne => 0,
FutureDefinitions::DontShadowThisOne => Self::DONT_SHADOW,
},
)
}
const fn definition(self) -> ScopedDefinitionId {
ScopedDefinitionId::from_u32(self.0 & Self::DEFINITION_MASK)
}
const fn can_be_shadowed(self) -> FutureDefinitions {
if self.0 & Self::DONT_SHADOW == 0 {
FutureDefinitions::ShadowThisOne
} else {
FutureDefinitions::DontShadowThisOne
}
}
}
impl LiveBinding {
fn new(
binding: ScopedDefinitionId,
narrowing_constraint: ScopedNarrowingConstraint,
reachability_constraint: ScopedReachabilityConstraintId,
can_be_shadowed: FutureDefinitions,
) -> Self {
Self {
binding: PackedDefinitionId::new(binding, can_be_shadowed),
narrowing_constraint,
reachability_constraint,
}
}
pub const fn binding(&self) -> ScopedDefinitionId {
self.binding.definition()
}
pub const fn narrowing_constraint(&self) -> ScopedNarrowingConstraint {
self.narrowing_constraint
}
pub const fn reachability_constraint(&self) -> ScopedReachabilityConstraintId {
self.reachability_constraint
}
const fn can_be_shadowed(&self) -> FutureDefinitions {
self.binding.can_be_shadowed()
}
}
static_assertions::assert_eq_size!(LiveBinding, [u32; 3]);
pub(super) type LiveBindingsIterator<'a> = std::slice::Iter<'a, LiveBinding>;
impl Bindings {
pub(super) fn unbound(reachability_constraint: ScopedReachabilityConstraintId) -> Self {
let initial_binding = LiveBinding::new(
ScopedDefinitionId::UNBOUND,
ScopedNarrowingConstraint::ALWAYS_TRUE,
reachability_constraint,
FutureDefinitions::ShadowThisOne,
);
Self {
unbound_narrowing_constraint: None,
live_bindings: smallvec![initial_binding],
}
}
pub(super) fn record_binding(
&mut self,
binding: ScopedDefinitionId,
reachability_constraint: ScopedReachabilityConstraintId,
is_class_scope: bool,
is_place_name: bool,
previous_definitions: PreviousDefinitions,
can_be_shadowed: FutureDefinitions,
) {
if is_class_scope && is_place_name && self.live_bindings[0].binding().is_unbound() {
self.unbound_narrowing_constraint = Some(self.live_bindings[0].narrowing_constraint);
}
if previous_definitions.are_shadowed() {
self.live_bindings
.retain(|b| b.can_be_shadowed() == FutureDefinitions::DontShadowThisOne);
}
self.live_bindings.push(LiveBinding::new(
binding,
ScopedNarrowingConstraint::ALWAYS_TRUE,
reachability_constraint,
can_be_shadowed,
));
}
fn record_narrowing_constraint(
&mut self,
narrowing_constraints: &mut NarrowingConstraintsBuilder,
constraint: ScopedNarrowingConstraint,
) {
for binding in &mut self.live_bindings {
binding.narrowing_constraint =
narrowing_constraints.add_and_constraint(binding.narrowing_constraint, constraint);
}
}
fn record_reachability_constraint(
&mut self,
reachability_constraints: &mut ReachabilityConstraintsBuilder,
constraint: ScopedReachabilityConstraintId,
) {
for binding in &mut self.live_bindings {
binding.reachability_constraint = reachability_constraints
.add_and_constraint(binding.reachability_constraint, constraint);
}
}
pub(super) fn iter(&self) -> LiveBindingsIterator<'_> {
self.live_bindings.iter()
}
pub(super) fn as_slice(&self) -> &[LiveBinding] {
&self.live_bindings
}
pub(super) fn merge(
&mut self,
b: Self,
narrowing_constraints: &mut NarrowingConstraintsBuilder,
reachability_constraints: &mut ReachabilityConstraintsBuilder,
) {
let a = std::mem::take(self);
if let Some((a, b)) = a
.unbound_narrowing_constraint
.zip(b.unbound_narrowing_constraint)
{
self.unbound_narrowing_constraint = Some(narrowing_constraints.add_or_constraint(a, b));
}
let a = a.live_bindings.into_iter();
let b = b.live_bindings.into_iter();
for zipped in a.merge_join_by(b, |a, b| a.binding().cmp(&b.binding())) {
match zipped {
EitherOrBoth::Both(a, b) => {
let narrowing_constraint = narrowing_constraints
.add_or_constraint(a.narrowing_constraint, b.narrowing_constraint);
let reachability_constraint = reachability_constraints
.add_or_constraint(a.reachability_constraint, b.reachability_constraint);
debug_assert_eq!(a.can_be_shadowed(), b.can_be_shadowed());
self.live_bindings.push(LiveBinding::new(
a.binding(),
narrowing_constraint,
reachability_constraint,
a.can_be_shadowed(),
));
}
EitherOrBoth::Left(binding) | EitherOrBoth::Right(binding) => {
self.live_bindings.push(binding);
}
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
pub(crate) struct PlaceState {
declarations: Declarations,
bindings: Bindings,
}
impl PlaceState {
pub(super) fn undefined(reachability: ScopedReachabilityConstraintId) -> Self {
Self {
declarations: Declarations::undeclared(reachability),
bindings: Bindings::unbound(reachability),
}
}
pub(super) fn record_binding(
&mut self,
binding_id: ScopedDefinitionId,
reachability_constraint: ScopedReachabilityConstraintId,
is_class_scope: bool,
is_place_name: bool,
previous_definitions: PreviousDefinitions,
can_be_shadowed: FutureDefinitions,
) {
debug_assert_ne!(binding_id, ScopedDefinitionId::UNBOUND);
self.bindings.record_binding(
binding_id,
reachability_constraint,
is_class_scope,
is_place_name,
previous_definitions,
can_be_shadowed,
);
}
pub(super) fn record_narrowing_constraint(
&mut self,
narrowing_constraints: &mut NarrowingConstraintsBuilder,
constraint: ScopedNarrowingConstraint,
) {
self.bindings
.record_narrowing_constraint(narrowing_constraints, constraint);
}
pub(super) fn record_narrowing_constraint_for_bindings_at_use(
&mut self,
narrowing_constraints: &mut NarrowingConstraintsBuilder,
constraint: ScopedNarrowingConstraint,
bindings_at_use: &Bindings,
) {
for binding in &mut self.bindings.live_bindings {
if bindings_at_use
.iter()
.any(|binding_at_use| binding_at_use.binding() == binding.binding())
{
binding.narrowing_constraint = narrowing_constraints
.add_and_constraint(binding.narrowing_constraint, constraint);
}
}
}
pub(super) fn record_narrowing_constraint_for_bindings(
&mut self,
narrowing_constraints: &mut NarrowingConstraintsBuilder,
constraint: ScopedNarrowingConstraint,
bindings: &[ScopedDefinitionId],
) {
for binding in &mut self.bindings.live_bindings {
if bindings.contains(&binding.binding()) {
binding.narrowing_constraint = narrowing_constraints
.add_and_constraint(binding.narrowing_constraint, constraint);
}
}
}
pub(super) fn record_reachability_constraint(
&mut self,
reachability_constraints: &mut ReachabilityConstraintsBuilder,
constraint: ScopedReachabilityConstraintId,
) {
self.bindings
.record_reachability_constraint(reachability_constraints, constraint);
self.declarations
.record_reachability_constraint(reachability_constraints, constraint);
}
pub(super) fn record_declaration(
&mut self,
declaration_id: ScopedDefinitionId,
reachability_constraint: ScopedReachabilityConstraintId,
) {
self.declarations.record_declaration(
declaration_id,
reachability_constraint,
PreviousDefinitions::AreShadowed,
);
}
pub(super) fn merge(
&mut self,
b: PlaceState,
narrowing_constraints: &mut NarrowingConstraintsBuilder,
reachability_constraints: &mut ReachabilityConstraintsBuilder,
) {
self.bindings
.merge(b.bindings, narrowing_constraints, reachability_constraints);
self.declarations
.merge(b.declarations, reachability_constraints);
}
pub(super) fn bindings(&self) -> &Bindings {
&self.bindings
}
pub(super) fn declarations(&self) -> &Declarations {
&self.declarations
}
pub(super) fn into_parts(self) -> (Bindings, Declarations) {
(self.bindings, self.declarations)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ruff_index::Idx;
use crate::predicate::ScopedPredicateId;
#[track_caller]
fn assert_bindings(place: &PlaceState, expected: &[(u32, ScopedNarrowingConstraint)]) {
let actual: Vec<(u32, ScopedNarrowingConstraint)> = place
.bindings()
.iter()
.map(|live_binding| {
(
live_binding.binding().as_u32(),
live_binding.narrowing_constraint,
)
})
.collect();
assert_eq!(actual, expected);
}
#[track_caller]
fn assert_declarations(place: &PlaceState, expected: &[&str]) {
let actual = place
.declarations()
.iter()
.map(
|LiveDeclaration {
declaration,
reachability_constraint: _,
}| {
if *declaration == ScopedDefinitionId::UNBOUND {
"undeclared".into()
} else {
declaration.as_u32().to_string()
}
},
)
.collect::<Vec<_>>();
assert_eq!(actual, expected);
}
#[test]
fn unbound() {
let sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
assert_bindings(&sym, &[(0, ScopedNarrowingConstraint::ALWAYS_TRUE)]);
}
#[test]
fn with() {
let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym.record_binding(
ScopedDefinitionId::from_u32(1),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
false,
true,
PreviousDefinitions::AreShadowed,
FutureDefinitions::ShadowThisOne,
);
assert_bindings(&sym, &[(1, ScopedNarrowingConstraint::ALWAYS_TRUE)]);
}
#[test]
fn future_definitions_can_opt_out_of_shadowing() {
let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym.record_binding(
ScopedDefinitionId::from_u32(1),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
false,
true,
PreviousDefinitions::AreKept,
FutureDefinitions::DontShadowThisOne,
);
sym.record_binding(
ScopedDefinitionId::from_u32(2),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
false,
true,
PreviousDefinitions::AreShadowed,
FutureDefinitions::ShadowThisOne,
);
assert_bindings(
&sym,
&[
(1, ScopedNarrowingConstraint::ALWAYS_TRUE),
(2, ScopedNarrowingConstraint::ALWAYS_TRUE),
],
);
sym.record_binding(
ScopedDefinitionId::from_u32(3),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
false,
true,
PreviousDefinitions::AreShadowed,
FutureDefinitions::ShadowThisOne,
);
assert_bindings(
&sym,
&[
(1, ScopedNarrowingConstraint::ALWAYS_TRUE),
(3, ScopedNarrowingConstraint::ALWAYS_TRUE),
],
);
}
#[test]
fn record_constraint() {
let mut narrowing_constraints = NarrowingConstraintsBuilder::default();
let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym.record_binding(
ScopedDefinitionId::from_u32(1),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
false,
true,
PreviousDefinitions::AreShadowed,
FutureDefinitions::ShadowThisOne,
);
let atom = narrowing_constraints.add_atom(ScopedPredicateId::new(0));
sym.record_narrowing_constraint(&mut narrowing_constraints, atom);
assert_bindings(&sym, &[(1, atom)]);
}
#[test]
fn merge() {
let mut narrowing_constraints = NarrowingConstraintsBuilder::default();
let mut reachability_constraints = ReachabilityConstraintsBuilder::default();
let mut sym1a = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym1a.record_binding(
ScopedDefinitionId::from_u32(1),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
false,
true,
PreviousDefinitions::AreShadowed,
FutureDefinitions::ShadowThisOne,
);
let atom0 = narrowing_constraints.add_atom(ScopedPredicateId::new(0));
sym1a.record_narrowing_constraint(&mut narrowing_constraints, atom0);
let mut sym1b = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym1b.record_binding(
ScopedDefinitionId::from_u32(1),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
false,
true,
PreviousDefinitions::AreShadowed,
FutureDefinitions::ShadowThisOne,
);
sym1b.record_narrowing_constraint(&mut narrowing_constraints, atom0);
sym1a.merge(
sym1b,
&mut narrowing_constraints,
&mut reachability_constraints,
);
let mut sym1 = sym1a;
assert_bindings(&sym1, &[(1, atom0)]);
let mut sym2a = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym2a.record_binding(
ScopedDefinitionId::from_u32(2),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
false,
true,
PreviousDefinitions::AreShadowed,
FutureDefinitions::ShadowThisOne,
);
let atom1 = narrowing_constraints.add_atom(ScopedPredicateId::new(1));
sym2a.record_narrowing_constraint(&mut narrowing_constraints, atom1);
let mut sym1b = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym1b.record_binding(
ScopedDefinitionId::from_u32(2),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
false,
true,
PreviousDefinitions::AreShadowed,
FutureDefinitions::ShadowThisOne,
);
let atom2 = narrowing_constraints.add_atom(ScopedPredicateId::new(2));
sym1b.record_narrowing_constraint(&mut narrowing_constraints, atom2);
sym2a.merge(
sym1b,
&mut narrowing_constraints,
&mut reachability_constraints,
);
let sym2 = sym2a;
let merged_constraint = sym2.bindings().iter().next().unwrap().narrowing_constraint;
assert_ne!(merged_constraint, ScopedNarrowingConstraint::ALWAYS_TRUE);
assert_ne!(merged_constraint, ScopedNarrowingConstraint::ALWAYS_FALSE);
assert_ne!(merged_constraint, atom1);
assert_ne!(merged_constraint, atom2);
let mut sym3a = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym3a.record_binding(
ScopedDefinitionId::from_u32(3),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
false,
true,
PreviousDefinitions::AreShadowed,
FutureDefinitions::ShadowThisOne,
);
let atom3 = narrowing_constraints.add_atom(ScopedPredicateId::new(3));
sym3a.record_narrowing_constraint(&mut narrowing_constraints, atom3);
let sym2b = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym3a.merge(
sym2b,
&mut narrowing_constraints,
&mut reachability_constraints,
);
let sym3 = sym3a;
let bindings: Vec<_> = sym3
.bindings()
.iter()
.map(|b| (b.binding().as_u32(), b.narrowing_constraint))
.collect();
assert_eq!(bindings.len(), 2);
assert_eq!(bindings[0].0, 0); assert_eq!(bindings[1].0, 3);
assert_eq!(bindings[1].1, atom3);
sym1.merge(
sym3,
&mut narrowing_constraints,
&mut reachability_constraints,
);
let sym = sym1;
let bindings: Vec<_> = sym
.bindings()
.iter()
.map(|b| (b.binding().as_u32(), b.narrowing_constraint))
.collect();
assert_eq!(bindings.len(), 3);
assert_eq!(bindings[0].0, 0); assert_eq!(bindings[1].0, 1);
assert_eq!(bindings[1].1, atom0);
assert_eq!(bindings[2].0, 3);
assert_eq!(bindings[2].1, atom3);
}
#[test]
fn no_declaration() {
let sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
assert_declarations(&sym, &["undeclared"]);
}
#[test]
fn record_declaration() {
let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym.record_declaration(
ScopedDefinitionId::from_u32(1),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
);
assert_declarations(&sym, &["1"]);
}
#[test]
fn record_declaration_override() {
let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym.record_declaration(
ScopedDefinitionId::from_u32(1),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
);
sym.record_declaration(
ScopedDefinitionId::from_u32(2),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
);
assert_declarations(&sym, &["2"]);
}
#[test]
fn record_declaration_merge() {
let mut narrowing_constraints = NarrowingConstraintsBuilder::default();
let mut reachability_constraints = ReachabilityConstraintsBuilder::default();
let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym.record_declaration(
ScopedDefinitionId::from_u32(1),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
);
let mut sym2 = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym2.record_declaration(
ScopedDefinitionId::from_u32(2),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
);
sym.merge(
sym2,
&mut narrowing_constraints,
&mut reachability_constraints,
);
assert_declarations(&sym, &["1", "2"]);
}
#[test]
fn record_declaration_merge_partial_undeclared() {
let mut narrowing_constraints = NarrowingConstraintsBuilder::default();
let mut reachability_constraints = ReachabilityConstraintsBuilder::default();
let mut sym = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym.record_declaration(
ScopedDefinitionId::from_u32(1),
ScopedReachabilityConstraintId::ALWAYS_TRUE,
);
let sym2 = PlaceState::undefined(ScopedReachabilityConstraintId::ALWAYS_TRUE);
sym.merge(
sym2,
&mut narrowing_constraints,
&mut reachability_constraints,
);
assert_declarations(&sym, &["undeclared", "1"]);
}
}