Skip to main content

rquickjs_core/context/
ctx.rs

1#[cfg(feature = "futures")]
2use core::future::Future;
3
4use alloc::{boxed::Box, ffi::CString, vec::Vec};
5use core::{
6    any::Any,
7    ffi::CStr,
8    mem::{self, MaybeUninit},
9    ptr::NonNull,
10    result::Result as StdResult,
11};
12
13#[cfg(feature = "std")]
14use std::{fs, path::Path, string::String as StdString};
15
16#[cfg(feature = "futures")]
17use crate::AsyncContext;
18use crate::{
19    markers::Invariant,
20    qjs,
21    runtime::{opaque::Opaque, UserDataError, UserDataGuard},
22    Atom, Error, FromJs, Function, IntoJs, JsLifetime, Object, Promise, Result, String, Value,
23};
24
25use super::Context;
26
27/// Eval options.
28#[non_exhaustive]
29pub struct EvalOptions {
30    /// Global code.
31    pub global: bool,
32    /// Force 'strict' mode.
33    pub strict: bool,
34    /// Don't include the stack frames before this eval in the Error() backtraces.
35    pub backtrace_barrier: bool,
36    /// Support top-level-await.
37    pub promise: bool,
38    /// Filename. Ignored when calling eval_file_*.
39    #[cfg(feature = "std")]
40    pub filename: Option<StdString>,
41}
42
43impl EvalOptions {
44    fn to_flag(&self) -> i32 {
45        let mut flag = if self.global {
46            qjs::JS_EVAL_TYPE_GLOBAL
47        } else {
48            qjs::JS_EVAL_TYPE_MODULE
49        };
50
51        if self.strict {
52            flag |= qjs::JS_EVAL_FLAG_STRICT;
53        }
54
55        if self.backtrace_barrier {
56            flag |= qjs::JS_EVAL_FLAG_BACKTRACE_BARRIER;
57        }
58
59        if self.promise {
60            flag |= qjs::JS_EVAL_FLAG_ASYNC;
61        }
62
63        flag as i32
64    }
65}
66
67impl Default for EvalOptions {
68    fn default() -> Self {
69        EvalOptions {
70            global: true,
71            strict: true,
72            backtrace_barrier: false,
73            promise: false,
74            #[cfg(feature = "std")]
75            filename: None,
76        }
77    }
78}
79
80/// Context in use, passed to [`Context::with`].
81#[derive(Debug)]
82pub struct Ctx<'js> {
83    ctx: NonNull<qjs::JSContext>,
84    _marker: Invariant<'js>,
85}
86
87impl<'js> Clone for Ctx<'js> {
88    fn clone(&self) -> Self {
89        unsafe { qjs::JS_DupContext(self.ctx.as_ptr()) };
90        Ctx {
91            ctx: self.ctx,
92            _marker: self._marker,
93        }
94    }
95}
96
97impl<'js> Drop for Ctx<'js> {
98    fn drop(&mut self) {
99        unsafe { qjs::JS_FreeContext(self.ctx.as_ptr()) };
100    }
101}
102
103unsafe impl Send for Ctx<'_> {}
104
105#[repr(C)] // Ensure C-compatible memory layout
106pub(crate) struct RefCountHeader {
107    pub ref_count: i32, // `int` in C is usually equivalent to `i32` in Rust
108}
109
110impl<'js> Ctx<'js> {
111    pub(crate) fn as_ptr(&self) -> *mut qjs::JSContext {
112        self.ctx.as_ptr()
113    }
114
115    pub(crate) unsafe fn from_ptr(ctx: *mut qjs::JSContext) -> Self {
116        unsafe { qjs::JS_DupContext(ctx) };
117        let ctx = NonNull::new_unchecked(ctx);
118        Ctx {
119            ctx,
120            _marker: Invariant::new(),
121        }
122    }
123
124    pub(crate) unsafe fn new(ctx: &'js Context) -> Self {
125        unsafe { qjs::JS_DupContext(ctx.0.ctx().as_ptr()) };
126        Ctx {
127            ctx: ctx.0.ctx(),
128            _marker: Invariant::new(),
129        }
130    }
131
132    #[cfg(feature = "futures")]
133    pub(crate) unsafe fn new_async(ctx: &'js AsyncContext) -> Self {
134        unsafe { qjs::JS_DupContext(ctx.0.ctx().as_ptr()) };
135        Ctx {
136            ctx: ctx.0.ctx(),
137            _marker: Invariant::new(),
138        }
139    }
140
141    pub(crate) unsafe fn eval_raw<S: Into<Vec<u8>>>(
142        &self,
143        source: S,
144        file_name: &CStr,
145        flag: i32,
146    ) -> Result<qjs::JSValue> {
147        let src = source.into();
148        let len = src.len();
149        let src = CString::new(src)?;
150
151        #[cfg(feature = "parallel")]
152        qjs::JS_UpdateStackTop(qjs::JS_GetRuntime(self.ctx.as_ptr()));
153
154        let val = qjs::JS_Eval(
155            self.ctx.as_ptr(),
156            src.as_ptr(),
157            len as _,
158            file_name.as_ptr(),
159            flag,
160        );
161        self.handle_exception(val)
162    }
163
164    /// Evaluate a script in global context.
165    pub fn eval<V: FromJs<'js>, S: Into<Vec<u8>>>(&self, source: S) -> Result<V> {
166        self.eval_with_options(source, Default::default())
167    }
168
169    /// Compiles a global script without evaluating it.
170    ///
171    /// This is useful for conformance runners which must distinguish an early
172    /// parse error from an exception produced while evaluating the script.
173    pub fn compile<S: Into<Vec<u8>>, N: Into<Vec<u8>>>(
174        &self,
175        source: S,
176        filename: N,
177        strict: bool,
178    ) -> Result<()> {
179        let filename = CString::new(filename)?;
180        let mut flags = qjs::JS_EVAL_TYPE_GLOBAL | qjs::JS_EVAL_FLAG_COMPILE_ONLY;
181        if strict {
182            flags |= qjs::JS_EVAL_FLAG_STRICT;
183        }
184        let value = unsafe { self.eval_raw(source, filename.as_c_str(), flags as i32)? };
185        unsafe { qjs::JS_FreeValue(self.as_ptr(), value) };
186        Ok(())
187    }
188
189    /// Evaluate a script in global context with top level await support.
190    ///
191    /// This function always returns a promise which resolves to the result of the evaluated
192    /// expression.
193    pub fn eval_promise<S: Into<Vec<u8>>>(&self, source: S) -> Result<Promise<'js>> {
194        self.eval_with_options(
195            source,
196            EvalOptions {
197                promise: true,
198                ..Default::default()
199            },
200        )
201    }
202
203    /// Evaluate a script with the given options.
204    pub fn eval_with_options<V: FromJs<'js>, S: Into<Vec<u8>>>(
205        &self,
206        source: S,
207        options: EvalOptions,
208    ) -> Result<V> {
209        #[cfg(feature = "std")]
210        let file_name = {
211            if let Some(filename) = &options.filename {
212                &CString::new(filename.clone())?
213            } else {
214                c"eval_script"
215            }
216        };
217
218        #[cfg(not(feature = "std"))]
219        let file_name = c"eval_script";
220
221        V::from_js(self, unsafe {
222            let val = self.eval_raw(source, file_name, options.to_flag())?;
223            Value::from_js_value(self.clone(), val)
224        })
225    }
226
227    #[cfg(feature = "std")]
228    /// Evaluate a script directly from a file.
229    pub fn eval_file<V: FromJs<'js>, P: AsRef<Path>>(&self, path: P) -> Result<V> {
230        self.eval_file_with_options(path, Default::default())
231    }
232
233    #[cfg(feature = "std")]
234    pub fn eval_file_with_options<V: FromJs<'js>, P: AsRef<Path>>(
235        &self,
236        path: P,
237        options: EvalOptions,
238    ) -> Result<V> {
239        let buffer = fs::read(path.as_ref())?;
240        let file_name = CString::new(
241            path.as_ref()
242                .file_name()
243                .unwrap()
244                .to_string_lossy()
245                .into_owned(),
246        )?;
247
248        V::from_js(self, unsafe {
249            let val = self.eval_raw(buffer, file_name.as_c_str(), options.to_flag())?;
250            Value::from_js_value(self.clone(), val)
251        })
252    }
253
254    /// Returns the global object of this context.
255    pub fn globals(&self) -> Object<'js> {
256        unsafe {
257            let v = qjs::JS_GetGlobalObject(self.ctx.as_ptr());
258            Object::from_js_value(self.clone(), v)
259        }
260    }
261
262    /// Returns the last raised JavaScript exception, if there is no exception the JavaScript value `null` is returned.
263    ///
264    /// # Usage
265    /// ```
266    /// # use rquickjs::{Error, Context, Runtime};
267    /// # let rt = Runtime::new().unwrap();
268    /// # let ctx = Context::full(&rt).unwrap();
269    /// # ctx.with(|ctx|{
270    /// if let Err(Error::Exception) = ctx.eval::<(),_>("throw 3"){
271    ///     assert_eq!(ctx.catch().as_int(),Some(3));
272    /// # }else{
273    /// #    panic!()
274    /// }
275    /// # });
276    /// ```
277    pub fn catch(&self) -> Value<'js> {
278        unsafe {
279            let v = qjs::JS_GetException(self.ctx.as_ptr());
280            Value::from_js_value(self.clone(), v)
281        }
282    }
283
284    /// Returns true if there is a pending JavaScript exception.
285    pub fn has_exception(&self) -> bool {
286        unsafe { qjs::JS_HasException(self.ctx.as_ptr()) }
287    }
288
289    /// Throws a JavaScript value as a new exception.
290    /// Always returns `Error::Exception`;
291    pub fn throw(&self, value: Value<'js>) -> Error {
292        unsafe {
293            let v = value.into_js_value();
294            qjs::JS_Throw(self.ctx.as_ptr(), v);
295        }
296        Error::Exception
297    }
298
299    /// Parse json into a JavaScript value.
300    pub fn json_parse<S>(&self, json: S) -> Result<Value<'js>>
301    where
302        S: Into<Vec<u8>>,
303    {
304        let src = json.into();
305        let len = src.len();
306        let src = CString::new(src)?;
307        unsafe {
308            let name = b"<input>\0";
309            let v = qjs::JS_ParseJSON(
310                self.as_ptr(),
311                src.as_ptr().cast(),
312                len.try_into().expect(qjs::SIZE_T_ERROR),
313                name.as_ptr().cast(),
314            );
315            self.handle_exception(v)?;
316            Ok(Value::from_js_value(self.clone(), v))
317        }
318    }
319
320    /// Stringify a JavaScript value into its JSON representation
321    pub fn json_stringify<V>(&self, value: V) -> Result<Option<String<'js>>>
322    where
323        V: IntoJs<'js>,
324    {
325        self.json_stringify_inner(&value.into_js(self)?, qjs::JS_UNDEFINED, qjs::JS_UNDEFINED)
326    }
327
328    /// Stringify a JavaScript value into its JSON representation with a possible replacer.
329    ///
330    /// The replacer is the same as the replacer argument for `JSON.stringify`.
331    /// It is is a function that alters the behavior of the stringification process.
332    pub fn json_stringify_replacer<V, R>(
333        &self,
334        value: V,
335        replacer: R,
336    ) -> Result<Option<String<'js>>>
337    where
338        V: IntoJs<'js>,
339        R: IntoJs<'js>,
340    {
341        let replacer = replacer.into_js(self)?;
342
343        self.json_stringify_inner(
344            &value.into_js(self)?,
345            replacer.as_js_value(),
346            qjs::JS_UNDEFINED,
347        )
348    }
349
350    /// Stringify a JavaScript value into its JSON representation with a possible replacer and
351    /// spaces
352    ///
353    /// The replacer is the same as the replacer argument for `JSON.stringify`.
354    /// It is is a function that alters the behavior of the stringification process.
355    ///
356    /// Space is either a number or a string which is used to insert whitespace into the output
357    /// string for readability purposes. This behaves the same as the space argument for
358    /// `JSON.stringify`.
359    pub fn json_stringify_replacer_space<V, R, S>(
360        &self,
361        value: V,
362        replacer: R,
363        space: S,
364    ) -> Result<Option<String<'js>>>
365    where
366        V: IntoJs<'js>,
367        R: IntoJs<'js>,
368        S: IntoJs<'js>,
369    {
370        let replacer = replacer.into_js(self)?;
371        let space = space.into_js(self)?;
372
373        self.json_stringify_inner(
374            &value.into_js(self)?,
375            replacer.as_js_value(),
376            space.as_js_value(),
377        )
378    }
379
380    // Inner non-generic version of json stringify>
381    fn json_stringify_inner(
382        &self,
383        value: &Value<'js>,
384        replacer: qjs::JSValueConst,
385        space: qjs::JSValueConst,
386    ) -> Result<Option<String<'js>>> {
387        unsafe {
388            let res = qjs::JS_JSONStringify(self.as_ptr(), value.as_js_value(), replacer, space);
389            self.handle_exception(res)?;
390            let v = Value::from_js_value(self.clone(), res);
391            if v.is_undefined() {
392                Ok(None)
393            } else {
394                let v = v.into_string().expect(
395                    "JS_JSONStringify did not return either an exception, undefined, or a string",
396                );
397                Ok(Some(v))
398            }
399        }
400    }
401
402    /// Creates javascipt promise along with its reject and resolve functions.
403    pub fn promise(&self) -> Result<(Promise<'js>, Function<'js>, Function<'js>)> {
404        let mut funcs = mem::MaybeUninit::<[qjs::JSValue; 2]>::uninit();
405
406        Ok(unsafe {
407            let promise = self.handle_exception(qjs::JS_NewPromiseCapability(
408                self.ctx.as_ptr(),
409                funcs.as_mut_ptr() as _,
410            ))?;
411            let [resolve, reject] = funcs.assume_init();
412            (
413                Promise::from_js_value(self.clone(), promise),
414                Function::from_js_value(self.clone(), resolve),
415                Function::from_js_value(self.clone(), reject),
416            )
417        })
418    }
419
420    /// Executes a quickjs job.
421    ///
422    /// Returns wether a job was actually executed.
423    /// If this function returned false, no job was pending.
424    pub fn execute_pending_job(&self) -> bool {
425        let mut ptr = MaybeUninit::<*mut qjs::JSContext>::uninit();
426        let rt = unsafe { qjs::JS_GetRuntime(self.ctx.as_ptr()) };
427        let res = unsafe { qjs::JS_ExecutePendingJob(rt, ptr.as_mut_ptr()) };
428        res != 0
429    }
430
431    pub(crate) unsafe fn get_opaque(&self) -> &Opaque<'js> {
432        Opaque::from_runtime_ptr(qjs::JS_GetRuntime(self.ctx.as_ptr()))
433    }
434
435    /// Spawn future using configured async runtime
436    #[cfg(feature = "futures")]
437    #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "futures")))]
438    pub fn spawn<F>(&self, future: F)
439    where
440        F: Future<Output = ()> + 'js,
441    {
442        unsafe { self.get_opaque().push(future) }
443    }
444
445    /// Create a new `Ctx` from a pointer to the context and a invariant lifetime.
446    ///
447    /// # Safety
448    /// User must ensure that a lock was acquired over the runtime and that invariant is a unique
449    /// lifetime which can't be coerced to a lifetime outside the scope of the lock of to the
450    /// lifetime of another runtime.
451    pub unsafe fn from_raw_invariant(ctx: NonNull<qjs::JSContext>, inv: Invariant<'js>) -> Self {
452        unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
453        Ctx { ctx, _marker: inv }
454    }
455
456    /// Create a new `Ctx` from a pointer to the context and a invariant lifetime.
457    ///
458    /// # Safety
459    /// User must ensure that a lock was acquired over the runtime and that invariant is a unique
460    /// lifetime which can't be coerced to a lifetime outside the scope of the lock of to the
461    /// lifetime of another runtime.
462    pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>) -> Self {
463        unsafe { qjs::JS_DupContext(ctx.as_ptr()) };
464        Ctx {
465            ctx,
466            _marker: Invariant::new(),
467        }
468    }
469
470    /// Returns the name of the current module or script that is running.
471    ///
472    /// It called from a javascript callback it will return the current running javascript script
473    /// name.
474    /// Otherwise it will return none.
475    pub fn script_or_module_name(&self, stack_level: isize) -> Option<Atom<'js>> {
476        let stack_level = core::ffi::c_int::try_from(stack_level).unwrap();
477        let atom = unsafe { qjs::JS_GetScriptOrModuleName(self.as_ptr(), stack_level) };
478        #[allow(clippy::useless_conversion)] //needed for multi platform binding support
479        if qjs::__JS_ATOM_NULL == atom.try_into().unwrap() {
480            unsafe { qjs::JS_FreeAtom(self.as_ptr(), atom) };
481            return None;
482        }
483        unsafe { Some(Atom::from_atom_val(self.clone(), atom)) }
484    }
485
486    /// Runs the quickjs garbage collector for a cycle.
487    ///
488    /// Quickjs uses reference counting with a collection cycle for cyclic references.
489    /// This runs the cyclic reference collector cycle, types which are not part of a reference cycle
490    /// will be freed the momement their reference count becomes zero.
491    pub fn run_gc(&self) {
492        unsafe { qjs::JS_RunGC(qjs::JS_GetRuntime(self.ctx.as_ptr())) }
493    }
494
495    /// Store a type in the runtime which can be retrieved later with `Ctx::userdata`.
496    ///
497    /// Returns the value from the argument if the userdata is currently being accessed and
498    /// insertion is not possible.
499    /// Otherwise returns the exising value for this type if it existed.
500    pub fn store_userdata<U>(&self, data: U) -> StdResult<Option<Box<U>>, UserDataError<U>>
501    where
502        U: JsLifetime<'js>,
503        U::Changed<'static>: Any,
504    {
505        unsafe { self.get_opaque().insert_userdata(data) }
506    }
507
508    /// Remove the userdata of the given type from the userdata storage.
509    ///
510    /// Returns Err(()) if the userdata is currently being accessed and removing isn't possible.
511    /// Returns Ok(None) if userdata of the given type wasn't inserted.
512    pub fn remove_userdata<U>(&self) -> StdResult<Option<Box<U>>, UserDataError<()>>
513    where
514        U: JsLifetime<'js>,
515        U::Changed<'static>: Any,
516    {
517        unsafe { self.get_opaque().remove_userdata() }
518    }
519
520    /// Retrieves a borrow to the userdata of the given type from the userdata storage.
521    ///
522    /// Returns None if userdata of the given type wasn't inserted.
523    pub fn userdata<U>(&self) -> Option<UserDataGuard<U>>
524    where
525        U: JsLifetime<'js>,
526        U::Changed<'static>: Any,
527    {
528        unsafe { self.get_opaque().get_userdata() }
529    }
530
531    /// Returns the pointer to the C library context.
532    pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
533        self.ctx
534    }
535}
536
537#[cfg(test)]
538mod test {
539    use crate::{CatchResultExt, JsLifetime};
540
541    #[test]
542    fn exports() {
543        use crate::{context::intrinsic, Context, Function, Module, Promise, Runtime};
544
545        let runtime = Runtime::new().unwrap();
546        let ctx = Context::custom::<(intrinsic::Promise, intrinsic::Eval)>(&runtime).unwrap();
547        ctx.with(|ctx| {
548            let (module, promise) = Module::declare(ctx, "test", "export default async () => 1;")
549                .unwrap()
550                .eval()
551                .unwrap();
552            promise.finish::<()>().unwrap();
553            let func: Function = module.get("default").unwrap();
554            func.call::<(), Promise>(()).unwrap();
555        });
556    }
557
558    #[test]
559    fn eval() {
560        use crate::{Context, Runtime};
561
562        let runtime = Runtime::new().unwrap();
563        let ctx = Context::full(&runtime).unwrap();
564        ctx.with(|ctx| {
565            let res: String = ctx
566                .eval(
567                    r#"
568                    function test() {
569                        var foo = "bar";
570                        return foo;
571                    }
572
573                    test()
574                "#,
575                )
576                .unwrap();
577
578            assert_eq!("bar".to_string(), res);
579        })
580    }
581
582    #[test]
583    fn eval_minimal_test() {
584        use crate::{Context, Runtime};
585
586        let runtime = Runtime::new().unwrap();
587        let ctx = Context::full(&runtime).unwrap();
588        ctx.with(|ctx| {
589            let res: i32 = ctx.eval(" 1 + 1 ").unwrap();
590            assert_eq!(2, res);
591        })
592    }
593
594    #[test]
595    #[should_panic(expected = "foo is not defined")]
596    fn eval_with_sloppy_code() {
597        use crate::{CatchResultExt, Context, Runtime};
598
599        let runtime = Runtime::new().unwrap();
600        let ctx = Context::full(&runtime).unwrap();
601        ctx.with(|ctx| {
602            let _: String = ctx
603                .eval(
604                    r#"
605                    function test() {
606                        foo = "bar";
607                        return foo;
608                    }
609
610                    test()
611                "#,
612                )
613                .catch(&ctx)
614                .unwrap();
615        })
616    }
617
618    #[test]
619    fn eval_with_options_no_strict_sloppy_code() {
620        use crate::{context::EvalOptions, Context, Runtime};
621
622        let runtime = Runtime::new().unwrap();
623        let ctx = Context::full(&runtime).unwrap();
624        ctx.with(|ctx| {
625            let res: String = ctx
626                .eval_with_options(
627                    r#"
628                    function test() {
629                        foo = "bar";
630                        return foo;
631                    }
632
633                    test()
634                "#,
635                    EvalOptions {
636                        strict: false,
637                        ..Default::default()
638                    },
639                )
640                .unwrap();
641
642            assert_eq!("bar".to_string(), res);
643        })
644    }
645
646    #[test]
647    #[should_panic(expected = "foo is not defined")]
648    fn eval_with_options_strict_sloppy_code() {
649        use crate::{context::EvalOptions, CatchResultExt, Context, Runtime};
650
651        let runtime = Runtime::new().unwrap();
652        let ctx = Context::full(&runtime).unwrap();
653        ctx.with(|ctx| {
654            let _: String = ctx
655                .eval_with_options(
656                    r#"
657                    function test() {
658                        foo = "bar";
659                        return foo;
660                    }
661
662                    test()
663                "#,
664                    EvalOptions {
665                        strict: true,
666                        ..Default::default()
667                    },
668                )
669                .catch(&ctx)
670                .unwrap();
671        })
672    }
673
674    #[test]
675    fn json_parse() {
676        use crate::{Array, Context, Object, Runtime};
677
678        let runtime = Runtime::new().unwrap();
679        let ctx = Context::full(&runtime).unwrap();
680        ctx.with(|ctx| {
681            let v = ctx
682                .json_parse(r#"{ "a": { "b": 1, "c": true }, "d": [0,"foo"] }"#)
683                .unwrap();
684            let obj = v.into_object().unwrap();
685            let inner_obj: Object = obj.get("a").unwrap();
686            assert_eq!(inner_obj.get::<_, i32>("b").unwrap(), 1);
687            assert!(inner_obj.get::<_, bool>("c").unwrap());
688            let inner_array: Array = obj.get("d").unwrap();
689            assert_eq!(inner_array.get::<i32>(0).unwrap(), 0);
690            assert_eq!(inner_array.get::<String>(1).unwrap(), "foo".to_string());
691        })
692    }
693
694    #[test]
695    fn json_stringify() {
696        use crate::{Array, Context, Object, Runtime};
697
698        let runtime = Runtime::new().unwrap();
699        let ctx = Context::full(&runtime).unwrap();
700        ctx.with(|ctx| {
701            let obj_inner = Object::new(ctx.clone()).unwrap();
702            obj_inner.set("b", 1).unwrap();
703            obj_inner.set("c", true).unwrap();
704
705            let array_inner = Array::new(ctx.clone()).unwrap();
706            array_inner.set(0, 0).unwrap();
707            array_inner.set(1, "foo").unwrap();
708
709            let obj = Object::new(ctx.clone()).unwrap();
710            obj.set("a", obj_inner).unwrap();
711            obj.set("d", array_inner).unwrap();
712
713            let str = ctx
714                .json_stringify(obj)
715                .unwrap()
716                .unwrap()
717                .to_string()
718                .unwrap();
719
720            assert_eq!(str, r#"{"a":{"b":1,"c":true},"d":[0,"foo"]}"#);
721        })
722    }
723
724    #[test]
725    fn userdata() {
726        use crate::{Context, Function, Runtime};
727
728        pub struct MyUserData<'js> {
729            base: Function<'js>,
730        }
731
732        unsafe impl<'js> JsLifetime<'js> for MyUserData<'js> {
733            type Changed<'to> = MyUserData<'to>;
734        }
735
736        let rt = Runtime::new().unwrap();
737        let ctx = Context::full(&rt).unwrap();
738
739        ctx.with(|ctx| {
740            let func = ctx.eval("() => 42").catch(&ctx).unwrap();
741            ctx.store_userdata(MyUserData { base: func }).unwrap();
742        });
743
744        ctx.with(|ctx| {
745            let userdata = ctx.userdata::<MyUserData>().unwrap();
746
747            assert!(ctx.remove_userdata::<MyUserData>().is_err());
748
749            let r: usize = userdata.base.call(()).unwrap();
750            assert_eq!(r, 42)
751        });
752
753        ctx.with(|ctx| {
754            ctx.remove_userdata::<MyUserData>().unwrap().unwrap();
755        })
756    }
757}