use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
pub const SYMBOL_ITERATOR: u64 = 1;
pub const SYMBOL_TO_PRIMITIVE: u64 = 2;
pub const SYMBOL_HAS_INSTANCE: u64 = 3;
pub const SYMBOL_TO_STRING_TAG: u64 = 4;
pub const SYMBOL_IS_CONCAT_SPREADABLE: u64 = 5;
pub const SYMBOL_SPECIES: u64 = 6;
pub const SYMBOL_MATCH: u64 = 7;
pub const SYMBOL_REPLACE: u64 = 8;
pub const SYMBOL_SEARCH: u64 = 9;
pub const SYMBOL_SPLIT: u64 = 10;
pub const SYMBOL_UNSCOPABLES: u64 = 11;
pub const SYMBOL_ASYNC_ITERATOR: u64 = 12;
pub const SYMBOL_MATCH_ALL: u64 = 13;
pub const SYMBOL_DISPOSE: u64 = 14;
pub const SYMBOL_ASYNC_DISPOSE: u64 = 15;
const FIRST_USER_SYMBOL_ID: u64 = 100;
const USER_SYMBOL_KEY_PREFIX: &str = "\u{0}sym:";
static NEXT_SYMBOL_ID: AtomicU64 = AtomicU64::new(FIRST_USER_SYMBOL_ID);
struct SymbolRegistry {
descriptions: HashMap<u64, Option<String>>,
for_registry: HashMap<String, u64>,
for_reverse: HashMap<u64, String>,
}
impl SymbolRegistry {
fn new() -> Self {
let mut reg = Self {
descriptions: HashMap::new(),
for_registry: HashMap::new(),
for_reverse: HashMap::new(),
};
reg.descriptions
.insert(SYMBOL_ITERATOR, Some("Symbol.iterator".into()));
reg.descriptions
.insert(SYMBOL_TO_PRIMITIVE, Some("Symbol.toPrimitive".into()));
reg.descriptions
.insert(SYMBOL_HAS_INSTANCE, Some("Symbol.hasInstance".into()));
reg.descriptions
.insert(SYMBOL_TO_STRING_TAG, Some("Symbol.toStringTag".into()));
reg.descriptions.insert(
SYMBOL_IS_CONCAT_SPREADABLE,
Some("Symbol.isConcatSpreadable".into()),
);
reg.descriptions
.insert(SYMBOL_SPECIES, Some("Symbol.species".into()));
reg.descriptions
.insert(SYMBOL_MATCH, Some("Symbol.match".into()));
reg.descriptions
.insert(SYMBOL_REPLACE, Some("Symbol.replace".into()));
reg.descriptions
.insert(SYMBOL_SEARCH, Some("Symbol.search".into()));
reg.descriptions
.insert(SYMBOL_SPLIT, Some("Symbol.split".into()));
reg.descriptions
.insert(SYMBOL_UNSCOPABLES, Some("Symbol.unscopables".into()));
reg.descriptions
.insert(SYMBOL_ASYNC_ITERATOR, Some("Symbol.asyncIterator".into()));
reg.descriptions
.insert(SYMBOL_MATCH_ALL, Some("Symbol.matchAll".into()));
reg.descriptions
.insert(SYMBOL_DISPOSE, Some("Symbol.dispose".into()));
reg.descriptions
.insert(SYMBOL_ASYNC_DISPOSE, Some("Symbol.asyncDispose".into()));
reg
}
}
thread_local! {
static REGISTRY: RefCell<SymbolRegistry> = RefCell::new(SymbolRegistry::new());
}
pub fn symbol_create(description: Option<String>) -> u64 {
let id = NEXT_SYMBOL_ID.fetch_add(1, Ordering::Relaxed);
REGISTRY.with(|r| {
r.borrow_mut().descriptions.insert(id, description);
});
id
}
pub fn symbol_for(key: &str) -> u64 {
REGISTRY.with(|r| {
let mut reg = r.borrow_mut();
if let Some(&id) = reg.for_registry.get(key) {
return id;
}
let id = NEXT_SYMBOL_ID.fetch_add(1, Ordering::Relaxed);
reg.descriptions.insert(id, Some(key.to_owned()));
reg.for_registry.insert(key.to_owned(), id);
reg.for_reverse.insert(id, key.to_owned());
id
})
}
pub fn symbol_key_for(id: u64) -> Option<String> {
REGISTRY.with(|r| r.borrow().for_reverse.get(&id).cloned())
}
pub fn symbol_description(id: u64) -> Option<String> {
REGISTRY.with(|r| r.borrow().descriptions.get(&id).cloned().flatten())
}
pub fn symbol_to_property_key(id: u64) -> String {
well_known_symbol_to_key(id)
.map(str::to_owned)
.unwrap_or_else(|| format!("{USER_SYMBOL_KEY_PREFIX}{id}"))
}
pub fn property_key_to_symbol(key: &str) -> Option<u64> {
well_known_symbol_from_key(key).or_else(|| {
key.strip_prefix(USER_SYMBOL_KEY_PREFIX)
.and_then(|id| id.parse::<u64>().ok())
})
}
pub fn is_symbol_property_key(key: &str) -> bool {
property_key_to_symbol(key).is_some()
}
pub fn well_known_symbol_to_key(id: u64) -> Option<&'static str> {
match id {
SYMBOL_ITERATOR => Some("@@iterator"),
SYMBOL_TO_PRIMITIVE => Some("@@toPrimitive"),
SYMBOL_HAS_INSTANCE => Some("@@hasInstance"),
SYMBOL_TO_STRING_TAG => Some("@@toStringTag"),
SYMBOL_IS_CONCAT_SPREADABLE => Some("@@isConcatSpreadable"),
SYMBOL_SPECIES => Some("@@species"),
SYMBOL_MATCH => Some("@@match"),
SYMBOL_REPLACE => Some("@@replace"),
SYMBOL_SEARCH => Some("@@search"),
SYMBOL_SPLIT => Some("@@split"),
SYMBOL_UNSCOPABLES => Some("@@unscopables"),
SYMBOL_ASYNC_ITERATOR => Some("@@asyncIterator"),
SYMBOL_MATCH_ALL => Some("@@matchAll"),
SYMBOL_DISPOSE => Some("@@dispose"),
SYMBOL_ASYNC_DISPOSE => Some("@@asyncDispose"),
_ => None,
}
}
pub fn well_known_symbol_from_key(key: &str) -> Option<u64> {
match key {
"@@iterator" => Some(SYMBOL_ITERATOR),
"@@toPrimitive" => Some(SYMBOL_TO_PRIMITIVE),
"@@hasInstance" => Some(SYMBOL_HAS_INSTANCE),
"@@toStringTag" => Some(SYMBOL_TO_STRING_TAG),
"@@isConcatSpreadable" => Some(SYMBOL_IS_CONCAT_SPREADABLE),
"@@species" => Some(SYMBOL_SPECIES),
"@@match" => Some(SYMBOL_MATCH),
"@@replace" => Some(SYMBOL_REPLACE),
"@@search" => Some(SYMBOL_SEARCH),
"@@split" => Some(SYMBOL_SPLIT),
"@@unscopables" => Some(SYMBOL_UNSCOPABLES),
"@@asyncIterator" => Some(SYMBOL_ASYNC_ITERATOR),
"@@matchAll" => Some(SYMBOL_MATCH_ALL),
"@@dispose" => Some(SYMBOL_DISPOSE),
"@@asyncDispose" => Some(SYMBOL_ASYNC_DISPOSE),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_unique_ids() {
let a = symbol_create(None);
let b = symbol_create(None);
assert_ne!(a, b);
}
#[test]
fn create_with_description() {
let id = symbol_create(Some("mySymbol".into()));
assert_eq!(symbol_description(id), Some("mySymbol".into()));
}
#[test]
fn create_without_description() {
let id = symbol_create(None);
assert_eq!(symbol_description(id), None);
}
#[test]
fn symbol_for_returns_same_id() {
let a = symbol_for("shared");
let b = symbol_for("shared");
assert_eq!(a, b);
}
#[test]
fn symbol_for_different_keys() {
let a = symbol_for("key_a");
let b = symbol_for("key_b");
assert_ne!(a, b);
}
#[test]
fn key_for_returns_key() {
let id = symbol_for("hello");
assert_eq!(symbol_key_for(id), Some("hello".into()));
}
#[test]
fn key_for_returns_none_for_non_registry_symbol() {
let id = symbol_create(Some("not in registry".into()));
assert_eq!(symbol_key_for(id), None);
}
#[test]
fn well_known_symbol_descriptions() {
assert_eq!(
symbol_description(SYMBOL_ITERATOR),
Some("Symbol.iterator".into())
);
assert_eq!(
symbol_description(SYMBOL_TO_PRIMITIVE),
Some("Symbol.toPrimitive".into())
);
assert_eq!(
symbol_description(SYMBOL_HAS_INSTANCE),
Some("Symbol.hasInstance".into())
);
assert_eq!(
symbol_description(SYMBOL_TO_STRING_TAG),
Some("Symbol.toStringTag".into())
);
assert_eq!(
symbol_description(SYMBOL_ASYNC_ITERATOR),
Some("Symbol.asyncIterator".into())
);
}
#[test]
fn well_known_symbols_not_in_for_registry() {
assert_eq!(symbol_key_for(SYMBOL_ITERATOR), None);
assert_eq!(symbol_key_for(SYMBOL_TO_PRIMITIVE), None);
}
#[test]
fn all_well_known_ids_are_distinct() {
let ids = [
SYMBOL_ITERATOR,
SYMBOL_TO_PRIMITIVE,
SYMBOL_HAS_INSTANCE,
SYMBOL_TO_STRING_TAG,
SYMBOL_IS_CONCAT_SPREADABLE,
SYMBOL_SPECIES,
SYMBOL_MATCH,
SYMBOL_REPLACE,
SYMBOL_SEARCH,
SYMBOL_SPLIT,
SYMBOL_UNSCOPABLES,
SYMBOL_ASYNC_ITERATOR,
SYMBOL_MATCH_ALL,
];
let mut sorted = ids.to_vec();
sorted.sort();
sorted.dedup();
assert_eq!(ids.len(), sorted.len());
}
#[test]
fn all_well_known_descriptions() {
let cases: &[(u64, &str)] = &[
(SYMBOL_ITERATOR, "Symbol.iterator"),
(SYMBOL_TO_PRIMITIVE, "Symbol.toPrimitive"),
(SYMBOL_HAS_INSTANCE, "Symbol.hasInstance"),
(SYMBOL_TO_STRING_TAG, "Symbol.toStringTag"),
(SYMBOL_IS_CONCAT_SPREADABLE, "Symbol.isConcatSpreadable"),
(SYMBOL_SPECIES, "Symbol.species"),
(SYMBOL_MATCH, "Symbol.match"),
(SYMBOL_REPLACE, "Symbol.replace"),
(SYMBOL_SEARCH, "Symbol.search"),
(SYMBOL_SPLIT, "Symbol.split"),
(SYMBOL_UNSCOPABLES, "Symbol.unscopables"),
(SYMBOL_ASYNC_ITERATOR, "Symbol.asyncIterator"),
(SYMBOL_MATCH_ALL, "Symbol.matchAll"),
];
for &(id, expected_desc) in cases {
assert_eq!(
symbol_description(id),
Some(expected_desc.to_string()),
"description mismatch for id={id}"
);
}
}
#[test]
fn user_symbols_do_not_overlap_well_known() {
let user = symbol_create(Some("user".into()));
assert!(user >= FIRST_USER_SYMBOL_ID);
}
#[test]
fn symbol_for_description_is_key() {
let id = symbol_for("globalKey");
assert_eq!(symbol_description(id), Some("globalKey".into()));
}
#[test]
fn symbol_for_empty_key() {
let id = symbol_for("");
assert_eq!(symbol_key_for(id), Some("".into()));
assert_eq!(symbol_description(id), Some("".into()));
}
#[test]
fn symbol_create_empty_description() {
let id = symbol_create(Some("".into()));
assert_eq!(symbol_description(id), Some("".into()));
}
#[test]
fn user_symbol_property_key_round_trips() {
let key = symbol_to_property_key(1234);
assert_eq!(property_key_to_symbol(&key), Some(1234));
}
#[test]
fn well_known_symbol_property_key_round_trips() {
let key = symbol_to_property_key(SYMBOL_TO_STRING_TAG);
assert_eq!(key, "@@toStringTag");
assert_eq!(property_key_to_symbol(&key), Some(SYMBOL_TO_STRING_TAG));
}
}