boa_engine/builtins/symbol/
mod.rs1#[cfg(test)]
19mod tests;
20
21use std::{hash::BuildHasherDefault, sync::LazyLock};
22
23use crate::{
24 Context, JsArgs, JsResult, JsString,
25 builtins::BuiltInObject,
26 context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
27 error::JsNativeError,
28 js_string,
29 object::JsObject,
30 property::Attribute,
31 realm::Realm,
32 string::StaticJsStrings,
33 symbol::JsSymbol,
34 value::JsValue,
35};
36use dashmap::DashMap;
37use rustc_hash::FxHasher;
38
39use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject};
40
41static GLOBAL_SYMBOL_REGISTRY: LazyLock<GlobalSymbolRegistry> =
42 LazyLock::new(GlobalSymbolRegistry::new);
43
44type FxDashMap<K, V> = DashMap<K, V, BuildHasherDefault<FxHasher>>;
45
46struct GlobalSymbolRegistry {
53 keys: FxDashMap<Box<[u16]>, JsSymbol>,
54 symbols: FxDashMap<JsSymbol, Box<[u16]>>,
55}
56
57impl GlobalSymbolRegistry {
58 fn new() -> Self {
59 Self {
60 keys: FxDashMap::default(),
61 symbols: FxDashMap::default(),
62 }
63 }
64
65 fn get_or_create_symbol(&self, key: &JsString) -> JsResult<JsSymbol> {
66 let slice = key.iter().collect::<Vec<_>>();
67 if let Some(symbol) = self.keys.get(&slice[..]) {
68 return Ok(symbol.clone());
69 }
70
71 let symbol = JsSymbol::new(Some(key.clone())).ok_or_else(|| {
72 JsNativeError::range()
73 .with_message("reached the maximum number of symbols that can be created")
74 })?;
75 self.keys
76 .insert(slice.clone().into_boxed_slice(), symbol.clone());
77 self.symbols
78 .insert(symbol.clone(), slice.into_boxed_slice());
79 Ok(symbol)
80 }
81
82 fn get_key(&self, sym: &JsSymbol) -> Option<JsString> {
83 if let Some(key) = self.symbols.get(sym) {
84 return Some(js_string!(&**key));
85 }
86
87 None
88 }
89}
90
91#[derive(Debug, Clone, Copy)]
93pub struct Symbol;
94
95impl IntrinsicObject for Symbol {
96 fn init(realm: &Realm) {
97 let symbol_async_iterator = JsSymbol::async_iterator();
98 let symbol_has_instance = JsSymbol::has_instance();
99 let symbol_is_concat_spreadable = JsSymbol::is_concat_spreadable();
100 let symbol_iterator = JsSymbol::iterator();
101 let symbol_match = JsSymbol::r#match();
102 let symbol_match_all = JsSymbol::match_all();
103 let symbol_replace = JsSymbol::replace();
104 let symbol_search = JsSymbol::search();
105 let symbol_species = JsSymbol::species();
106 let symbol_split = JsSymbol::split();
107 let symbol_to_primitive = JsSymbol::to_primitive();
108 let symbol_to_string_tag = JsSymbol::to_string_tag();
109 let symbol_unscopables = JsSymbol::unscopables();
110 let symbol_dispose = JsSymbol::dispose();
111 let symbol_async_dispose = JsSymbol::async_dispose();
112
113 let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
114
115 let to_primitive = BuiltInBuilder::callable(realm, Self::to_primitive)
116 .name(js_string!("[Symbol.toPrimitive]"))
117 .length(1)
118 .build();
119
120 let get_description = BuiltInBuilder::callable(realm, Self::get_description)
121 .name(js_string!("get description"))
122 .build();
123
124 BuiltInBuilder::from_standard_constructor::<Self>(realm)
125 .static_method(Self::for_, js_string!("for"), 1)
126 .static_method(Self::key_for, js_string!("keyFor"), 1)
127 .static_property(
128 js_string!("asyncIterator"),
129 symbol_async_iterator,
130 attribute,
131 )
132 .static_property(js_string!("hasInstance"), symbol_has_instance, attribute)
133 .static_property(
134 js_string!("isConcatSpreadable"),
135 symbol_is_concat_spreadable,
136 attribute,
137 )
138 .static_property(js_string!("iterator"), symbol_iterator, attribute)
139 .static_property(js_string!("match"), symbol_match, attribute)
140 .static_property(js_string!("matchAll"), symbol_match_all, attribute)
141 .static_property(js_string!("replace"), symbol_replace, attribute)
142 .static_property(js_string!("search"), symbol_search, attribute)
143 .static_property(js_string!("species"), symbol_species, attribute)
144 .static_property(js_string!("split"), symbol_split, attribute)
145 .static_property(
146 js_string!("toPrimitive"),
147 symbol_to_primitive.clone(),
148 attribute,
149 )
150 .static_property(
151 js_string!("toStringTag"),
152 symbol_to_string_tag.clone(),
153 attribute,
154 )
155 .static_property(js_string!("unscopables"), symbol_unscopables, attribute)
156 .static_property(js_string!("dispose"), symbol_dispose, attribute)
157 .static_property(js_string!("asyncDispose"), symbol_async_dispose, attribute)
158 .method(Self::to_string, js_string!("toString"), 0)
159 .method(Self::value_of, js_string!("valueOf"), 0)
160 .accessor(
161 js_string!("description"),
162 Some(get_description),
163 None,
164 Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
165 )
166 .property(
167 symbol_to_string_tag,
168 Self::NAME,
169 Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
170 )
171 .property(
172 symbol_to_primitive,
173 to_primitive,
174 Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
175 )
176 .build();
177 }
178
179 fn get(intrinsics: &Intrinsics) -> JsObject {
180 Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
181 }
182}
183
184impl BuiltInObject for Symbol {
185 const NAME: JsString = StaticJsStrings::SYMBOL;
186}
187
188impl BuiltInConstructor for Symbol {
189 const CONSTRUCTOR_ARGUMENTS: usize = 0;
190 const PROTOTYPE_STORAGE_SLOTS: usize = 6;
191 const CONSTRUCTOR_STORAGE_SLOTS: usize = 17;
192
193 const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
194 StandardConstructors::symbol;
195
196 fn constructor(
208 new_target: &JsValue,
209 args: &[JsValue],
210 context: &mut Context,
211 ) -> JsResult<JsValue> {
212 if !new_target.is_undefined() {
214 return Err(JsNativeError::typ()
215 .with_message("Symbol is not a constructor")
216 .into());
217 }
218
219 let description = match args.first() {
222 Some(value) if !value.is_undefined() => Some(value.to_string(context)?),
223 _ => None,
224 };
225
226 Ok(JsSymbol::new(description)
228 .ok_or_else(|| {
229 JsNativeError::range()
230 .with_message("reached the maximum number of symbols that can be created")
231 })?
232 .into())
233 }
234}
235
236impl Symbol {
237 fn this_symbol_value(value: &JsValue) -> JsResult<JsSymbol> {
238 value
239 .as_symbol()
240 .or_else(|| {
241 value
242 .as_object()
243 .and_then(|obj| obj.downcast_ref::<JsSymbol>().as_deref().cloned())
244 })
245 .ok_or_else(|| {
246 JsNativeError::typ()
247 .with_message("'this' is not a Symbol")
248 .into()
249 })
250 }
251
252 #[allow(clippy::wrong_self_convention)]
263 pub(crate) fn to_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
264 let symbol = Self::this_symbol_value(this)?;
266
267 Ok(symbol.descriptive_string().into())
269 }
270
271 pub(crate) fn value_of(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
282 let symbol = Self::this_symbol_value(this)?;
284 Ok(symbol.into())
285 }
286
287 pub(crate) fn get_description(
298 this: &JsValue,
299 _: &[JsValue],
300 _: &mut Context,
301 ) -> JsResult<JsValue> {
302 let sym = Self::this_symbol_value(this)?;
305
306 Ok(sym
308 .description()
309 .map_or(JsValue::undefined(), JsValue::from))
310 }
311
312 pub(crate) fn for_(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
321 let string_key = args
323 .first()
324 .cloned()
325 .unwrap_or_default()
326 .to_string(context)?;
327 GLOBAL_SYMBOL_REGISTRY
334 .get_or_create_symbol(&string_key)
335 .map(JsValue::from)
336 }
337
338 pub(crate) fn key_for(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
348 let sym = args.get_or_undefined(0).as_symbol().ok_or_else(|| {
350 JsNativeError::typ().with_message("Symbol.keyFor: sym is not a symbol")
351 })?;
352
353 Ok(GLOBAL_SYMBOL_REGISTRY
359 .get_key(&sym)
360 .map(JsValue::from)
361 .unwrap_or_default())
362 }
363
364 pub(crate) fn to_primitive(
376 this: &JsValue,
377 _: &[JsValue],
378 _: &mut Context,
379 ) -> JsResult<JsValue> {
380 let sym = Self::this_symbol_value(this)?;
381 Ok(sym.into())
383 }
384}