Skip to content

Commit 6200e4b

Browse files
authored
fix: make Scope's unique_id truly unique and add a getter for it (#4527)
Right now, `Scope::unique_id` is always the same as the parent Scope's `index + 1`. This means that if two different scopes are created as children of the same scope, they will have the same `unique_id`, belying its name. In this PR, add a new `ScopeContext` struct that all Scopes in an AST share; this new struct contains a container which controls the identifiers that are given out to scopes in the tree. Also make the field accessible via a getter function, as well as on `BindingLocator`, so that `BindingLocator` and `Scope` can be correlated. Relates to #4519.
1 parent f9e509d commit 6200e4b

File tree

2 files changed

+75
-5
lines changed

2 files changed

+75
-5
lines changed

core/ast/src/scope.rs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ pub(crate) struct Inner {
105105
function: bool,
106106
// Has the `this` been accessed/escaped outside the function environment boundary.
107107
this_escaped: Cell<bool>,
108+
109+
context: Rc<ScopeContext>,
108110
}
109111

110112
impl Scope {
@@ -119,22 +121,23 @@ impl Scope {
119121
bindings: RefCell::default(),
120122
function: true,
121123
this_escaped: Cell::new(false),
124+
context: Rc::default(),
122125
}),
123126
}
124127
}
125128

126129
/// Creates a new scope.
127130
#[must_use]
128131
pub fn new(parent: Self, function: bool) -> Self {
129-
let index = parent.inner.index.get() + 1;
130132
Self {
131133
inner: Rc::new(Inner {
132-
unique_id: index,
133-
outer: Some(parent),
134-
index: Cell::new(index),
134+
unique_id: parent.inner.context.next_unique_id(),
135+
index: Cell::new(parent.inner.index.get() + 1),
135136
bindings: RefCell::default(),
136137
function,
137138
this_escaped: Cell::new(false),
139+
context: parent.inner.context.clone(),
140+
outer: Some(parent),
138141
}),
139142
}
140143
}
@@ -494,6 +497,33 @@ impl Scope {
494497
pub fn outer(&self) -> Option<Self> {
495498
self.inner.outer.clone()
496499
}
500+
501+
/// Returns the unique ID of this scope.
502+
#[must_use]
503+
pub fn unique_id(&self) -> u32 {
504+
self.inner.unique_id
505+
}
506+
}
507+
508+
/// Additional state that all Scopes of a single AST share for bookkeeping.
509+
#[derive(Debug, PartialEq, Default)]
510+
struct ScopeContext {
511+
/// A counter for unique IDs for Scopes generated as part of this scope tree.
512+
///
513+
/// The value is the highest unique ID assigned so far (initialized with 0
514+
/// which is also the unique ID of the global scope).
515+
///
516+
/// Only used in the root scope.
517+
unique_id_ctr: Cell<u32>,
518+
}
519+
520+
impl ScopeContext {
521+
/// Returns the next unique ID for a scope in this scope tree.
522+
fn next_unique_id(&self) -> u32 {
523+
let id = self.unique_id_ctr.get() + 1;
524+
self.unique_id_ctr.set(id);
525+
id
526+
}
497527
}
498528

499529
/// A reference to an identifier in a scope.
@@ -624,6 +654,12 @@ impl BindingLocator {
624654
pub fn set_binding_index(&mut self, index: u32) {
625655
self.binding_index = index;
626656
}
657+
658+
/// Returns the unique scope ID of the binding.
659+
#[must_use]
660+
pub fn unique_scope_id(&self) -> u32 {
661+
self.unique_scope_id
662+
}
627663
}
628664

629665
/// Action that is returned when a fallible binding operation.
@@ -637,7 +673,7 @@ pub enum BindingLocatorError {
637673
}
638674

639675
/// The scope in which a binding is located.
640-
#[derive(Clone, Copy, Debug)]
676+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
641677
pub enum BindingLocatorScope {
642678
/// The binding is located on the global object.
643679
GlobalObject,

core/ast/tests/scope.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,37 @@ fn script_function_mapped_arguments_accessed() {
279279
assert!(a.is_lexical());
280280
assert!(a.local());
281281
}
282+
283+
#[test]
284+
fn multiple_scopes_with_same_parent_have_distinct_ids() {
285+
let global = Scope::new_global();
286+
let child1 = Scope::new(global.clone(), false);
287+
let child2 = Scope::new(global.clone(), false);
288+
let child3 = Scope::new(child1.clone(), false);
289+
let child4 = Scope::new(child1.clone(), false);
290+
291+
// Check uniqueness by inserting in a set and asserting that there are
292+
// 5 elements numbered from 0 to 4.
293+
let mut ids = [global, child1, child2, child3, child4]
294+
.into_iter()
295+
.map(|scope| scope.unique_id())
296+
.collect::<Vec<u32>>();
297+
ids.sort_unstable();
298+
299+
assert_eq!(ids, vec![0, 1, 2, 3, 4]);
300+
}
301+
302+
#[test]
303+
fn can_correlate_binding_to_scope() {
304+
// GIVEN
305+
let global = Scope::new_global();
306+
let child1 = Scope::new(global.clone(), false);
307+
let child2 = Scope::new(child1.clone(), false);
308+
let _unused = child1.create_mutable_binding("x".into(), false);
309+
310+
// WHEN
311+
let binding = child2.get_identifier_reference("x".into());
312+
313+
// THEN
314+
assert_eq!(binding.locator().unique_scope_id(), child1.unique_id());
315+
}

0 commit comments

Comments
 (0)