#![deny(
unsafe_op_in_unsafe_fn,
clippy::undocumented_unsafe_blocks,
clippy::missing_safety_doc
)]
use crate::{
js_string,
string::{JsString, StaticJsStrings},
};
use boa_gc::{Finalize, Trace};
use tag_ptr::{Tagged, UnwrappedTagged};
use boa_macros::{JsData, js_str};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::{
hash::{Hash, Hasher},
mem::ManuallyDrop,
ptr::NonNull,
sync::{Arc, atomic::Ordering},
};
use portable_atomic::AtomicU64;
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 fn_name(self) -> JsString {
match self {
Self::AsyncIterator => StaticJsStrings::FN_SYMBOL_ASYNC_ITERATOR,
Self::HasInstance => StaticJsStrings::FN_SYMBOL_HAS_INSTANCE,
Self::IsConcatSpreadable => StaticJsStrings::FN_SYMBOL_IS_CONCAT_SPREADABLE,
Self::Iterator => StaticJsStrings::FN_SYMBOL_ITERATOR,
Self::Match => StaticJsStrings::FN_SYMBOL_MATCH,
Self::MatchAll => StaticJsStrings::FN_SYMBOL_MATCH_ALL,
Self::Replace => StaticJsStrings::FN_SYMBOL_REPLACE,
Self::Search => StaticJsStrings::FN_SYMBOL_SEARCH,
Self::Species => StaticJsStrings::FN_SYMBOL_SPECIES,
Self::Split => StaticJsStrings::FN_SYMBOL_SPLIT,
Self::ToPrimitive => StaticJsStrings::FN_SYMBOL_TO_PRIMITIVE,
Self::ToStringTag => StaticJsStrings::FN_SYMBOL_TO_STRING_TAG,
Self::Unscopables => StaticJsStrings::FN_SYMBOL_UNSCOPABLES,
}
}
const fn hash(self) -> u64 {
self as u64
}
fn from_tag(tag: usize) -> Option<Self> {
Self::try_from_primitive(u8::try_from(tag).ok()?).ok()
}
}
#[derive(Debug, Clone)]
pub(crate) struct RawJsSymbol {
hash: u64,
description: Option<Box<[u16]>>,
}
#[derive(Trace, Finalize, JsData)]
#[boa_gc(unsafe_empty_trace)]
#[allow(clippy::module_name_repetitions)]
pub struct JsSymbol {
repr: Tagged<RawJsSymbol>,
}
unsafe impl Send for JsSymbol {}
unsafe impl Sync for JsSymbol {}
macro_rules! well_known_symbols {
( $( $(#[$attr:meta])* ($name:ident, $variant:path) ),+$(,)? ) => {
$(
$(#[$attr])* #[must_use] pub const fn $name() -> JsSymbol {
JsSymbol {
repr: Tagged::from_tag($variant.hash() as usize),
}
}
)+
};
}
impl JsSymbol {
#[inline]
#[must_use]
pub fn new(description: Option<JsString>) -> Option<Self> {
let hash = get_id()?;
let arc = Arc::new(RawJsSymbol {
hash,
description: description.map(|s| s.iter().collect::<Vec<_>>().into_boxed_slice()),
});
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.as_ref().map(|v| js_string!(&**v)) }
}
UnwrappedTagged::Tag(tag) => {
let wk = unsafe { WellKnown::from_tag(tag).unwrap_unchecked() };
Some(wk.description())
}
}
}
#[inline]
#[must_use]
pub fn fn_name(&self) -> JsString {
if let UnwrappedTagged::Tag(tag) = self.repr.unwrap() {
let wk = unsafe { WellKnown::from_tag(tag).unwrap_unchecked() };
return wk.fn_name();
}
self.description()
.map(|s| js_string!(js_str!("["), &s, js_str!("]")))
.unwrap_or_default()
}
#[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!(js_str!("Symbol("), desc, js_str!(")")),
)
}
#[inline]
#[must_use]
#[allow(unused, reason = "only used in nan-boxed implementation of JsValue")]
pub(crate) fn into_raw(self) -> NonNull<RawJsSymbol> {
ManuallyDrop::new(self).repr.as_inner_ptr()
}
#[inline]
#[must_use]
#[allow(unused, reason = "only used in nan-boxed implementation of JsValue")]
pub(crate) unsafe fn from_raw(ptr: NonNull<RawJsSymbol>) -> Self {
Self {
repr: Tagged::from_non_null(ptr),
}
}
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> {
Some(self.cmp(other))
}
}
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);
}
}