diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index 8d2bf56266..13629f07ef 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -24,9 +24,50 @@ use crate::{ property::Attribute, symbol::{JsSymbol, WellKnownSymbols}, value::Value, - BoaProfiler, Context, Result, + BoaProfiler, Context, JsString, Result, }; +use std::cell::RefCell; + +use rustc_hash::FxHashMap; + +thread_local! { + static GLOBAL_SYMBOL_REGISTRY: RefCell = RefCell::new(GlobalSymbolRegistry::new()); +} + +struct GlobalSymbolRegistry { + keys: FxHashMap, + symbols: FxHashMap, +} + +impl GlobalSymbolRegistry { + fn new() -> Self { + Self { + keys: FxHashMap::default(), + symbols: FxHashMap::default(), + } + } + + fn get_or_insert_key(&mut self, key: JsString) -> JsSymbol { + if let Some(symbol) = self.keys.get(&key) { + return symbol.clone(); + } + + let symbol = JsSymbol::new(Some(key.clone())); + self.keys.insert(key.clone(), symbol.clone()); + self.symbols.insert(symbol.clone(), key); + symbol + } + + fn get_symbol(&self, sym: JsSymbol) -> Option { + if let Some(key) = self.symbols.get(&sym) { + return Some(key.clone()); + } + + None + } +} + #[derive(Debug, Clone, Copy)] pub struct Symbol; @@ -69,6 +110,8 @@ impl BuiltIn for Symbol { ) .name(Self::NAME) .length(Self::LENGTH) + .static_method(Self::for_, "for", 1) + .static_method(Self::key_for, "keyFor", 1) .static_property("asyncIterator", symbol_async_iterator, attribute) .static_property("hasInstance", symbol_has_instance, attribute) .static_property("isConcatSpreadable", symbol_is_concat_spreadable, attribute) @@ -186,4 +229,61 @@ impl Symbol { Ok(Value::undefined()) } } + + /// `Symbol.for( key )` + /// + /// More information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.for + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/for + pub(crate) fn for_(_: &Value, args: &[Value], context: &mut Context) -> Result { + // 1. Let stringKey be ? ToString(key). + let string_key = args + .get(0) + .cloned() + .unwrap_or_default() + .to_string(context)?; + // 2. For each element e of the GlobalSymbolRegistry List, do + // a. If SameValue(e.[[Key]], stringKey) is true, return e.[[Symbol]]. + // 3. Assert: GlobalSymbolRegistry does not currently contain an entry for stringKey. + // 4. Let newSymbol be a new unique Symbol value whose [[Description]] value is stringKey. + // 5. Append the Record { [[Key]]: stringKey, [[Symbol]]: newSymbol } to the GlobalSymbolRegistry List. + // 6. Return newSymbol. + Ok(GLOBAL_SYMBOL_REGISTRY + .with(move |registry| { + let mut registry = registry.borrow_mut(); + registry.get_or_insert_key(string_key) + }) + .into()) + } + + /// `Symbol.keyFor( sym )` + /// + /// + /// More information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.keyfor + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/keyFor + pub(crate) fn key_for(_: &Value, args: &[Value], context: &mut Context) -> Result { + let sym = args.get(0).cloned().unwrap_or_default(); + // 1. If Type(sym) is not Symbol, throw a TypeError exception. + if let Some(sym) = sym.as_symbol() { + // 2. For each element e of the GlobalSymbolRegistry List (see 20.4.2.2), do + // a. If SameValue(e.[[Symbol]], sym) is true, return e.[[Key]]. + // 3. Assert: GlobalSymbolRegistry does not currently contain an entry for sym. + // 4. Return undefined. + let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| { + let registry = registry.borrow(); + registry.get_symbol(sym) + }); + + Ok(symbol.map(Value::from).unwrap_or_default()) + } else { + context.throw_type_error("Symbol.keyFor: sym is not a symbol") + } + } }