#[cfg(test)]
mod tests;
use std::{hash::BuildHasherDefault, sync::LazyLock};
use crate::{
Context, JsArgs, JsResult, JsString,
builtins::BuiltInObject,
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
js_string,
object::JsObject,
property::Attribute,
realm::Realm,
string::StaticJsStrings,
symbol::JsSymbol,
value::JsValue,
};
use dashmap::DashMap;
use rustc_hash::FxHasher;
use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject};
static GLOBAL_SYMBOL_REGISTRY: LazyLock<GlobalSymbolRegistry> =
LazyLock::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.iter().collect::<Vec<_>>();
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.clone().into_boxed_slice(), symbol.clone());
self.symbols
.insert(symbol.clone(), slice.into_boxed_slice());
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 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(js_string!("[Symbol.toPrimitive]"))
.length(1)
.build();
let get_description = BuiltInBuilder::callable(realm, Self::get_description)
.name(js_string!("get description"))
.build();
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.static_method(Self::for_, js_string!("for"), 1)
.static_method(Self::key_for, js_string!("keyFor"), 1)
.static_property(
js_string!("asyncIterator"),
symbol_async_iterator,
attribute,
)
.static_property(js_string!("hasInstance"), symbol_has_instance, attribute)
.static_property(
js_string!("isConcatSpreadable"),
symbol_is_concat_spreadable,
attribute,
)
.static_property(js_string!("iterator"), symbol_iterator, attribute)
.static_property(js_string!("match"), symbol_match, attribute)
.static_property(js_string!("matchAll"), symbol_match_all, attribute)
.static_property(js_string!("replace"), symbol_replace, attribute)
.static_property(js_string!("search"), symbol_search, attribute)
.static_property(js_string!("species"), symbol_species, attribute)
.static_property(js_string!("split"), symbol_split, attribute)
.static_property(
js_string!("toPrimitive"),
symbol_to_primitive.clone(),
attribute,
)
.static_property(
js_string!("toStringTag"),
symbol_to_string_tag.clone(),
attribute,
)
.static_property(js_string!("unscopables"), symbol_unscopables, attribute)
.method(Self::to_string, js_string!("toString"), 0)
.method(Self::value_of, js_string!("valueOf"), 0)
.accessor(
js_string!("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: JsString = StaticJsStrings::SYMBOL;
}
impl BuiltInConstructor for Symbol {
const CONSTRUCTOR_ARGUMENTS: usize = 0;
const PROTOTYPE_STORAGE_SLOTS: usize = 6;
const CONSTRUCTOR_STORAGE_SLOTS: usize = 15;
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.first() {
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.downcast_ref::<JsSymbol>().as_deref().cloned())
})
.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(symbol.into())
}
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
.first()
.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())
}
}