use boa_gc::{Finalize, Trace};
use crate::{
Context, JsObject, JsResult, JsValue,
object::{
JsData,
internal_methods::{
CallValue, InternalMethodCallContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
},
},
};
#[derive(Debug, Trace, Finalize)]
pub struct BoundFunction {
target_function: JsObject,
this: JsValue,
args: Vec<JsValue>,
}
impl JsData for BoundFunction {
fn internal_methods(&self) -> &'static InternalObjectMethods {
static CONSTRUCTOR_METHODS: InternalObjectMethods = InternalObjectMethods {
__call__: bound_function_exotic_call,
__construct__: bound_function_exotic_construct,
..ORDINARY_INTERNAL_METHODS
};
static FUNCTION_METHODS: InternalObjectMethods = InternalObjectMethods {
__call__: bound_function_exotic_call,
..ORDINARY_INTERNAL_METHODS
};
if self.target_function.is_constructor() {
&CONSTRUCTOR_METHODS
} else {
&FUNCTION_METHODS
}
}
}
impl BoundFunction {
pub fn create(
target_function: JsObject,
this: JsValue,
args: Vec<JsValue>,
context: &mut Context,
) -> JsResult<JsObject> {
let proto = target_function.__get_prototype_of__(context)?;
Ok(JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
proto,
Self {
target_function,
this,
args,
},
))
}
#[must_use]
pub const fn this(&self) -> &JsValue {
&self.this
}
#[must_use]
pub const fn target_function(&self) -> &JsObject {
&self.target_function
}
#[must_use]
pub fn args(&self) -> &[JsValue] {
self.args.as_slice()
}
}
#[allow(clippy::unnecessary_wraps)]
fn bound_function_exotic_call(
obj: &JsObject,
argument_count: usize,
context: &mut InternalMethodCallContext<'_>,
) -> JsResult<CallValue> {
let bound_function = obj
.downcast_ref::<BoundFunction>()
.expect("bound function exotic method should only be callable from bound function objects");
let target = bound_function.target_function();
context
.vm
.stack
.calling_convention_set_function(argument_count, target.clone().into());
let bound_this = bound_function.this();
context
.vm
.stack
.calling_convention_set_this(argument_count, bound_this.clone());
let bound_args = bound_function.args();
context
.vm
.stack
.calling_convention_insert_arguments(argument_count, bound_args);
Ok(target.__call__(bound_args.len() + argument_count))
}
#[allow(clippy::unnecessary_wraps)]
fn bound_function_exotic_construct(
function_object: &JsObject,
argument_count: usize,
context: &mut InternalMethodCallContext<'_>,
) -> JsResult<CallValue> {
let new_target = context.vm.stack.pop();
debug_assert!(new_target.is_object(), "new.target should be an object");
let bound_function = function_object
.downcast_ref::<BoundFunction>()
.expect("bound function exotic method should only be callable from bound function objects");
let target = bound_function.target_function();
let bound_args = bound_function.args();
context
.vm
.stack
.calling_convention_insert_arguments(argument_count, bound_args);
let function_object: JsValue = function_object.clone().into();
let new_target = if JsValue::same_value(&function_object, &new_target) {
target.clone().into()
} else {
new_target
};
context.vm.stack.push(new_target);
Ok(target.__construct__(bound_args.len() + argument_count))
}