Skip to content

Commit

Permalink
feat(traverse): add generate_uid_in_current_hoist_scope method (#7423)
Browse files Browse the repository at this point in the history
Add an API to handle variable hoisting.
  • Loading branch information
Dunqing committed Nov 26, 2024
1 parent 3a1ef6a commit e8c99bb
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 127 deletions.
19 changes: 17 additions & 2 deletions crates/oxc_traverse/scripts/lib/walk.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,26 @@ function generateWalkForStruct(type, types) {
// but we don't take that into account.
// Visitor should not do that though, so maybe it's OK.
// In final version, we should not make `scope_id` fields `Cell`s to prevent this.

enterScopeCode = `
let previous_scope_id = ctx.current_scope_id();
ctx.set_current_scope_id((*(${makeFieldCode(scopeIdField)})).get().unwrap());
let current_scope_id = (*(${makeFieldCode(scopeIdField)})).get().unwrap();
ctx.set_current_scope_id(current_scope_id);
`;
exitScopeCode = `ctx.set_current_scope_id(previous_scope_id);`;

exitScopeCode = 'ctx.set_current_scope_id(previous_scope_id);';

// const Var = Self::Top.bits() | Self::Function.bits() | Self::ClassStaticBlock.bits() | Self::TsModuleBlock.bits();
// `Function` type is a special case as its flags are set dynamically depending on the parent.
let isVarHoistingScope = type.name == 'Function' ||
['Top', 'Function', 'ClassStaticBlock', 'TsModuleBlock'].some(flag => scopeArgs.flags.includes(flag));
if (isVarHoistingScope) {
enterScopeCode += `
let previous_hoist_scope_id = ctx.current_hoist_scope_id();
ctx.set_current_hoist_scope_id(current_scope_id);
`;
exitScopeCode += 'ctx.set_current_hoist_scope_id(previous_hoist_scope_id);';
}
}

const fieldsCodes = visitedFields.map((field, index) => {
Expand Down
34 changes: 26 additions & 8 deletions crates/oxc_traverse/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use oxc_ast::{
ast::{Expression, IdentifierReference, Statement},
AstBuilder,
};
use oxc_semantic::{NodeId, ScopeTree, SymbolTable};
use oxc_span::{Atom, CompactStr, Span, SPAN};
use oxc_semantic::{ScopeTree, SymbolTable};
use oxc_span::{Atom, CompactStr, Span};
use oxc_syntax::{
reference::{ReferenceFlags, ReferenceId},
scope::{ScopeFlags, ScopeId},
Expand Down Expand Up @@ -186,6 +186,14 @@ impl<'a> TraverseCtx<'a> {
self.scoping.current_scope_id()
}

/// Get current var hoisting scope ID.
///
/// Shortcut for `ctx.scoping.current_hoist_scope_id`.
#[inline]
pub fn current_hoist_scope_id(&self) -> ScopeId {
self.scoping.current_hoist_scope_id()
}

/// Get current scope flags.
///
/// Shortcut for `ctx.scoping.current_scope_flags`.
Expand Down Expand Up @@ -356,12 +364,7 @@ impl<'a> TraverseCtx<'a> {
// Get name for UID
let name = self.generate_uid_name(name);
let name_atom = self.ast.atom(&name);

// Add binding to scope
let symbol_id =
self.symbols_mut().create_symbol(SPAN, name.clone(), flags, scope_id, NodeId::DUMMY);
self.scopes_mut().add_binding(scope_id, name, symbol_id);

let symbol_id = self.scoping.add_binding(name, scope_id, flags);
BoundIdentifier::new(name_atom, symbol_id)
}

Expand Down Expand Up @@ -421,6 +424,15 @@ impl<'a> TraverseCtx<'a> {
self.generate_uid_based_on_node(node, self.current_scope_id(), flags)
}

/// Generate UID in current hoist scope.
///
/// See also comments on [`TraverseScoping::generate_uid_name`] for important information
/// on how UIDs are generated. There are some potential "gotchas".
#[inline]
pub fn generate_uid_in_current_hoist_scope(&mut self, name: &str) -> BoundIdentifier<'a> {
self.generate_uid(name, self.current_hoist_scope_id(), SymbolFlags::FunctionScopedVariable)
}

/// Create a reference bound to a `SymbolId`.
///
/// This is a shortcut for `ctx.scoping.create_bound_reference`.
Expand Down Expand Up @@ -623,4 +635,10 @@ impl<'a> TraverseCtx<'a> {
pub(crate) fn set_current_scope_id(&mut self, scope_id: ScopeId) {
self.scoping.set_current_scope_id(scope_id);
}

/// Shortcut for `ctx.scoping.set_current_hoist_scope_id`, to make `walk_*` methods less verbose.
#[inline]
pub(crate) fn set_current_hoist_scope_id(&mut self, scope_id: ScopeId) {
self.scoping.set_current_hoist_scope_id(scope_id);
}
}
39 changes: 31 additions & 8 deletions crates/oxc_traverse/src/context/scoping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct TraverseScoping {
symbols: SymbolTable,
uid_names: Option<FxHashSet<CompactStr>>,
current_scope_id: ScopeId,
current_hoist_scope_id: ScopeId,
}

// Public methods
Expand All @@ -40,6 +41,12 @@ impl TraverseScoping {
self.current_scope_id
}

/// Get current var hoisting scope ID
#[inline]
pub(crate) fn current_hoist_scope_id(&self) -> ScopeId {
self.current_hoist_scope_id
}

/// Get current scope flags
#[inline]
pub fn current_scope_flags(&self) -> ScopeFlags {
Expand Down Expand Up @@ -164,6 +171,21 @@ impl TraverseScoping {
self.scopes.delete_scope(scope_id);
}

/// Add binding to [`ScopeTree`] and [`SymbolTable`].
#[inline]
pub(crate) fn add_binding(
&mut self,
name: CompactStr,
scope_id: ScopeId,
flags: SymbolFlags,
) -> SymbolId {
let symbol_id =
self.symbols.create_symbol(SPAN, name.clone(), flags, scope_id, NodeId::DUMMY);
self.scopes.add_binding(scope_id, name, symbol_id);

symbol_id
}

/// Generate binding.
///
/// Creates a symbol with the provided name and flags and adds it to the specified scope.
Expand All @@ -173,13 +195,7 @@ impl TraverseScoping {
scope_id: ScopeId,
flags: SymbolFlags,
) -> BoundIdentifier<'a> {
let owned_name = name.to_compact_str();

// Add binding to scope
let symbol_id =
self.symbols.create_symbol(SPAN, owned_name.clone(), flags, scope_id, NodeId::DUMMY);
self.scopes.add_binding(scope_id, owned_name, symbol_id);

let symbol_id = self.add_binding(name.to_compact_str(), scope_id, flags);
BoundIdentifier::new(name, symbol_id)
}

Expand Down Expand Up @@ -386,8 +402,9 @@ impl TraverseScoping {
scopes,
symbols,
uid_names: None,
// Dummy value. Immediately overwritten in `walk_program`.
// Dummy values. Both immediately overwritten in `walk_program`.
current_scope_id: ScopeId::new(0),
current_hoist_scope_id: ScopeId::new(0),
}
}

Expand All @@ -397,6 +414,12 @@ impl TraverseScoping {
self.current_scope_id = scope_id;
}

/// Set current hoist scope ID
#[inline]
pub(crate) fn set_current_hoist_scope_id(&mut self, scope_id: ScopeId) {
self.current_hoist_scope_id = scope_id;
}

/// Get `uid_names`.
///
/// Iterate through all symbols and unresolved references in AST and identify any var names
Expand Down
Loading

0 comments on commit e8c99bb

Please sign in to comment.