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:
parent
d9b9925914
commit
3e15e59cd1
11 changed files with 959 additions and 499 deletions
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(¶meter_state, &color_label(state, "rest"));
|
||||
match ¶meter.binding {
|
||||
FunctionParameterBinding::Identifier(ident) => {
|
||||
dump_identifier(ident, &ident.range, &child_state(¶meter_state, !has_default));
|
||||
FunctionParameterBinding::Identifier(id) => {
|
||||
dump_identifier_id(*id, &child_state(¶meter_state, !has_default));
|
||||
}
|
||||
FunctionParameterBinding::BindingPattern(pattern) => {
|
||||
dump_binding_pattern(pattern, &child_state(¶meter_state, !has_default), state);
|
||||
|
|
@ -1034,10 +1056,9 @@ fn dump_function(function_data: &FunctionData, class_name: &str, range: &SourceR
|
|||
}
|
||||
} else {
|
||||
match ¶meter.binding {
|
||||
FunctionParameterBinding::Identifier(ident) => {
|
||||
dump_identifier(
|
||||
ident,
|
||||
&ident.range,
|
||||
FunctionParameterBinding::Identifier(id) => {
|
||||
dump_identifier_id(
|
||||
*id,
|
||||
&child_state(¶meters_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
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ¶meter.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(_)) => {}
|
||||
|
|
|
|||
|
|
@ -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 = ¶meter_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).
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}],
|
||||
|
|
|
|||
|
|
@ -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 ¶meter {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue