use core::ptr::NonNull;
use std::borrow::Cow;
use oxc_ast::ast::{FormalParameters, FunctionBody};
use oxc_semantic::ScopeId;
use oxc_span::Span;
use crate::{
ecmascript::{
Agent, ArgumentsList, BUILTIN_STRING_MEMORY, ECMAScriptCodeEvaluationState,
ECMAScriptFunctionHeapData, Environment, ExceptionType, ExecutionContext, Function,
FunctionEnvironment, FunctionInternalProperties, InternalSlots, JsResult, Object,
OrdinaryObject, PrivateEnvironment, PropertyDescriptor, PropertyKey, ProtoIntrinsics,
Realm, ScriptOrModule, SourceCode, String, ThisBindingStatus, Value,
evaluate_async_function_body, evaluate_async_generator_body, evaluate_function_body,
evaluate_generator_body, function_handle, get_active_script_or_module,
new_function_environment, ordinary_create_from_constructor,
ordinary_object_create_with_intrinsics, to_object,
},
engine::{Bindable, Executable, GcScope, NoGcScope, Scopable, bindable_handle},
heap::{
ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access,
},
ndt,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ECMAScriptFunction<'a>(BaseIndex<'a, ECMAScriptFunctionHeapData<'static>>);
function_handle!(ECMAScriptFunction);
arena_vec_access!(
ECMAScriptFunction,
'a,
ECMAScriptFunctionHeapData,
ecmascript_functions
);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ConstructorStatus {
NonConstructor,
ConstructorFunction,
BaseClass,
DerivedClass,
}
impl ConstructorStatus {
pub(crate) fn is_constructor(self) -> bool {
self != ConstructorStatus::NonConstructor
}
pub(crate) fn is_class_constructor(self) -> bool {
matches!(
self,
ConstructorStatus::BaseClass | ConstructorStatus::DerivedClass
)
}
pub(crate) fn is_derived_class(self) -> bool {
self == ConstructorStatus::DerivedClass
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ThisMode {
Lexical,
Strict,
Global,
}
#[derive(Debug, Clone, Copy)]
enum FunctionAstPtr {
Function(NonNull<oxc_ast::ast::Function<'static>>),
AsyncFunction(NonNull<oxc_ast::ast::Function<'static>>),
Generator(NonNull<oxc_ast::ast::Function<'static>>),
AsyncGenerator(NonNull<oxc_ast::ast::Function<'static>>),
ClassConstructor(NonNull<oxc_ast::ast::Function<'static>>),
Arrow(NonNull<oxc_ast::ast::ArrowFunctionExpression<'static>>),
AsyncArrow(NonNull<oxc_ast::ast::ArrowFunctionExpression<'static>>),
}
impl FunctionAstPtr {
#[allow(unused_variables)]
pub(crate) unsafe fn as_ref<'a>(self) -> FunctionAstRef<'a> {
unsafe {
match self {
FunctionAstPtr::Function(ptr) => FunctionAstRef::Function(ptr.as_ref()),
FunctionAstPtr::AsyncFunction(ptr) => FunctionAstRef::AsyncFunction(ptr.as_ref()),
FunctionAstPtr::Generator(ptr) => FunctionAstRef::Generator(ptr.as_ref()),
FunctionAstPtr::AsyncGenerator(ptr) => FunctionAstRef::AsyncGenerator(ptr.as_ref()),
FunctionAstPtr::Arrow(ptr) => FunctionAstRef::Arrow(ptr.as_ref()),
FunctionAstPtr::AsyncArrow(ptr) => FunctionAstRef::AsyncArrow(ptr.as_ref()),
FunctionAstPtr::ClassConstructor(ptr) => {
FunctionAstRef::ClassConstructor(ptr.as_ref())
}
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum FunctionAstRef<'a> {
Function(&'a oxc_ast::ast::Function<'a>),
AsyncFunction(&'a oxc_ast::ast::Function<'a>),
Generator(&'a oxc_ast::ast::Function<'a>),
AsyncGenerator(&'a oxc_ast::ast::Function<'a>),
ClassConstructor(&'a oxc_ast::ast::Function<'a>),
Arrow(&'a oxc_ast::ast::ArrowFunctionExpression<'a>),
AsyncArrow(&'a oxc_ast::ast::ArrowFunctionExpression<'a>),
}
impl<'a> From<&'a oxc_ast::ast::Function<'a>> for FunctionAstRef<'a> {
fn from(f: &'a oxc_ast::ast::Function<'a>) -> Self {
match (f.r#async, f.generator) {
(true, true) => FunctionAstRef::AsyncGenerator(f),
(true, false) => FunctionAstRef::AsyncFunction(f),
(false, true) => FunctionAstRef::Generator(f),
(false, false) => FunctionAstRef::Function(f),
}
}
}
impl<'a> From<&'a oxc_ast::ast::ArrowFunctionExpression<'a>> for FunctionAstRef<'a> {
fn from(f: &'a oxc_ast::ast::ArrowFunctionExpression<'a>) -> Self {
match f.r#async {
true => FunctionAstRef::AsyncArrow(f),
false => FunctionAstRef::Arrow(f),
}
}
}
impl<'ast> FunctionAstRef<'ast> {
pub(crate) fn scope_id(&self) -> ScopeId {
match self {
FunctionAstRef::Function(f)
| FunctionAstRef::AsyncFunction(f)
| FunctionAstRef::Generator(f)
| FunctionAstRef::AsyncGenerator(f)
| FunctionAstRef::ClassConstructor(f) => f.scope_id(),
FunctionAstRef::Arrow(f) | FunctionAstRef::AsyncArrow(f) => f.scope_id(),
}
}
#[inline]
pub(crate) fn formal_parameters(&self) -> &'ast FormalParameters<'ast> {
match self {
Self::Function(f)
| Self::AsyncFunction(f)
| Self::Generator(f)
| Self::AsyncGenerator(f)
| Self::ClassConstructor(f) => f.params.as_ref(),
Self::Arrow(f) | Self::AsyncArrow(f) => f.params.as_ref(),
}
}
#[inline]
pub(crate) fn ecmascript_code(&self) -> &'ast FunctionBody<'ast> {
match self {
Self::Function(f)
| Self::AsyncFunction(f)
| Self::Generator(f)
| Self::AsyncGenerator(f)
| Self::ClassConstructor(f) => {
unsafe { f.body.as_ref().unwrap_unchecked() }
}
FunctionAstRef::Arrow(f) | FunctionAstRef::AsyncArrow(f) => f.body.as_ref(),
}
}
#[inline]
pub(crate) fn is_async(&self) -> bool {
matches!(
self,
Self::AsyncFunction(_) | Self::AsyncGenerator(_) | Self::AsyncArrow(_)
)
}
#[inline]
pub(crate) fn is_concise_body(&self) -> bool {
match self {
FunctionAstRef::Arrow(f) | FunctionAstRef::AsyncArrow(f) => f.expression,
_ => false,
}
}
#[inline]
pub(crate) fn is_generator(&self) -> bool {
matches!(self, Self::Generator(_) | Self::AsyncGenerator(_))
}
#[inline]
fn as_ptr(&self) -> FunctionAstPtr {
let static_ref =
unsafe { core::mem::transmute::<&FunctionAstRef<'_>, &FunctionAstRef<'static>>(self) };
match static_ref {
FunctionAstRef::Function(f) => FunctionAstPtr::Function(NonNull::from_ref(f)),
FunctionAstRef::AsyncFunction(f) => FunctionAstPtr::AsyncFunction(NonNull::from_ref(f)),
FunctionAstRef::Generator(f) => FunctionAstPtr::Generator(NonNull::from_ref(f)),
FunctionAstRef::AsyncGenerator(f) => {
FunctionAstPtr::AsyncGenerator(NonNull::from_ref(f))
}
FunctionAstRef::ClassConstructor(f) => {
FunctionAstPtr::ClassConstructor(NonNull::from_ref(f))
}
FunctionAstRef::Arrow(f) => FunctionAstPtr::Arrow(NonNull::from_ref(f)),
FunctionAstRef::AsyncArrow(f) => FunctionAstPtr::AsyncArrow(NonNull::from_ref(f)),
}
}
}
#[derive(Debug)]
pub(crate) struct ECMAScriptFunctionObjectHeapData<'a> {
pub(crate) environment: Environment<'a>,
pub(crate) private_environment: Option<PrivateEnvironment<'a>>,
ast: FunctionAstPtr,
pub(crate) constructor_status: ConstructorStatus,
pub(crate) realm: Realm<'a>,
pub(crate) script_or_module: ScriptOrModule<'a>,
pub(crate) this_mode: ThisMode,
pub(crate) strict: bool,
pub(crate) home_object: Option<Object<'a>>,
pub(crate) source_text: Span,
pub(crate) source_code: SourceCode<'a>,
}
pub(crate) struct OrdinaryFunctionCreateParams<'ast, 'gc> {
pub(crate) function_prototype: Option<Object<'gc>>,
pub(crate) source_code: Option<SourceCode<'gc>>,
pub(crate) source_text: Span,
pub(crate) ast: FunctionAstRef<'ast>,
pub(crate) lexical_this: bool,
pub(crate) env: Environment<'gc>,
pub(crate) private_env: Option<PrivateEnvironment<'gc>>,
}
impl<'a> ECMAScriptFunction<'a> {
pub fn is_strict(self, agent: &Agent) -> bool {
self.get(agent).ecmascript_function.strict
}
pub(crate) fn get_this_mode(self, agent: &Agent) -> ThisMode {
self.get(agent).ecmascript_function.this_mode
}
#[inline]
pub(crate) fn get_executable(self, agent: &Agent) -> Executable<'a> {
self.get(agent).compiled_bytecode.unwrap().unbind()
}
#[inline]
pub(crate) fn get_source_code(self, agent: &Agent) -> SourceCode<'a> {
self.get(agent).ecmascript_function.source_code
}
pub fn realm(self, agent: &Agent) -> Realm<'a> {
self.get(agent).ecmascript_function.realm
}
#[inline]
#[allow(unused_variables)]
pub(crate) fn get_ast<'gc>(self, agent: &Agent, gc: NoGcScope<'gc, '_>) -> FunctionAstRef<'gc> {
unsafe { self.get(agent).ecmascript_function.ast.as_ref() }
}
#[inline]
#[allow(unused_variables)]
pub(crate) unsafe fn get_ast_unbound(self, agent: &Agent) -> FunctionAstRef<'a> {
unsafe { self.get(agent).ecmascript_function.ast.as_ref() }
}
pub fn is_constructor(self, agent: &Agent) -> bool {
self.get(agent).ecmascript_function.constructor_status != ConstructorStatus::NonConstructor
}
}
impl<'a> FunctionInternalProperties<'a> for ECMAScriptFunction<'a> {
fn get_name(self, agent: &Agent) -> &String<'a> {
self.get(agent)
.name
.as_ref()
.unwrap_or(&String::EMPTY_STRING)
}
fn get_length(self, agent: &Agent) -> u8 {
self.get(agent).length
}
#[inline(always)]
fn get_function_backing_object(self, agent: &Agent) -> Option<OrdinaryObject<'static>> {
self.get(agent).object_index.unbind()
}
fn set_function_backing_object(
self,
agent: &mut Agent,
backing_object: OrdinaryObject<'static>,
) {
assert!(
self.get_mut(agent)
.object_index
.replace(backing_object.unbind())
.is_none()
);
}
fn function_prototype(self, agent: &Agent) -> Option<Object<'static>> {
if let Some(object_index) = self.get_backing_object(agent) {
object_index.internal_prototype(agent)
} else {
let realm = self.get(agent).ecmascript_function.realm;
let intrinsics = realm.get(agent).intrinsics();
let f = unsafe { self.get_ast_unbound(agent) };
let proto = match (f.is_async(), f.is_generator()) {
(false, false) => intrinsics.function_prototype().into(),
(false, true) => intrinsics.generator_function_prototype().into(),
(true, false) => intrinsics.async_function_prototype().into(),
(true, true) => intrinsics.async_generator_function_prototype().into(),
};
Some(proto)
}
}
fn function_call<'gc>(
self,
agent: &mut Agent,
this_argument: Value,
arguments_list: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
agent.check_call_depth(gc.nogc()).unbind()?;
let f = self.bind(gc.nogc());
let mut id = 0;
ndt::javascript_call_start!(|| {
let args = create_name_and_id(agent, f);
id = args.1;
args
});
let arguments_list = arguments_list.bind(gc.nogc());
let _ = agent.running_execution_context();
let callee_context = prepare_for_ordinary_call(agent, f, None, gc.nogc());
let local_env = callee_context
.ecmascript_code
.as_ref()
.unwrap()
.lexical_environment
.bind(gc.nogc());
if f.get(agent)
.ecmascript_function
.constructor_status
.is_class_constructor()
{
let error = agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"class constructors must be invoked with 'new'",
gc.nogc(),
);
agent.pop_execution_context();
return Err(error.unbind());
}
let Environment::Function(local_env) = local_env else {
panic!("localEnv is not a Function Environment Record");
};
ordinary_call_bind_this(agent, f, local_env, this_argument, gc.nogc());
let result = ordinary_call_evaluate_body(agent, f.unbind(), arguments_list.unbind(), gc);
let _callee_context = agent.pop_execution_context();
ndt::javascript_call_done!(|| id);
result
}
fn function_construct<'gc>(
self,
agent: &mut Agent,
arguments: ArgumentsList,
new_target: Function,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Object<'gc>> {
let mut f = self.bind(gc.nogc());
let mut id = 0;
ndt::javascript_constructor_start!(|| {
let args = create_name_and_id(agent, f);
id = args.1;
args
});
let mut new_target = new_target.bind(gc.nogc());
let mut arguments_list = arguments.bind(gc.nogc());
let is_base = !f
.get(agent)
.ecmascript_function
.constructor_status
.is_derived_class();
let this_argument = if is_base {
let scoped_self_fn = f.scope(agent, gc.nogc());
let scoped_new_target = new_target.scope(agent, gc.nogc());
let unbound_new_target = new_target.unbind();
let mut args = arguments_list.unbind();
let this_argument = args
.with_scoped(
agent,
|agent, _, gc| {
ordinary_create_from_constructor(
agent,
unbound_new_target,
ProtoIntrinsics::Object,
gc,
)
},
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
f = scoped_self_fn.get(agent).bind(gc.nogc());
new_target = scoped_new_target.get(agent).bind(gc.nogc());
arguments_list = args.bind(gc.nogc());
Some(this_argument)
} else {
None
};
let callee_context =
prepare_for_ordinary_call(agent, f, Some(new_target.into()), gc.nogc());
let constructor_env = callee_context
.ecmascript_code
.as_ref()
.unwrap()
.lexical_environment
.bind(gc.nogc());
let Environment::Function(constructor_env) = constructor_env else {
panic!("constructorEnv is not a Function Environment Record");
};
if is_base {
ordinary_call_bind_this(
agent,
f,
constructor_env,
this_argument.unwrap().into(),
gc.nogc(),
);
}
let scoped_constructor_env = constructor_env.scope(agent, gc.nogc());
let scoped_this_argument = this_argument.map(|f| f.scope(agent, gc.nogc()));
let result =
ordinary_call_evaluate_body(agent, f.unbind(), arguments_list.unbind(), gc.reborrow());
let _callee_context = agent.pop_execution_context();
let value = result.unbind()?.bind(gc.nogc());
let result = if let Ok(value) = Object::try_from(value) {
Ok(value.unbind())
} else
if is_base {
Ok(scoped_this_argument.unwrap().get(agent))
} else
if !value.is_undefined() {
let message = format!(
"derived class constructor returned invalid value {}",
value
.unbind()
.string_repr(agent, gc.reborrow())
.to_string_lossy_(agent)
);
let message = String::from_string(agent, message, gc.nogc());
Err(agent.throw_exception_with_message(
ExceptionType::TypeError,
message.unbind(),
gc.into_nogc(),
))
} else {
let Ok(this_binding) = Object::try_from(
scoped_constructor_env
.get(agent)
.get_this_binding(agent, gc.into_nogc())?,
) else {
unreachable!();
};
Ok(this_binding)
};
ndt::javascript_constructor_done!(|| id);
result
}
}
#[inline(never)]
fn create_name_and_id<'a>(agent: &'a Agent, f: ECMAScriptFunction<'a>) -> (Cow<'a, str>, u64) {
let id = f
.get(agent)
.compiled_bytecode
.map_or(0, |b| b.get(agent).instructions.as_ptr() as u64);
let name = f.get_name(agent).to_string_lossy_(agent);
(name, id)
}
pub(crate) fn prepare_for_ordinary_call<'a>(
agent: &'a mut Agent,
f: ECMAScriptFunction,
new_target: Option<Object>,
gc: NoGcScope,
) -> &'a ExecutionContext {
let f = f.bind(gc);
let new_target = new_target.bind(gc);
let ecmascript_function_object = &f.get(agent).ecmascript_function;
let private_environment = ecmascript_function_object.private_environment.bind(gc);
let is_strict_mode = ecmascript_function_object.strict;
let script_or_module = ecmascript_function_object.script_or_module;
let source_code = ecmascript_function_object.source_code;
let _caller_context = agent.running_execution_context();
let callee_realm = ecmascript_function_object.realm;
let local_env = new_function_environment(agent, f, new_target, gc);
let callee_context = ExecutionContext {
ecmascript_code: Some(ECMAScriptCodeEvaluationState {
lexical_environment: Environment::Function(local_env.unbind()),
variable_environment: Environment::Function(local_env.unbind()),
private_environment: private_environment.unbind(),
is_strict_mode,
source_code: source_code.unbind(),
}),
function: Some(f.unbind().into()),
realm: callee_realm.unbind(),
script_or_module: Some(script_or_module.unbind()),
};
agent.push_execution_context(callee_context);
agent.running_execution_context()
}
pub(crate) fn ordinary_call_bind_this(
agent: &mut Agent,
f: ECMAScriptFunction,
local_env: FunctionEnvironment,
this_argument: Value,
gc: NoGcScope,
) {
let function_heap_data = &f.get(agent).ecmascript_function;
let this_mode = function_heap_data.this_mode;
if this_mode == ThisMode::Lexical {
return;
}
let callee_realm = function_heap_data.realm.bind(gc);
let this_value = if this_mode == ThisMode::Strict {
this_argument
} else {
if this_argument == Value::Undefined || this_argument == Value::Null {
let global_env = agent.get_realm_record_by_id(callee_realm).global_env;
let global_env = global_env.unwrap();
global_env.get_this_binding(agent).into()
} else {
to_object(agent, this_argument, gc).unwrap().into()
}
};
assert_ne!(
local_env.get_this_binding_status(agent),
ThisBindingStatus::Initialized
);
local_env.bind_this_value(agent, this_value, gc).unwrap();
}
pub(crate) fn evaluate_body<'gc>(
agent: &mut Agent,
function_object: ECMAScriptFunction,
arguments_list: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
let function_object = function_object.bind(gc.nogc());
let f = function_object.get_ast(agent, gc.nogc());
match (f.is_generator(), f.is_async()) {
(false, true) => {
Ok(
evaluate_async_function_body(agent, function_object.unbind(), arguments_list, gc)
.into(),
)
}
(false, false) => {
evaluate_function_body(agent, function_object.unbind(), arguments_list, gc)
}
(true, false) => {
evaluate_generator_body(agent, function_object.unbind(), arguments_list, gc)
}
_ => evaluate_async_generator_body(agent, function_object.unbind(), arguments_list, gc),
}
}
pub(crate) fn ordinary_call_evaluate_body<'gc>(
agent: &mut Agent,
f: ECMAScriptFunction,
arguments_list: ArgumentsList,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Value<'gc>> {
evaluate_body(agent, f, arguments_list, gc)
}
pub(crate) fn ordinary_function_create<'gc>(
agent: &mut Agent,
params: OrdinaryFunctionCreateParams<'_, 'gc>,
gc: NoGcScope<'gc, '_>,
) -> ECMAScriptFunction<'gc> {
let (source_code, outer_env_is_strict) = if let Some(source_code) = params.source_code {
(source_code, false)
} else {
(
agent.current_source_code(gc),
agent.is_evaluating_strict_code(),
)
};
let strict = outer_env_is_strict || params.ast.ecmascript_code().has_use_strict_directive();
let ecmascript_function = ECMAScriptFunctionObjectHeapData {
environment: params.env.unbind(),
private_environment: params.private_env.unbind(),
ast: params.ast.as_ptr(),
constructor_status: ConstructorStatus::NonConstructor,
realm: agent.current_realm(gc),
script_or_module: get_active_script_or_module(agent, gc).unwrap().unbind(),
this_mode: if params.lexical_this {
ThisMode::Lexical
} else if strict {
ThisMode::Strict
} else {
ThisMode::Global
},
strict,
home_object: None,
source_text: params.source_text,
source_code: source_code.unbind(),
};
let mut function = ECMAScriptFunctionHeapData {
object_index: None,
length: 0,
ecmascript_function,
compiled_bytecode: None,
name: None,
};
if let Some(function_prototype) = params.function_prototype
&& function_prototype
!= agent
.current_realm_record()
.intrinsics()
.function_prototype()
.into()
{
function.object_index = Some(
OrdinaryObject::create_object(agent, Some(function_prototype), &[])
.expect("Should perform GC here"),
);
}
let len = expected_arguments_count(params.ast.formal_parameters());
set_ecmascript_function_length(agent, &mut function, len, gc).unwrap();
agent.heap.create(function)
}
fn expected_arguments_count(params: &FormalParameters) -> usize {
let mut count = 0;
for param in params.items.iter() {
if param.initializer.is_some() {
break;
}
count += 1;
}
count
}
pub(crate) fn make_constructor<'a>(
agent: &mut Agent,
function: impl FunctionInternalProperties<'a>,
writable_prototype: Option<bool>,
prototype: Option<OrdinaryObject>,
gc: NoGcScope,
) {
let writable_prototype = writable_prototype.unwrap_or(true);
match function.into() {
Function::BoundFunction(_) => unreachable!(),
Function::ECMAScriptFunction(idx) => {
let data = idx.get_mut(agent);
debug_assert!(!data.ecmascript_function.constructor_status.is_constructor());
data.ecmascript_function.constructor_status = ConstructorStatus::ConstructorFunction;
}
Function::BuiltinFunction(_) => {
}
Function::BuiltinConstructorFunction(_)
| Function::BuiltinPromiseResolvingFunction(_)
| Function::BuiltinPromiseFinallyFunction(_)
| Function::BuiltinProxyRevokerFunction => unreachable!(),
}
let prototype = prototype.unwrap_or_else(|| {
let prototype = OrdinaryObject::try_from(ordinary_object_create_with_intrinsics(
agent,
ProtoIntrinsics::Object,
None,
gc,
))
.unwrap();
let f: Value = function.into();
prototype
.property_storage()
.set(
agent,
prototype.into(),
BUILTIN_STRING_MEMORY.constructor.into(),
PropertyDescriptor {
value: Some(f.unbind()),
writable: Some(writable_prototype),
enumerable: Some(false),
configurable: Some(true),
..Default::default()
},
gc,
)
.expect("Failed to allocate memory for constructor");
prototype
});
let backing_object = function
.get_backing_object(agent)
.unwrap_or_else(|| function.create_backing_object(agent));
backing_object
.property_storage()
.set(
agent,
function.into(),
BUILTIN_STRING_MEMORY.prototype.into(),
PropertyDescriptor {
value: Some(prototype.unbind().into()),
writable: Some(writable_prototype),
enumerable: Some(false),
configurable: Some(false),
..Default::default()
},
gc,
)
.expect("Failed to allocate memory for constructor");
}
#[inline]
pub(crate) fn make_method(agent: &mut Agent, f: ECMAScriptFunction, home_object: Object) {
f.get_mut(agent).ecmascript_function.home_object = Some(home_object.unbind());
}
pub(crate) enum SetFunctionNamePrefix {
Get,
Set,
Bound,
}
impl SetFunctionNamePrefix {
fn into_str(self) -> &'static str {
match self {
SetFunctionNamePrefix::Get => "get ",
SetFunctionNamePrefix::Set => "set ",
SetFunctionNamePrefix::Bound => "bound ",
}
}
}
fn prefix_into_str(prefix: Option<SetFunctionNamePrefix>) -> &'static str {
match prefix {
Some(p) => p.into_str(),
None => "",
}
}
pub(crate) fn set_function_name<'a>(
agent: &mut Agent,
function: impl Into<Function<'a>>,
name: PropertyKey,
prefix: Option<SetFunctionNamePrefix>,
gc: NoGcScope,
) {
let name: String = match name {
PropertyKey::Symbol(s) => {
s.description(agent)
.map_or(String::EMPTY_STRING, |descriptor| {
let descriptor = descriptor.to_string_lossy_(agent);
String::from_string(
agent,
format!("{}[{descriptor}]", prefix_into_str(prefix)),
gc,
)
})
}
PropertyKey::Integer(integer) => String::from_string(
agent,
format!("{}{}", prefix_into_str(prefix), integer.into_i64()),
gc,
),
PropertyKey::SmallString(str) => {
if let Some(prefix) = prefix {
String::from_string(
agent,
format!("{}{}", prefix.into_str(), str.to_string_lossy()),
gc,
)
} else {
str.into()
}
}
PropertyKey::String(str) => {
if let Some(prefix) = prefix {
String::from_string(
agent,
format!("{}{}", prefix.into_str(), str.to_string_lossy(agent)),
gc,
)
} else {
str.into()
}
}
PropertyKey::PrivateName(p) => p
.get_description(agent, gc)
.expect("Should always find PrivateName in scope when calling SetFunctionName"),
};
match function.into() {
Function::BoundFunction(idx) => {
let function = idx.get_mut(agent);
assert!(function.name.is_none());
function.name = Some(name.unbind());
}
Function::BuiltinFunction(_idx) => unreachable!(),
Function::ECMAScriptFunction(idx) => {
let function = idx.get_mut(agent);
assert!(function.name.is_none());
function.name = Some(name.unbind());
}
Function::BuiltinConstructorFunction(_)
| Function::BuiltinPromiseResolvingFunction(_)
| Function::BuiltinPromiseFinallyFunction(_)
| Function::BuiltinProxyRevokerFunction => unreachable!(),
}
}
fn set_ecmascript_function_length<'a>(
agent: &mut Agent,
function: &mut ECMAScriptFunctionHeapData,
length: usize,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, ()> {
if length > u8::MAX as usize {
return Err(agent.throw_exception_with_static_message(
ExceptionType::SyntaxError,
"Too many arguments in function call (only 255 allowed)",
gc,
));
}
function.length = length as u8;
Ok(())
}
impl HeapMarkAndSweep for ECMAScriptFunction<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
queues.ecmascript_functions.push(*self);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
compactions.ecmascript_functions.shift_index(&mut self.0);
}
}
impl HeapSweepWeakReference for ECMAScriptFunction<'static> {
fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option<Self> {
compactions
.ecmascript_functions
.shift_weak_index(self.0)
.map(Self)
}
}
impl<'a> CreateHeapData<ECMAScriptFunctionHeapData<'a>, ECMAScriptFunction<'a>> for Heap {
fn create(&mut self, data: ECMAScriptFunctionHeapData<'a>) -> ECMAScriptFunction<'a> {
self.ecmascript_functions.push(data.unbind());
self.alloc_counter += core::mem::size_of::<ECMAScriptFunctionHeapData<'static>>();
ECMAScriptFunction(BaseIndex::last(&self.ecmascript_functions))
}
}
bindable_handle!(ECMAScriptFunctionHeapData);
impl HeapMarkAndSweep for ECMAScriptFunctionHeapData<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
object_index,
length: _,
ecmascript_function,
compiled_bytecode,
name,
} = self;
let ECMAScriptFunctionObjectHeapData {
environment,
private_environment,
ast: _,
constructor_status: _,
realm,
script_or_module,
this_mode: _,
strict: _,
home_object,
source_text: _,
source_code,
} = ecmascript_function;
object_index.mark_values(queues);
compiled_bytecode.mark_values(queues);
name.mark_values(queues);
environment.mark_values(queues);
private_environment.mark_values(queues);
realm.mark_values(queues);
script_or_module.mark_values(queues);
home_object.mark_values(queues);
source_code.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
object_index,
length: _,
ecmascript_function,
compiled_bytecode,
name,
} = self;
let ECMAScriptFunctionObjectHeapData {
environment,
private_environment,
ast: _,
constructor_status: _,
realm,
script_or_module,
this_mode: _,
strict: _,
home_object,
source_text: _,
source_code,
} = ecmascript_function;
object_index.sweep_values(compactions);
compiled_bytecode.sweep_values(compactions);
name.sweep_values(compactions);
environment.sweep_values(compactions);
private_environment.sweep_values(compactions);
realm.sweep_values(compactions);
script_or_module.sweep_values(compactions);
home_object.sweep_values(compactions);
source_code.sweep_values(compactions);
}
}