rquickjs_core/value/
symbol.rs1use alloc::{ffi::CString, vec::Vec};
2
3use crate::{qjs, Atom, Ctx, Result, Value};
4
5#[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 pub fn new(ctx: Ctx<'js>) -> Result<Self> {
27 Self::new_inner(ctx, None::<&str>, false)
28 }
29
30 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 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 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 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 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 to_primitive => JS_ATOM_Symbol_toPrimitive
79 iterator => JS_ATOM_Symbol_iterator
81 r#match => JS_ATOM_Symbol_match
83 match_all => JS_ATOM_Symbol_matchAll
85 replace => JS_ATOM_Symbol_replace
87 search => JS_ATOM_Symbol_search
89 split => JS_ATOM_Symbol_split
91 has_instance => JS_ATOM_Symbol_hasInstance
93 species => JS_ATOM_Symbol_species
95 unscopables => JS_ATOM_Symbol_unscopables
97 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 let c: Symbol<'_> = ctx.eval("Symbol.for('shared')").unwrap();
166 assert_eq!(a, c);
167 });
168 }
169}