LibJS: Move identifiers into a contiguous IdentifierArena

Replace per-AST-node Rc<Identifier> with a Copy IdentifierId index
into a Vec<Identifier> arena, plumbed through Parser, scope_collector,
codegen, ast_dump, and the FFI. The arena lives on the parser during
parse, ships out via Arc<AstArena> on ParsedProgram, and is shared by
each child Generator and FunctionPayload through Arc clones.

Eliminates the per-occurrence Rc::new in the parser: every identifier
reference, parameter binding, function name, class name, and
binding-pattern target lands in the arena's Vec instead of getting its
own malloc plus Rc control block. Identifier field reads in codegen
become direct array indexing.

Identifier still carries Cell<>-wrapped scope-analysis state, so
AstArena is not yet Send + Sync; the existing unsafe-impl-Send wrapper
on ParsedProgram covers cross-thread handoff. Removing the Cells is
the next step.
This commit is contained in:
Andreas Kling 2026-05-04 19:31:33 +02:00 committed by Andreas Kling
parent d9b9925914
commit 3e15e59cd1
11 changed files with 959 additions and 499 deletions

View file

@ -640,6 +640,10 @@ impl FunctionTable {
pub struct FunctionPayload {
pub data: FunctionData,
pub function_table: FunctionTable,
/// Shared access to the program-wide identifier/scope/string tables.
/// Each lazy-compile SFD carries an Arc clone so it can resolve its
/// identifier IDs without depending on a parent generator.
pub arena: Arc<AstArena>,
}
// =============================================================================
@ -1037,14 +1041,14 @@ pub struct FunctionParameter {
#[derive(Clone, Debug)]
pub enum FunctionParameterBinding {
Identifier(Rc<Identifier>),
Identifier(IdentifierId),
BindingPattern(BindingPattern),
}
/// Shared data for FunctionDeclaration and FunctionExpression.
#[derive(Debug)]
pub struct FunctionData {
pub name: Option<Rc<Identifier>>,
pub name: Option<IdentifierId>,
pub source_text_start: u32,
pub source_text_end: u32,
pub body: Box<Statement>,
@ -1069,7 +1073,7 @@ pub struct FunctionData {
/// Shared data for ClassDeclaration and ClassExpression.
#[derive(Clone, Debug)]
pub struct ClassData {
pub name: Option<Rc<Identifier>>,
pub name: Option<IdentifierId>,
pub source_text_start: u32,
pub source_text_end: u32,
pub constructor: Option<Box<Expression>>,
@ -1152,7 +1156,7 @@ pub struct BindingEntry {
/// - `Expression`: computed property key (`{ [expression]: x }`)
#[derive(Clone, Debug)]
pub enum BindingEntryName {
Identifier(Rc<Identifier>),
Identifier(IdentifierId),
Expression(Box<Expression>),
}
@ -1163,7 +1167,7 @@ pub enum BindingEntryName {
/// - `MemberExpression`: assignment target (`{ x: obj.property }`)
#[derive(Clone, Debug)]
pub enum BindingEntryAlias {
Identifier(Rc<Identifier>),
Identifier(IdentifierId),
BindingPattern(Box<BindingPattern>),
MemberExpression(Box<Expression>),
}
@ -1181,7 +1185,7 @@ pub struct VariableDeclarator {
#[derive(Clone, Debug)]
pub enum VariableDeclaratorTarget {
Identifier(Rc<Identifier>),
Identifier(IdentifierId),
BindingPattern(BindingPattern),
}
@ -1254,7 +1258,7 @@ pub enum OptionalChainReference {
mode: OptionalChainMode,
},
MemberReference {
identifier: Rc<Identifier>,
identifier: IdentifierId,
mode: OptionalChainMode,
},
PrivateMemberReference {
@ -1349,7 +1353,7 @@ pub struct CatchClause {
#[derive(Clone, Debug)]
pub enum CatchBinding {
Identifier(Rc<Identifier>),
Identifier(IdentifierId),
BindingPattern(BindingPattern),
}
@ -1646,7 +1650,7 @@ pub enum ExpressionKind {
RegExpLiteral(Box<RegExpLiteralData>),
// Identifiers
Identifier(Rc<Identifier>),
Identifier(IdentifierId),
PrivateIdentifier(Box<PrivateIdentifier>),
// Operators
@ -1754,7 +1758,7 @@ pub struct VariableDeclarationData {
#[derive(Clone, Debug)]
pub struct FunctionDeclarationData {
pub function_id: FunctionId,
pub name: Option<Rc<Identifier>>,
pub name: Option<IdentifierId>,
pub kind: FunctionKind,
pub is_hoisted: Cell<bool>,
}

View file

@ -68,12 +68,16 @@ struct DumpState<'a> {
use_color: bool,
output: Option<&'a RefCell<String>>,
function_table: &'a FunctionTable,
identifiers: &'a crate::ast::IdentifierArena,
}
impl DumpState<'_> {
fn function_table(&self) -> &FunctionTable {
self.function_table
}
fn identifier(&self, id: crate::ast::IdentifierId) -> &crate::ast::Identifier {
&self.identifiers[id]
}
}
fn print_node(state: &DumpState, text: &str) {
@ -118,6 +122,7 @@ fn child_state<'a>(state: &DumpState<'a>, is_last: bool) -> DumpState<'a> {
use_color: state.use_color,
output: state.output,
function_table: state.function_table,
identifiers: state.identifiers,
}
}
@ -303,7 +308,12 @@ fn dump_labeled_statement(label: &str, statement: &Statement, is_last: bool, sta
// Entry point
// ============================================================================
pub fn dump_program(program: &Statement, use_color: bool, function_table: &FunctionTable) {
pub fn dump_program(
program: &Statement,
use_color: bool,
function_table: &FunctionTable,
identifiers: &crate::ast::IdentifierArena,
) {
let state = DumpState {
prefix: String::new(),
is_last: false,
@ -311,12 +321,17 @@ pub fn dump_program(program: &Statement, use_color: bool, function_table: &Funct
use_color,
output: None,
function_table,
identifiers,
};
dump_statement(program, &state);
println!();
}
pub fn dump_program_to_string(program: &Statement, function_table: &FunctionTable) -> String {
pub fn dump_program_to_string(
program: &Statement,
function_table: &FunctionTable,
identifiers: &crate::ast::IdentifierArena,
) -> String {
let output = RefCell::new(String::new());
let state = DumpState {
prefix: String::new(),
@ -325,6 +340,7 @@ pub fn dump_program_to_string(program: &Statement, function_table: &FunctionTabl
use_color: false,
output: Some(&output),
function_table,
identifiers,
};
dump_statement(program, &state);
output.into_inner()
@ -682,7 +698,7 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
}
ExpressionKind::Identifier(ident) => {
dump_identifier(ident, &expression.range, state);
dump_identifier(state.identifier(*ident), &expression.range, state);
}
ExpressionKind::PrivateIdentifier(ident) => {
@ -798,7 +814,7 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
}
OptionalChainReference::MemberReference { identifier, mode } => {
print_node(&ref_state, &format!("MemberReference({})", optional_mode_str(*mode)));
dump_identifier(identifier, &identifier.range, &child_state(&ref_state, true));
dump_identifier_id(*identifier, &child_state(&ref_state, true));
}
OptionalChainReference::PrivateMemberReference {
private_identifier,
@ -942,6 +958,12 @@ fn dump_expression(expression: &Expression, state: &DumpState) {
// Identifier dumper
// ============================================================================
fn dump_identifier_id(id: crate::ast::IdentifierId, state: &DumpState) {
let ident = state.identifier(id);
let range = ident.range;
dump_identifier(ident, &range, state);
}
fn dump_identifier(ident: &Identifier, range: &SourceRange, state: &DumpState) {
let mut desc = color_node_name(state, "Identifier");
desc.push_str(&format!(" {}", color_string_utf16(state, &ident.name)));
@ -990,8 +1012,8 @@ fn dump_function(function_data: &FunctionData, class_name: &str, range: &SourceR
if is_generator {
desc.push('*');
}
let name_str = match &function_data.name {
Some(ident) => utf16_to_string(&ident.name),
let name_str = match function_data.name {
Some(id) => utf16_to_string(&state.identifier(id).name),
None => String::new(),
};
desc.push_str(&format!(" {}", color_string(state, &name_str)));
@ -1025,8 +1047,8 @@ fn dump_function(function_data: &FunctionData, class_name: &str, range: &SourceR
if parameter.is_rest {
print_node(&parameter_state, &color_label(state, "rest"));
match &parameter.binding {
FunctionParameterBinding::Identifier(ident) => {
dump_identifier(ident, &ident.range, &child_state(&parameter_state, !has_default));
FunctionParameterBinding::Identifier(id) => {
dump_identifier_id(*id, &child_state(&parameter_state, !has_default));
}
FunctionParameterBinding::BindingPattern(pattern) => {
dump_binding_pattern(pattern, &child_state(&parameter_state, !has_default), state);
@ -1034,10 +1056,9 @@ fn dump_function(function_data: &FunctionData, class_name: &str, range: &SourceR
}
} else {
match &parameter.binding {
FunctionParameterBinding::Identifier(ident) => {
dump_identifier(
ident,
&ident.range,
FunctionParameterBinding::Identifier(id) => {
dump_identifier_id(
*id,
&child_state(&parameters_state, i == function_data.parameters.len() - 1),
);
}
@ -1065,8 +1086,8 @@ fn dump_function(function_data: &FunctionData, class_name: &str, range: &SourceR
}
fn dump_class(class_data: &ClassData, range: &SourceRange, state: &DumpState, root_state: &DumpState) {
let name_str = match &class_data.name {
Some(ident) => utf16_to_string(&ident.name),
let name_str = match class_data.name {
Some(id) => utf16_to_string(&state.identifier(id).name),
None => String::new(),
};
print_node(
@ -1205,9 +1226,8 @@ fn dump_binding_pattern(pattern: &BindingPattern, state: &DumpState, root_state:
&child_state(&entry_state, !has_alias && !has_initializer),
&color_label(root_state, "name"),
);
dump_identifier(
ident,
&ident.range,
dump_identifier_id(
*ident,
&child_state(&child_state(&entry_state, !has_alias && !has_initializer), true),
);
}
@ -1232,11 +1252,7 @@ fn dump_binding_pattern(pattern: &BindingPattern, state: &DumpState, root_state:
);
match alias {
BindingEntryAlias::Identifier(ident) => {
dump_identifier(
ident,
&ident.range,
&child_state(&child_state(&entry_state, !has_initializer), true),
);
dump_identifier_id(*ident, &child_state(&child_state(&entry_state, !has_initializer), true));
}
BindingEntryAlias::BindingPattern(sub) => {
dump_binding_pattern(
@ -1283,7 +1299,7 @@ fn dump_variable_declarator(declaration: &VariableDeclarator, state: &DumpState,
let has_init = declaration.init.is_some();
match &declaration.target {
VariableDeclaratorTarget::Identifier(ident) => {
dump_identifier(ident, &ident.range, &child_state(state, !has_init));
dump_identifier_id(*ident, &child_state(state, !has_init));
}
VariableDeclaratorTarget::BindingPattern(pattern) => {
dump_binding_pattern(pattern, &child_state(state, !has_init), root_state);
@ -1337,7 +1353,7 @@ fn dump_catch_clause(clause: &CatchClause, state: &DumpState, root_state: &DumpS
match parameter {
CatchBinding::Identifier(ident) => {
print_node(&child_state(state, false), &color_label(root_state, "parameter"));
dump_identifier(ident, &ident.range, &child_state(&child_state(state, false), true));
dump_identifier_id(*ident, &child_state(&child_state(state, false), true));
}
CatchBinding::BindingPattern(pattern) => {
print_node(&child_state(state, false), &color_label(root_state, "parameter"));

File diff suppressed because it is too large Load diff

View file

@ -322,6 +322,7 @@ pub unsafe fn create_shared_function_data(
source_code_ptr: *const c_void,
is_strict: bool,
name_override: Option<&[u16]>,
arena: std::sync::Arc<crate::ast::AstArena>,
) -> *mut c_void {
unsafe {
use crate::ast::FunctionParameterBinding;
@ -332,8 +333,9 @@ pub unsafe fn create_shared_function_data(
let (name_ptr, name_len) = if let Some(name) = name_override {
(name.as_ptr(), name.len())
} else if let Some(ref name_ident) = function_data.name {
(name_ident.name.as_ptr(), name_ident.name.len())
} else if let Some(name_ident) = function_data.name {
let name = &arena.identifiers[name_ident].name;
(name.as_ptr(), name.len())
} else {
(std::ptr::null(), 0)
};
@ -347,8 +349,8 @@ pub unsafe fn create_shared_function_data(
.parameters
.iter()
.map(|p| {
if let FunctionParameterBinding::Identifier(ref id) = p.binding {
FFIUtf16Slice::from(id.name.as_ref())
if let FunctionParameterBinding::Identifier(id) = p.binding {
FFIUtf16Slice::from(arena.identifiers[id].name.as_ref())
} else {
unreachable!("has_simple_parameter_list guarantees all bindings are identifiers")
}
@ -369,6 +371,7 @@ pub unsafe fn create_shared_function_data(
let payload = Box::new(crate::ast::FunctionPayload {
data: *function_data,
function_table: subtable,
arena,
});
let rust_ast_ptr = Box::into_raw(payload) as *mut c_void;
@ -410,8 +413,9 @@ pub unsafe fn create_sfd_for_gdi(
vm_ptr: *mut c_void,
source_code_ptr: *const c_void,
is_strict: bool,
arena: std::sync::Arc<crate::ast::AstArena>,
) -> *mut c_void {
unsafe { create_shared_function_data(function_data, subtable, vm_ptr, source_code_ptr, is_strict, None) }
unsafe { create_shared_function_data(function_data, subtable, vm_ptr, source_code_ptr, is_strict, None, arena) }
}
unsafe fn materialize_shared_function_data(
@ -437,6 +441,7 @@ unsafe fn materialize_shared_function_data(
source_code_ptr,
generator.strict,
pending.name_override.as_ref().map(|name| name.as_slice()),
generator.arena.clone(),
);
if let Some((name, is_private)) = &pending.class_field_initializer_name {
rust_sfd_set_class_field_initializer_name(sfd_ptr, name.as_ptr(), name.len(), *is_private);

View file

@ -17,8 +17,9 @@ use super::basic_block::{BasicBlock, SourceMapEntry};
use super::ffi::{AbstractOperationKind, WellKnownSymbolKind};
use super::instruction::Instruction;
use super::operand::*;
use crate::ast::{FunctionData, FunctionId, FunctionTable, LocalType, Position, Utf16String};
use crate::ast::{AstArena, FunctionData, FunctionId, FunctionTable, IdentifierId, LocalType, Position, Utf16String};
use crate::u32_from_usize;
use std::sync::Arc;
/// Identifies an operand that auto-frees its register when the last
/// clone is dropped.
@ -300,6 +301,20 @@ pub struct Generator {
// Side table owning all FunctionData from the parser. Codegen
// takes ownership of individual entries via `take()`.
pub function_table: crate::ast::FunctionTable,
// --- AST arena ---
// Shared (read-only post-parse) storage for identifiers, scopes, and
// interned strings. Cloning is a refcount bump — multiple generators
// (top-level + nested IIFE + lazy children) share the same arena.
pub arena: Arc<AstArena>,
}
impl Generator {
/// Convenience: look up an identifier by ID in this generator's arena.
#[inline]
pub fn identifier(&self, id: IdentifierId) -> &crate::ast::Identifier {
&self.arena.identifiers[id]
}
}
macro_rules! singleton_constant {
@ -425,6 +440,7 @@ impl Generator {
source_code_ptr: std::ptr::null(),
source_len: 0,
function_table: crate::ast::FunctionTable::new(),
arena: Arc::new(AstArena::new()),
free_register_pool,
}
}

View file

@ -4,6 +4,13 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
// AstArena currently transitively contains `Cell<>` fields on `Identifier` (set
// by the scope collector during parse). Once those cells are removed, the
// arena will be naturally `Send + Sync` and this allow can go away. For now
// we hand-wave it the same way we already do with `unsafe impl Send for
// ParsedProgram` -- the arena is move-only-across-threads at the API layer.
#![allow(clippy::arc_with_non_send_sync)]
//! # LibJS Parser
//!
//! A JavaScript parser that produces an AST.
@ -111,6 +118,7 @@ use std::rc::Rc;
pub struct ParsedProgram {
program: ast::Statement,
function_table: ast::FunctionTable,
arena: std::sync::Arc<ast::AstArena>,
scope_ref: Rc<RefCell<ast::ScopeData>>,
is_strict_mode: bool,
has_top_level_await: bool,
@ -176,6 +184,7 @@ fn abort_on_panic<F: FnOnce() -> R, R>(f: F) -> R {
unsafe fn write_ast_dump_output(
program: &ast::Statement,
function_table: &ast::FunctionTable,
identifiers: &ast::IdentifierArena,
output_ptr: *mut *mut u8,
output_len: *mut usize,
) {
@ -183,7 +192,7 @@ unsafe fn write_ast_dump_output(
if output_ptr.is_null() || output_len.is_null() {
return;
}
let dump_string = ast_dump::dump_program_to_string(program, function_table);
let dump_string = ast_dump::dump_program_to_string(program, function_table, identifiers);
let mut boxed = dump_string.into_bytes().into_boxed_slice();
*output_ptr = boxed.as_mut_ptr();
*output_len = boxed.len();
@ -336,11 +345,13 @@ fn precompile_eager_functions(generator: &mut bytecode::generator::Generator) {
let payload = ast::FunctionPayload {
data: *function_data,
function_table: subtable,
arena: generator.arena.clone(),
};
let (function_data, precompiled) = compile_function_payload_to_bytecode(
payload,
generator.source_len,
generator.builtin_abstract_operations_enabled,
generator.arena.clone(),
);
pending.function_data = Some(function_data);
@ -422,7 +433,9 @@ pub unsafe extern "C" fn rust_compile_program(
return std::ptr::null_mut();
}
parser.scope_collector.analyze(initiated_by_eval);
parser
.scope_collector
.analyze(initiated_by_eval, &parser.arena.identifiers);
let scope_ref = if let StatementKind::Program(ref data) = program.inner {
data.scope.clone()
@ -432,6 +445,8 @@ pub unsafe extern "C" fn rust_compile_program(
let mut generator = new_program_generator(starts_in_strict_mode, vm_ptr, source_code_ptr, source_len);
generator.function_table = std::mem::take(&mut parser.function_table);
generator.arena = std::sync::Arc::new(std::mem::take(&mut parser.arena));
// Make a clone of the Arc so we can keep using it after generator is consumed.
compile_program_body(&mut generator, &program, &scope_ref, vm_ptr, source_code_ptr)
})
}
@ -487,12 +502,12 @@ pub unsafe extern "C" fn rust_parse_program(
}
if errors.is_empty() {
parser.scope_collector.analyze(false);
parser.scope_collector.analyze(false, &parser.arena.identifiers);
}
// Dump AST if requested (after scope analysis).
if dump_ast && errors.is_empty() {
ast_dump::dump_program(&program, use_color, &parser.function_table);
ast_dump::dump_program(&program, use_color, &parser.function_table, &parser.arena.identifiers);
}
let (scope_ref, is_strict, has_tla) = if errors.is_empty() {
@ -510,6 +525,7 @@ pub unsafe extern "C" fn rust_parse_program(
let parsed = ParsedProgram {
program,
function_table: std::mem::take(&mut parser.function_table),
arena: std::sync::Arc::new(std::mem::take(&mut parser.arena)),
scope_ref,
is_strict_mode: is_strict,
has_top_level_await: has_tla,
@ -584,8 +600,10 @@ pub unsafe extern "C" fn rust_compile_parsed_program_off_thread(
}
let mut parsed = Box::from_raw(parsed);
let arena_arc = parsed.arena.clone();
let bytecode = if parsed.has_top_level_await {
let mut generator = new_module_async_generator(source_len, std::mem::take(&mut parsed.function_table));
generator.arena = arena_arc;
generator.eager_compile_direct_iifes = true;
let assembled = compile_module_as_async_to_bytecode(&parsed.program, &parsed.scope_ref, &mut generator);
precompile_eager_functions(&mut generator);
@ -597,6 +615,7 @@ pub unsafe extern "C" fn rust_compile_parsed_program_off_thread(
std::ptr::null(),
source_len,
);
generator.arena = arena_arc;
generator.eager_compile_direct_iifes = true;
generator.function_table = std::mem::take(&mut parsed.function_table);
let assembled = compile_program_body_to_bytecode(&mut generator, &parsed.program, &parsed.scope_ref);
@ -641,7 +660,8 @@ pub unsafe extern "C" fn rust_parsed_program_ast_dump(
unsafe {
let parsed = &mut *parsed;
let dump = parsed.ast_dump.get_or_insert_with(|| {
ast_dump::dump_program_to_string(&parsed.program, &parsed.function_table).into_bytes()
ast_dump::dump_program_to_string(&parsed.program, &parsed.function_table, &parsed.arena.identifiers)
.into_bytes()
});
*output_ptr = dump.as_ptr();
*output_len = dump.len();
@ -673,6 +693,7 @@ pub unsafe extern "C" fn rust_compile_parsed_script(
let mut generator = new_program_generator(parsed.is_strict_mode, vm_ptr, source_code_ptr, source_len);
generator.function_table = std::mem::take(&mut parsed.function_table);
generator.arena = parsed.arena.clone();
let exec_ptr = compile_program_body(
&mut generator,
&parsed.program,
@ -691,6 +712,7 @@ pub unsafe extern "C" fn rust_compile_parsed_script(
source_code_ptr,
gdi_context,
&mut generator.function_table,
&parsed.arena,
);
exec_ptr
@ -735,6 +757,7 @@ pub unsafe extern "C" fn rust_materialize_compiled_script(
source_code_ptr,
gdi_context,
&mut bytecode.generator.function_table,
&compiled.parsed.arena,
);
exec_ptr
@ -793,9 +816,15 @@ pub unsafe extern "C" fn rust_compile_eval(
return std::ptr::null_mut();
}
parser.scope_collector.analyze(true);
parser.scope_collector.analyze(true, &parser.arena.identifiers);
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
write_ast_dump_output(
&program,
&parser.function_table,
&parser.arena.identifiers,
ast_dump_output,
ast_dump_output_len,
);
let (scope_ref, is_strict) = if let StatementKind::Program(ref data) = program.inner {
(data.scope.clone(), data.is_strict_mode)
@ -803,8 +832,10 @@ pub unsafe extern "C" fn rust_compile_eval(
return std::ptr::null_mut();
};
let arena_arc = std::sync::Arc::new(std::mem::take(&mut parser.arena));
let mut generator = new_program_generator(is_strict, vm_ptr, source_code_ptr, source_len);
generator.function_table = std::mem::take(&mut parser.function_table);
generator.arena = arena_arc.clone();
let exec_ptr = compile_program_body(&mut generator, &program, &scope_ref, vm_ptr, source_code_ptr);
if exec_ptr.is_null() {
return std::ptr::null_mut();
@ -817,6 +848,7 @@ pub unsafe extern "C" fn rust_compile_eval(
source_code_ptr,
gdi_context,
&mut generator.function_table,
&arena_arc,
);
exec_ptr
@ -968,7 +1000,9 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
// Run scope analysis. Use analyze_as_dynamic_function() to suppress
// marking identifiers as global, matching the C++ path which parses
// as a FunctionExpression (no Program scope for globals to bind to).
parser.scope_collector.analyze_as_dynamic_function();
parser
.scope_collector
.analyze_as_dynamic_function(&parser.arena.identifiers);
if parser.scope_collector.has_errors() {
if let Some(cb) = error_callback {
@ -980,7 +1014,13 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
return std::ptr::null_mut();
}
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
write_ast_dump_output(
&program,
&parser.function_table,
&parser.arena.identifiers,
ast_dump_output,
ast_dump_output_len,
);
// Extract the FunctionExpression from the program.
// The program should contain a single ExpressionStatement wrapping a FunctionExpression.
@ -1017,8 +1057,9 @@ pub unsafe extern "C" fn rust_compile_dynamic_function(
let is_strict = function_data.is_strict_mode;
let subtable = parser.function_table.extract_reachable(&function_data);
let arena = std::sync::Arc::new(std::mem::take(&mut parser.arena));
bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, is_strict)
bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, is_strict, arena)
})
}
}
@ -1071,9 +1112,15 @@ pub unsafe extern "C" fn rust_compile_builtin_file(
panic!("Parse errors in builtin file: {}", errors.join("; "));
}
parser.scope_collector.analyze(false);
parser.scope_collector.analyze(false, &parser.arena.identifiers);
write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len);
write_ast_dump_output(
&program,
&parser.function_table,
&parser.arena.identifiers,
ast_dump_output,
ast_dump_output_len,
);
let scope_ref = if let StatementKind::Program(ref data) = program.inner {
data.scope.clone()
@ -1081,6 +1128,7 @@ pub unsafe extern "C" fn rust_compile_builtin_file(
return;
};
let arena = std::sync::Arc::new(std::mem::take(&mut parser.arena));
let scope = scope_ref.borrow();
for child in &scope.children {
if let StatementKind::FunctionDeclaration(ref fd) = child.inner {
@ -1092,11 +1140,13 @@ pub unsafe extern "C" fn rust_compile_builtin_file(
vm_ptr,
source_code_ptr,
true, // strict
arena.clone(),
);
if !sfd_ptr.is_null()
&& let Some(name_ident) = &fd.name
&& let Some(name_ident) = fd.name
{
push_function(ctx, sfd_ptr, name_ident.name.as_ptr(), name_ident.name.len());
let name = &arena.identifiers[name_ident].name;
push_function(ctx, sfd_ptr, name.as_ptr(), name.len());
}
}
}
@ -1151,6 +1201,7 @@ pub unsafe extern "C" fn rust_compile_parsed_module(
module_context,
cb,
&mut parsed.function_table,
&parsed.arena,
);
// 4. Compute requested modules (sorted by source offset).
@ -1177,6 +1228,7 @@ pub unsafe extern "C" fn rust_compile_parsed_module(
}
let mut generator = new_program_generator(true, vm_ptr, source_code_ptr, source_len);
generator.function_table = std::mem::take(&mut parsed.function_table);
generator.arena = parsed.arena.clone();
compile_program_body(
&mut generator,
&parsed.program,
@ -1228,6 +1280,7 @@ pub unsafe extern "C" fn rust_materialize_compiled_module(
module_context,
cb,
&mut bytecode.generator.function_table,
&compiled.parsed.arena,
);
extract_requested_modules(&compiled.parsed.scope_ref.borrow(), module_context, cb);
@ -1422,9 +1475,11 @@ pub unsafe extern "C" fn rust_compile_module(
return std::ptr::null_mut();
}
let parsed_ref = &*parsed;
write_ast_dump_output(
&(*parsed).program,
&(*parsed).function_table,
&parsed_ref.program,
&parsed_ref.function_table,
&parsed_ref.arena.identifiers,
ast_dump_output,
ast_dump_output_len,
);
@ -1589,7 +1644,9 @@ unsafe fn extract_module_declarations(
ctx: *mut c_void,
cb: &ModuleCallbacks,
function_table: &mut ast::FunctionTable,
arena: &std::sync::Arc<ast::AstArena>,
) {
let identifiers = &arena.identifiers;
unsafe {
use ast::StatementKind;
@ -1597,7 +1654,7 @@ unsafe fn extract_module_declarations(
// Var declared names (walk all nesting levels).
for child in &scope.children {
collect_module_var_names(&child.inner, ctx, cb.push_var_name);
collect_module_var_names(&child.inner, ctx, cb.push_var_name, identifiers);
}
// Lexical bindings and functions to initialize.
@ -1616,19 +1673,25 @@ unsafe fn extract_module_declarations(
match declaration {
StatementKind::FunctionDeclaration(fd) => {
let is_default = is_exported && fd.name.as_ref().is_some_and(|n| n.name == default_name);
let is_default = is_exported && fd.name.is_some_and(|n| identifiers[n].name == default_name);
let function_data = function_table.take(fd.function_id);
let subtable = function_table.extract_reachable(&function_data);
let sfd_ptr =
bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, true);
let sfd_ptr = bytecode::ffi::create_sfd_for_gdi(
function_data,
subtable,
vm_ptr,
source_code_ptr,
true,
arena.clone(),
);
if sfd_ptr.is_null() {
continue;
}
// Get the binding name from the AST (e.g., "*default*" for anonymous defaults).
let binding_name = if let Some(name_ident) = &fd.name {
name_ident.name.to_utf16_string()
let binding_name = if let Some(name_ident) = fd.name {
identifiers[name_ident].name.to_utf16_string()
} else {
continue;
};
@ -1651,21 +1714,22 @@ unsafe fn extract_module_declarations(
(cb.push_lexical_binding)(ctx, binding_name.as_ptr(), binding_name.len(), false, function_index);
}
StatementKind::ClassDeclaration(class_data) => {
if let Some(ref name_ident) = class_data.name {
(cb.push_lexical_binding)(ctx, name_ident.name.as_ptr(), name_ident.name.len(), false, -1);
if let Some(name_ident) = class_data.name {
let name = &identifiers[name_ident].name;
(cb.push_lexical_binding)(ctx, name.as_ptr(), name.len(), false, -1);
}
}
StatementKind::VariableDeclaration(vd) if vd.kind != ast::DeclarationKind::Var => {
let is_constant = vd.kind == ast::DeclarationKind::Const;
for declaration in &vd.declarations {
for_each_bound_name(&declaration.target, &mut |name| {
for_each_bound_name(&declaration.target, identifiers, &mut |name| {
(cb.push_lexical_binding)(ctx, name.as_ptr(), name.len(), is_constant, -1);
});
}
}
StatementKind::UsingDeclaration(declarations) => {
for declaration in declarations.iter() {
for_each_bound_name(&declaration.target, &mut |name| {
for_each_bound_name(&declaration.target, identifiers, &mut |name| {
(cb.push_lexical_binding)(ctx, name.as_ptr(), name.len(), false, -1);
});
}
@ -1681,24 +1745,25 @@ unsafe fn collect_module_var_names(
statement: &ast::StatementKind,
ctx: *mut c_void,
push_var_name: ModuleNameCallback,
identifiers: &ast::IdentifierArena,
) {
unsafe {
match statement {
ast::StatementKind::VariableDeclaration(vd) if vd.kind == ast::DeclarationKind::Var => {
for declaration in &vd.declarations {
for_each_bound_name(&declaration.target, &mut |name| {
for_each_bound_name(&declaration.target, identifiers, &mut |name| {
push_var_name(ctx, name.as_ptr(), name.len());
});
}
}
ast::StatementKind::Export(export_data) => {
if let Some(ref stmt) = export_data.statement {
collect_module_var_names(&stmt.inner, ctx, push_var_name);
collect_module_var_names(&stmt.inner, ctx, push_var_name, identifiers);
}
}
_ => {
for_each_child_statement(statement, &mut |child| {
collect_module_var_names(child, ctx, push_var_name);
collect_module_var_names(child, ctx, push_var_name, identifiers);
});
}
}
@ -1843,16 +1908,20 @@ unsafe extern "C" {
/// Recursively collect var-declared names from a statement and all nested
/// statements, excluding function/class bodies (which create new var scopes).
fn collect_var_names_recursive(statement: &ast::StatementKind, push_name: &mut dyn FnMut(&[u16])) {
fn collect_var_names_recursive(
statement: &ast::StatementKind,
identifiers: &ast::IdentifierArena,
push_name: &mut dyn FnMut(&[u16]),
) {
match statement {
ast::StatementKind::VariableDeclaration(vd) if vd.kind == ast::DeclarationKind::Var => {
for declaration in &vd.declarations {
for_each_bound_name(&declaration.target, push_name);
for_each_bound_name(&declaration.target, identifiers, push_name);
}
}
_ => {
for_each_child_statement(statement, &mut |child| {
collect_var_names_recursive(child, push_name);
collect_var_names_recursive(child, identifiers, push_name);
});
}
}
@ -1875,16 +1944,18 @@ fn extract_gdi_common(
push_annex_b_name: &mut dyn FnMut(&[u16]),
push_lexical_binding: &mut dyn FnMut(&[u16], bool),
function_table: &mut ast::FunctionTable,
arena: &std::sync::Arc<ast::AstArena>,
) {
let identifiers = &arena.identifiers;
use ast::{DeclarationKind, StatementKind};
// Var names (var declarations at any nesting level + top-level function declarations)
for child in &scope.children {
collect_var_names_recursive(&child.inner, push_var_name);
collect_var_names_recursive(&child.inner, identifiers, push_var_name);
if let StatementKind::FunctionDeclaration(ref fd) = child.inner
&& let Some(ref name_ident) = fd.name
&& let Some(name_ident) = fd.name
{
push_var_name(&name_ident.name);
push_var_name(&identifiers[name_ident].name);
}
}
@ -1894,29 +1965,36 @@ fn extract_gdi_common(
let mut last_position: std::collections::HashMap<ast::SharedUtf16String, usize> = std::collections::HashMap::new();
for (i, child) in scope.children.iter().enumerate() {
if let StatementKind::FunctionDeclaration(ref fd) = child.inner
&& let Some(ref name_ident) = fd.name
&& let Some(name_ident) = fd.name
{
last_position.insert(name_ident.name.clone(), i);
last_position.insert(identifiers[name_ident].name.clone(), i);
}
}
for (i, child) in scope.children.iter().enumerate() {
if let StatementKind::FunctionDeclaration(ref fd) = child.inner
&& let Some(ref name_ident) = fd.name
&& last_position.get(&name_ident.name).copied() == Some(i)
&& let Some(name_ident) = fd.name
&& last_position.get(&identifiers[name_ident].name).copied() == Some(i)
{
let function_data = function_table.take(fd.function_id);
let subtable = function_table.extract_reachable(&function_data);
let sfd_ptr = unsafe {
bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, is_strict)
bytecode::ffi::create_sfd_for_gdi(
function_data,
subtable,
vm_ptr,
source_code_ptr,
is_strict,
arena.clone(),
)
};
assert!(!sfd_ptr.is_null(), "create_sfd_for_gdi returned null");
push_function(sfd_ptr, name_ident.name.as_slice());
push_function(sfd_ptr, identifiers[name_ident].name.as_slice());
}
}
// Var-scoped names (var VariableDeclaration names, excluding function declarations)
for child in &scope.children {
collect_var_names_recursive(&child.inner, push_var_scoped_name);
collect_var_names_recursive(&child.inner, identifiers, push_var_scoped_name);
}
for name in &scope.annexb_function_names {
@ -1928,21 +2006,21 @@ fn extract_gdi_common(
StatementKind::VariableDeclaration(vd) if vd.kind != DeclarationKind::Var => {
let is_constant = vd.kind == DeclarationKind::Const;
for declaration in &vd.declarations {
for_each_bound_name(&declaration.target, &mut |name| {
for_each_bound_name(&declaration.target, identifiers, &mut |name| {
push_lexical_binding(name, is_constant);
});
}
}
StatementKind::UsingDeclaration(declarations) => {
for declaration in declarations.iter() {
for_each_bound_name(&declaration.target, &mut |name| {
for_each_bound_name(&declaration.target, identifiers, &mut |name| {
push_lexical_binding(name, false);
});
}
}
StatementKind::ClassDeclaration(class_data) => {
if let Some(ref name) = class_data.name {
push_lexical_binding(&name.name, false);
if let Some(name) = class_data.name {
push_lexical_binding(&identifiers[name].name, false);
}
}
_ => {}
@ -1959,6 +2037,7 @@ unsafe fn extract_eval_gdi(
source_code_ptr: *const c_void,
ctx: *mut c_void,
function_table: &mut ast::FunctionTable,
arena: &std::sync::Arc<ast::AstArena>,
) {
unsafe {
use bytecode::ffi::{
@ -1981,6 +2060,7 @@ unsafe fn extract_eval_gdi(
eval_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const);
},
function_table,
arena,
);
}
}
@ -1994,7 +2074,9 @@ unsafe fn extract_script_gdi(
source_code_ptr: *const c_void,
ctx: *mut c_void,
function_table: &mut ast::FunctionTable,
arena: &std::sync::Arc<ast::AstArena>,
) {
let identifiers = &arena.identifiers;
unsafe {
use ast::{DeclarationKind, StatementKind};
use bytecode::ffi::{
@ -2007,21 +2089,22 @@ unsafe fn extract_script_gdi(
match &child.inner {
StatementKind::VariableDeclaration(vd) if vd.kind != DeclarationKind::Var => {
for declaration in &vd.declarations {
for_each_bound_name(&declaration.target, &mut |name| {
for_each_bound_name(&declaration.target, identifiers, &mut |name| {
script_gdi_push_lexical_name(ctx, name.as_ptr(), name.len());
});
}
}
StatementKind::UsingDeclaration(declarations) => {
for declaration in declarations.iter() {
for_each_bound_name(&declaration.target, &mut |name| {
for_each_bound_name(&declaration.target, identifiers, &mut |name| {
script_gdi_push_lexical_name(ctx, name.as_ptr(), name.len());
});
}
}
StatementKind::ClassDeclaration(class_data) => {
if let Some(ref name) = class_data.name {
script_gdi_push_lexical_name(ctx, name.name.as_ptr(), name.name.len());
if let Some(name) = class_data.name {
let n = &identifiers[name].name;
script_gdi_push_lexical_name(ctx, n.as_ptr(), n.len());
}
}
_ => {}
@ -2041,6 +2124,7 @@ unsafe fn extract_script_gdi(
script_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const);
},
function_table,
arena,
);
}
}
@ -2107,26 +2191,34 @@ fn for_each_child_statement(statement: &ast::StatementKind, f: &mut dyn FnMut(&a
}
}
fn for_each_bound_name(target: &ast::VariableDeclaratorTarget, f: &mut dyn FnMut(&[u16])) {
fn for_each_bound_name(
target: &ast::VariableDeclaratorTarget,
identifiers: &ast::IdentifierArena,
f: &mut dyn FnMut(&[u16]),
) {
match target {
ast::VariableDeclaratorTarget::Identifier(id) => f(&id.name),
ast::VariableDeclaratorTarget::Identifier(id) => f(&identifiers[*id].name),
ast::VariableDeclaratorTarget::BindingPattern(pattern) => {
for_each_bound_name_in_pattern(pattern, f);
for_each_bound_name_in_pattern(pattern, identifiers, f);
}
}
}
fn for_each_bound_name_in_pattern(pattern: &ast::BindingPattern, f: &mut dyn FnMut(&[u16])) {
fn for_each_bound_name_in_pattern(
pattern: &ast::BindingPattern,
identifiers: &ast::IdentifierArena,
f: &mut dyn FnMut(&[u16]),
) {
for entry in &pattern.entries {
match &entry.alias {
None => {
if let Some(ast::BindingEntryName::Identifier(id)) = &entry.name {
f(&id.name);
f(&identifiers[*id].name);
}
}
Some(ast::BindingEntryAlias::Identifier(id)) => f(&id.name),
Some(ast::BindingEntryAlias::Identifier(id)) => f(&identifiers[*id].name),
Some(ast::BindingEntryAlias::BindingPattern(inner)) => {
for_each_bound_name_in_pattern(inner, f);
for_each_bound_name_in_pattern(inner, identifiers, f);
}
Some(ast::BindingEntryAlias::MemberExpression(_)) => {}
}
@ -2197,8 +2289,9 @@ pub unsafe extern "C" fn rust_compile_function(
return std::ptr::null_mut();
}
let payload = Box::from_raw(rust_function_ast as *mut ast::FunctionPayload);
let arena = payload.arena.clone();
let (_function_data, mut precompiled) =
compile_function_payload_to_bytecode(*payload, source_len, builtin_abstract_operations_enabled);
compile_function_payload_to_bytecode(*payload, source_len, builtin_abstract_operations_enabled, arena);
precompiled.generator.vm_ptr = vm_ptr;
precompiled.generator.source_code_ptr = source_code_ptr;
@ -2219,6 +2312,7 @@ fn compile_function_payload_to_bytecode(
payload: ast::FunctionPayload,
source_len: usize,
builtin_abstract_operations_enabled: bool,
arena: std::sync::Arc<ast::AstArena>,
) -> (Box<ast::FunctionData>, Box<bytecode::generator::PrecompiledFunction>) {
let function_data = Box::new(payload.data);
@ -2230,9 +2324,10 @@ fn compile_function_payload_to_bytecode(
// Compute SFD metadata before codegen so the generator can optimize
// direct `this` access when it does not need environment resolution.
let sfd_metadata = compute_sfd_metadata(&function_data);
let sfd_metadata = compute_sfd_metadata(&function_data, &arena.identifiers);
let mut generator = bytecode::generator::Generator::new();
generator.arena = arena;
generator.strict = function_data.is_strict_mode;
generator.this_value_needs_environment_resolution = sfd_metadata.this_value_needs_environment_resolution;
generator.builtin_abstract_operations_enabled = builtin_abstract_operations_enabled;
@ -2340,7 +2435,10 @@ struct BodyScopeInfo {
/// Compute FDI runtime metadata matching the C++ SharedFunctionInstanceData
/// constructor (ECMA-262 §10.2.11).
fn compute_sfd_metadata(function_data: &ast::FunctionData) -> bytecode::generator::FunctionSfdMetadata {
fn compute_sfd_metadata(
function_data: &ast::FunctionData,
identifiers: &ast::IdentifierArena,
) -> bytecode::generator::FunctionSfdMetadata {
let body_scope = match &function_data.body.inner {
ast::StatementKind::FunctionBody { scope, .. } => Some(scope),
_ => None,
@ -2401,13 +2499,14 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> bytecode::generato
let mut parameters_in_environment: usize = 0;
for parameter in &function_data.parameters {
match &parameter.binding {
ast::FunctionParameterBinding::Identifier(ident) => {
ast::FunctionParameterBinding::Identifier(id) => {
let ident = &identifiers[*id];
if parameter_names.insert(ident.name.to_utf16_string()) && !ident.is_local() {
parameters_in_environment += 1;
}
}
ast::FunctionParameterBinding::BindingPattern(pattern) => {
for_each_binding_pattern_identifier(pattern, &mut |ident| {
for_each_binding_pattern_identifier(pattern, identifiers, &mut |ident| {
if parameter_names.insert(ident.name.to_utf16_string()) && !ident.is_local() {
parameters_in_environment += 1;
}
@ -2467,7 +2566,7 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> bytecode::generato
}
// §10.2.11 step 30: lexical environment.
let non_local_lex_count = count_non_local_lex_declarations(body_scope);
let non_local_lex_count = count_non_local_lex_declarations(body_scope, identifiers);
if strict {
// Lex env == var env == function env.
function_environment_bindings_count += non_local_lex_count;
@ -2489,7 +2588,7 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> bytecode::generato
}
}
let non_local_lex_count = count_non_local_lex_declarations(body_scope);
let non_local_lex_count = count_non_local_lex_declarations(body_scope, identifiers);
if strict {
// Lex env == var env.
var_environment_bindings_count += non_local_lex_count;
@ -2542,7 +2641,7 @@ unsafe fn write_sfd_metadata(sfd_ptr: *mut c_void, metadata: &bytecode::generato
/// Count non-local lexically-declared identifiers in a function body scope.
/// Returns the count (used for environment sizing in the function_environment_needed
/// computation).
fn count_non_local_lex_declarations(scope: &Rc<RefCell<ast::ScopeData>>) -> usize {
fn count_non_local_lex_declarations(scope: &Rc<RefCell<ast::ScopeData>>, identifiers: &ast::IdentifierArena) -> usize {
let sd = scope.borrow();
let mut count = 0;
for child in &sd.children {
@ -2551,18 +2650,18 @@ fn count_non_local_lex_declarations(scope: &Rc<RefCell<ast::ScopeData>>) -> usiz
use parser::DeclarationKind;
if vd.kind == DeclarationKind::Let || vd.kind == DeclarationKind::Const {
for declaration in &vd.declarations {
count_non_local_names_in_target(&declaration.target, &mut count);
count_non_local_names_in_target(&declaration.target, &mut count, identifiers);
}
}
}
ast::StatementKind::UsingDeclaration(declarations) => {
for declaration in declarations.iter() {
count_non_local_names_in_target(&declaration.target, &mut count);
count_non_local_names_in_target(&declaration.target, &mut count, identifiers);
}
}
ast::StatementKind::ClassDeclaration(class_data) => {
if let Some(ref name_ident) = class_data.name
&& !name_ident.is_local()
if let Some(name_ident) = class_data.name
&& !identifiers[name_ident].is_local()
{
count += 1;
}
@ -2573,33 +2672,41 @@ fn count_non_local_lex_declarations(scope: &Rc<RefCell<ast::ScopeData>>) -> usiz
count
}
fn count_non_local_names_in_target(target: &ast::VariableDeclaratorTarget, count: &mut usize) {
fn count_non_local_names_in_target(
target: &ast::VariableDeclaratorTarget,
count: &mut usize,
identifiers: &ast::IdentifierArena,
) {
match target {
ast::VariableDeclaratorTarget::Identifier(ident) => {
if !ident.is_local() {
ast::VariableDeclaratorTarget::Identifier(id) => {
if !identifiers[*id].is_local() {
*count += 1;
}
}
ast::VariableDeclaratorTarget::BindingPattern(pattern) => {
count_non_local_names_in_binding_pattern(pattern, count);
count_non_local_names_in_binding_pattern(pattern, count, identifiers);
}
}
}
fn count_non_local_names_in_binding_pattern(pattern: &ast::BindingPattern, count: &mut usize) {
fn count_non_local_names_in_binding_pattern(
pattern: &ast::BindingPattern,
count: &mut usize,
identifiers: &ast::IdentifierArena,
) {
for entry in &pattern.entries {
match &entry.alias {
Some(ast::BindingEntryAlias::Identifier(ident)) => {
if !ident.is_local() {
Some(ast::BindingEntryAlias::Identifier(id)) => {
if !identifiers[*id].is_local() {
*count += 1;
}
}
Some(ast::BindingEntryAlias::BindingPattern(sub)) => {
count_non_local_names_in_binding_pattern(sub, count);
count_non_local_names_in_binding_pattern(sub, count, identifiers);
}
None => {
if let Some(ast::BindingEntryName::Identifier(ident)) = &entry.name
&& !ident.is_local()
if let Some(ast::BindingEntryName::Identifier(id)) = &entry.name
&& !identifiers[*id].is_local()
{
*count += 1;
}
@ -2609,16 +2716,20 @@ fn count_non_local_names_in_binding_pattern(pattern: &ast::BindingPattern, count
}
}
fn for_each_binding_pattern_identifier(pattern: &ast::BindingPattern, callback: &mut dyn FnMut(&Rc<ast::Identifier>)) {
fn for_each_binding_pattern_identifier(
pattern: &ast::BindingPattern,
identifiers: &ast::IdentifierArena,
callback: &mut dyn FnMut(&ast::Identifier),
) {
for entry in &pattern.entries {
match &entry.alias {
Some(ast::BindingEntryAlias::Identifier(ident)) => callback(ident),
Some(ast::BindingEntryAlias::Identifier(id)) => callback(&identifiers[*id]),
Some(ast::BindingEntryAlias::BindingPattern(sub)) => {
for_each_binding_pattern_identifier(sub, callback);
for_each_binding_pattern_identifier(sub, identifiers, callback);
}
None => {
if let Some(ast::BindingEntryName::Identifier(ident)) = &entry.name {
callback(ident);
if let Some(ast::BindingEntryName::Identifier(id)) = &entry.name {
callback(&identifiers[*id]);
}
}
Some(ast::BindingEntryAlias::MemberExpression(_)) => {}

View file

@ -34,11 +34,10 @@
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use crate::ast::{
BindingPattern, Expression, ExpressionKind, FunctionData, FunctionId, FunctionParameter, FunctionTable, Identifier,
PrivateIdentifier, ProgramData, ScopeData, SharedUtf16String, SourceRange, Statement, StatementKind, Utf16String,
AstArena, BindingPattern, Expression, ExpressionKind, FunctionData, FunctionId, FunctionParameter, FunctionTable,
Identifier, IdentifierId, PrivateIdentifier, ProgramData, ScopeData, SharedUtf16String, SourceRange, Statement,
StatementKind, Utf16String,
};
use crate::lexer::{Lexer, ch};
use crate::scope_collector::{ScopeCollector, ScopeCollectorState};
@ -74,7 +73,7 @@ pub struct ParamInfo {
pub name: Utf16String,
pub is_rest: bool,
pub is_from_pattern: bool,
pub identifier: Option<Rc<Identifier>>,
pub identifier: Option<IdentifierId>,
}
/// Result of parsing a property key (object literal or class element).
@ -251,7 +250,7 @@ pub struct Parser<'a> {
/// Caller drains this after calling parse_binding_pattern.
/// Each entry is (name, identifier) — allows scope analysis to annotate
/// binding pattern identifiers with local variable info.
pub(crate) pattern_bound_names: Vec<(SharedUtf16String, Rc<Identifier>)>,
pub(crate) pattern_bound_names: Vec<(SharedUtf16String, IdentifierId)>,
/// Set during synthesize_binding_pattern to allow MemberExpressions as binding targets.
allow_member_expressions: bool,
@ -285,6 +284,10 @@ pub struct Parser<'a> {
/// Side table owning all FunctionData produced during parsing.
pub function_table: FunctionTable,
/// Bulk storage for identifiers, scopes, and interned strings. Replaces
/// the old per-node `Rc<Identifier>` heap allocations.
pub arena: AstArena,
/// Stack of nested function ids discovered while parsing each active function.
///
/// When the parser finishes a function, the top list becomes that
@ -339,6 +342,7 @@ impl<'a> Parser<'a> {
scope_collector: ScopeCollector::new(),
exported_names: HashSet::new(),
function_table: FunctionTable::new(),
arena: AstArena::new(),
function_context_stack: Vec::new(),
arrow_function_failed_positions: HashSet::new(),
}
@ -382,8 +386,9 @@ impl<'a> Parser<'a> {
function_id
}
pub(crate) fn make_identifier(&self, start: Position, name: impl Into<SharedUtf16String>) -> Rc<Identifier> {
Rc::new(Identifier::new(self.range_from(start), name.into()))
pub(crate) fn make_identifier(&mut self, start: Position, name: impl Into<SharedUtf16String>) -> IdentifierId {
let identifier = Identifier::new(self.range_from(start), name.into());
self.arena.identifiers.insert(identifier)
}
pub(crate) fn token_identifier_name(&self, token: &Token) -> SharedUtf16String {
@ -417,11 +422,15 @@ impl<'a> Parser<'a> {
info_index += 1;
(pi.name.clone(), pi.is_rest, pi.is_from_pattern)
} else {
(id.name.to_utf16_string(), parameter.is_rest, false)
(
self.arena.identifiers[*id].name.to_utf16_string(),
parameter.is_rest,
false,
)
};
entries.push(ParameterEntry {
name,
identifier: Some(id.clone()),
identifier: Some(*id),
is_rest,
is_from_pattern,
is_first_from_pattern: false,
@ -445,7 +454,7 @@ impl<'a> Parser<'a> {
let pi = &parameter_info[info_index];
entries.push(ParameterEntry {
name: pi.name.clone(),
identifier: pi.identifier.clone(),
identifier: pi.identifier,
is_rest: pi.is_rest,
is_from_pattern: true,
is_first_from_pattern: false,
@ -455,8 +464,10 @@ impl<'a> Parser<'a> {
}
}
}
self.scope_collector
.set_function_parameters(&entries, has_parameter_expressions);
let Self {
scope_collector, arena, ..
} = self;
scope_collector.set_function_parameters(&entries, has_parameter_expressions, &arena.identifiers);
}
// === Token access ===
@ -1097,7 +1108,7 @@ impl<'a> Parser<'a> {
// Collect all declared names at module level.
let mut declared_names: HashSet<Utf16String> = HashSet::new();
for child in children {
collect_module_declared_names(child, &mut declared_names);
collect_module_declared_names(child, &mut declared_names, &self.arena.identifiers);
}
// Check each export's local bindings.
@ -1352,32 +1363,40 @@ fn is_use_strict(raw: &[u16]) -> bool {
}
/// Collect all binding names introduced by a variable declarator target.
fn collect_binding_names(target: &crate::ast::VariableDeclaratorTarget, names: &mut HashSet<Utf16String>) {
fn collect_binding_names(
target: &crate::ast::VariableDeclaratorTarget,
names: &mut HashSet<Utf16String>,
identifiers: &crate::ast::IdentifierArena,
) {
match target {
crate::ast::VariableDeclaratorTarget::Identifier(identifier) => {
names.insert(identifier.name.to_utf16_string());
names.insert(identifiers[*identifier].name.to_utf16_string());
}
crate::ast::VariableDeclaratorTarget::BindingPattern(pattern) => {
collect_binding_pattern_names(pattern, names);
collect_binding_pattern_names(pattern, names, identifiers);
}
}
}
/// Collect all binding names from a binding pattern (object or array destructuring).
fn collect_binding_pattern_names(pattern: &crate::ast::BindingPattern, names: &mut HashSet<Utf16String>) {
fn collect_binding_pattern_names(
pattern: &crate::ast::BindingPattern,
names: &mut HashSet<Utf16String>,
identifiers: &crate::ast::IdentifierArena,
) {
for entry in &pattern.entries {
if let Some(ref alias) = entry.alias {
match alias {
crate::ast::BindingEntryAlias::Identifier(identifier) => {
names.insert(identifier.name.to_utf16_string());
names.insert(identifiers[*identifier].name.to_utf16_string());
}
crate::ast::BindingEntryAlias::BindingPattern(nested) => {
collect_binding_pattern_names(nested, names);
collect_binding_pattern_names(nested, names, identifiers);
}
crate::ast::BindingEntryAlias::MemberExpression(_) => {}
}
} else if let Some(crate::ast::BindingEntryName::Identifier(identifier)) = &entry.name {
names.insert(identifier.name.to_utf16_string());
names.insert(identifiers[*identifier].name.to_utf16_string());
}
}
}
@ -1386,23 +1405,27 @@ fn collect_binding_pattern_names(pattern: &crate::ast::BindingPattern, names: &m
/// This includes lexical declarations (let/const), function/class declarations, imports,
/// and also `var` declarations which hoist to module scope even when nested inside
/// blocks, loops, if/else, etc.
fn collect_module_declared_names(statement: &crate::ast::Statement, names: &mut HashSet<Utf16String>) {
fn collect_module_declared_names(
statement: &crate::ast::Statement,
names: &mut HashSet<Utf16String>,
identifiers: &crate::ast::IdentifierArena,
) {
use crate::ast::*;
match &statement.inner {
StatementKind::VariableDeclaration(data) => {
// All top-level declarations (var, let, const) are module-scoped.
for decl in &data.declarations {
collect_binding_names(&decl.target, names);
collect_binding_names(&decl.target, names, identifiers);
}
}
StatementKind::FunctionDeclaration(data) => {
if let Some(ref name) = data.name {
names.insert(name.name.to_utf16_string());
if let Some(name) = data.name {
names.insert(identifiers[name].name.to_utf16_string());
}
}
StatementKind::ClassDeclaration(data) => {
if let Some(ref name) = data.name {
names.insert(name.name.to_utf16_string());
if let Some(name) = data.name {
names.insert(identifiers[name].name.to_utf16_string());
}
}
StatementKind::Import(data) => {
@ -1412,12 +1435,12 @@ fn collect_module_declared_names(statement: &crate::ast::Statement, names: &mut
}
StatementKind::Export(data) => {
if let Some(ref stmt) = data.statement {
collect_module_declared_names(stmt, names);
collect_module_declared_names(stmt, names, identifiers);
}
}
// For any other statement, recurse to find hoisted var declarations.
_ => {
collect_var_declared_names(statement, names);
collect_var_declared_names(statement, names, identifiers);
}
}
}
@ -1426,61 +1449,65 @@ fn collect_module_declared_names(statement: &crate::ast::Statement, names: &mut
/// `var` declarations are hoisted to the enclosing function/module scope,
/// so we must walk into blocks, loops, if/else, switch, try/catch, etc.
/// We do NOT walk into function bodies since `var` does not hoist out of functions.
fn collect_var_declared_names(statement: &crate::ast::Statement, names: &mut HashSet<Utf16String>) {
fn collect_var_declared_names(
statement: &crate::ast::Statement,
names: &mut HashSet<Utf16String>,
identifiers: &crate::ast::IdentifierArena,
) {
use crate::ast::*;
match &statement.inner {
StatementKind::VariableDeclaration(data) if matches!(data.kind, DeclarationKind::Var) => {
for decl in &data.declarations {
collect_binding_names(&decl.target, names);
collect_binding_names(&decl.target, names, identifiers);
}
}
StatementKind::Block(scope) => {
for child in &scope.borrow().children {
collect_var_declared_names(child, names);
collect_var_declared_names(child, names, identifiers);
}
}
StatementKind::If(data) => {
collect_var_declared_names(&data.consequent, names);
collect_var_declared_names(&data.consequent, names, identifiers);
if let Some(ref alt) = data.alternate {
collect_var_declared_names(alt, names);
collect_var_declared_names(alt, names, identifiers);
}
}
StatementKind::While(data) | StatementKind::DoWhile(data) => {
collect_var_declared_names(&data.body, names);
collect_var_declared_names(&data.body, names, identifiers);
}
StatementKind::With(data) => {
collect_var_declared_names(&data.body, names);
collect_var_declared_names(&data.body, names, identifiers);
}
StatementKind::For(data) => {
if let Some(ForInit::Declaration(ref decl)) = data.init {
collect_var_declared_names(decl, names);
collect_var_declared_names(decl, names, identifiers);
}
collect_var_declared_names(&data.body, names);
collect_var_declared_names(&data.body, names, identifiers);
}
StatementKind::ForInOf(data) => {
if let ForInOfLhs::Declaration(ref decl) = data.lhs {
collect_var_declared_names(decl, names);
collect_var_declared_names(decl, names, identifiers);
}
collect_var_declared_names(&data.body, names);
collect_var_declared_names(&data.body, names, identifiers);
}
StatementKind::Switch(data) => {
for case in &data.cases {
for child in &case.scope.borrow().children {
collect_var_declared_names(child, names);
collect_var_declared_names(child, names, identifiers);
}
}
}
StatementKind::Try(data) => {
collect_var_declared_names(&data.block, names);
collect_var_declared_names(&data.block, names, identifiers);
if let Some(ref handler) = data.handler {
collect_var_declared_names(&handler.body, names);
collect_var_declared_names(&handler.body, names, identifiers);
}
if let Some(ref finalizer) = data.finalizer {
collect_var_declared_names(finalizer, names);
collect_var_declared_names(finalizer, names, identifiers);
}
}
StatementKind::Labelled(data) => {
collect_var_declared_names(&data.item, names);
collect_var_declared_names(&data.item, names, identifiers);
}
// Don't recurse into functions (var doesn't hoist out of functions).
// Don't recurse into let/const (they are block-scoped, not hoisted).

View file

@ -8,7 +8,6 @@
use std::cell::Cell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use crate::ast::*;
use crate::lexer::ch;
@ -18,7 +17,7 @@ use crate::parser::{
};
use crate::token::TokenType;
fn expression_into_identifier(expression: Expression) -> Rc<Identifier> {
fn expression_into_identifier(expression: Expression) -> IdentifierId {
match expression.inner {
ExpressionKind::Identifier(id) => id,
_ => unreachable!("expected Identifier expression"),
@ -26,12 +25,12 @@ fn expression_into_identifier(expression: Expression) -> Rc<Identifier> {
}
/// Extract bound names from a declaration for export statements.
fn get_declaration_export_names(statement: &Statement) -> Vec<Utf16String> {
fn get_declaration_export_names(statement: &Statement, identifiers: &IdentifierArena) -> Vec<Utf16String> {
match &statement.inner {
StatementKind::VariableDeclaration(vd) => {
let mut names = Vec::new();
for declaration in &vd.declarations {
collect_declarator_names(&declaration.target, &mut names);
collect_declarator_names(&declaration.target, &mut names, identifiers);
}
names
}
@ -39,21 +38,21 @@ fn get_declaration_export_names(statement: &Statement) -> Vec<Utf16String> {
let mut names = Vec::new();
for declaration in declarations.iter() {
if let VariableDeclaratorTarget::Identifier(id) = &declaration.target {
names.push(id.name.to_utf16_string());
names.push(identifiers[*id].name.to_utf16_string());
}
}
names
}
StatementKind::FunctionDeclaration(fd) => {
if let Some(ref name) = fd.name {
vec![name.name.to_utf16_string()]
if let Some(name) = fd.name {
vec![identifiers[name].name.to_utf16_string()]
} else {
Vec::new()
}
}
StatementKind::ClassDeclaration(class) => {
if let Some(ref name) = class.name {
vec![name.name.to_utf16_string()]
if let Some(name) = class.name {
vec![identifiers[name].name.to_utf16_string()]
} else {
Vec::new()
}
@ -62,24 +61,28 @@ fn get_declaration_export_names(statement: &Statement) -> Vec<Utf16String> {
}
}
fn collect_declarator_names(target: &VariableDeclaratorTarget, names: &mut Vec<Utf16String>) {
fn collect_declarator_names(
target: &VariableDeclaratorTarget,
names: &mut Vec<Utf16String>,
identifiers: &IdentifierArena,
) {
match target {
VariableDeclaratorTarget::Identifier(id) => names.push(id.name.to_utf16_string()),
VariableDeclaratorTarget::BindingPattern(pat) => collect_pattern_names(pat, names),
VariableDeclaratorTarget::Identifier(id) => names.push(identifiers[*id].name.to_utf16_string()),
VariableDeclaratorTarget::BindingPattern(pat) => collect_pattern_names(pat, names, identifiers),
}
}
fn collect_pattern_names(pat: &BindingPattern, names: &mut Vec<Utf16String>) {
fn collect_pattern_names(pat: &BindingPattern, names: &mut Vec<Utf16String>, identifiers: &IdentifierArena) {
for entry in &pat.entries {
match &entry.alias {
Some(BindingEntryAlias::Identifier(id)) => names.push(id.name.to_utf16_string()),
Some(BindingEntryAlias::BindingPattern(nested)) => collect_pattern_names(nested, names),
Some(BindingEntryAlias::Identifier(id)) => names.push(identifiers[*id].name.to_utf16_string()),
Some(BindingEntryAlias::BindingPattern(nested)) => collect_pattern_names(nested, names, identifiers),
_ => {}
}
if entry.alias.is_none()
&& let Some(BindingEntryName::Identifier(id)) = &entry.name
{
names.push(id.name.to_utf16_string());
names.push(identifiers[*id].name.to_utf16_string());
}
}
}
@ -150,11 +153,15 @@ impl Parser<'_> {
let id = self.make_identifier(declaration_start, name.clone());
if kind == DeclarationKind::Var {
self.scope_collector.add_var_declaration(
&[(name.as_slice(), Some(id.clone()))],
let Self {
scope_collector, arena, ..
} = self;
scope_collector.add_var_declaration(
&[(name.as_slice(), Some(id))],
declaration_line,
declaration_column,
Some(DeclarationKind::Var),
&arena.identifiers,
);
} else {
self.scope_collector.add_lexical_declaration(
@ -162,7 +169,10 @@ impl Parser<'_> {
declaration_line,
declaration_column,
);
self.scope_collector.register_identifier(id.clone(), Some(kind));
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, Some(kind), &arena.identifiers);
}
VariableDeclaratorTarget::Identifier(id)
@ -188,14 +198,20 @@ impl Parser<'_> {
// Register bound names with scope collector.
if kind == DeclarationKind::Var {
let entries: Vec<(&[u16], Option<Rc<Identifier>>)> = bound_names
.iter()
.map(|(n, id)| (n.as_slice(), Some(id.clone())))
.collect();
let entries: Vec<(&[u16], Option<IdentifierId>)> =
bound_names.iter().map(|(n, id)| (n.as_slice(), Some(*id))).collect();
// NOTE: Binding pattern identifiers don't get declaration_kind,
// matching C++ behavior where only simple identifiers do.
self.scope_collector
.add_var_declaration(&entries, declaration_line, declaration_column, None);
let Self {
scope_collector, arena, ..
} = self;
scope_collector.add_var_declaration(
&entries,
declaration_line,
declaration_column,
None,
&arena.identifiers,
);
} else {
let refs: Vec<&[u16]> = bound_names.iter().map(|(n, _)| n.as_slice()).collect();
self.scope_collector
@ -204,8 +220,11 @@ impl Parser<'_> {
// so they get is_local() annotations.
// NOTE: C++ does not pass declaration_kind for binding pattern identifiers,
// only for simple identifier declarations.
let Self {
scope_collector, arena, ..
} = self;
for (_name, id) in &bound_names {
self.scope_collector.register_identifier(id.clone(), None);
scope_collector.register_identifier(*id, None, &arena.identifiers);
}
}
@ -301,7 +320,10 @@ impl Parser<'_> {
.add_lexical_declaration(&[name.as_slice()], declaration_line, declaration_column);
// C++ calls parse_lexical_binding() without declaration_kind for using,
// so we pass None to match.
self.scope_collector.register_identifier(id.clone(), None);
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, None, &arena.identifiers);
let init = if self.match_token(TokenType::Equals) {
self.consume();
@ -378,13 +400,18 @@ impl Parser<'_> {
self.last_function_kind = kind;
// Register function declaration in parent scope (before opening function scope).
self.scope_collector.add_function_declaration(
let strict = self.flags.strict_mode;
let Self {
scope_collector, arena, ..
} = self;
scope_collector.add_function_declaration(
&fn_name,
name.clone(),
name,
kind,
self.flags.strict_mode,
strict,
declaration_line,
declaration_column,
&arena.identifiers,
);
let fn_name_for_scope = if fn_name.is_empty() {
@ -404,7 +431,7 @@ impl Parser<'_> {
start,
saved_might_need_arguments,
);
let decl_name = fd.name.clone();
let decl_name = fd.name;
let decl_kind = fd.kind;
let function_id = self.insert_function_data(fd);
self.statement(
@ -452,8 +479,11 @@ impl Parser<'_> {
// This must happen before open_function_scope so that the identifier group
// exists with declaration_kind=None, preventing later var declarations
// with the same name from setting a spurious declaration_kind.
if let Some(ref id) = name {
self.scope_collector.register_identifier(id.clone(), None);
if let Some(id) = name {
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, None, &arena.identifiers);
}
// Open function scope (function expression name is bound within its own scope).
@ -482,7 +512,7 @@ impl Parser<'_> {
#[allow(clippy::too_many_arguments)]
fn parse_function_common(
&mut self,
name: Option<Rc<Identifier>>,
name: Option<IdentifierId>,
fn_name: &[u16],
kind: FunctionKind,
is_async: bool,
@ -704,13 +734,14 @@ impl Parser<'_> {
// The inner class scope (opened/closed inside parse_class_expression)
// binds the name for self-reference. The outer scope needs the name
// registered as a lexical declaration so it's visible to sibling code.
if let Some(ref name_ident) = data.name {
self.scope_collector.add_lexical_declaration(
&[&name_ident.name as &[u16]],
start.line,
start.column,
);
self.scope_collector.register_identifier(name_ident.clone(), None);
if let Some(name_ident) = data.name {
let name_slice = self.arena.identifiers[name_ident].name.clone();
self.scope_collector
.add_lexical_declaration(&[name_slice.as_slice()], start.line, start.column);
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(name_ident, None, &arena.identifiers);
}
self.statement(start, StatementKind::ClassDeclaration(data))
}
@ -735,7 +766,10 @@ impl Parser<'_> {
if has_super {
let arguments_name = Utf16String::from(utf16!("args"));
let arguments_ref = Rc::new(Identifier::new(self.range_from(start), arguments_name.clone().into()));
let arguments_ref = self
.arena
.identifiers
.insert(Identifier::new(self.range_from(start), arguments_name.clone().into()));
let arguments_expression = self.expression(start, ExpressionKind::Identifier(arguments_ref));
let super_call = self.expression(
@ -754,7 +788,10 @@ impl Parser<'_> {
StatementKind::Block(ScopeData::shared_with_children(vec![return_statement])),
);
let arguments_binding = Rc::new(Identifier::new(self.range_from(start), arguments_name.into()));
let arguments_binding = self
.arena
.identifiers
.insert(Identifier::new(self.range_from(start), arguments_name.into()));
let parameters = vec![FunctionParameter {
binding: FunctionParameterBinding::Identifier(arguments_binding),
default_value: None,
@ -1238,15 +1275,16 @@ impl Parser<'_> {
if !is_arrow {
self.check_identifier_name_for_assignment_validity(&value, false);
}
let id = Rc::new(Identifier::new(
self.range_from(formal_parameters_start),
self.token_identifier_name(&token),
));
let name = self.token_identifier_name(&token);
let id = self
.arena
.identifiers
.insert(Identifier::new(self.range_from(formal_parameters_start), name));
parameter_info.push(ParamInfo {
name: value,
is_rest: rest,
is_from_pattern: false,
identifier: Some(id.clone()),
identifier: Some(id),
});
(FunctionParameterBinding::Identifier(id), false)
} else if self.match_token(TokenType::CurlyOpen) || self.match_token(TokenType::BracketOpen) {
@ -1263,7 +1301,7 @@ impl Parser<'_> {
} else {
self.expected("parameter name");
self.consume();
let id = Rc::new(Identifier::new(
let id = self.arena.identifiers.insert(Identifier::new(
self.range_from(parameter_start),
Utf16String::default().into(),
));
@ -1444,7 +1482,10 @@ impl Parser<'_> {
let token = self.consume_property_key_token();
let (value, _has_octal) = self.parse_string_value(&token);
let id = self.make_identifier(entry_start, value);
self.scope_collector.register_identifier(id.clone(), None);
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, None, &arena.identifiers);
entry_name = Some(BindingEntryName::Identifier(id));
} else if self.match_token(TokenType::BigIntLiteral) {
let token = self.consume_property_key_token();
@ -1455,14 +1496,20 @@ impl Parser<'_> {
value.to_vec()
};
let id = self.make_identifier(entry_start, name_value);
self.scope_collector.register_identifier(id.clone(), None);
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, None, &arena.identifiers);
entry_name = Some(BindingEntryName::Identifier(id));
} else {
let token = self.consume_property_key_token();
let name = self.token_identifier_name(&token);
entry_name_value = name.clone();
let id = self.make_identifier(entry_start, name);
self.scope_collector.register_identifier(id.clone(), None);
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, None, &arena.identifiers);
entry_name = Some(BindingEntryName::Identifier(id));
}
} else if self.match_token(TokenType::BracketOpen) {
@ -1504,7 +1551,7 @@ impl Parser<'_> {
let token = self.consume();
let name = self.token_identifier_name(&token);
let id = self.make_identifier(alias_start, name.clone());
self.pattern_bound_names.push((name, id.clone()));
self.pattern_bound_names.push((name, id));
entry_alias = Some(BindingEntryAlias::Identifier(id));
} else {
self.expected("identifier or binding pattern");
@ -1518,8 +1565,8 @@ impl Parser<'_> {
if entry_is_keyword {
self.syntax_error("Binding pattern target may not be a reserved word");
}
if let Some(BindingEntryName::Identifier(ref id)) = entry_name {
self.pattern_bound_names.push((entry_name_value, id.clone()));
if let Some(BindingEntryName::Identifier(id)) = entry_name {
self.pattern_bound_names.push((entry_name_value, id));
}
}
}
@ -1537,7 +1584,8 @@ impl Parser<'_> {
entry_alias = Some(BindingEntryAlias::MemberExpression(Box::new(expression)));
} else if Self::is_identifier(&expression) {
let id = expression_into_identifier(expression);
self.pattern_bound_names.push((id.name.clone(), id.clone()));
let name = self.arena.identifiers[id].name.clone();
self.pattern_bound_names.push((name, id));
entry_alias = Some(BindingEntryAlias::Identifier(id));
} else {
self.syntax_error("Invalid destructuring assignment target");
@ -1551,7 +1599,7 @@ impl Parser<'_> {
let token = self.consume();
let name = self.token_identifier_name(&token);
let id = self.make_identifier(alias_start, name.clone());
self.pattern_bound_names.push((name, id.clone()));
self.pattern_bound_names.push((name, id));
entry_alias = Some(BindingEntryAlias::Identifier(id));
} else {
self.expected("identifier or binding pattern");
@ -1792,9 +1840,9 @@ impl Parser<'_> {
let declaration = self.parse_function_declaration_for_export(has_default_name);
if !has_default_name
&& let StatementKind::FunctionDeclaration(ref fd) = declaration.inner
&& let Some(ref name_id) = fd.name
&& let Some(name_id) = fd.name
{
local_name = Some(name_id.name.to_utf16_string());
local_name = Some(self.arena.identifiers[name_id].name.to_utf16_string());
}
statement = Some(Box::new(declaration));
} else if self.match_token(TokenType::Class) {
@ -1802,9 +1850,9 @@ impl Parser<'_> {
if next.token_type != TokenType::CurlyOpen && next.token_type != TokenType::Extends {
let declaration = self.parse_class_declaration();
if let StatementKind::ClassDeclaration(ref class) = declaration.inner
&& let Some(ref name_id) = class.name
&& let Some(name_id) = class.name
{
local_name = Some(name_id.name.to_utf16_string());
local_name = Some(self.arena.identifiers[name_id].name.to_utf16_string());
}
statement = Some(Box::new(declaration));
} else {
@ -1877,7 +1925,7 @@ impl Parser<'_> {
check_for_from = FromSpecifier::Required;
} else if self.match_declaration() {
let declaration = self.parse_declaration();
let names = get_declaration_export_names(&declaration);
let names = get_declaration_export_names(&declaration, &self.arena.identifiers);
for name in &names {
entries.push(ExportEntry {
kind: ExportEntryKind::NamedExport,
@ -1888,7 +1936,7 @@ impl Parser<'_> {
statement = Some(Box::new(declaration));
} else if self.match_token(TokenType::Var) {
let var_declaration = self.parse_variable_declaration(false);
let names = get_declaration_export_names(&var_declaration);
let names = get_declaration_export_names(&var_declaration, &self.arena.identifiers);
for name in &names {
entries.push(ExportEntry {
kind: ExportEntryKind::NamedExport,

View file

@ -7,7 +7,6 @@
//! Expression parsing: primary, secondary (binary/postfix), unary, and
//! precedence climbing.
use std::rc::Rc;
use std::sync::Arc;
use crate::ast::*;
@ -178,16 +177,19 @@ impl Parser<'_> {
// C++ checks for freestanding `arguments` references here (after
// parse_primary_expression), NOT during consume(). This avoids
// falsely flagging parameter names like `function f(arguments)`.
if let ExpressionKind::Identifier(ref id) = expression.inner
&& id.name == utf16!("arguments")
if let ExpressionKind::Identifier(id) = expression.inner
&& self.arena.identifiers[id].name == utf16!("arguments")
{
// https://tc39.es/ecma262/#sec-class-static-initialization-blocks
// It is a Syntax Error if ContainsArguments of ClassStaticBlockBody is true.
if self.flags.in_class_static_init_block {
self.syntax_error("'arguments' is not allowed in class static initialization blocks");
} else if !self.flags.strict_mode && !self.scope_collector.has_declaration_in_current_function(&id.name) {
self.scope_collector
.set_contains_access_to_arguments_object_in_non_strict_mode();
} else {
let name = self.arena.identifiers[id].name.clone();
if !self.flags.strict_mode && !self.scope_collector.has_declaration_in_current_function(&name) {
self.scope_collector
.set_contains_access_to_arguments_object_in_non_strict_mode();
}
}
}
@ -447,8 +449,12 @@ impl Parser<'_> {
return (arrow, false);
}
let token = self.consume_and_check_identifier();
let id = self.make_identifier(start, self.token_identifier_name(&token));
self.scope_collector.register_identifier(id.clone(), None);
let name = self.token_identifier_name(&token);
let id = self.make_identifier(start, name);
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, None, &arena.identifiers);
(self.expression(start, ExpressionKind::Identifier(id)), true)
}
@ -583,14 +589,22 @@ impl Parser<'_> {
self.syntax_error("'yield' is not allowed as an identifier in this context");
}
let token = self.consume_and_check_identifier();
let id = self.make_identifier(start, self.token_identifier_name(&token));
self.scope_collector.register_identifier(id.clone(), None);
let name = self.token_identifier_name(&token);
let id = self.make_identifier(start, name);
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, None, &arena.identifiers);
(self.expression(start, ExpressionKind::Identifier(id)), true)
} else if self.match_token(TokenType::EscapedKeyword) {
self.syntax_error("Keyword must not contain escaped characters");
let token = self.consume_and_check_identifier();
let id = self.make_identifier(start, self.token_identifier_name(&token));
self.scope_collector.register_identifier(id.clone(), None);
let name = self.token_identifier_name(&token);
let id = self.make_identifier(start, name);
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, None, &arena.identifiers);
(self.expression(start, ExpressionKind::Identifier(id)), true)
} else {
self.expected("primary expression");
@ -801,9 +815,14 @@ impl Parser<'_> {
// Register synthesized identifiers with the scope collector so
// they get resolved as locals during analyze().
let bound_names: Vec<_> = self.pattern_bound_names.drain(..).collect();
for (name, id) in &bound_names {
for (name, _id) in &bound_names {
self.check_identifier_name_for_assignment_validity(name, false);
self.scope_collector.register_identifier(id.clone(), None);
}
let Self {
scope_collector, arena, ..
} = self;
for (_name, id) in &bound_names {
scope_collector.register_identifier(*id, None, &arena.identifiers);
}
self.pattern_bound_names = saved_bound_names;
self.consume();
@ -829,8 +848,9 @@ impl Parser<'_> {
if !Self::is_simple_assignment_target(&lhs, allow_call, self.flags.strict_mode) {
self.syntax_error("Invalid left-hand side in assignment");
}
if let ExpressionKind::Identifier(ref id) = lhs.inner {
self.check_identifier_name_for_assignment_validity(&id.name, false);
if let ExpressionKind::Identifier(id) = lhs.inner {
let name = self.arena.identifiers[id].name.clone();
self.check_identifier_name_for_assignment_validity(&name, false);
}
self.consume();
let rhs = self.parse_expression(min_precedence, Associativity::Right, forbidden);
@ -966,8 +986,9 @@ impl Parser<'_> {
if !Self::is_simple_assignment_target(&lhs, true, self.flags.strict_mode) {
self.syntax_error("Invalid left-hand side in postfix operation");
}
if let ExpressionKind::Identifier(ref id) = lhs.inner {
self.check_identifier_name_for_assignment_validity(&id.name, false);
if let ExpressionKind::Identifier(id) = lhs.inner {
let name = self.arena.identifiers[id].name.clone();
self.check_identifier_name_for_assignment_validity(&name, false);
}
self.consume();
(
@ -986,8 +1007,9 @@ impl Parser<'_> {
if !Self::is_simple_assignment_target(&lhs, true, self.flags.strict_mode) {
self.syntax_error("Invalid left-hand side in postfix operation");
}
if let ExpressionKind::Identifier(ref id) = lhs.inner {
self.check_identifier_name_for_assignment_validity(&id.name, false);
if let ExpressionKind::Identifier(id) = lhs.inner {
let name = self.arena.identifiers[id].name.clone();
self.check_identifier_name_for_assignment_validity(&name, false);
}
self.consume();
(
@ -1021,8 +1043,9 @@ impl Parser<'_> {
if !Self::is_simple_assignment_target(&expression, true, self.flags.strict_mode) {
self.syntax_error("Invalid left-hand side in prefix operation");
}
if let ExpressionKind::Identifier(ref id) = expression.inner {
self.check_identifier_name_for_assignment_validity(&id.name, false);
if let ExpressionKind::Identifier(id) = expression.inner {
let name = self.arena.identifiers[id].name.clone();
self.check_identifier_name_for_assignment_validity(&name, false);
}
self.expression(
start,
@ -1039,8 +1062,9 @@ impl Parser<'_> {
if !Self::is_simple_assignment_target(&expression, true, self.flags.strict_mode) {
self.syntax_error("Invalid left-hand side in prefix operation");
}
if let ExpressionKind::Identifier(ref id) = expression.inner {
self.check_identifier_name_for_assignment_validity(&id.name, false);
if let ExpressionKind::Identifier(id) = expression.inner {
let name = self.arena.identifiers[id].name.clone();
self.check_identifier_name_for_assignment_validity(&name, false);
}
self.expression(
start,
@ -1188,8 +1212,8 @@ impl Parser<'_> {
let arguments = self.parse_arguments();
// Check the actual callee expression kind, matching C++ which does
// is<Identifier>(callee) && callee.string() == "eval".
if let ExpressionKind::Identifier(ref id) = callee.inner
&& id.name == utf16!("eval")
if let ExpressionKind::Identifier(id) = callee.inner
&& self.arena.identifiers[id].name == utf16!("eval")
{
self.scope_collector.set_contains_direct_call_to_eval();
self.scope_collector.set_uses_this();
@ -1596,7 +1620,12 @@ impl Parser<'_> {
&& let Some(kv) = &key_value
{
let id = self.make_identifier(obj_start, kv.clone());
self.scope_collector.register_identifier(id.clone(), None);
{
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, None, &arena.identifiers);
}
let value = self.expression(obj_start, ExpressionKind::Identifier(id));
self.consume(); // consume '='
// NB: Add a syntax error for CoverInitializedName. This error will
@ -1627,7 +1656,12 @@ impl Parser<'_> {
self.syntax_error(&format!("'{name_str}' is a reserved keyword"));
}
let id = self.make_identifier(obj_start, kv);
self.scope_collector.register_identifier(id.clone(), None);
{
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, None, &arena.identifiers);
}
let value = self.expression(obj_start, ExpressionKind::Identifier(id));
return ObjectProperty {
range: self.range_from(obj_start),
@ -2230,10 +2264,13 @@ impl Parser<'_> {
self.syntax_error("'await' is a reserved identifier in async functions");
}
// C++ uses rule_start (arrow function start, which is `async` for async arrows).
let binding = Rc::new(Identifier::new(self.range_from(start), value.clone().into()));
let binding = self
.arena
.identifiers
.insert(Identifier::new(self.range_from(start), value.clone().into()));
parsed = ParsedParameters {
parameters: vec![FunctionParameter {
binding: FunctionParameterBinding::Identifier(binding.clone()),
binding: FunctionParameterBinding::Identifier(binding),
default_value: None,
is_rest: false,
}],

View file

@ -7,7 +7,6 @@
//! Statement parsing: if, for, while, switch, try, etc.
use std::collections::HashSet;
use std::rc::Rc;
use crate::ast::*;
use crate::parser::{Associativity, ForbiddenTokens, PRECEDENCE_COMMA, Parser, Position};
@ -463,16 +462,16 @@ impl Parser<'_> {
// are still valid here.
if init_starts_with_async_keyword
&& let LocalForInit::Expression(ref expression) = init
&& let ExpressionKind::Identifier(ref ident) = expression.inner
&& ident.name == utf16!("async")
&& let ExpressionKind::Identifier(ident) = expression.inner
&& self.arena.identifiers[ident].name == utf16!("async")
{
self.syntax_error("for-of statement may not use 'async' as the left-hand side");
}
// https://tc39.es/ecma262/#sec-for-in-and-for-of-statements
if let LocalForInit::Expression(ref expression) = init
&& let ExpressionKind::Member(ref data) = expression.inner
&& let ExpressionKind::Identifier(ref ident) = data.object.inner
&& ident.name == utf16!("let")
&& let ExpressionKind::Identifier(ident) = data.object.inner
&& self.arena.identifiers[ident].name == utf16!("let")
{
self.syntax_error("For of statement may not start with let.");
}
@ -735,8 +734,12 @@ impl Parser<'_> {
self.scope_collector.add_catch_parameter_pattern(&bound_names);
// Register each binding pattern identifier for scope analysis
// so they get is_local() annotations (matching variable declarations).
for (_name, id) in &self.pattern_bound_names {
self.scope_collector.register_identifier(id.clone(), None);
let pattern_bound_ids: Vec<IdentifierId> = self.pattern_bound_names.iter().map(|(_, id)| *id).collect();
let Self {
scope_collector, arena, ..
} = self;
for id in pattern_bound_ids {
scope_collector.register_identifier(id, None, &arena.identifiers);
}
Some(CatchBinding::BindingPattern(pattern))
} else if self.match_identifier() {
@ -744,12 +747,16 @@ impl Parser<'_> {
let token = self.consume();
let value = self.token_value(&token).to_vec();
self.check_identifier_name_for_assignment_validity(&value, false);
let id = Rc::new(Identifier::new(
self.range_from(parameter_start),
self.token_identifier_name(&token),
));
self.scope_collector.register_identifier(id.clone(), None);
self.scope_collector.add_catch_parameter_identifier(&value, id.clone());
let name = self.token_identifier_name(&token);
let id = self
.arena
.identifiers
.insert(Identifier::new(self.range_from(parameter_start), name));
let Self {
scope_collector, arena, ..
} = self;
scope_collector.register_identifier(id, None, &arena.identifiers);
scope_collector.add_catch_parameter_identifier(&value, id);
Some(CatchBinding::Identifier(id))
} else {
self.expected("catch parameter");
@ -763,7 +770,7 @@ impl Parser<'_> {
// Collect catch parameter names for post-body validation.
let catch_names: Vec<SharedUtf16String> = match &parameter {
Some(CatchBinding::Identifier(id)) => vec![id.name.clone()],
Some(CatchBinding::Identifier(id)) => vec![self.arena.identifiers[*id].name.clone()],
Some(CatchBinding::BindingPattern(_)) => self.pattern_bound_names.iter().map(|(n, _)| n.clone()).collect(),
None => Vec::new(),
};
@ -780,9 +787,10 @@ impl Parser<'_> {
match &child.inner {
StatementKind::VariableDeclaration(vd) if vd.kind != DeclarationKind::Var => {
for decl in &vd.declarations {
if let VariableDeclaratorTarget::Identifier(ref id) = decl.target {
if let VariableDeclaratorTarget::Identifier(id) = decl.target {
let id_name = self.arena.identifiers[id].name.clone();
for cn in &catch_names {
if cn.as_slice() == id.name.as_slice() {
if cn.as_slice() == id_name.as_slice() {
let n = String::from_utf16_lossy(cn);
self.syntax_error(&format!(
"Identifier '{n}' already declared as catch parameter"
@ -793,18 +801,20 @@ impl Parser<'_> {
}
}
StatementKind::FunctionDeclaration(fd) if fd.name.is_some() => {
let id = fd.name.as_ref().unwrap();
let id = fd.name.unwrap();
let id_name = self.arena.identifiers[id].name.clone();
for cn in &catch_names {
if cn.as_slice() == id.name.as_slice() {
if cn.as_slice() == id_name.as_slice() {
let n = String::from_utf16_lossy(cn);
self.syntax_error(&format!("Identifier '{n}' already declared as catch parameter"));
}
}
}
StatementKind::ClassDeclaration(data) => {
if let Some(ref id) = data.name {
if let Some(id) = data.name {
let id_name = self.arena.identifiers[id].name.clone();
for cn in &catch_names {
if cn.as_slice() == id.name.as_slice() {
if cn.as_slice() == id_name.as_slice() {
let n = String::from_utf16_lossy(cn);
self.syntax_error(&format!("Identifier '{n}' already declared as catch parameter"));
}
@ -986,9 +996,14 @@ impl Parser<'_> {
let pattern = self.synthesize_binding_pattern(init_start);
let bound_names: Vec<_> = self.pattern_bound_names.drain(..).collect();
for (name, id) in &bound_names {
for (name, _id) in &bound_names {
self.check_identifier_name_for_assignment_validity(name, false);
self.scope_collector.register_identifier(id.clone(), None);
}
let Self {
scope_collector, arena, ..
} = self;
for (_name, id) in &bound_names {
scope_collector.register_identifier(*id, None, &arena.identifiers);
}
ForInOfLhs::Pattern(pattern)
} else {

View file

@ -55,8 +55,8 @@ use std::collections::HashMap;
use std::rc::Rc;
use crate::ast::{
FunctionScopeData, Identifier, LocalBinding, LocalVarKind, LocalVariable, ScopeData, SharedUtf16String,
Utf16String, VarToInit,
FunctionScopeData, IdentifierArena, IdentifierId, LocalBinding, LocalVarKind, LocalVariable, ScopeData,
SharedUtf16String, Utf16String, VarToInit,
};
use crate::parser::{DeclarationKind, FunctionKind, ParseError, ProgramType};
use crate::u32_from_usize;
@ -139,7 +139,7 @@ struct ScopeVariable {
flags: VarFlags,
/// The Identifier AST node for the `var` declaration (used to build
/// FunctionScopeData). None if not a var.
var_identifier: Option<Rc<Identifier>>,
var_identifier: Option<IdentifierId>,
}
/// Groups all Identifier AST nodes that share the same name within a scope.
@ -154,7 +154,7 @@ struct IdentifierGroup {
/// (prevents local variable optimization since `with` can shadow anything).
used_inside_with_statement: bool,
/// All Identifier AST nodes with this name in this scope.
identifiers: Vec<Rc<Identifier>>,
identifiers: Vec<IdentifierId>,
/// If this name was declared (var/let/const), tracks the declaration kind
/// so we can annotate each Identifier AST node.
declaration_kind: Option<DeclarationKind>,
@ -178,7 +178,7 @@ struct ParameterName {
/// Entry describing a single parameter binding for scope analysis.
pub struct ParameterEntry {
pub name: Utf16String,
pub identifier: Option<Rc<Identifier>>,
pub identifier: Option<IdentifierId>,
pub is_rest: bool,
pub is_from_pattern: bool,
pub is_first_from_pattern: bool,
@ -525,17 +525,18 @@ impl ScopeCollector {
// class static initializer).
pub fn add_var_declaration(
&mut self,
bound_names: &[(&[u16], Option<Rc<Identifier>>)],
bound_names: &[(&[u16], Option<IdentifierId>)],
declaration_line: u32,
declaration_column: u32,
declaration_kind: Option<DeclarationKind>,
identifiers: &IdentifierArena,
) {
let index = self.current.expect("no current scope");
for (name, identifier) in bound_names {
// Register the declaration identifier so it participates in scope analysis.
if let Some(id) = identifier {
self.register_identifier(id.clone(), declaration_kind);
self.register_identifier(*id, declaration_kind, identifiers);
}
let mut scope_index = index;
@ -546,7 +547,7 @@ impl ScopeCollector {
}
let var = self.records[scope_index].variable(name);
var.flags |= VarFlags::VAR;
var.var_identifier = identifier.clone();
var.var_identifier = *identifier;
if self.records[scope_index].is_top_level() {
break;
}
@ -562,21 +563,23 @@ impl ScopeCollector {
// a `var` binding to the enclosing function scope.
// In strict mode (or for async/generator functions), block-scoped function
// declarations are treated as lexical bindings and are NOT Annex-B hoisted.
#[allow(clippy::too_many_arguments)]
pub fn add_function_declaration(
&mut self,
name: &[u16],
name_identifier: Option<Rc<Identifier>>,
name_identifier: Option<IdentifierId>,
function_kind: FunctionKind,
strict_mode: bool,
declaration_line: u32,
declaration_column: u32,
identifiers: &IdentifierArena,
) {
let index = self.current.expect("no current scope");
let scope_level = self.records[index].scope_level;
// Register the name identifier so it participates in scope analysis.
if let Some(ref id) = name_identifier {
self.register_identifier(id.clone(), None);
if let Some(id) = name_identifier {
self.register_identifier(id, None, identifiers);
}
if scope_level != ScopeLevel::NotTopLevel && scope_level != ScopeLevel::ModuleTopLevel {
@ -628,7 +631,7 @@ impl ScopeCollector {
}
}
pub fn add_catch_parameter_identifier(&mut self, name: &[u16], identifier: Rc<Identifier>) {
pub fn add_catch_parameter_identifier(&mut self, name: &[u16], identifier: IdentifierId) {
let index = self.current.expect("no current scope");
let var = self.records[index].variable(name);
var.flags |= VarFlags::VAR | VarFlags::BOUND | VarFlags::CATCH_PARAMETER;
@ -637,13 +640,19 @@ impl ScopeCollector {
// === Identifier registration ===
pub fn register_identifier(&mut self, id: Rc<Identifier>, declaration_kind: Option<DeclarationKind>) {
pub fn register_identifier(
&mut self,
id: IdentifierId,
declaration_kind: Option<DeclarationKind>,
identifiers: &IdentifierArena,
) {
let index = self.current.expect("no current scope");
let name = identifiers[id].name.clone();
self.records[index]
.identifier_groups
.entry(id.name.clone())
.entry(name)
.and_modify(|group| {
group.identifiers.push(id.clone());
group.identifiers.push(id);
if declaration_kind.is_some() && group.declaration_kind.is_none() {
group.declaration_kind = declaration_kind;
}
@ -658,7 +667,12 @@ impl ScopeCollector {
// === Function parameters ===
pub fn set_function_parameters(&mut self, entries: &[ParameterEntry], has_parameter_expressions: bool) {
pub fn set_function_parameters(
&mut self,
entries: &[ParameterEntry],
has_parameter_expressions: bool,
identifiers: &IdentifierArena,
) {
let index = self.current.expect("no current scope");
self.records[index].has_function_parameters = true;
self.records[index].has_parameter_expressions = has_parameter_expressions;
@ -681,8 +695,8 @@ impl ScopeCollector {
is_rest: entry.is_rest,
});
}
if let Some(ref id) = entry.identifier {
self.register_identifier(id.clone(), None);
if let Some(id) = entry.identifier {
self.register_identifier(id, None, identifiers);
}
let var = self.records[index].variables.entry(entry.name.clone()).or_default();
var.flags |= VarFlags::PARAMETER_CANDIDATE | VarFlags::FORBIDDEN_LEXICAL;
@ -845,32 +859,38 @@ impl ScopeCollector {
// === Post-parse analysis ===
pub fn analyze(&mut self, initiated_by_eval: bool) {
self.analyze_inner(initiated_by_eval, false);
pub fn analyze(&mut self, initiated_by_eval: bool, identifiers: &IdentifierArena) {
self.analyze_inner(initiated_by_eval, false, identifiers);
}
/// Like analyze(), but suppresses marking identifiers as global.
/// Used for dynamic functions (new Function(...)) where the source is
/// parsed as a Script but identifiers must not use GetGlobal/SetGlobal,
/// matching the C++ path which parses as a FunctionExpression.
pub fn analyze_as_dynamic_function(&mut self) {
self.analyze_inner(false, true);
pub fn analyze_as_dynamic_function(&mut self, identifiers: &IdentifierArena) {
self.analyze_inner(false, true, identifiers);
}
fn analyze_inner(&mut self, initiated_by_eval: bool, suppress_globals: bool) {
fn analyze_inner(&mut self, initiated_by_eval: bool, suppress_globals: bool, identifiers: &IdentifierArena) {
if !self.records.is_empty() {
self.analyze_recursive(0, initiated_by_eval, suppress_globals);
self.analyze_recursive(0, initiated_by_eval, suppress_globals, identifiers);
}
}
/// Analyze a scope and all its descendants, bottom-up.
/// Children are analyzed first so that unresolved identifiers bubble up
/// to their parent, and eval poisoning propagates outward.
fn analyze_recursive(&mut self, index: usize, initiated_by_eval: bool, suppress_globals: bool) {
fn analyze_recursive(
&mut self,
index: usize,
initiated_by_eval: bool,
suppress_globals: bool,
identifiers: &IdentifierArena,
) {
// Process children first (bottom-up traversal).
let children = std::mem::take(&mut self.records[index].children);
for child_index in children {
self.analyze_recursive(child_index, initiated_by_eval, suppress_globals);
self.analyze_recursive(child_index, initiated_by_eval, suppress_globals, identifiers);
}
// Steps 1-3 must run even for scopes without scope_data (e.g. catch
@ -881,9 +901,15 @@ impl ScopeCollector {
// 1. Propagate eval() flags from children to parent.
Self::propagate_eval_poisoning(&mut self.records, index);
// 2. Match identifier references to declarations; optimize as locals.
Self::resolve_identifiers(&mut self.records, index, initiated_by_eval, suppress_globals);
Self::resolve_identifiers(
&mut self.records,
index,
initiated_by_eval,
suppress_globals,
identifiers,
);
// 3. Annex B: hoist block-scoped functions to enclosing function scope.
Self::hoist_functions(&mut self.records, index);
Self::hoist_functions(&mut self.records, index, identifiers);
// 4. For function-like scopes, build the var declaration list that
// the bytecode generator uses to initialize function-scoped variables.
@ -893,7 +919,7 @@ impl ScopeCollector {
|| st == ScopeType::ClassStaticInit
|| st == ScopeType::ClassField;
if needs_fsd {
Self::build_function_scope_data(&self.records, index);
Self::build_function_scope_data(&self.records, index, identifiers);
}
}
}
@ -931,7 +957,13 @@ impl ScopeCollector {
/// - It's NOT captured by a nested function
/// - It's NOT used inside a `with` statement
/// - The scope chain is NOT poisoned by `eval()`
fn resolve_identifiers(records: &mut [ScopeRecord], index: usize, initiated_by_eval: bool, suppress_globals: bool) {
fn resolve_identifiers(
records: &mut [ScopeRecord],
index: usize,
initiated_by_eval: bool,
suppress_globals: bool,
identifiers: &IdentifierArena,
) {
// identifier_groups is an IndexMap, so iteration is in source order
// of first reference. Local variable indices follow that order.
let groups = std::mem::take(&mut records[index].identifier_groups);
@ -941,7 +973,7 @@ impl ScopeCollector {
// so the bytecode generator knows how to handle TDZ checks, etc.
if let Some(dk) = group.declaration_kind {
for id in &group.identifiers {
id.declaration_kind.set(Some(dk));
identifiers[*id].declaration_kind.set(Some(dk));
}
}
@ -1018,7 +1050,7 @@ impl ScopeCollector {
&& var_flags.intersects(VarFlags::BOUND)
{
for id in &group.identifiers {
id.is_inside_scope_with_eval.set(true);
identifiers[*id].is_inside_scope_with_eval.set(true);
}
}
@ -1046,8 +1078,9 @@ impl ScopeCollector {
let can_use_global = !(suppress_globals || group.used_inside_with_statement || initiated_by_eval);
if can_use_global {
for id in &group.identifiers {
if !id.is_inside_scope_with_eval.get() {
id.is_global.set(true);
let identifier = &identifiers[*id];
if !identifier.is_inside_scope_with_eval.get() {
identifier.is_global.set(true);
}
}
}
@ -1100,8 +1133,9 @@ impl ScopeCollector {
let argument_index = records[ls].get_parameter_index(&name);
if let Some(ai) = argument_index {
for id in &group.identifiers {
id.local_index.set(ai);
id.local_type.set(Some(crate::ast::LocalType::Argument));
let identifier = &identifiers[*id];
identifier.local_index.set(ai);
identifier.local_type.set(Some(crate::ast::LocalType::Argument));
}
} else {
let lvi = u32_from_usize(sd.local_variables.len());
@ -1110,8 +1144,9 @@ impl ScopeCollector {
kind: LocalVarKind::Var,
});
for id in &group.identifiers {
id.local_index.set(lvi);
id.local_type.set(Some(crate::ast::LocalType::Variable));
let identifier = &identifiers[*id];
identifier.local_index.set(lvi);
identifier.local_type.set(Some(crate::ast::LocalType::Variable));
}
}
} else {
@ -1122,8 +1157,9 @@ impl ScopeCollector {
kind,
});
for id in &group.identifiers {
id.local_index.set(lvi);
id.local_type.set(Some(crate::ast::LocalType::Variable));
let identifier = &identifiers[*id];
identifier.local_index.set(lvi);
identifier.local_type.set(Some(crate::ast::LocalType::Variable));
}
}
}
@ -1142,7 +1178,7 @@ impl ScopeCollector {
if records[index].eval_in_current_function {
for id in &group.identifiers {
id.is_inside_scope_with_eval.set(true);
identifiers[*id].is_inside_scope_with_eval.set(true);
}
}
@ -1172,7 +1208,7 @@ impl ScopeCollector {
// - vars_to_initialize: var-declared names and their local variable indices
// - functions_to_initialize: function declarations to instantiate (in reverse order)
// - arguments object metadata (has_argument_parameter, has_function_named_arguments, etc.)
fn build_function_scope_data(records: &[ScopeRecord], index: usize) {
fn build_function_scope_data(records: &[ScopeRecord], index: usize, identifiers: &IdentifierArena) {
let record = &records[index];
let Some(ref scope_data) = record.scope_data else {
return;
@ -1202,15 +1238,15 @@ impl ScopeCollector {
let sd = scope_data.borrow();
for (i, child) in sd.children.iter().enumerate() {
if let crate::ast::StatementKind::FunctionDeclaration(ref fd) = child.inner
&& let Some(ref name_ident) = fd.name
&& let Some(name_ident) = fd.name
{
last_position.insert(name_ident.name.clone(), i);
last_position.insert(identifiers[name_ident].name.clone(), i);
}
}
for (i, child) in sd.children.iter().enumerate() {
if let crate::ast::StatementKind::FunctionDeclaration(ref fd) = child.inner
&& let Some(ref name_ident) = fd.name
&& last_position.get(&name_ident.name).copied() == Some(i)
&& let Some(name_ident) = fd.name
&& last_position.get(&identifiers[name_ident].name).copied() == Some(i)
{
functions_to_initialize.push(crate::ast::FunctionToInit { child_index: i });
}
@ -1227,7 +1263,8 @@ impl ScopeCollector {
let is_parameter = var.flags.intersects(VarFlags::FORBIDDEN_LEXICAL);
let is_function_name = last_position.contains_key(name.as_slice());
let local_info = if let Some(ref ident) = var.var_identifier {
let local_info = if let Some(ident_id) = var.var_identifier {
let ident = &identifiers[ident_id];
if ident.is_local() {
Some(LocalBinding {
local_type: ident.local_type.get().expect("is_local() implies local_type is Some"),
@ -1311,7 +1348,7 @@ impl ScopeCollector {
/// The function propagates upward through block scopes until it reaches
/// a function/program scope (top level) or is blocked by an existing
/// lexical or function declaration with the same name.
fn hoist_functions(records: &mut [ScopeRecord], index: usize) {
fn hoist_functions(records: &mut [ScopeRecord], index: usize, identifiers: &IdentifierArena) {
let functions = std::mem::take(&mut records[index].functions_to_hoist);
for function in functions {
@ -1346,7 +1383,7 @@ impl ScopeCollector {
let bs = block_scope.borrow();
for child in &bs.children {
if let crate::ast::StatementKind::FunctionDeclaration(ref fd) = child.inner
&& fd.name.as_ref().is_some_and(|n| n.name == function.name)
&& fd.name.is_some_and(|n| identifiers[n].name == function.name)
{
fd.is_hoisted.set(true);
}