use std::cell::Cell;
use ahash::{AHashMap, AHashSet};
use oxc_ast::ast::BindingIdentifier;
use oxc_ecmascript::BoundNames;
use oxc_span::Ident;
use crate::{
ecmascript::{
BUILTIN_STRING_MEMORY, Contains, ContainsExpression, ContainsSymbol, FunctionAstRef,
LexicallyDeclaredNames, LexicallyScopedDeclaration, LexicallyScopedDeclarations, Value,
VarDeclaredNames, VarScopedDeclaration, VarScopedDeclarations,
},
engine::{
Instruction,
bytecode::bytecode_compiler::{
CompileContext, ValueOutput, compile_context::StackVariable, variable_escapes_scope,
},
},
};
use super::{CompileEvaluation, simple_array_pattern};
pub(crate) fn instantiation<'s>(
ctx: &mut CompileContext<'_, 's, '_, '_>,
stack_variables: &mut Vec<StackVariable>,
func: FunctionAstRef<'s>,
strict: bool,
is_lexical: bool,
) {
let body = func.ecmascript_code();
let formals = func.formal_parameters();
let has_parameter_expressions = formals.contains_expression();
let mut functions = AHashMap::new();
body.var_scoped_declarations(&mut |d| {
let VarScopedDeclaration::Function(d) = d else {
return;
};
#[cfg(feature = "typescript")]
if d.declare {
return;
}
let f_name = d.id.as_ref().unwrap().name;
functions.insert(f_name, d);
});
let mut arguments_object_needed = true;
if arguments_object_needed && is_lexical {
arguments_object_needed = false;
} else if arguments_object_needed {
formals.bound_names(&mut |name| {
if arguments_object_needed && name.name == Ident::new_const("arguments") {
arguments_object_needed = false;
}
});
}
if arguments_object_needed && !has_parameter_expressions {
if functions.contains_key(&Ident::new_const("arguments")) {
arguments_object_needed = false;
} else {
body.lexically_declared_names(&mut |binding| {
if binding.name == Ident::new_const("arguments") {
arguments_object_needed = false;
}
})
}
}
if arguments_object_needed {
arguments_object_needed = ctx.scope_contains_direct_eval(func.scope_id())
|| Contains::contains(&func, ContainsSymbol::Arguments);
}
let mut parameter_names = AHashSet::with_capacity(formals.parameters_count());
let mut env_parameters = Vec::with_capacity(formals.parameters_count());
let mut stack_parameters = Vec::with_capacity(if arguments_object_needed {
0
} else {
formals.parameters_count()
});
let mut has_duplicates = false;
formals.bound_names(&mut |identifier| {
let added = parameter_names.insert(identifier.name);
if added {
if arguments_object_needed || variable_escapes_scope(ctx, identifier) {
env_parameters.push(identifier.name);
} else {
stack_parameters.push(identifier.symbol_id());
}
} else {
has_duplicates = true;
}
});
if !strict && has_parameter_expressions {
ctx.add_instruction(Instruction::EnterDeclarativeEnvironment);
}
for param_name in &stack_parameters {
stack_variables.push(ctx.push_stack_variable(*param_name, false));
}
for param_name in &env_parameters {
let param_name = ctx.create_string(param_name);
ctx.add_instruction_with_identifier(
Instruction::CreateMutableBinding,
param_name.to_property_key(),
);
if has_duplicates {
ctx.add_instruction_with_identifier(
Instruction::ResolveBinding,
param_name.to_property_key(),
);
ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined);
ctx.add_instruction(Instruction::InitializeReferencedBinding);
}
}
if arguments_object_needed {
ctx.add_instruction(Instruction::CreateUnmappedArgumentsObject);
if strict {
ctx.add_instruction_with_identifier(
Instruction::CreateImmutableBinding,
BUILTIN_STRING_MEMORY.arguments.to_property_key(),
);
} else {
ctx.add_instruction_with_identifier(
Instruction::CreateMutableBinding,
BUILTIN_STRING_MEMORY.arguments.to_property_key(),
);
}
ctx.add_instruction_with_identifier(
Instruction::ResolveBinding,
BUILTIN_STRING_MEMORY.arguments.to_property_key(),
);
ctx.add_instruction(Instruction::InitializeReferencedBinding);
parameter_names.insert("arguments".into());
}
if formals.has_parameter() {
if has_parameter_expressions {
let lexical_binding_state = ctx.lexical_binding_state;
ctx.lexical_binding_state = !has_duplicates;
let _ = formals.compile(ctx);
ctx.lexical_binding_state = lexical_binding_state;
} else {
simple_array_pattern(
ctx,
formals.items.iter().map(|param| Some(¶m.pattern)),
formals.rest.as_ref().map(|r| &r.rest),
formals.items.len(),
!has_duplicates,
);
}
}
ctx.add_instruction(Instruction::IteratorPop);
if !has_parameter_expressions {
let mut instantiated_var_names = parameter_names.clone();
body.var_declared_names(&mut |d| {
let n = d.name;
if !instantiated_var_names.insert(n) {
return;
}
let n_string = ctx.create_string(&n);
if variable_escapes_scope(ctx, d) {
ctx.add_instruction_with_identifier(
Instruction::CreateMutableBinding,
n_string.to_property_key(),
);
ctx.add_instruction_with_identifier(
Instruction::ResolveBinding,
n_string.to_property_key(),
);
ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined);
ctx.add_instruction(Instruction::InitializeReferencedBinding);
} else {
stack_variables.push(ctx.push_stack_variable(d.symbol_id(), false));
}
});
if !strict {
ctx.add_instruction(Instruction::EnterDeclarativeEnvironment);
}
} else {
let mut instantiated_var_names = AHashSet::new();
body.var_declared_names(&mut |d| {
let n = d.name;
if !instantiated_var_names.insert(n) {
return;
}
let n_string = ctx.create_string(&n);
if !parameter_names.contains(&n) || functions.contains_key(&n) {
ctx.add_instruction_with_constant(Instruction::LoadConstant, Value::Undefined);
} else {
ctx.add_instruction_with_identifier(
Instruction::ResolveBinding,
n_string.to_property_key(),
);
ctx.add_instruction(Instruction::GetValue);
ctx.add_instruction(Instruction::Load);
}
ctx.add_instruction_with_constant(Instruction::LoadConstant, n_string);
});
ctx.add_instruction_with_immediate_and_immediate(
Instruction::InitializeVariableEnvironment,
instantiated_var_names.len(),
strict.into(),
);
}
{
let is_constant_declaration = Cell::new(false);
let cb = &mut |identifier: &BindingIdentifier<'s>| {
if variable_escapes_scope(ctx, identifier) {
let dn = ctx.create_string(&identifier.name);
ctx.add_instruction_with_identifier(
if is_constant_declaration.get() {
Instruction::CreateImmutableBinding
} else {
Instruction::CreateMutableBinding
},
dn.to_property_key(),
);
} else {
stack_variables.push(ctx.push_stack_variable(identifier.symbol_id(), false));
}
};
let mut create_default_export = false;
body.lexically_scoped_declarations(&mut |d| {
match d {
LexicallyScopedDeclaration::Variable(decl) => {
is_constant_declaration.set(decl.kind.is_const());
decl.id.bound_names(cb);
is_constant_declaration.set(false);
}
LexicallyScopedDeclaration::Function(decl) => {
#[cfg(feature = "typescript")]
if decl.declare {
return;
}
decl.bound_names(cb);
}
LexicallyScopedDeclaration::Class(decl) => {
decl.bound_names(cb);
}
LexicallyScopedDeclaration::DefaultExport => {
create_default_export = true;
}
#[cfg(feature = "typescript")]
LexicallyScopedDeclaration::TSEnum(decl) => {
decl.id.bound_names(cb);
}
}
});
if create_default_export {
let dn = BUILTIN_STRING_MEMORY._default_;
ctx.add_instruction_with_identifier(
Instruction::CreateMutableBinding,
dn.to_property_key(),
);
}
}
for f in functions.values() {
f.compile(ctx);
let f = f.id.as_ref().unwrap().compile(ctx);
f.put_value(ctx, ValueOutput::Value).unwrap();
}
}