Skip to main content

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