feat(semantic)!: add ScopeTree:get_child_ids API behind a runtime flag (#5403)

The runtime API for enabling `get_child_ids` is `SemanticBuilder::with_scope_tree_child_ids`.

I've tested this PR in Rolldown to ensure correctness.
This commit is contained in:
Boshen 2024-09-02 16:22:26 +00:00
parent 927034d09b
commit 01cc2ceac0
4 changed files with 63 additions and 0 deletions

View file

@ -186,6 +186,12 @@ impl<'a> SemanticBuilder<'a> {
self self
} }
#[must_use]
pub fn with_scope_tree_child_ids(mut self, yes: bool) -> Self {
self.scope.build_child_ids = yes;
self
}
/// Get the built module record from `build_module_record` /// Get the built module record from `build_module_record`
pub fn module_record(&self) -> Arc<ModuleRecord> { pub fn module_record(&self) -> Arc<ModuleRecord> {
Arc::clone(&self.module_record) Arc::clone(&self.module_record)

View file

@ -28,13 +28,23 @@ pub type UnresolvedReferences = FxHashMap<CompactStr, Vec<ReferenceId>>;
pub struct ScopeTree { pub struct ScopeTree {
/// Maps a scope to the parent scope it belongs in. /// Maps a scope to the parent scope it belongs in.
parent_ids: IndexVec<ScopeId, Option<ScopeId>>, parent_ids: IndexVec<ScopeId, Option<ScopeId>>,
/// Maps a scope to direct children scopes.
child_ids: IndexVec<ScopeId, Vec<ScopeId>>,
/// Runtime flag for constructing child_ids.
pub(crate) build_child_ids: bool,
/// Maps a scope to its node id. /// Maps a scope to its node id.
node_ids: IndexVec<ScopeId, AstNodeId>, node_ids: IndexVec<ScopeId, AstNodeId>,
flags: IndexVec<ScopeId, ScopeFlags>, flags: IndexVec<ScopeId, ScopeFlags>,
/// Symbol bindings in a scope. /// Symbol bindings in a scope.
/// ///
/// A binding is a mapping from an identifier name to its [`SymbolId`] /// A binding is a mapping from an identifier name to its [`SymbolId`]
bindings: IndexVec<ScopeId, Bindings>, bindings: IndexVec<ScopeId, Bindings>,
pub(crate) root_unresolved_references: UnresolvedReferences, pub(crate) root_unresolved_references: UnresolvedReferences,
} }
@ -154,6 +164,11 @@ impl ScopeTree {
pub fn set_parent_id(&mut self, scope_id: ScopeId, parent_id: Option<ScopeId>) { pub fn set_parent_id(&mut self, scope_id: ScopeId, parent_id: Option<ScopeId>) {
self.parent_ids[scope_id] = parent_id; self.parent_ids[scope_id] = parent_id;
if self.build_child_ids {
if let Some(parent_id) = parent_id {
self.child_ids[parent_id].push(scope_id);
}
}
} }
/// Get a variable binding by name that was declared in the top-level scope /// Get a variable binding by name that was declared in the top-level scope
@ -232,6 +247,12 @@ impl ScopeTree {
&mut self.bindings[scope_id] &mut self.bindings[scope_id]
} }
/// Get the child scopes of a scope
#[inline]
pub fn get_child_ids(&self, scope_id: ScopeId) -> &[ScopeId] {
&self.child_ids[scope_id]
}
/// Create a scope. /// Create a scope.
#[inline] #[inline]
pub fn add_scope( pub fn add_scope(
@ -244,6 +265,12 @@ impl ScopeTree {
self.flags.push(flags); self.flags.push(flags);
self.bindings.push(Bindings::default()); self.bindings.push(Bindings::default());
self.node_ids.push(node_id); self.node_ids.push(node_id);
if self.build_child_ids {
self.child_ids.push(vec![]);
if let Some(parent_id) = parent_id {
self.child_ids[parent_id].push(scope_id);
}
}
scope_id scope_id
} }
@ -265,5 +292,8 @@ impl ScopeTree {
self.flags.reserve(additional); self.flags.reserve(additional);
self.bindings.reserve(additional); self.bindings.reserve(additional);
self.node_ids.reserve(additional); self.node_ids.reserve(additional);
if self.build_child_ids {
self.child_ids.reserve(additional);
}
} }
} }

View file

@ -220,3 +220,21 @@ fn var_hoisting() {
.is_in_scope(ScopeFlags::Top) .is_in_scope(ScopeFlags::Top)
.test(); .test();
} }
#[test]
fn get_child_ids() {
let test = SemanticTester::js(
"
function foo() {
}
",
)
.with_scope_tree_child_ids(true);
let semantic = test.build();
let (_symbols, scopes) = semantic.into_symbol_table_and_scope_tree();
let child_scope_ids = scopes.get_child_ids(scopes.root_scope_id());
assert_eq!(child_scope_ids.len(), 1);
let child_scope_ids = scopes.get_child_ids(child_scope_ids[0]);
assert!(child_scope_ids.is_empty());
}

View file

@ -25,6 +25,8 @@ pub struct SemanticTester<'a> {
/// ///
/// [`ControlFlowGraph`]: oxc_cfg::ControlFlowGraph /// [`ControlFlowGraph`]: oxc_cfg::ControlFlowGraph
cfg: bool, cfg: bool,
/// Build a child ids for scope tree?
scope_tree_child_ids: bool,
/// Expect semantic analysis to produce errors. /// Expect semantic analysis to produce errors.
/// ///
/// Default is `false`. /// Default is `false`.
@ -63,6 +65,7 @@ impl<'a> SemanticTester<'a> {
source_type, source_type,
source_text, source_text,
cfg: false, cfg: false,
scope_tree_child_ids: false,
expect_errors: false, expect_errors: false,
} }
} }
@ -95,6 +98,11 @@ impl<'a> SemanticTester<'a> {
self self
} }
pub fn with_scope_tree_child_ids(mut self, yes: bool) -> Self {
self.scope_tree_child_ids = yes;
self
}
/// The program being tested is expected to produce errors during semantic analysis. /// The program being tested is expected to produce errors during semantic analysis.
/// ///
/// By default, programs are expected to be error-free. /// By default, programs are expected to be error-free.
@ -163,6 +171,7 @@ impl<'a> SemanticTester<'a> {
.with_check_syntax_error(true) .with_check_syntax_error(true)
.with_trivias(parse.trivias) .with_trivias(parse.trivias)
.with_cfg(self.cfg) .with_cfg(self.cfg)
.with_scope_tree_child_ids(self.scope_tree_child_ids)
.build(program) .build(program)
} }