rquickjs_core/value/
function.rs

1//! JavaScript function functionality
2
3use crate::{
4    atom::PredefinedAtom,
5    class::{Class, JsClass},
6    function::ffi::RustFunc,
7    qjs, Ctx, Error, FromJs, IntoJs, Object, Result, Value,
8};
9
10mod args;
11mod ffi;
12mod into_func;
13mod params;
14mod types;
15
16pub use args::{Args, IntoArg, IntoArgs};
17pub use ffi::RustFunction;
18pub use params::{FromParam, FromParams, ParamRequirement, Params, ParamsAccessor};
19#[cfg(feature = "futures")]
20pub use types::Async;
21pub use types::{Exhaustive, Flat, Func, FuncArg, MutFn, Null, OnceFn, Opt, Rest, This};
22
23/// A trait for converting a Rust function to a JavaScript function.
24pub trait IntoJsFunc<'js, P> {
25    /// Returns the requirements this function has for the set of arguments used to call this
26    /// function.
27    fn param_requirements() -> ParamRequirement;
28
29    /// Call the function with the given parameters.
30    fn call<'a>(&self, params: Params<'a, 'js>) -> Result<Value<'js>>;
31}
32
33/// A trait for functions callable from JavaScript but static,
34/// Used for implementing callable objects.
35pub trait StaticJsFunction {
36    fn call<'a, 'js>(params: Params<'a, 'js>) -> Result<Value<'js>>;
37}
38
39/// A JavaScript function.
40#[derive(Clone, Debug, PartialEq, Eq, Hash)]
41#[repr(transparent)]
42pub struct Function<'js>(pub(crate) Object<'js>);
43
44impl<'js> Function<'js> {
45    /// Create a new function from a Rust function which implements [`IntoJsFunc`].
46    pub fn new<P, F>(ctx: Ctx<'js>, f: F) -> Result<Self>
47    where
48        F: IntoJsFunc<'js, P> + 'js,
49    {
50        let func = Box::new(move |params: Params<'_, 'js>| {
51            params.check_params(F::param_requirements())?;
52            f.call(params)
53        }) as Box<dyn RustFunc<'js> + 'js>;
54
55        let cls = Class::instance(ctx, RustFunction(func))?;
56        debug_assert!(cls.is_function());
57        Function(cls.into_inner()).with_length(F::param_requirements().min())
58    }
59
60    /// Call the function with given arguments.
61    pub fn call<A, R>(&self, args: A) -> Result<R>
62    where
63        A: IntoArgs<'js>,
64        R: FromJs<'js>,
65    {
66        let ctx = self.ctx();
67        let num = args.num_args();
68        let mut accum_args = Args::new(ctx.clone(), num);
69        args.into_args(&mut accum_args)?;
70        self.call_arg(accum_args)
71    }
72
73    /// Call the function with given arguments in the form of an [`Args`] object.
74    pub fn call_arg<R>(&self, args: Args<'js>) -> Result<R>
75    where
76        R: FromJs<'js>,
77    {
78        args.apply(self)
79    }
80
81    /// Defer call the function with given arguments.
82    ///
83    /// Calling a function with defer is equivalent to calling a JavaScript function with
84    /// `setTimeout(func,0)`.
85    pub fn defer<A>(&self, args: A) -> Result<()>
86    where
87        A: IntoArgs<'js>,
88    {
89        let ctx = self.ctx();
90        let num = args.num_args();
91        let mut accum_args = Args::new(ctx.clone(), num);
92        args.into_args(&mut accum_args)?;
93        self.defer_arg(accum_args)?;
94        Ok(())
95    }
96
97    /// Defer a function call with given arguments.
98    pub fn defer_arg(&self, args: Args<'js>) -> Result<()> {
99        args.defer(self.clone())
100    }
101
102    /// Set the `name` property of this function
103    pub fn set_name<S: AsRef<str>>(&self, name: S) -> Result<()> {
104        let name = name.as_ref().into_js(self.ctx())?;
105        unsafe {
106            let res = qjs::JS_DefinePropertyValue(
107                self.0.ctx.as_ptr(),
108                self.0.as_js_value(),
109                PredefinedAtom::Name as qjs::JSAtom,
110                name.into_js_value(),
111                (qjs::JS_PROP_CONFIGURABLE | qjs::JS_PROP_THROW) as _,
112            );
113            if res < 0 {
114                return Err(self.0.ctx.raise_exception());
115            }
116        };
117        Ok(())
118    }
119
120    /// Set the `name` property of this function and then return self.
121    pub fn with_name<S: AsRef<str>>(self, name: S) -> Result<Self> {
122        self.set_name(name)?;
123        Ok(self)
124    }
125
126    /// Sets the `length` property of the function.
127    pub fn set_length(&self, len: usize) -> Result<()> {
128        let len = len.into_js(self.ctx())?;
129        unsafe {
130            let res = qjs::JS_DefinePropertyValue(
131                self.0.ctx.as_ptr(),
132                self.0.as_js_value(),
133                PredefinedAtom::Length as qjs::JSAtom,
134                len.into_js_value(),
135                (qjs::JS_PROP_CONFIGURABLE | qjs::JS_PROP_THROW) as _,
136            );
137            if res < 0 {
138                return Err(self.0.ctx.raise_exception());
139            }
140        };
141        Ok(())
142    }
143
144    /// Sets the `length` property of the function and return self.
145    pub fn with_length(self, len: usize) -> Result<Self> {
146        self.set_length(len)?;
147        Ok(self)
148    }
149
150    /// Returns the prototype which all JavaScript function by default have as its prototype, i.e.
151    /// `Function.prototype`.
152    pub fn prototype(ctx: Ctx<'js>) -> Object<'js> {
153        let res = unsafe {
154            let v = qjs::JS_GetFunctionProto(ctx.as_ptr());
155            Value::from_js_value(ctx, v)
156        };
157        // as far is I know this should always be an object.
158        res.into_object()
159            .expect("`Function.prototype` wasn't an object")
160    }
161
162    /// Returns whether this function is an constructor.
163    pub fn is_constructor(&self) -> bool {
164        ((unsafe { qjs::JS_IsConstructor(self.ctx().as_ptr(), self.0.as_js_value()) }) as i32) != 0
165    }
166
167    /// Set whether this function is a constructor or not.
168    pub fn set_constructor(&self, is_constructor: bool) {
169        unsafe {
170            qjs::JS_SetConstructorBit(
171                self.ctx().as_ptr(),
172                self.0.as_js_value(),
173                is_constructor.into(),
174            )
175        };
176    }
177
178    /// Set whether this function is a constructor or not then return self.
179    pub fn with_constructor(self, is_constructor: bool) -> Self {
180        self.set_constructor(is_constructor);
181        self
182    }
183}
184
185/// A function which can be used as a constructor.
186///
187/// Is a subtype of function.
188#[derive(Debug, Clone)]
189#[repr(transparent)]
190pub struct Constructor<'js>(pub(crate) Function<'js>);
191
192impl<'js> Constructor<'js> {
193    /// Creates a Rust constructor function for a Rust class.
194    ///
195    /// Note that this function creates a constructor from a given function, the returned constructor
196    /// is thus not the same as the one returned from [`JsClass::constructor`].
197    pub fn new_class<C, F, P>(ctx: Ctx<'js>, f: F) -> Result<Self>
198    where
199        F: IntoJsFunc<'js, P> + 'js,
200        C: JsClass<'js>,
201    {
202        let func = Box::new(move |params: Params<'_, 'js>| -> Result<Value<'js>> {
203            params.check_params(F::param_requirements())?;
204            let this = params.this();
205            let ctx = params.ctx().clone();
206
207            // get the prototype of thie class from itself or the inate class prototype.
208            let proto = this
209                .into_function()
210                .map(|func| func.get(PredefinedAtom::Prototype))
211                .unwrap_or_else(|| Class::<C>::prototype(&ctx))?;
212
213            let res = f.call(params)?;
214            res.as_object()
215                .ok_or_else(|| Error::IntoJs {
216                    from: res.type_of().as_str(),
217                    to: "object",
218                    message: Some("rust constructor function did not return a object".to_owned()),
219                })?
220                .set_prototype(proto.as_ref())?;
221            Ok(res)
222        });
223        let func = Function(Class::instance(ctx.clone(), RustFunction(func))?.into_inner())
224            .with_name(C::NAME)?
225            .with_constructor(true);
226        unsafe {
227            qjs::JS_SetConstructor(
228                ctx.as_ptr(),
229                func.as_js_value(),
230                Class::<C>::prototype(&ctx)?
231                    .as_ref()
232                    .map(|x| x.as_js_value())
233                    .unwrap_or(qjs::JS_NULL),
234            )
235        };
236        Ok(Constructor(func))
237    }
238
239    /// Create a new Rust constructor function with a given prototype.
240    ///
241    /// Useful if the function does not return a Rust class.
242    pub fn new_prototype<F, P>(ctx: &Ctx<'js>, prototype: Object<'js>, f: F) -> Result<Self>
243    where
244        F: IntoJsFunc<'js, P> + 'js,
245    {
246        let proto_clone = prototype.clone();
247        let func = Box::new(move |params: Params<'_, 'js>| -> Result<Value<'js>> {
248            params.check_params(F::param_requirements())?;
249            let this = params.this();
250            let proto = this
251                .as_function()
252                .map(|func| func.get(PredefinedAtom::Prototype))
253                .unwrap_or_else(|| Ok(Some(proto_clone.clone())))?;
254
255            let res = f.call(params)?;
256            res.as_object()
257                .ok_or_else(|| Error::IntoJs {
258                    from: res.type_of().as_str(),
259                    to: "object",
260                    message: Some("rust constructor function did not return a object".to_owned()),
261                })?
262                .set_prototype(proto.as_ref())?;
263            Ok(res)
264        });
265        let func = Function(Class::instance(ctx.clone(), RustFunction(func))?.into_inner())
266            .with_constructor(true);
267        unsafe {
268            qjs::JS_SetConstructor(ctx.as_ptr(), func.as_js_value(), prototype.as_js_value())
269        };
270        Ok(Constructor(func))
271    }
272
273    /// Call the constructor as a constructor.
274    ///
275    /// Equivalent to calling any constructor function with the new keyword.
276    pub fn construct<A, R>(&self, args: A) -> Result<R>
277    where
278        A: IntoArgs<'js>,
279        R: FromJs<'js>,
280    {
281        let ctx = self.ctx();
282        let num = args.num_args();
283        let mut accum_args = Args::new(ctx.clone(), num);
284        args.into_args(&mut accum_args)?;
285        self.construct_args(accum_args)
286    }
287
288    /// Call the constructor as a constructor with an [`Args`] object.
289    ///
290    /// Equivalent to calling any constructor function with the new keyword.
291    pub fn construct_args<R>(&self, args: Args<'js>) -> Result<R>
292    where
293        R: FromJs<'js>,
294    {
295        args.construct(self)
296    }
297}
298
299#[cfg(test)]
300mod test {
301    use crate::{prelude::*, *};
302    use approx::assert_abs_diff_eq as assert_approx_eq;
303
304    #[test]
305    fn call_js_fn_with_no_args_and_no_return() {
306        test_with(|ctx| {
307            let f: Function = ctx.eval("() => {}").unwrap();
308
309            let _: () = ().apply(&f).unwrap();
310            let _: () = f.call(()).unwrap();
311        })
312    }
313
314    #[test]
315    fn call_js_fn_with_no_args_and_return() {
316        test_with(|ctx| {
317            let f: Function = ctx.eval("() => 42").unwrap();
318
319            let res: i32 = ().apply(&f).unwrap();
320            assert_eq!(res, 42);
321
322            let res: i32 = f.call(()).unwrap();
323            assert_eq!(res, 42);
324        })
325    }
326
327    #[test]
328    fn call_js_fn_with_1_arg_and_return() {
329        test_with(|ctx| {
330            let f: Function = ctx.eval("a => a + 4").unwrap();
331
332            let res: i32 = (3,).apply(&f).unwrap();
333            assert_eq!(res, 7);
334
335            let res: i32 = f.call((1,)).unwrap();
336            assert_eq!(res, 5);
337        })
338    }
339
340    #[test]
341    fn call_js_fn_with_2_args_and_return() {
342        test_with(|ctx| {
343            let f: Function = ctx.eval("(a, b) => a * b + 4").unwrap();
344
345            let res: i32 = (3, 4).apply(&f).unwrap();
346            assert_eq!(res, 16);
347
348            let res: i32 = f.call((5, 1)).unwrap();
349            assert_eq!(res, 9);
350        })
351    }
352
353    #[test]
354    fn call_js_fn_with_var_args_and_return() {
355        let res: Vec<i8> = test_with(|ctx| {
356            let func: Function = ctx
357                .eval(
358                    r#"
359                  (...x) => [x.length, ...x]
360                "#,
361                )
362                .unwrap();
363            func.call((Rest(vec![1, 2, 3]),)).unwrap()
364        });
365        assert_eq!(res.len(), 4);
366        assert_eq!(res[0], 3);
367        assert_eq!(res[1], 1);
368        assert_eq!(res[2], 2);
369        assert_eq!(res[3], 3);
370    }
371
372    #[test]
373    fn call_js_fn_with_rest_args_and_return() {
374        let res: Vec<i8> = test_with(|ctx| {
375            let func: Function = ctx
376                .eval(
377                    r#"
378                  (a, b, ...x) => [a, b, x.length, ...x]
379                "#,
380                )
381                .unwrap();
382            func.call((-2, -1, Rest(vec![1, 2]))).unwrap()
383        });
384        assert_eq!(res.len(), 5);
385        assert_eq!(res[0], -2);
386        assert_eq!(res[1], -1);
387        assert_eq!(res[2], 2);
388        assert_eq!(res[3], 1);
389        assert_eq!(res[4], 2);
390    }
391
392    #[test]
393    fn call_js_fn_with_no_args_and_throw() {
394        test_with(|ctx| {
395            let f: Function = ctx
396                .eval("() => { throw new Error('unimplemented'); }")
397                .unwrap();
398
399            if let Err(Error::Exception) = f.call::<_, ()>(()) {
400                let exception = Exception::from_js(&ctx, ctx.catch()).unwrap();
401                assert_eq!(exception.message().as_deref(), Some("unimplemented"));
402            } else {
403                panic!("Should throws");
404            }
405        })
406    }
407
408    #[test]
409    fn call_js_fn_with_this_and_no_args_and_return() {
410        test_with(|ctx| {
411            let f: Function = ctx.eval("function f() { return this.val; } f").unwrap();
412            let obj = Object::new(ctx).unwrap();
413            obj.set("val", 42).unwrap();
414
415            let res: i32 = (This(obj.clone()),).apply(&f).unwrap();
416            assert_eq!(res, 42);
417            let res: i32 = f.call((This(obj),)).unwrap();
418            assert_eq!(res, 42);
419        })
420    }
421
422    #[test]
423    fn call_js_fn_with_this_and_1_arg_and_return() {
424        test_with(|ctx| {
425            let f: Function = ctx
426                .eval("function f(a) { return this.val * a; } f")
427                .unwrap();
428            let obj = Object::new(ctx).unwrap();
429            obj.set("val", 3).unwrap();
430
431            let res: i32 = (This(obj.clone()), 2).apply(&f).unwrap();
432            assert_eq!(res, 6);
433            let res: i32 = f.call((This(obj), 3)).unwrap();
434            assert_eq!(res, 9);
435        })
436    }
437
438    #[test]
439    fn call_js_fn_with_1_arg_deferred() {
440        let rt = Runtime::new().unwrap();
441        let ctx = Context::full(&rt).unwrap();
442        assert!(!rt.is_job_pending());
443        ctx.with(|ctx| {
444            let g = ctx.globals();
445            let f: Function = ctx.eval("(obj) => { obj.called = true; }").unwrap();
446            f.defer((g.clone(),)).unwrap();
447            let c: Value = g.get("called").unwrap();
448            assert_eq!(c.type_of(), Type::Undefined);
449        });
450        assert!(rt.is_job_pending());
451        rt.execute_pending_job().unwrap();
452        ctx.with(|ctx| {
453            let g = ctx.globals();
454            let c: Value = g.get("called").unwrap();
455            assert_eq!(c.type_of(), Type::Bool);
456        });
457    }
458
459    fn test() {
460        println!("test");
461    }
462
463    #[test]
464    fn static_callback() {
465        test_with(|ctx| {
466            let f = Function::new(ctx.clone(), test).unwrap();
467            f.set_name("test").unwrap();
468            let eval: Function = ctx.eval("a => { a() }").unwrap();
469            (f.clone(),).apply::<()>(&eval).unwrap();
470            f.call::<_, ()>(()).unwrap();
471
472            let name: StdString = f.clone().into_inner().get("name").unwrap();
473            assert_eq!(name, "test");
474
475            let get_name: Function = ctx.eval("a => a.name").unwrap();
476            let name: StdString = get_name.call((f.clone(),)).unwrap();
477            assert_eq!(name, "test");
478        })
479    }
480
481    #[test]
482    fn const_callback() {
483        use std::sync::{Arc, Mutex};
484        test_with(|ctx| {
485            #[allow(clippy::mutex_atomic)]
486            let called = Arc::new(Mutex::new(false));
487            let called_clone = called.clone();
488            let f = Function::new(ctx.clone(), move || {
489                (*called_clone.lock().unwrap()) = true;
490            })
491            .unwrap();
492            f.set_name("test").unwrap();
493
494            let eval: Function = ctx.eval("a => { a() }").unwrap();
495            eval.call::<_, ()>((f.clone(),)).unwrap();
496            f.call::<_, ()>(()).unwrap();
497            assert!(*called.lock().unwrap());
498
499            let name: StdString = f.clone().into_inner().get("name").unwrap();
500            assert_eq!(name, "test");
501
502            let get_name: Function = ctx.eval("a => a.name").unwrap();
503            let name: StdString = get_name.call((f.clone(),)).unwrap();
504            assert_eq!(name, "test");
505        })
506    }
507
508    #[test]
509    fn mutable_callback() {
510        test_with(|ctx| {
511            let mut v = 0;
512            let f = Function::new(
513                ctx.clone(),
514                MutFn::new(move || {
515                    v += 1;
516                    v
517                }),
518            )
519            .unwrap();
520            f.set_name("test").unwrap();
521
522            let eval: Function = ctx.eval("a => a()").unwrap();
523            assert_eq!(eval.call::<_, i32>((f.clone(),)).unwrap(), 1);
524            assert_eq!(eval.call::<_, i32>((f.clone(),)).unwrap(), 2);
525            assert_eq!(eval.call::<_, i32>((f.clone(),)).unwrap(), 3);
526
527            let name: StdString = f.clone().into_inner().get("name").unwrap();
528            assert_eq!(name, "test");
529
530            let get_name: Function = ctx.eval("a => a.name").unwrap();
531            let name: StdString = get_name.call((f.clone(),)).unwrap();
532            assert_eq!(name, "test");
533        })
534    }
535
536    #[test]
537    #[should_panic(
538        expected = "Error borrowing function: can't borrow a value as it is already borrowed"
539    )]
540    fn recursively_called_mutable_callback() {
541        test_with(|ctx| {
542            let mut v = 0;
543            let f = Function::new(
544                ctx.clone(),
545                MutFn::new(move |ctx: Ctx| {
546                    v += 1;
547                    ctx.globals()
548                        .get::<_, Function>("foo")
549                        .unwrap()
550                        .call::<_, ()>(())
551                        .catch(&ctx)
552                        .unwrap();
553                    v
554                }),
555            )
556            .unwrap();
557            ctx.globals().set("foo", f.clone()).unwrap();
558            f.call::<_, ()>(()).unwrap();
559        })
560    }
561
562    #[test]
563    #[should_panic(
564        expected = "Error borrowing function: tried to use a value, which can only be used once, again."
565    )]
566    fn repeatedly_called_once_callback() {
567        test_with(|ctx| {
568            let mut v = 0;
569            let f = Function::new(
570                ctx.clone(),
571                OnceFn::from(move || {
572                    v += 1;
573                    v
574                }),
575            )
576            .unwrap();
577            ctx.globals().set("foo", f.clone()).unwrap();
578            f.call::<_, ()>(()).catch(&ctx).unwrap();
579            f.call::<_, ()>(()).catch(&ctx).unwrap();
580        })
581    }
582
583    #[test]
584    fn multiple_const_callbacks() {
585        test_with(|ctx| {
586            let globals = ctx.globals();
587            globals.set("one", Func::new(|| 1f64)).unwrap();
588            globals.set("neg", Func::new(|a: f64| -a)).unwrap();
589            globals
590                .set("add", Func::new(|a: f64, b: f64| a + b))
591                .unwrap();
592
593            let r: f64 = ctx.eval("neg(add(one(), 2))").unwrap();
594            assert_approx_eq!(r, -3.0);
595        })
596    }
597
598    #[test]
599    fn mutable_callback_which_can_fail() {
600        test_with(|ctx| {
601            let globals = ctx.globals();
602            let mut id_alloc = 0;
603            globals
604                .set(
605                    "new_id",
606                    Func::from(MutFn::from(move || {
607                        id_alloc += 1;
608                        if id_alloc < 4 {
609                            Ok(id_alloc)
610                        } else {
611                            Err(Error::Unknown)
612                        }
613                    })),
614                )
615                .unwrap();
616
617            let id: u32 = ctx.eval("new_id()").unwrap();
618            assert_eq!(id, 1);
619            let id: u32 = ctx.eval("new_id()").unwrap();
620            assert_eq!(id, 2);
621            let id: u32 = ctx.eval("new_id()").unwrap();
622            assert_eq!(id, 3);
623            let _err = ctx.eval::<u32, _>("new_id()").unwrap_err();
624        })
625    }
626
627    #[test]
628    fn mutable_callback_with_ctx_which_reads_globals() {
629        test_with(|ctx| {
630            let globals = ctx.globals();
631            let mut id_alloc = 0;
632            globals
633                .set(
634                    "new_id",
635                    Func::from(MutFn::from(move |ctx: Ctx| {
636                        let initial: Option<u32> = ctx.globals().get("initial_id")?;
637                        if let Some(initial) = initial {
638                            id_alloc += 1;
639                            Ok(id_alloc + initial)
640                        } else {
641                            Err(Error::Unknown)
642                        }
643                    })),
644                )
645                .unwrap();
646
647            let _err = ctx.eval::<u32, _>("new_id()").unwrap_err();
648            globals.set("initial_id", 10).unwrap();
649
650            let id: u32 = ctx.eval("new_id()").unwrap();
651            assert_eq!(id, 11);
652            let id: u32 = ctx.eval("new_id()").unwrap();
653            assert_eq!(id, 12);
654            let id: u32 = ctx.eval("new_id()").unwrap();
655            assert_eq!(id, 13);
656        })
657    }
658
659    #[test]
660    fn call_rust_fn_with_ctx_and_value() {
661        test_with(|ctx| {
662            let func = Func::from(|ctx, val| {
663                struct Args<'js>(Ctx<'js>, Value<'js>);
664                let Args(ctx, val) = Args(ctx, val);
665                ctx.globals().set("test_str", val).unwrap();
666            });
667            ctx.globals().set("test_fn", func).unwrap();
668            ctx.eval::<(), _>(
669                r#"
670                  test_fn("test_str")
671                "#,
672            )
673            .unwrap();
674            let val: StdString = ctx.globals().get("test_str").unwrap();
675            assert_eq!(val, "test_str");
676        });
677    }
678
679    #[test]
680    fn call_rust_fn_with_this_and_args() {
681        let res: f64 = test_with(|ctx| {
682            let func = Function::new(ctx.clone(), |this: This<Object>, a: f64, b: f64| {
683                let x: f64 = this.get("x").unwrap();
684                let y: f64 = this.get("y").unwrap();
685                this.set("r", a * x + b * y).unwrap();
686            })
687            .unwrap();
688            ctx.globals().set("test_fn", func).unwrap();
689            ctx.eval(
690                r#"
691                  let test_obj = { x: 1, y: 2 };
692                  test_fn.call(test_obj, 3, 4);
693                  test_obj.r
694                "#,
695            )
696            .unwrap()
697        });
698        assert_eq!(res, 11.0);
699    }
700
701    #[test]
702    fn apply_rust_fn_with_this_and_args() {
703        let res: f32 = test_with(|ctx| {
704            let func = Function::new(ctx.clone(), |this: This<Object>, x: f32, y: f32| {
705                let a: f32 = this.get("a").unwrap();
706                let b: f32 = this.get("b").unwrap();
707                a * x + b * y
708            })
709            .unwrap();
710            ctx.globals().set("test_fn", func).unwrap();
711            ctx.eval(
712                r#"
713                  let test_obj = { a: 1, b: 2 };
714                  test_fn.apply(test_obj, [3, 4])
715                "#,
716            )
717            .unwrap()
718        });
719        assert_eq!(res, 11.0);
720    }
721
722    #[test]
723    fn bind_rust_fn_with_this_and_call_with_args() {
724        let res: f32 = test_with(|ctx| {
725            let func = Function::new(ctx.clone(), |this: This<Object>, x: f32, y: f32| {
726                let a: f32 = this.get("a").unwrap();
727                let b: f32 = this.get("b").unwrap();
728                a * x + b * y
729            })
730            .unwrap();
731            ctx.globals().set("test_fn", func).unwrap();
732            ctx.eval(
733                r#"
734                  let test_obj = { a: 1, b: 2 };
735                  test_fn.bind(test_obj)(3, 4)
736                "#,
737            )
738            .unwrap()
739        });
740        assert_eq!(res, 11.0);
741    }
742
743    #[test]
744    fn call_rust_fn_with_var_args() {
745        let res: Vec<i8> = test_with(|ctx| {
746            let func = Function::new(ctx.clone(), |args: Rest<i8>| {
747                use std::iter::once;
748                once(args.len() as i8)
749                    .chain(args.iter().cloned())
750                    .collect::<Vec<_>>()
751            })
752            .unwrap();
753            ctx.globals().set("test_fn", func).unwrap();
754            ctx.eval(
755                r#"
756                  test_fn(1, 2, 3)
757                "#,
758            )
759            .unwrap()
760        });
761        assert_eq!(res.len(), 4);
762        assert_eq!(res[0], 3);
763        assert_eq!(res[1], 1);
764        assert_eq!(res[2], 2);
765        assert_eq!(res[3], 3);
766    }
767
768    #[test]
769    fn call_rust_fn_with_rest_args() {
770        let res: Vec<i8> = test_with(|ctx| {
771            let func = Function::new(ctx.clone(), |arg1: i8, arg2: i8, args: Rest<i8>| {
772                use std::iter::once;
773                once(arg1)
774                    .chain(once(arg2))
775                    .chain(once(args.len() as i8))
776                    .chain(args.iter().cloned())
777                    .collect::<Vec<_>>()
778            })
779            .unwrap();
780            ctx.globals().set("test_fn", func).unwrap();
781            ctx.eval(
782                r#"
783                  test_fn(-2, -1, 1, 2)
784                "#,
785            )
786            .unwrap()
787        });
788        assert_eq!(res.len(), 5);
789        assert_eq!(res[0], -2);
790        assert_eq!(res[1], -1);
791        assert_eq!(res[2], 2);
792        assert_eq!(res[3], 1);
793        assert_eq!(res[4], 2);
794    }
795
796    #[test]
797    fn js_fn_wrappers() {
798        test_with(|ctx| {
799            let global = ctx.globals();
800            global
801                .set(
802                    "cat",
803                    Func::from(|a: StdString, b: StdString| format!("{a}{b}")),
804                )
805                .unwrap();
806            let res: StdString = ctx.eval("cat(\"foo\", \"bar\")").unwrap();
807            assert_eq!(res, "foobar");
808
809            let mut log = Vec::<StdString>::new();
810            global
811                .set(
812                    "log",
813                    Func::from(MutFn::from(move |msg: StdString| {
814                        log.push(msg);
815                        log.len() as u32
816                    })),
817                )
818                .unwrap();
819            let n: u32 = ctx.eval("log(\"foo\") + log(\"bar\")").unwrap();
820            assert_eq!(n, 3);
821        });
822    }
823}