Skip to main content

rquickjs_core/value/
symbol.rs

1use alloc::{ffi::CString, vec::Vec};
2
3use crate::{qjs, Atom, Ctx, Result, Value};
4
5/// Rust representation of a JavaScript symbol.
6#[derive(Debug, Clone, PartialEq, Hash)]
7#[repr(transparent)]
8pub struct Symbol<'js>(pub(crate) Value<'js>);
9
10impl<'js> Symbol<'js> {
11    fn new_inner(
12        ctx: Ctx<'js>,
13        description: Option<impl Into<Vec<u8>>>,
14        is_global: bool,
15    ) -> Result<Self> {
16        let c_str = description.map(CString::new).transpose()?;
17        let ptr = c_str.as_ref().map_or(core::ptr::null(), |s| s.as_ptr());
18        unsafe {
19            let val = qjs::JS_NewSymbol(ctx.as_ptr(), ptr, is_global);
20            let val = ctx.handle_exception(val)?;
21            Ok(Symbol(Value::from_js_value(ctx, val)))
22        }
23    }
24
25    /// Create a new unique local symbol without a description (equivalent to `Symbol()`).
26    pub fn new(ctx: Ctx<'js>) -> Result<Self> {
27        Self::new_inner(ctx, None::<&str>, false)
28    }
29
30    /// Create a new unique local symbol with a description (equivalent to `Symbol(description)`).
31    pub fn with_description(ctx: Ctx<'js>, description: impl Into<Vec<u8>>) -> Result<Self> {
32        Self::new_inner(ctx, Some(description), false)
33    }
34
35    /// Create or retrieve a global symbol for the given key (equivalent to `Symbol.for(key)`).
36    pub fn new_global(ctx: Ctx<'js>, description: impl Into<Vec<u8>>) -> Result<Self> {
37        Self::new_inner(ctx, Some(description), true)
38    }
39
40    /// Get the symbol description
41    pub fn description(&self) -> Result<Value<'js>> {
42        let atom = Atom::from_str(self.0.ctx.clone(), "description")?;
43        unsafe {
44            let val = qjs::JS_GetProperty(self.0.ctx.as_ptr(), self.0.as_js_value(), atom.atom);
45            let val = self.0.ctx.handle_exception(val)?;
46            Ok(Value::from_js_value(self.0.ctx.clone(), val))
47        }
48    }
49
50    /// Convert a symbol into a atom.
51    pub fn as_atom(&self) -> Atom<'js> {
52        Atom::from_value(self.0.ctx().clone(), &self.0)
53            .expect("symbols should always convert to atoms")
54    }
55}
56
57macro_rules! impl_symbols {
58    ($($(#[$m:meta])? $fn_name:ident => $const_name:ident)*) => {
59        impl<'js> Symbol<'js> {
60            $(
61            $(#[$m])*
62            pub fn $fn_name(ctx: Ctx<'js>) -> Self {
63                // No-op in most cases but with certain dump flags static symbols maintain a ref count.
64                let v = unsafe {
65                    let v = qjs::JS_AtomToValue(ctx.as_ptr(),qjs::$const_name as qjs::JSAtom);
66                    Value::from_js_value(ctx, v)
67                };
68
69                v.into_symbol().unwrap()
70            }
71            )*
72        }
73    };
74}
75
76impl_symbols! {
77    /// returns the symbol for `toPrimitive`
78    to_primitive => JS_ATOM_Symbol_toPrimitive
79    /// returns the symbol for `iterator`
80    iterator => JS_ATOM_Symbol_iterator
81    /// returns the symbol for `match`
82    r#match => JS_ATOM_Symbol_match
83    /// returns the symbol for `matchAll`
84    match_all => JS_ATOM_Symbol_matchAll
85    /// returns the symbol for `replace`
86    replace => JS_ATOM_Symbol_replace
87    /// returns the symbol for `search`
88    search => JS_ATOM_Symbol_search
89    /// returns the symbol for `split`
90    split => JS_ATOM_Symbol_split
91    /// returns the symbol for `hasInstance`
92    has_instance => JS_ATOM_Symbol_hasInstance
93    /// returns the symbol for `species`
94    species => JS_ATOM_Symbol_species
95    /// returns the symbol for `unscopables`
96    unscopables => JS_ATOM_Symbol_unscopables
97    /// returns the symbol for `asyncIterator`
98    async_iterator => JS_ATOM_Symbol_asyncIterator
99}
100
101#[cfg(test)]
102mod test {
103    use crate::*;
104
105    #[test]
106    fn description() {
107        test_with(|ctx| {
108            let s: Symbol<'_> = ctx.eval("Symbol('foo bar baz')").unwrap();
109            assert_eq!(
110                s.description()
111                    .unwrap()
112                    .into_string()
113                    .unwrap()
114                    .to_string()
115                    .unwrap(),
116                "foo bar baz"
117            );
118
119            let s: Symbol<'_> = ctx.eval("Symbol()").unwrap();
120            assert!(s.description().unwrap().is_undefined());
121        });
122    }
123
124    #[test]
125    fn new_without_description() {
126        test_with(|ctx| {
127            let s = Symbol::new(ctx).unwrap();
128            assert!(s.description().unwrap().is_undefined());
129        });
130    }
131
132    #[test]
133    fn new_with_description() {
134        test_with(|ctx| {
135            let s = Symbol::with_description(ctx, "test").unwrap();
136            assert_eq!(
137                s.description()
138                    .unwrap()
139                    .into_string()
140                    .unwrap()
141                    .to_string()
142                    .unwrap(),
143                "test"
144            );
145        });
146    }
147
148    #[test]
149    fn new_unique() {
150        test_with(|ctx| {
151            let a = Symbol::with_description(ctx.clone(), "same").unwrap();
152            let b = Symbol::with_description(ctx, "same").unwrap();
153            assert_ne!(a, b);
154        });
155    }
156
157    #[test]
158    fn new_global() {
159        test_with(|ctx| {
160            let a = Symbol::new_global(ctx.clone(), "shared").unwrap();
161            let b = Symbol::new_global(ctx.clone(), "shared").unwrap();
162            assert_eq!(a, b);
163
164            // Should also match Symbol.for() from JS
165            let c: Symbol<'_> = ctx.eval("Symbol.for('shared')").unwrap();
166            assert_eq!(a, c);
167        });
168    }
169}