boa/
symbol.rs

1//! This module implements the global `Symbol` object.
2//!
3//! The data type symbol is a primitive data type.
4//! The `Symbol()` function returns a value of type symbol, has static properties that expose
5//! several members of built-in objects, has static methods that expose the global symbol registry,
6//! and resembles a built-in object class, but is incomplete as a constructor because it does not
7//! support the syntax "`new Symbol()`".
8//!
9//! Every symbol value returned from `Symbol()` is unique.
10//!
11//! More information:
12//! - [MDN documentation][mdn]
13//! - [ECMAScript reference][spec]
14//!
15//! [spec]: https://tc39.es/ecma262/#sec-symbol-value
16//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
17
18use crate::{
19    gc::{empty_trace, Finalize, Trace},
20    JsString,
21};
22use std::{
23    cell::Cell,
24    fmt::{self, Display},
25    hash::{Hash, Hasher},
26    rc::Rc,
27};
28
29/// A structure that contains the JavaScript well known symbols.
30///
31/// # Examples
32/// ```
33///# use boa::symbol::WellKnownSymbols;
34///
35/// let iterator = WellKnownSymbols::iterator();
36/// assert_eq!(iterator.description().as_deref(), Some("Symbol.iterator"));
37/// ```
38/// This is equivalent to `let iterator = Symbol.iterator` in JavaScript.
39#[derive(Debug, Clone)]
40pub struct WellKnownSymbols {
41    async_iterator: JsSymbol,
42    has_instance: JsSymbol,
43    is_concat_spreadable: JsSymbol,
44    iterator: JsSymbol,
45    match_: JsSymbol,
46    match_all: JsSymbol,
47    replace: JsSymbol,
48    search: JsSymbol,
49    species: JsSymbol,
50    split: JsSymbol,
51    to_primitive: JsSymbol,
52    to_string_tag: JsSymbol,
53    unscopables: JsSymbol,
54}
55
56/// Reserved number of symbols.
57///
58/// This is where the well known symbol live
59/// and internal engine symbols.
60const RESERVED_SYMBOL_HASHES: u64 = 128;
61
62thread_local! {
63    /// Cached well known symbols
64    static WELL_KNOW_SYMBOLS: WellKnownSymbols = WellKnownSymbols::new();
65
66    /// Symbol hash.
67    ///
68    /// For now this is an incremented u64 number.
69    static SYMBOL_HASH_COUNT: Cell<u64> = Cell::new(RESERVED_SYMBOL_HASHES);
70}
71
72impl WellKnownSymbols {
73    /// Create the well known symbols.
74    fn new() -> Self {
75        let mut count = 0;
76
77        let async_iterator = JsSymbol::with_hash(count, Some("Symbol.asyncIterator".into()));
78        count += 1;
79        let has_instance = JsSymbol::with_hash(count, Some("Symbol.hasInstance".into()));
80        count += 1;
81        let is_concat_spreadable =
82            JsSymbol::with_hash(count, Some("Symbol.isConcatSpreadable".into()));
83        count += 1;
84        let iterator = JsSymbol::with_hash(count, Some("Symbol.iterator".into()));
85        count += 1;
86        let match_ = JsSymbol::with_hash(count, Some("Symbol.match".into()));
87        count += 1;
88        let match_all = JsSymbol::with_hash(count, Some("Symbol.matchAll".into()));
89        count += 1;
90        let replace = JsSymbol::with_hash(count, Some("Symbol.replace".into()));
91        count += 1;
92        let search = JsSymbol::with_hash(count, Some("Symbol.search".into()));
93        count += 1;
94        let species = JsSymbol::with_hash(count, Some("Symbol.species".into()));
95        count += 1;
96        let split = JsSymbol::with_hash(count, Some("Symbol.split".into()));
97        count += 1;
98        let to_primitive = JsSymbol::with_hash(count, Some("Symbol.toPrimitive".into()));
99        count += 1;
100        let to_string_tag = JsSymbol::with_hash(count, Some("Symbol.toStringTag".into()));
101        count += 1;
102        let unscopables = JsSymbol::with_hash(count, Some("Symbol.unscopables".into()));
103
104        Self {
105            async_iterator,
106            has_instance,
107            is_concat_spreadable,
108            iterator,
109            match_,
110            match_all,
111            replace,
112            search,
113            species,
114            split,
115            to_primitive,
116            to_string_tag,
117            unscopables,
118        }
119    }
120
121    /// The `Symbol.asyncIterator` well known symbol.
122    ///
123    /// A method that returns the default AsyncIterator for an object.
124    /// Called by the semantics of the `for-await-of` statement.
125    #[inline]
126    pub fn async_iterator() -> JsSymbol {
127        WELL_KNOW_SYMBOLS.with(|symbols| symbols.async_iterator.clone())
128    }
129
130    /// The `Symbol.hasInstance` well known symbol.
131    ///
132    /// A method that determines if a `constructor` object
133    /// recognizes an object as one of the `constructor`'s instances.
134    /// Called by the semantics of the instanceof operator.
135    #[inline]
136    pub fn has_instance() -> JsSymbol {
137        WELL_KNOW_SYMBOLS.with(|symbols| symbols.has_instance.clone())
138    }
139
140    /// The `Symbol.isConcatSpreadable` well known symbol.
141    ///
142    /// A Boolean valued property that if `true` indicates that
143    /// an object should be flattened to its array elements
144    /// by `Array.prototype.concat`.
145    #[inline]
146    pub fn is_concat_spreadable() -> JsSymbol {
147        WELL_KNOW_SYMBOLS.with(|symbols| symbols.is_concat_spreadable.clone())
148    }
149
150    /// The `Symbol.iterator` well known symbol.
151    ///
152    /// A method that returns the default Iterator for an object.
153    /// Called by the semantics of the `for-of` statement.
154    #[inline]
155    pub fn iterator() -> JsSymbol {
156        WELL_KNOW_SYMBOLS.with(|symbols| symbols.iterator.clone())
157    }
158
159    /// The `Symbol.match` well known symbol.
160    ///
161    /// A regular expression method that matches the regular expression
162    /// against a string. Called by the `String.prototype.match` method.
163    #[inline]
164    pub fn match_() -> JsSymbol {
165        WELL_KNOW_SYMBOLS.with(|symbols| symbols.match_.clone())
166    }
167
168    /// The `Symbol.matchAll` well known symbol.
169    ///
170    /// A regular expression method that returns an iterator, that yields
171    /// matches of the regular expression against a string.
172    /// Called by the `String.prototype.matchAll` method.
173    #[inline]
174    pub fn match_all() -> JsSymbol {
175        WELL_KNOW_SYMBOLS.with(|symbols| symbols.match_all.clone())
176    }
177
178    /// The `Symbol.replace` well known symbol.
179    ///
180    /// A regular expression method that replaces matched substrings
181    /// of a string. Called by the `String.prototype.replace` method.
182    #[inline]
183    pub fn replace() -> JsSymbol {
184        WELL_KNOW_SYMBOLS.with(|symbols| symbols.replace.clone())
185    }
186
187    /// The `Symbol.search` well known symbol.
188    ///
189    /// A regular expression method that returns the index within a
190    /// string that matches the regular expression.
191    /// Called by the `String.prototype.search` method.
192    #[inline]
193    pub fn search() -> JsSymbol {
194        WELL_KNOW_SYMBOLS.with(|symbols| symbols.search.clone())
195    }
196
197    /// The `Symbol.species` well known symbol.
198    ///
199    /// A function valued property that is the `constructor` function
200    /// that is used to create derived objects.
201    #[inline]
202    pub fn species() -> JsSymbol {
203        WELL_KNOW_SYMBOLS.with(|symbols| symbols.species.clone())
204    }
205
206    /// The `Symbol.split` well known symbol.
207    ///
208    /// A regular expression method that splits a string at the indices
209    /// that match the regular expression.
210    /// Called by the `String.prototype.split` method.
211    #[inline]
212    pub fn split() -> JsSymbol {
213        WELL_KNOW_SYMBOLS.with(|symbols| symbols.split.clone())
214    }
215
216    /// The `Symbol.toPrimitive` well known symbol.
217    ///
218    /// A method that converts an object to a corresponding primitive value.
219    /// Called by the `ToPrimitive` (`Value::to_primitve`) abstract operation.
220    #[inline]
221    pub fn to_primitive() -> JsSymbol {
222        WELL_KNOW_SYMBOLS.with(|symbols| symbols.to_primitive.clone())
223    }
224
225    /// The `Symbol.toStringTag` well known symbol.
226    ///
227    /// A String valued property that is used in the creation of the default
228    /// string description of an object.
229    /// Accessed by the built-in method `Object.prototype.toString`.
230    #[inline]
231    pub fn to_string_tag() -> JsSymbol {
232        WELL_KNOW_SYMBOLS.with(|symbols| symbols.to_string_tag.clone())
233    }
234
235    /// The `Symbol.unscopables` well known symbol.
236    ///
237    /// An object valued property whose own and inherited property names are property
238    /// names that are excluded from the `with` environment bindings of the associated object.
239    #[inline]
240    pub fn unscopables() -> JsSymbol {
241        WELL_KNOW_SYMBOLS.with(|symbols| symbols.unscopables.clone())
242    }
243}
244
245/// The inner representation of a JavaScript symbol.
246#[derive(Debug, Clone)]
247struct Inner {
248    hash: u64,
249    description: Option<JsString>,
250}
251
252/// This represents a JavaScript symbol primitive.
253#[derive(Debug, Clone)]
254pub struct JsSymbol {
255    inner: Rc<Inner>,
256}
257
258impl JsSymbol {
259    /// Create a new symbol.
260    #[inline]
261    pub fn new(description: Option<JsString>) -> Self {
262        let hash = SYMBOL_HASH_COUNT.with(|count| {
263            let hash = count.get();
264            count.set(hash + 1);
265            hash
266        });
267
268        Self {
269            inner: Rc::new(Inner { hash, description }),
270        }
271    }
272
273    /// Create a new symbol with a specified hash and description.
274    #[inline]
275    fn with_hash(hash: u64, description: Option<JsString>) -> Self {
276        Self {
277            inner: Rc::new(Inner { hash, description }),
278        }
279    }
280
281    /// Returns the `Symbol`s description.
282    #[inline]
283    pub fn description(&self) -> Option<JsString> {
284        self.inner.description.clone()
285    }
286
287    /// Returns the `Symbol`s hash.
288    ///
289    /// The hash is guaranteed to be unique.
290    #[inline]
291    pub fn hash(&self) -> u64 {
292        self.inner.hash
293    }
294}
295
296impl Finalize for JsSymbol {}
297
298// Safety: `JsSymbol` does not contain any object that require trace,
299// so this is safe.
300unsafe impl Trace for JsSymbol {
301    empty_trace!();
302}
303
304impl Display for JsSymbol {
305    #[inline]
306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307        match &self.inner.description {
308            Some(desc) => write!(f, "Symbol({})", desc),
309            None => write!(f, "Symbol()"),
310        }
311    }
312}
313
314impl Eq for JsSymbol {}
315
316impl PartialEq for JsSymbol {
317    #[inline]
318    fn eq(&self, other: &Self) -> bool {
319        self.inner.hash == other.inner.hash
320    }
321}
322
323impl PartialOrd for JsSymbol {
324    #[inline]
325    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
326        self.inner.hash.partial_cmp(&other.inner.hash)
327    }
328}
329
330impl Ord for JsSymbol {
331    #[inline]
332    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
333        self.inner.hash.cmp(&other.inner.hash)
334    }
335}
336
337impl Hash for JsSymbol {
338    #[inline]
339    fn hash<H: Hasher>(&self, state: &mut H) {
340        self.inner.hash.hash(state);
341    }
342}