#[cfg(test)]
mod tests;
use std::hash::BuildHasherDefault;
use crate::{
builtins::BuiltInObject,
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
js_string,
object::JsObject,
property::Attribute,
realm::Realm,
string::utf16,
symbol::JsSymbol,
value::JsValue,
Context, JsArgs, JsResult, JsString,
};
use boa_profiler::Profiler;
use dashmap::DashMap;
use once_cell::sync::Lazy;
use rustc_hash::FxHasher;
use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject};
static GLOBAL_SYMBOL_REGISTRY: Lazy<GlobalSymbolRegistry> = Lazy::new(GlobalSymbolRegistry::new);
type FxDashMap<K, V> = DashMap<K, V, BuildHasherDefault<FxHasher>>;
struct GlobalSymbolRegistry {
keys: FxDashMap<Box<[u16]>, JsSymbol>,
symbols: FxDashMap<JsSymbol, Box<[u16]>>,
}
impl GlobalSymbolRegistry {
fn new() -> Self {
Self {
keys: FxDashMap::default(),
symbols: FxDashMap::default(),
}
}
fn get_or_create_symbol(&self, key: &JsString) -> JsResult<JsSymbol> {
let slice = &**key;
if let Some(symbol) = self.keys.get(slice) {
return Ok(symbol.clone());
}
let symbol = JsSymbol::new(Some(key.clone())).ok_or_else(|| {
JsNativeError::range()
.with_message("reached the maximum number of symbols that can be created")
})?;
self.keys.insert(slice.into(), symbol.clone());
self.symbols.insert(symbol.clone(), slice.into());
Ok(symbol)
}
fn get_key(&self, sym: &JsSymbol) -> Option<JsString> {
if let Some(key) = self.symbols.get(sym) {
return Some(js_string!(&**key));
}
None
}
}
#[derive(Debug, Clone, Copy)]
pub struct Symbol;
impl IntrinsicObject for Symbol {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let symbol_async_iterator = JsSymbol::async_iterator();
let symbol_has_instance = JsSymbol::has_instance();
let symbol_is_concat_spreadable = JsSymbol::is_concat_spreadable();
let symbol_iterator = JsSymbol::iterator();
let symbol_match = JsSymbol::r#match();
let symbol_match_all = JsSymbol::match_all();
let symbol_replace = JsSymbol::replace();
let symbol_search = JsSymbol::search();
let symbol_species = JsSymbol::species();
let symbol_split = JsSymbol::split();
let symbol_to_primitive = JsSymbol::to_primitive();
let symbol_to_string_tag = JsSymbol::to_string_tag();
let symbol_unscopables = JsSymbol::unscopables();
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
let to_primitive = BuiltInBuilder::callable(realm, Self::to_primitive)
.name("[Symbol.toPrimitive]")
.length(1)
.build();
let get_description = BuiltInBuilder::callable(realm, Self::get_description)
.name("get description")
.build();
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.static_method(Self::for_, "for", 1)
.static_method(Self::key_for, "keyFor", 1)
.static_property(utf16!("asyncIterator"), symbol_async_iterator, attribute)
.static_property(utf16!("hasInstance"), symbol_has_instance, attribute)
.static_property(
utf16!("isConcatSpreadable"),
symbol_is_concat_spreadable,
attribute,
)
.static_property(utf16!("iterator"), symbol_iterator, attribute)
.static_property(utf16!("match"), symbol_match, attribute)
.static_property(utf16!("matchAll"), symbol_match_all, attribute)
.static_property(utf16!("replace"), symbol_replace, attribute)
.static_property(utf16!("search"), symbol_search, attribute)
.static_property(utf16!("species"), symbol_species, attribute)
.static_property(utf16!("split"), symbol_split, attribute)
.static_property(
utf16!("toPrimitive"),
symbol_to_primitive.clone(),
attribute,
)
.static_property(
utf16!("toStringTag"),
symbol_to_string_tag.clone(),
attribute,
)
.static_property(utf16!("unscopables"), symbol_unscopables, attribute)
.method(Self::to_string, "toString", 0)
.method(Self::value_of, "valueOf", 0)
.accessor(
utf16!("description"),
Some(get_description),
None,
Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE,
)
.property(
symbol_to_string_tag,
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.property(
symbol_to_primitive,
to_primitive,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
}
impl BuiltInObject for Symbol {
const NAME: &'static str = "Symbol";
}
impl BuiltInConstructor for Symbol {
const LENGTH: usize = 0;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::symbol;
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
if !new_target.is_undefined() {
return Err(JsNativeError::typ()
.with_message("Symbol is not a constructor")
.into());
}
let description = match args.get(0) {
Some(value) if !value.is_undefined() => Some(value.to_string(context)?),
_ => None,
};
Ok(JsSymbol::new(description)
.ok_or_else(|| {
JsNativeError::range()
.with_message("reached the maximum number of symbols that can be created")
})?
.into())
}
}
impl Symbol {
fn this_symbol_value(value: &JsValue) -> JsResult<JsSymbol> {
value
.as_symbol()
.or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol()))
.ok_or_else(|| {
JsNativeError::typ()
.with_message("'this' is not a Symbol")
.into()
})
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(
this: &JsValue,
_: &[JsValue],
_: &mut Context<'_>,
) -> JsResult<JsValue> {
let symbol = Self::this_symbol_value(this)?;
Ok(symbol.descriptive_string().into())
}
pub(crate) fn value_of(
this: &JsValue,
_: &[JsValue],
_: &mut Context<'_>,
) -> JsResult<JsValue> {
let symbol = Self::this_symbol_value(this)?;
Ok(JsValue::Symbol(symbol))
}
pub(crate) fn get_description(
this: &JsValue,
_: &[JsValue],
_: &mut Context<'_>,
) -> JsResult<JsValue> {
let sym = Self::this_symbol_value(this)?;
Ok(sym
.description()
.map_or(JsValue::undefined(), JsValue::from))
}
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)?;
GLOBAL_SYMBOL_REGISTRY
.get_or_create_symbol(&string_key)
.map(JsValue::from)
}
pub(crate) fn key_for(_: &JsValue, args: &[JsValue], _: &mut Context<'_>) -> JsResult<JsValue> {
let sym = args.get_or_undefined(0).as_symbol().ok_or_else(|| {
JsNativeError::typ().with_message("Symbol.keyFor: sym is not a symbol")
})?;
Ok(GLOBAL_SYMBOL_REGISTRY
.get_key(&sym)
.map(JsValue::from)
.unwrap_or_default())
}
pub(crate) fn to_primitive(
this: &JsValue,
_: &[JsValue],
_: &mut Context<'_>,
) -> JsResult<JsValue> {
let sym = Self::this_symbol_value(this)?;
Ok(sym.into())
}
}