1#[cfg(test)]
19mod tests;
20
21use crate::{
22 builtins::BuiltIn,
23 object::{ConstructorBuilder, FunctionBuilder},
24 property::Attribute,
25 symbol::{JsSymbol, WellKnownSymbols},
26 value::JsValue,
27 BoaProfiler, Context, JsResult, JsString,
28};
29
30use std::cell::RefCell;
31
32use rustc_hash::FxHashMap;
33
34use super::JsArgs;
35
36thread_local! {
37 static GLOBAL_SYMBOL_REGISTRY: RefCell<GlobalSymbolRegistry> = RefCell::new(GlobalSymbolRegistry::new());
38}
39
40struct GlobalSymbolRegistry {
41 keys: FxHashMap<JsString, JsSymbol>,
42 symbols: FxHashMap<JsSymbol, JsString>,
43}
44
45impl GlobalSymbolRegistry {
46 fn new() -> Self {
47 Self {
48 keys: FxHashMap::default(),
49 symbols: FxHashMap::default(),
50 }
51 }
52
53 fn get_or_insert_key(&mut self, key: JsString) -> JsSymbol {
54 if let Some(symbol) = self.keys.get(&key) {
55 return symbol.clone();
56 }
57
58 let symbol = JsSymbol::new(Some(key.clone()));
59 self.keys.insert(key.clone(), symbol.clone());
60 self.symbols.insert(symbol.clone(), key);
61 symbol
62 }
63
64 fn get_symbol(&self, sym: JsSymbol) -> Option<JsString> {
65 if let Some(key) = self.symbols.get(&sym) {
66 return Some(key.clone());
67 }
68
69 None
70 }
71}
72
73#[derive(Debug, Clone, Copy)]
74pub struct Symbol;
75
76impl BuiltIn for Symbol {
77 const NAME: &'static str = "Symbol";
78
79 fn attribute() -> Attribute {
80 Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
81 }
82
83 fn init(context: &mut Context) -> (&'static str, JsValue, Attribute) {
84 let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
85
86 let symbol_async_iterator = WellKnownSymbols::async_iterator();
87 let symbol_has_instance = WellKnownSymbols::has_instance();
88 let symbol_is_concat_spreadable = WellKnownSymbols::is_concat_spreadable();
89 let symbol_iterator = WellKnownSymbols::iterator();
90 let symbol_match = WellKnownSymbols::match_();
91 let symbol_match_all = WellKnownSymbols::match_all();
92 let symbol_replace = WellKnownSymbols::replace();
93 let symbol_search = WellKnownSymbols::search();
94 let symbol_species = WellKnownSymbols::species();
95 let symbol_split = WellKnownSymbols::split();
96 let symbol_to_primitive = WellKnownSymbols::to_primitive();
97 let symbol_to_string_tag = WellKnownSymbols::to_string_tag();
98 let symbol_unscopables = WellKnownSymbols::unscopables();
99
100 let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
101
102 let get_description = FunctionBuilder::native(context, Self::get_description)
103 .name("get description")
104 .constructable(false)
105 .build();
106
107 let symbol_object = ConstructorBuilder::with_standard_object(
108 context,
109 Self::constructor,
110 context.standard_objects().symbol_object().clone(),
111 )
112 .name(Self::NAME)
113 .length(Self::LENGTH)
114 .static_method(Self::for_, "for", 1)
115 .static_method(Self::key_for, "keyFor", 1)
116 .static_property("asyncIterator", symbol_async_iterator, attribute)
117 .static_property("hasInstance", symbol_has_instance, attribute)
118 .static_property("isConcatSpreadable", symbol_is_concat_spreadable, attribute)
119 .static_property("iterator", symbol_iterator, attribute)
120 .static_property("match", symbol_match, attribute)
121 .static_property("matchAll", symbol_match_all, attribute)
122 .static_property("replace", symbol_replace, attribute)
123 .static_property("search", symbol_search, attribute)
124 .static_property("species", symbol_species, attribute)
125 .static_property("split", symbol_split, attribute)
126 .static_property("toPrimitive", symbol_to_primitive, attribute)
127 .static_property("toStringTag", symbol_to_string_tag.clone(), attribute)
128 .static_property("unscopables", symbol_unscopables, attribute)
129 .method(Self::to_string, "toString", 0)
130 .accessor(
131 "description",
132 Some(get_description),
133 None,
134 Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
135 )
136 .callable(true)
137 .constructable(false)
138 .property(
139 symbol_to_string_tag,
140 Self::NAME,
141 Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
142 )
143 .build();
144
145 (Self::NAME, symbol_object.into(), Self::attribute())
146 }
147}
148
149impl Symbol {
150 pub(crate) const LENGTH: usize = 0;
152
153 pub(crate) fn constructor(
165 new_target: &JsValue,
166 args: &[JsValue],
167 context: &mut Context,
168 ) -> JsResult<JsValue> {
169 if new_target.is_undefined() {
170 return context.throw_type_error("Symbol is not a constructor");
171 }
172 let description = match args.get(0) {
173 Some(value) if !value.is_undefined() => Some(value.to_string(context)?),
174 _ => None,
175 };
176
177 Ok(JsSymbol::new(description).into())
178 }
179
180 fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult<JsSymbol> {
181 match value {
182 JsValue::Symbol(ref symbol) => return Ok(symbol.clone()),
183 JsValue::Object(ref object) => {
184 let object = object.borrow();
185 if let Some(symbol) = object.as_symbol() {
186 return Ok(symbol);
187 }
188 }
189 _ => {}
190 }
191
192 Err(context.construct_type_error("'this' is not a Symbol"))
193 }
194
195 #[allow(clippy::wrong_self_convention)]
206 pub(crate) fn to_string(
207 this: &JsValue,
208 _: &[JsValue],
209 context: &mut Context,
210 ) -> JsResult<JsValue> {
211 let symbol = Self::this_symbol_value(this, context)?;
212 Ok(symbol.to_string().into())
213 }
214
215 pub(crate) fn get_description(
226 this: &JsValue,
227 _: &[JsValue],
228 context: &mut Context,
229 ) -> JsResult<JsValue> {
230 let symbol = Self::this_symbol_value(this, context)?;
231 if let Some(ref description) = symbol.description() {
232 Ok(description.clone().into())
233 } else {
234 Ok(JsValue::undefined())
235 }
236 }
237
238 pub(crate) fn for_(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
247 let string_key = args
249 .get(0)
250 .cloned()
251 .unwrap_or_default()
252 .to_string(context)?;
253 Ok(GLOBAL_SYMBOL_REGISTRY
260 .with(move |registry| {
261 let mut registry = registry.borrow_mut();
262 registry.get_or_insert_key(string_key)
263 })
264 .into())
265 }
266
267 pub(crate) fn key_for(
277 _: &JsValue,
278 args: &[JsValue],
279 context: &mut Context,
280 ) -> JsResult<JsValue> {
281 let sym = args.get_or_undefined(0);
282 if let Some(sym) = sym.as_symbol() {
284 let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| {
289 let registry = registry.borrow();
290 registry.get_symbol(sym)
291 });
292
293 Ok(symbol.map(JsValue::from).unwrap_or_default())
294 } else {
295 context.throw_type_error("Symbol.keyFor: sym is not a symbol")
296 }
297 }
298}