#![deny(
unsafe_op_in_unsafe_fn,
clippy::undocumented_unsafe_blocks,
clippy::missing_safety_doc
)]
use crate::{
js_string,
string::{common::StaticJsStrings, utf16},
tagged::{Tagged, UnwrappedTagged},
JsString,
};
use boa_gc::{empty_trace, Finalize, Trace};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::{
hash::{Hash, Hasher},
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
};
const RESERVED_SYMBOL_HASHES: u64 = 127;
fn get_id() -> Option<u64> {
static SYMBOL_HASH_COUNT: AtomicU64 = AtomicU64::new(RESERVED_SYMBOL_HASHES + 1);
SYMBOL_HASH_COUNT
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
value.checked_add(1)
})
.ok()
}
#[derive(Debug, Clone, Copy, TryFromPrimitive, IntoPrimitive)]
#[repr(u8)]
enum WellKnown {
AsyncIterator,
HasInstance,
IsConcatSpreadable,
Iterator,
Match,
MatchAll,
Replace,
Search,
Species,
Split,
ToPrimitive,
ToStringTag,
Unscopables,
}
impl WellKnown {
const fn description(self) -> JsString {
match self {
Self::AsyncIterator => StaticJsStrings::symbol_async_iterator(),
Self::HasInstance => StaticJsStrings::symbol_has_instance(),
Self::IsConcatSpreadable => StaticJsStrings::symbol_is_concat_spreadable(),
Self::Iterator => StaticJsStrings::symbol_iterator(),
Self::Match => StaticJsStrings::symbol_match(),
Self::MatchAll => StaticJsStrings::symbol_match_all(),
Self::Replace => StaticJsStrings::symbol_replace(),
Self::Search => StaticJsStrings::symbol_search(),
Self::Species => StaticJsStrings::symbol_species(),
Self::Split => StaticJsStrings::symbol_split(),
Self::ToPrimitive => StaticJsStrings::symbol_to_primitive(),
Self::ToStringTag => StaticJsStrings::symbol_to_string_tag(),
Self::Unscopables => StaticJsStrings::symbol_unscopables(),
}
}
const fn hash(self) -> u64 {
self as u64
}
const fn tag(self) -> usize {
self as usize
}
fn from_tag(tag: usize) -> Option<Self> {
Self::try_from_primitive(u8::try_from(tag).ok()?).ok()
}
}
#[derive(Debug, Clone)]
struct Inner {
hash: u64,
description: Option<JsString>,
}
pub struct JsSymbol {
repr: Tagged<Inner>,
}
unsafe impl Send for JsSymbol {}
unsafe impl Sync for JsSymbol {}
impl Finalize for JsSymbol {}
unsafe impl Trace for JsSymbol {
empty_trace!();
}
macro_rules! well_known_symbols {
( $( $(#[$attr:meta])* ($name:ident, $variant:path) ),+$(,)? ) => {
$(
$(#[$attr])* pub(crate) const fn $name() -> JsSymbol {
JsSymbol {
repr: Tagged::from_tag($variant.tag()),
}
}
)+
};
}
impl JsSymbol {
#[inline]
#[must_use]
pub fn new(description: Option<JsString>) -> Option<Self> {
let hash = get_id()?;
let arc = Arc::new(Inner { hash, description });
Some(Self {
repr: unsafe { Tagged::from_ptr(Arc::into_raw(arc).cast_mut()) },
})
}
#[inline]
#[must_use]
pub fn description(&self) -> Option<JsString> {
match self.repr.unwrap() {
UnwrappedTagged::Ptr(ptr) => {
unsafe { ptr.as_ref().description.clone() }
}
UnwrappedTagged::Tag(tag) => {
let wk = unsafe { WellKnown::from_tag(tag).unwrap_unchecked() };
Some(wk.description())
}
}
}
#[inline]
#[must_use]
pub fn hash(&self) -> u64 {
match self.repr.unwrap() {
UnwrappedTagged::Ptr(ptr) => {
unsafe { ptr.as_ref().hash }
}
UnwrappedTagged::Tag(tag) => {
unsafe { WellKnown::from_tag(tag).unwrap_unchecked().hash() }
}
}
}
#[must_use]
pub fn descriptive_string(&self) -> JsString {
self.description().as_ref().map_or_else(
|| js_string!("Symbol()"),
|desc| js_string!(utf16!("Symbol("), desc, utf16!(")")),
)
}
well_known_symbols! {
(async_iterator, WellKnown::AsyncIterator),
(has_instance, WellKnown::HasInstance),
(is_concat_spreadable, WellKnown::IsConcatSpreadable),
(iterator, WellKnown::Iterator),
(r#match, WellKnown::Match),
(match_all, WellKnown::MatchAll),
(replace, WellKnown::Replace),
(search, WellKnown::Search),
(species, WellKnown::Species),
(split, WellKnown::Split),
(to_primitive, WellKnown::ToPrimitive),
(to_string_tag, WellKnown::ToStringTag),
(unscopables, WellKnown::Unscopables),
}
}
impl Clone for JsSymbol {
fn clone(&self) -> Self {
if let UnwrappedTagged::Ptr(ptr) = self.repr.unwrap() {
unsafe {
let arc = Arc::from_raw(ptr.as_ptr().cast_const());
std::mem::forget(arc.clone());
std::mem::forget(arc);
}
}
Self { repr: self.repr }
}
}
impl Drop for JsSymbol {
fn drop(&mut self) {
if let UnwrappedTagged::Ptr(ptr) = self.repr.unwrap() {
unsafe { drop(Arc::from_raw(ptr.as_ptr().cast_const())) }
}
}
}
impl std::fmt::Debug for JsSymbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JsSymbol")
.field("hash", &self.hash())
.field("description", &self.description())
.finish()
}
}
impl std::fmt::Display for JsSymbol {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.description() {
Some(desc) => write!(f, "Symbol({})", desc.to_std_string_escaped()),
None => write!(f, "Symbol()"),
}
}
}
impl Eq for JsSymbol {}
impl PartialEq for JsSymbol {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.hash() == other.hash()
}
}
impl PartialOrd for JsSymbol {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.hash().partial_cmp(&other.hash())
}
}
impl Ord for JsSymbol {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.hash().cmp(&other.hash())
}
}
impl Hash for JsSymbol {
fn hash<H: Hasher>(&self, state: &mut H) {
self.hash().hash(state);
}
}