use crate::js_string;
use crate::{
Context, JsNativeError, JsResult, JsValue, NativeFunction, TryIntoJsResult,
builtins::function::ConstructorKind, native_function::NativeFunctionObject, object::JsObject,
value::TryFromJs,
};
use boa_gc::{Finalize, Trace};
use std::marker::PhantomData;
use std::ops::Deref;
pub trait TryIntoJsArguments {
fn into_js_args(self, cx: &mut Context) -> JsResult<Vec<JsValue>>;
}
macro_rules! impl_try_into_js_args {
($($n: ident: $t: ident),*) => {
impl<$($t),*> TryIntoJsArguments for ($($t,)*) where $($t: TryIntoJsResult),* {
fn into_js_args(self, cx: &mut Context) -> JsResult<Vec<JsValue>> {
let ($($n,)*) = self;
Ok(vec![$($n.try_into_js_result(cx)?),*])
}
}
};
}
impl_try_into_js_args!(a: A);
impl_try_into_js_args!(a: A, b: B);
impl_try_into_js_args!(a: A, b: B, c: C);
impl_try_into_js_args!(a: A, b: B, c: C, d: D);
impl_try_into_js_args!(a: A, b: B, c: C, d: D, e: E);
#[derive(Debug, Clone, Trace, Finalize)]
pub struct TypedJsFunction<A: TryIntoJsArguments, R: TryFromJs> {
inner: JsFunction,
_args: PhantomData<A>,
_ret: PhantomData<R>,
}
impl<A: TryIntoJsArguments, R: TryFromJs> TypedJsFunction<A, R> {
#[must_use]
pub fn into_inner(self) -> JsFunction {
self.inner.clone()
}
#[must_use]
pub fn as_js_function(&self) -> &JsFunction {
&self.inner
}
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub fn call(&self, context: &mut Context, args: A) -> JsResult<R> {
self.call_with_this(&JsValue::undefined(), context, args)
}
#[inline]
#[cfg_attr(feature = "native-backtrace", track_caller)]
pub fn call_with_this(&self, this: &JsValue, context: &mut Context, args: A) -> JsResult<R> {
let arguments = args.into_js_args(context)?;
let result = self.inner.call(this, &arguments, context)?;
R::try_from_js(&result, context)
}
}
impl<A: TryIntoJsArguments, R: TryFromJs> TryFromJs for TypedJsFunction<A, R> {
fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
if let Some(o) = value.as_object() {
JsFunction::from_object(o.clone())
.ok_or_else(|| {
JsNativeError::typ()
.with_message("object is not a function")
.into()
})
.map(JsFunction::typed)
} else {
Err(JsNativeError::typ()
.with_message("value is not a Function object")
.into())
}
}
}
impl<A: TryIntoJsArguments, R: TryFromJs> From<TypedJsFunction<A, R>> for JsValue {
#[inline]
fn from(o: TypedJsFunction<A, R>) -> Self {
o.into_inner().into()
}
}
impl<A: TryIntoJsArguments, R: TryFromJs> From<TypedJsFunction<A, R>> for JsFunction {
fn from(value: TypedJsFunction<A, R>) -> Self {
value.inner.clone()
}
}
#[derive(Debug, Clone, Trace, Finalize)]
pub struct JsFunction {
inner: JsObject,
}
impl JsFunction {
pub(crate) fn from_object_unchecked(object: JsObject) -> Self {
Self { inner: object }
}
pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self {
Self {
inner: JsObject::from_proto_and_data(
None,
NativeFunctionObject {
f: NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())),
name: js_string!(),
constructor: constructor.then_some(ConstructorKind::Base),
realm: None,
},
),
}
}
#[inline]
#[must_use]
pub fn from_object(object: JsObject) -> Option<Self> {
object
.is_callable()
.then(|| Self::from_object_unchecked(object))
}
#[inline]
#[must_use]
pub fn typed<A: TryIntoJsArguments, R: TryFromJs>(self) -> TypedJsFunction<A, R> {
TypedJsFunction {
inner: self,
_args: PhantomData,
_ret: PhantomData,
}
}
}
impl From<JsFunction> for JsObject {
#[inline]
fn from(o: JsFunction) -> Self {
o.inner.clone()
}
}
impl From<JsFunction> for JsValue {
#[inline]
fn from(o: JsFunction) -> Self {
o.inner.clone().into()
}
}
impl Deref for JsFunction {
type Target = JsObject;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl TryFromJs for JsFunction {
fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
if let Some(o) = value.as_object() {
Self::from_object(o.clone()).ok_or_else(|| {
JsNativeError::typ()
.with_message("object is not a function")
.into()
})
} else {
Err(JsNativeError::typ()
.with_message("value is not a Function object")
.into())
}
}
}