use std::collections::VecDeque;
use crate::{
ecmascript::{
Agent, ArgumentsList, AsyncGeneratorHeapData, AsyncGeneratorState, AwaitReactionRecord,
BUILTIN_STRING_MEMORY, ECMAScriptFunction, Environment, FunctionAstRef, GeneratorHeapData,
GeneratorState, JsResult, OrdinaryFunctionCreateParams, PrivateEnvironment, Promise,
PromiseCapability, PromiseReactionHandler, PropertyDescriptor, PropertyKey,
ProtoIntrinsics, SourceCode, SuspendedGeneratorState, ThisMode, Value, inner_promise_then,
make_constructor, ordinary_function_create, ordinary_object_create_with_intrinsics,
ordinary_populate_from_constructor, set_function_name, try_define_property_or_throw,
unwrap_try,
},
engine::{Bindable, Executable, ExecutionResult, GcScope, NoGcScope, Scopable, Vm},
heap::{ArenaAccess, ArenaAccessMut, CreateHeapData},
};
use oxc_ast::ast::{self};
pub(crate) trait ContainsExpression {
fn contains_expression(&self) -> bool;
}
impl ContainsExpression for ast::FormalParameters<'_> {
fn contains_expression(&self) -> bool {
self.items.iter().any(|p| p.contains_expression())
|| self
.rest
.as_ref()
.is_some_and(|rest| rest.contains_expression())
}
}
impl ContainsExpression for ast::FormalParameter<'_> {
fn contains_expression(&self) -> bool {
self.initializer.is_some() ||
self.pattern.contains_expression()
}
}
impl ContainsExpression for ast::FormalParameterRest<'_> {
fn contains_expression(&self) -> bool {
self.rest.argument.contains_expression()
}
}
impl ContainsExpression for ast::BindingPattern<'_> {
fn contains_expression(&self) -> bool {
match &self {
ast::BindingPattern::BindingIdentifier(_) => false,
ast::BindingPattern::ObjectPattern(pattern) => pattern.contains_expression(),
ast::BindingPattern::ArrayPattern(pattern) => pattern.contains_expression(),
ast::BindingPattern::AssignmentPattern(_) => true,
}
}
}
impl ContainsExpression for ast::ObjectPattern<'_> {
fn contains_expression(&self) -> bool {
for property in &self.properties {
if property.computed || property.value.contains_expression() {
return true;
}
}
if let Some(rest) = &self.rest {
debug_assert!(!rest.argument.contains_expression());
}
false
}
}
impl ContainsExpression for ast::ArrayPattern<'_> {
fn contains_expression(&self) -> bool {
for pattern in self.elements.iter().flatten() {
if pattern.contains_expression() {
return true;
}
}
if let Some(rest) = &self.rest {
rest.argument.contains_expression()
} else {
false
}
}
}
pub(crate) fn instantiate_ordinary_function_object<'a>(
agent: &mut Agent,
function: &ast::Function<'_>,
env: Environment<'a>,
private_env: Option<PrivateEnvironment<'a>>,
gc: NoGcScope<'a, '_>,
) -> ECMAScriptFunction<'a> {
let pk_name = if let Some(id) = &function.id {
let name = &id.name;
PropertyKey::from_str(agent, name, gc)
} else {
PropertyKey::from(BUILTIN_STRING_MEMORY.default)
};
let source_text = function.span;
let params = OrdinaryFunctionCreateParams {
function_prototype: None,
source_code: None,
source_text,
ast: FunctionAstRef::from(function),
lexical_this: false,
env,
private_env,
};
let f = ordinary_function_create(agent, params, gc);
set_function_name(agent, f, pk_name, None, gc);
if !function.r#async && !function.generator {
make_constructor(agent, f, None, None, gc);
}
if function.generator {
let prototype = ordinary_object_create_with_intrinsics(
agent,
ProtoIntrinsics::Object,
Some(if function.r#async {
agent
.current_realm_record()
.intrinsics()
.async_generator_prototype()
.into()
} else {
agent
.current_realm_record()
.intrinsics()
.generator_prototype()
.into()
}),
gc,
);
unwrap_try(try_define_property_or_throw(
agent,
f,
BUILTIN_STRING_MEMORY.prototype.to_property_key(),
PropertyDescriptor {
value: Some(prototype.unbind().into()),
writable: Some(true),
enumerable: Some(false),
configurable: Some(false),
..Default::default()
},
None,
gc,
));
}
f
}
pub(crate) struct CompileFunctionBodyData<'a> {
pub(crate) ast: FunctionAstRef<'a>,
pub(crate) source_code: SourceCode<'a>,
pub(crate) is_strict: bool,
pub(crate) is_lexical: bool,
}
impl<'a> CompileFunctionBodyData<'a> {
fn new(agent: &mut Agent, function: ECMAScriptFunction<'a>, gc: NoGcScope<'a, '_>) -> Self {
let ast = function.get_ast(agent, gc);
let source_code = function.get_source_code(agent);
let is_strict = function.is_strict(agent);
let this_mode = function.get_this_mode(agent);
CompileFunctionBodyData {
ast,
source_code,
is_strict,
is_lexical: this_mode == ThisMode::Lexical,
}
}
}
pub(crate) fn evaluate_function_body<'gc>(
agent: &mut Agent,
function_object: ECMAScriptFunction,
arguments_list: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let arguments_list = arguments_list.bind(gc.nogc());
let function_object = function_object.bind(gc.nogc());
let exe = if let Some(exe) = function_object.get(agent).compiled_bytecode {
exe.bind(gc.nogc())
} else {
let data = CompileFunctionBodyData::new(agent, function_object, gc.nogc());
let exe = Executable::compile_function_body(agent, data, gc.nogc());
function_object.get_mut(agent).compiled_bytecode = Some(exe.unbind());
exe
};
let exe = exe.scope(agent, gc.nogc());
Vm::execute(agent, exe, Some(arguments_list.unbind().as_mut_slice()), gc).into_js_result()
}
pub(crate) fn evaluate_async_function_body<'a>(
agent: &mut Agent,
function_object: ECMAScriptFunction,
arguments_list: ArgumentsList,
mut gc: GcScope<'a, '_>,
) -> Promise<'a> {
let arguments_list = arguments_list.bind(gc.nogc());
let function_object = function_object.bind(gc.nogc());
let scoped_function_object = function_object.scope(agent, gc.nogc());
let PromiseCapability {
promise,
must_be_unresolved,
} = PromiseCapability::new(agent, gc.nogc());
let promise = promise.scope(agent, gc.nogc());
let exe = if let Some(exe) = function_object.get(agent).compiled_bytecode {
exe.bind(gc.nogc())
} else {
let data = CompileFunctionBodyData::new(agent, function_object, gc.nogc());
let exe = Executable::compile_function_body(agent, data, gc.nogc());
function_object.get_mut(agent).compiled_bytecode = Some(exe.unbind());
exe
};
let exe = exe.scope(agent, gc.nogc());
let result = Vm::execute(
agent,
exe,
Some(arguments_list.unbind().as_mut_slice()),
gc.reborrow(),
)
.unbind();
let gc = gc.into_nogc();
let result = result.bind(gc);
let promise = unsafe { promise.take(agent) }.bind(gc);
match result {
ExecutionResult::Return(result) => {
let promise_capability = PromiseCapability::from_promise(promise, must_be_unresolved);
unwrap_try(promise_capability.try_resolve(agent, result, gc));
}
ExecutionResult::Throw(err) => {
let promise_capability = PromiseCapability::from_promise(promise, must_be_unresolved);
promise_capability.reject(agent, err.value(), gc);
}
ExecutionResult::Await {
vm,
promise: resolve_promise,
} => {
let promise_capability = PromiseCapability::from_promise(promise, must_be_unresolved);
let handler = PromiseReactionHandler::Await(agent.heap.create(AwaitReactionRecord {
vm: Some(vm),
async_executable: Some(scoped_function_object.get(agent).into()),
execution_context: Some(agent.running_execution_context().clone()),
return_promise_capability: promise_capability,
}));
inner_promise_then(agent, resolve_promise, handler, handler, None, gc);
}
ExecutionResult::Yield { .. } => unreachable!(),
}
promise
}
pub(crate) fn evaluate_generator_body<'gc>(
agent: &mut Agent,
function_object: ECMAScriptFunction,
arguments_list: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let arguments_list = arguments_list.bind(gc.nogc());
let function_object = function_object.bind(gc.nogc());
let exe = if let Some(exe) = function_object.get(agent).compiled_bytecode {
exe.scope(agent, gc.nogc())
} else {
let data = CompileFunctionBodyData::new(agent, function_object, gc.nogc());
let exe = Executable::compile_function_body(agent, data, gc.nogc());
function_object.get_mut(agent).compiled_bytecode = Some(exe.unbind());
exe.scope(agent, gc.nogc())
};
let function_object = function_object.scope(agent, gc.nogc());
let vm = match Vm::execute(
agent,
exe.clone(),
Some(arguments_list.unbind().as_mut_slice()),
gc.reborrow(),
) {
ExecutionResult::Throw(err) => {
return Err(err.unbind().bind(gc.into_nogc()));
}
ExecutionResult::Yield { vm, yielded_value } => {
debug_assert!(yielded_value.is_undefined());
vm
}
_ => unreachable!(),
};
let g = agent
.heap
.create(GeneratorHeapData {
object_index: None,
generator_state: Some(GeneratorState::SuspendedStart(SuspendedGeneratorState {
vm,
executable: unsafe { exe.take(agent) },
execution_context: agent.running_execution_context().clone(),
})),
})
.bind(gc.nogc());
ordinary_populate_from_constructor(
agent,
g.unbind().into(),
unsafe { function_object.take(agent) }.into(),
ProtoIntrinsics::Generator,
gc,
)
.map(Into::into)
}
pub(crate) fn evaluate_async_generator_body<'gc>(
agent: &mut Agent,
function_object: ECMAScriptFunction,
arguments_list: ArgumentsList,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let function_object = function_object.bind(gc.nogc());
let arguments_list = arguments_list.bind(gc.nogc());
let exe = if let Some(exe) = function_object.get(agent).compiled_bytecode {
exe.scope(agent, gc.nogc())
} else {
let data = CompileFunctionBodyData::new(agent, function_object, gc.nogc());
let exe = Executable::compile_function_body(agent, data, gc.nogc());
function_object.get_mut(agent).compiled_bytecode = Some(exe.unbind());
exe.scope(agent, gc.nogc())
};
let function_object = function_object.scope(agent, gc.nogc());
let vm = match Vm::execute(
agent,
exe.clone(),
Some(arguments_list.unbind().as_mut_slice()),
gc.reborrow(),
) {
ExecutionResult::Throw(err) => {
return Err(err.unbind().bind(gc.into_nogc()));
}
ExecutionResult::Yield { vm, yielded_value } => {
debug_assert!(yielded_value.is_undefined());
vm
}
_ => unreachable!(),
};
let generator = agent
.heap
.create(AsyncGeneratorHeapData {
object_index: None,
executable: Some(unsafe { exe.take(agent) }),
async_generator_state: Some(AsyncGeneratorState::SuspendedStart {
vm,
execution_context: agent.running_execution_context().clone(),
queue: VecDeque::new(),
}),
})
.bind(gc.nogc());
ordinary_populate_from_constructor(
agent,
generator.unbind().into(),
unsafe { function_object.take(agent) }.into(),
ProtoIntrinsics::AsyncGenerator,
gc,
)
.map(Into::into)
}