use oxc_ecmascript::BoundNames;
use crate::engine::bytecode::bytecode_compiler::{
StatementResult, compile_context::BlockEnvPrep, variable_escapes_scope,
};
use super::{
CompileContext, CompileEvaluation, Instruction, LexicallyScopedDeclaration,
LexicallyScopedDeclarations,
};
pub(super) fn instantiation<'s, 'gc>(
ctx: &mut CompileContext<'_, 's, 'gc, '_>,
code: &'s impl LexicallyScopedDeclarations<'s>,
cb: impl FnOnce(&mut CompileContext<'_, 's, 'gc, '_>) -> StatementResult<'gc>,
) -> StatementResult<'gc> {
let mut block_prep = Vec::new();
code.lexically_scoped_declarations(&mut |d| {
handle_block_lexically_scoped_declaration(ctx, &mut block_prep, d);
});
let result = cb(ctx);
for prop in block_prep.into_iter().rev() {
prop.exit(ctx);
}
result
}
fn handle_block_lexically_scoped_declaration<'s>(
ctx: &mut CompileContext<'_, 's, '_, '_>,
block_prep: &mut Vec<BlockEnvPrep>,
d: LexicallyScopedDeclaration<'s>,
) {
match d {
LexicallyScopedDeclaration::Variable(decl) if decl.kind.is_const() => {
decl.id.bound_names(&mut |identifier| {
if handle_lexical_variable(ctx, identifier, block_prep, None) {
let dn = ctx.create_string(&identifier.name);
ctx.add_instruction_with_identifier(
Instruction::CreateImmutableBinding,
dn.to_property_key(),
);
}
})
}
LexicallyScopedDeclaration::Variable(decl) => decl.id.bound_names(&mut |identifier| {
if handle_lexical_variable(ctx, identifier, block_prep, None) {
let dn = ctx.create_string(&identifier.name);
ctx.add_instruction_with_identifier(
Instruction::CreateMutableBinding,
dn.to_property_key(),
);
}
}),
LexicallyScopedDeclaration::Function(decl) => {
let Some(identifier) = &decl.id else {
unreachable!()
};
if handle_lexical_variable(ctx, identifier, block_prep, Some(decl)) {
let dn = ctx.create_string(&identifier.name);
ctx.add_instruction_with_identifier(
Instruction::CreateMutableBinding,
dn.to_property_key(),
);
decl.compile(ctx);
ctx.add_instruction_with_identifier(
Instruction::ResolveBinding,
dn.to_property_key(),
);
ctx.add_instruction(Instruction::InitializeReferencedBinding);
}
}
LexicallyScopedDeclaration::Class(decl) => {
decl.bound_names(&mut |identifier| {
if handle_lexical_variable(ctx, identifier, block_prep, None) {
let dn = ctx.create_string(&identifier.name);
ctx.add_instruction_with_identifier(
Instruction::CreateMutableBinding,
dn.to_property_key(),
);
}
});
}
LexicallyScopedDeclaration::DefaultExport => unreachable!(),
#[cfg(feature = "typescript")]
LexicallyScopedDeclaration::TSEnum(decl) => {
if handle_lexical_variable(ctx, &decl.id, block_prep, None) {
let dn = ctx.create_string(&decl.id.name);
ctx.add_instruction_with_identifier(
Instruction::CreateMutableBinding,
dn.to_property_key(),
);
}
}
}
}
fn handle_lexical_variable<'s>(
ctx: &mut CompileContext<'_, 's, '_, '_>,
identifier: &oxc_ast::ast::BindingIdentifier,
block_prep: &mut Vec<BlockEnvPrep>,
f: Option<&'s oxc_ast::ast::Function<'s>>,
) -> bool {
if variable_escapes_scope(ctx, identifier) {
if !block_prep.iter().any(|p| p.is_env()) {
block_prep.push(BlockEnvPrep::Env(ctx.enter_lexical_scope()));
}
true
} else {
let var = if let Some(f) = f {
f.compile(ctx);
ctx.push_stack_variable(identifier.symbol_id(), true)
} else {
ctx.push_stack_variable(identifier.symbol_id(), false)
};
block_prep.push(BlockEnvPrep::Var(var));
false
}
}