boa_engine/object/builtins/
jsfunction.rs1use crate::js_string;
3use crate::{
4 Context, JsNativeError, JsResult, JsValue, NativeFunction, TryIntoJsResult,
5 builtins::function::ConstructorKind, native_function::NativeFunctionObject, object::JsObject,
6 value::TryFromJs,
7};
8use boa_gc::{Finalize, Trace};
9use std::marker::PhantomData;
10use std::ops::Deref;
11
12pub trait TryIntoJsArguments {
15 fn into_js_args(self, cx: &mut Context) -> JsResult<Vec<JsValue>>;
19}
20
21macro_rules! impl_try_into_js_args {
22 ($($n: ident: $t: ident),*) => {
23 impl<$($t),*> TryIntoJsArguments for ($($t,)*) where $($t: TryIntoJsResult),* {
24 fn into_js_args(self, cx: &mut Context) -> JsResult<Vec<JsValue>> {
25 let ($($n,)*) = self;
26 Ok(vec![$($n.try_into_js_result(cx)?),*])
27 }
28 }
29 };
30}
31
32impl_try_into_js_args!(a: A);
33impl_try_into_js_args!(a: A, b: B);
34impl_try_into_js_args!(a: A, b: B, c: C);
35impl_try_into_js_args!(a: A, b: B, c: C, d: D);
36impl_try_into_js_args!(a: A, b: B, c: C, d: D, e: E);
37
38#[derive(Debug, Clone, Trace, Finalize)]
46pub struct TypedJsFunction<A: TryIntoJsArguments, R: TryFromJs> {
47 inner: JsFunction,
48 _args: PhantomData<A>,
49 _ret: PhantomData<R>,
50}
51
52impl<A: TryIntoJsArguments, R: TryFromJs> TypedJsFunction<A, R> {
53 #[must_use]
55 pub fn into_inner(self) -> JsFunction {
56 self.inner.clone()
57 }
58
59 #[must_use]
61 pub fn as_js_function(&self) -> &JsFunction {
62 &self.inner
63 }
64
65 #[inline]
67 #[cfg_attr(feature = "native-backtrace", track_caller)]
68 pub fn call(&self, context: &mut Context, args: A) -> JsResult<R> {
69 self.call_with_this(&JsValue::undefined(), context, args)
70 }
71
72 #[inline]
74 #[cfg_attr(feature = "native-backtrace", track_caller)]
75 pub fn call_with_this(&self, this: &JsValue, context: &mut Context, args: A) -> JsResult<R> {
76 let arguments = args.into_js_args(context)?;
77 let result = self.inner.call(this, &arguments, context)?;
78 R::try_from_js(&result, context)
79 }
80}
81
82impl<A: TryIntoJsArguments, R: TryFromJs> TryFromJs for TypedJsFunction<A, R> {
83 fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
84 if let Some(o) = value.as_object() {
85 JsFunction::from_object(o.clone())
86 .ok_or_else(|| {
87 JsNativeError::typ()
88 .with_message("object is not a function")
89 .into()
90 })
91 .map(JsFunction::typed)
92 } else {
93 Err(JsNativeError::typ()
94 .with_message("value is not a Function object")
95 .into())
96 }
97 }
98}
99
100impl<A: TryIntoJsArguments, R: TryFromJs> From<TypedJsFunction<A, R>> for JsValue {
101 #[inline]
102 fn from(o: TypedJsFunction<A, R>) -> Self {
103 o.into_inner().into()
104 }
105}
106
107impl<A: TryIntoJsArguments, R: TryFromJs> From<TypedJsFunction<A, R>> for JsFunction {
108 fn from(value: TypedJsFunction<A, R>) -> Self {
109 value.inner.clone()
110 }
111}
112
113#[derive(Debug, Clone, Trace, Finalize)]
115pub struct JsFunction {
116 inner: JsObject,
117}
118
119impl JsFunction {
120 pub(crate) fn from_object_unchecked(object: JsObject) -> Self {
122 Self { inner: object }
123 }
124
125 pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self {
131 Self {
132 inner: JsObject::from_proto_and_data(
133 None,
134 NativeFunctionObject {
135 f: NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())),
136 name: js_string!(),
137 constructor: constructor.then_some(ConstructorKind::Base),
138 realm: None,
139 },
140 ),
141 }
142 }
143
144 #[inline]
148 #[must_use]
149 pub fn from_object(object: JsObject) -> Option<Self> {
150 object
151 .is_callable()
152 .then(|| Self::from_object_unchecked(object))
153 }
154
155 #[inline]
157 #[must_use]
158 pub fn typed<A: TryIntoJsArguments, R: TryFromJs>(self) -> TypedJsFunction<A, R> {
159 TypedJsFunction {
160 inner: self,
161 _args: PhantomData,
162 _ret: PhantomData,
163 }
164 }
165}
166
167impl From<JsFunction> for JsObject {
168 #[inline]
169 fn from(o: JsFunction) -> Self {
170 o.inner.clone()
171 }
172}
173
174impl From<JsFunction> for JsValue {
175 #[inline]
176 fn from(o: JsFunction) -> Self {
177 o.inner.clone().into()
178 }
179}
180
181impl Deref for JsFunction {
182 type Target = JsObject;
183
184 #[inline]
185 fn deref(&self) -> &Self::Target {
186 &self.inner
187 }
188}
189
190impl TryFromJs for JsFunction {
191 fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
192 if let Some(o) = value.as_object() {
193 Self::from_object(o.clone()).ok_or_else(|| {
194 JsNativeError::typ()
195 .with_message("object is not a function")
196 .into()
197 })
198 } else {
199 Err(JsNativeError::typ()
200 .with_message("value is not a Function object")
201 .into())
202 }
203 }
204}