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