#[cfg(test)]
mod tests;
use crate::{
builtins::BuiltIn,
object::{ConstructorBuilder, FunctionBuilder},
property::Attribute,
symbol::{JsSymbol, WellKnownSymbols},
value::JsValue,
BoaProfiler, Context, JsResult, JsString,
};
use std::cell::RefCell;
use rustc_hash::FxHashMap;
use super::JsArgs;
thread_local! {
static GLOBAL_SYMBOL_REGISTRY: RefCell<GlobalSymbolRegistry> = RefCell::new(GlobalSymbolRegistry::new());
}
struct GlobalSymbolRegistry {
keys: FxHashMap<JsString, JsSymbol>,
symbols: FxHashMap<JsSymbol, JsString>,
}
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<JsString> {
if let Some(key) = self.symbols.get(&sym) {
return Some(key.clone());
}
None
}
}
#[derive(Debug, Clone, Copy)]
pub struct Symbol;
impl BuiltIn for Symbol {
const NAME: &'static str = "Symbol";
fn attribute() -> Attribute {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
}
fn init(context: &mut Context) -> (&'static str, JsValue, Attribute) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
let symbol_async_iterator = WellKnownSymbols::async_iterator();
let symbol_has_instance = WellKnownSymbols::has_instance();
let symbol_is_concat_spreadable = WellKnownSymbols::is_concat_spreadable();
let symbol_iterator = WellKnownSymbols::iterator();
let symbol_match = WellKnownSymbols::match_();
let symbol_match_all = WellKnownSymbols::match_all();
let symbol_replace = WellKnownSymbols::replace();
let symbol_search = WellKnownSymbols::search();
let symbol_species = WellKnownSymbols::species();
let symbol_split = WellKnownSymbols::split();
let symbol_to_primitive = WellKnownSymbols::to_primitive();
let symbol_to_string_tag = WellKnownSymbols::to_string_tag();
let symbol_unscopables = WellKnownSymbols::unscopables();
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
let get_description = FunctionBuilder::native(context, Self::get_description)
.name("get description")
.constructable(false)
.build();
let symbol_object = ConstructorBuilder::with_standard_object(
context,
Self::constructor,
context.standard_objects().symbol_object().clone(),
)
.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)
.static_property("iterator", symbol_iterator, attribute)
.static_property("match", symbol_match, attribute)
.static_property("matchAll", symbol_match_all, attribute)
.static_property("replace", symbol_replace, attribute)
.static_property("search", symbol_search, attribute)
.static_property("species", symbol_species, attribute)
.static_property("split", symbol_split, attribute)
.static_property("toPrimitive", symbol_to_primitive, attribute)
.static_property("toStringTag", symbol_to_string_tag.clone(), attribute)
.static_property("unscopables", symbol_unscopables, attribute)
.method(Self::to_string, "toString", 0)
.accessor(
"description",
Some(get_description),
None,
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.callable(true)
.constructable(false)
.property(
symbol_to_string_tag,
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.build();
(Self::NAME, symbol_object.into(), Self::attribute())
}
}
impl Symbol {
pub(crate) const LENGTH: usize = 0;
pub(crate) fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
if new_target.is_undefined() {
return context.throw_type_error("Symbol is not a constructor");
}
let description = match args.get(0) {
Some(value) if !value.is_undefined() => Some(value.to_string(context)?),
_ => None,
};
Ok(JsSymbol::new(description).into())
}
fn this_symbol_value(value: &JsValue, context: &mut Context) -> JsResult<JsSymbol> {
match value {
JsValue::Symbol(ref symbol) => return Ok(symbol.clone()),
JsValue::Object(ref object) => {
let object = object.borrow();
if let Some(symbol) = object.as_symbol() {
return Ok(symbol);
}
}
_ => {}
}
Err(context.construct_type_error("'this' is not a Symbol"))
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let symbol = Self::this_symbol_value(this, context)?;
Ok(symbol.to_string().into())
}
pub(crate) fn get_description(
this: &JsValue,
_: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let symbol = Self::this_symbol_value(this, context)?;
if let Some(ref description) = symbol.description() {
Ok(description.clone().into())
} else {
Ok(JsValue::undefined())
}
}
pub(crate) fn for_(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let string_key = args
.get(0)
.cloned()
.unwrap_or_default()
.to_string(context)?;
Ok(GLOBAL_SYMBOL_REGISTRY
.with(move |registry| {
let mut registry = registry.borrow_mut();
registry.get_or_insert_key(string_key)
})
.into())
}
pub(crate) fn key_for(
_: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let sym = args.get_or_undefined(0);
if let Some(sym) = sym.as_symbol() {
let symbol = GLOBAL_SYMBOL_REGISTRY.with(move |registry| {
let registry = registry.borrow();
registry.get_symbol(sym)
});
Ok(symbol.map(JsValue::from).unwrap_or_default())
} else {
context.throw_type_error("Symbol.keyFor: sym is not a symbol")
}
}
}