Boa 0.10.0

Boa is a Javascript lexer, parser and Just-in-Time compiler written in Rust. Currently, it has support for some of the language.
Documentation
//! This module implements the global `Symbol` object.
//!
//! The data type symbol is a primitive data type.
//! The `Symbol()` function returns a value of type symbol, has static properties that expose
//! several members of built-in objects, has static methods that expose the global symbol registry,
//! and resembles a built-in object class, but is incomplete as a constructor because it does not
//! support the syntax "`new Symbol()`".
//!
//! Every symbol value returned from `Symbol()` is unique.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-symbol-value
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol

#[cfg(test)]
mod tests;

use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{
    property::{Attribute, Property},
    value::{RcString, RcSymbol, Value},
    BoaProfiler, Context, Result,
};
use gc::{Finalize, Trace};

/// A structure that contains the JavaScript well known symbols.
#[derive(Debug, Clone)]
pub struct WellKnownSymbols {
    async_iterator: RcSymbol,
    has_instance: RcSymbol,
    is_concat_spreadable: RcSymbol,
    iterator: RcSymbol,
    match_: RcSymbol,
    match_all: RcSymbol,
    replace: RcSymbol,
    search: RcSymbol,
    species: RcSymbol,
    split: RcSymbol,
    to_primitive: RcSymbol,
    to_string_tag: RcSymbol,
    unscopables: RcSymbol,
}

impl WellKnownSymbols {
    pub(crate) fn new() -> (Self, u32) {
        let mut count = 0;

        let async_iterator = Symbol::new(count, Some("Symbol.asyncIterator".into())).into();
        count += 1;
        let has_instance = Symbol::new(count, Some("Symbol.hasInstance".into())).into();
        count += 1;
        let is_concat_spreadable =
            Symbol::new(count, Some("Symbol.isConcatSpreadable".into())).into();
        count += 1;
        let iterator = Symbol::new(count, Some("Symbol.iterator".into())).into();
        count += 1;
        let match_ = Symbol::new(count, Some("Symbol.match".into())).into();
        count += 1;
        let match_all = Symbol::new(count, Some("Symbol.matchAll".into())).into();
        count += 1;
        let replace = Symbol::new(count, Some("Symbol.replace".into())).into();
        count += 1;
        let search = Symbol::new(count, Some("Symbol.search".into())).into();
        count += 1;
        let species = Symbol::new(count, Some("Symbol.species".into())).into();
        count += 1;
        let split = Symbol::new(count, Some("Symbol.split".into())).into();
        count += 1;
        let to_primitive = Symbol::new(count, Some("Symbol.toPrimitive".into())).into();
        count += 1;
        let to_string_tag = Symbol::new(count, Some("Symbol.toStringTag".into())).into();
        count += 1;
        let unscopables = Symbol::new(count, Some("Symbol.unscopables".into())).into();
        count += 1;

        (
            Self {
                async_iterator,
                has_instance,
                is_concat_spreadable,
                iterator,
                match_,
                match_all,
                replace,
                search,
                species,
                split,
                to_primitive,
                to_string_tag,
                unscopables,
            },
            count,
        )
    }

    /// The `Symbol.asyncIterator` well known symbol.
    ///
    /// A method that returns the default AsyncIterator for an object.
    /// Called by the semantics of the `for-await-of` statement.
    #[inline]
    pub fn async_iterator_symbol(&self) -> RcSymbol {
        self.async_iterator.clone()
    }

    /// The `Symbol.hasInstance` well known symbol.
    ///
    /// A method that determines if a `constructor` object
    /// recognizes an object as one of the `constructor`'s instances.
    /// Called by the semantics of the instanceof operator.
    #[inline]
    pub fn has_instance_symbol(&self) -> RcSymbol {
        self.async_iterator.clone()
    }

    /// The `Symbol.isConcatSpreadable` well known symbol.
    ///
    /// A Boolean valued property that if `true` indicates that
    /// an object should be flattened to its array elements
    /// by `Array.prototype.concat`.
    #[inline]
    pub fn is_concat_spreadable_symbol(&self) -> RcSymbol {
        self.is_concat_spreadable.clone()
    }

    /// The `Symbol.iterator` well known symbol.
    ///
    /// A method that returns the default Iterator for an object.
    /// Called by the semantics of the `for-of` statement.
    #[inline]
    pub fn iterator_symbol(&self) -> RcSymbol {
        self.iterator.clone()
    }

    /// The `Symbol.match` well known symbol.
    ///
    /// A regular expression method that matches the regular expression
    /// against a string. Called by the `String.prototype.match` method.
    #[inline]
    pub fn match_symbol(&self) -> RcSymbol {
        self.match_.clone()
    }

    /// The `Symbol.matchAll` well known symbol.
    ///
    /// A regular expression method that returns an iterator, that yields
    /// matches of the regular expression against a string.
    /// Called by the `String.prototype.matchAll` method.
    #[inline]
    pub fn match_all_symbol(&self) -> RcSymbol {
        self.match_all.clone()
    }

    /// The `Symbol.replace` well known symbol.
    ///
    /// A regular expression method that replaces matched substrings
    /// of a string. Called by the `String.prototype.replace` method.
    #[inline]
    pub fn replace_symbol(&self) -> RcSymbol {
        self.replace.clone()
    }

    /// The `Symbol.search` well known symbol.
    ///
    /// A regular expression method that returns the index within a
    /// string that matches the regular expression.
    /// Called by the `String.prototype.search` method.
    #[inline]
    pub fn search_symbol(&self) -> RcSymbol {
        self.search.clone()
    }

    /// The `Symbol.species` well known symbol.
    ///
    /// A function valued property that is the `constructor` function
    /// that is used to create derived objects.
    #[inline]
    pub fn species_symbol(&self) -> RcSymbol {
        self.species.clone()
    }

    /// The `Symbol.split` well known symbol.
    ///
    /// A regular expression method that splits a string at the indices
    /// that match the regular expression.
    /// Called by the `String.prototype.split` method.
    #[inline]
    pub fn split_symbol(&self) -> RcSymbol {
        self.split.clone()
    }

    /// The `Symbol.toPrimitive` well known symbol.
    ///
    /// A method that converts an object to a corresponding primitive value.
    /// Called by the `ToPrimitive` (`Value::to_primitve`) abstract operation.
    #[inline]
    pub fn to_primitive_symbol(&self) -> RcSymbol {
        self.to_primitive.clone()
    }

    /// The `Symbol.toStringTag` well known symbol.
    ///
    /// A String valued property that is used in the creation of the default
    /// string description of an object.
    /// Accessed by the built-in method `Object.prototype.toString`.
    #[inline]
    pub fn to_string_tag_symbol(&self) -> RcSymbol {
        self.to_string_tag.clone()
    }

    /// The `Symbol.unscopables` well known symbol.
    ///
    /// An object valued property whose own and inherited property names are property
    /// names that are excluded from the `with` environment bindings of the associated object.
    #[inline]
    pub fn unscopables_symbol(&self) -> RcSymbol {
        self.unscopables.clone()
    }
}

#[derive(Debug, Finalize, Trace, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Symbol {
    hash: u32,
    description: Option<RcString>,
}

impl Symbol {
    pub(crate) fn new(hash: u32, description: Option<RcString>) -> Self {
        Self { hash, description }
    }
}

impl Symbol {
    /// The name of the object.
    pub(crate) const NAME: &'static str = "Symbol";

    /// The amount of arguments this function object takes.
    pub(crate) const LENGTH: usize = 0;

    /// Returns the `Symbol`s description.
    pub fn description(&self) -> Option<&str> {
        self.description.as_deref()
    }

    /// Returns the `Symbol`s hash.
    pub fn hash(&self) -> u32 {
        self.hash
    }

    fn this_symbol_value(value: &Value, ctx: &mut Context) -> Result<RcSymbol> {
        match value {
            Value::Symbol(ref symbol) => return Ok(symbol.clone()),
            Value::Object(ref object) => {
                let object = object.borrow();
                if let Some(symbol) = object.as_symbol() {
                    return Ok(symbol);
                }
            }
            _ => {}
        }

        Err(ctx.construct_type_error("'this' is not a Symbol"))
    }

    /// The `Symbol()` constructor returns a value of type symbol.
    ///
    /// It is incomplete as a constructor because it does not support
    /// the syntax `new Symbol()` and it is not intended to be subclassed.
    ///
    /// More information:
    /// - [ECMAScript reference][spec]
    /// - [MDN documentation][mdn]
    ///
    /// [spec]: https://tc39.es/ecma262/#sec-symbol-description
    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol
    pub(crate) fn call(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
        let description = match args.get(0) {
            Some(ref value) if !value.is_undefined() => Some(value.to_string(ctx)?),
            _ => None,
        };

        Ok(ctx.construct_symbol(description).into())
    }

    /// `Symbol.prototype.toString()`
    ///
    /// This method returns a string representing the specified `Symbol` object.
    ///
    /// /// More information:
    /// - [MDN documentation][mdn]
    /// - [ECMAScript reference][spec]
    ///
    /// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.tostring
    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toString
    #[allow(clippy::wrong_self_convention)]
    pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Context) -> Result<Value> {
        let symbol = Self::this_symbol_value(this, ctx)?;
        let description = symbol.description().unwrap_or("");
        Ok(Value::from(format!("Symbol({})", description)))
    }

    /// Initialise the `Symbol` object on the global object.
    #[inline]
    pub fn init(context: &mut Context) -> (&'static str, Value) {
        // Define the Well-Known Symbols
        // https://tc39.es/ecma262/#sec-well-known-symbols
        let well_known_symbols = context.well_known_symbols();

        let symbol_async_iterator = well_known_symbols.async_iterator_symbol();
        let symbol_has_instance = well_known_symbols.has_instance_symbol();
        let symbol_is_concat_spreadable = well_known_symbols.is_concat_spreadable_symbol();
        let symbol_iterator = well_known_symbols.iterator_symbol();
        let symbol_match = well_known_symbols.match_symbol();
        let symbol_match_all = well_known_symbols.match_all_symbol();
        let symbol_replace = well_known_symbols.replace_symbol();
        let symbol_search = well_known_symbols.search_symbol();
        let symbol_species = well_known_symbols.species_symbol();
        let symbol_split = well_known_symbols.split_symbol();
        let symbol_to_primitive = well_known_symbols.to_primitive_symbol();
        let symbol_to_string_tag = well_known_symbols.to_string_tag_symbol();
        let symbol_unscopables = well_known_symbols.unscopables_symbol();

        let global = context.global_object();
        let _timer = BoaProfiler::global().start_event(Self::NAME, "init");

        // Create prototype object
        let prototype = Value::new_object(Some(global));

        make_builtin_fn(Self::to_string, "toString", &prototype, 0, context);

        let symbol_object = make_constructor_fn(
            Self::NAME,
            Self::LENGTH,
            Self::call,
            global,
            prototype,
            false,
            true,
        );

        {
            let mut symbol_object = symbol_object.as_object_mut().unwrap();
            let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
            symbol_object.insert_property(
                "asyncIterator",
                Property::data_descriptor(symbol_async_iterator.into(), attribute),
            );
            symbol_object.insert_property(
                "hasInstance",
                Property::data_descriptor(symbol_has_instance.into(), attribute),
            );
            symbol_object.insert_property(
                "isConcatSpreadable",
                Property::data_descriptor(symbol_is_concat_spreadable.into(), attribute),
            );
            symbol_object.insert_property(
                "iterator",
                Property::data_descriptor(symbol_iterator.into(), attribute),
            );
            symbol_object.insert_property(
                "match",
                Property::data_descriptor(symbol_match.into(), attribute),
            );
            symbol_object.insert_property(
                "matchAll",
                Property::data_descriptor(symbol_match_all.into(), attribute),
            );
            symbol_object.insert_property(
                "replace",
                Property::data_descriptor(symbol_replace.into(), attribute),
            );
            symbol_object.insert_property(
                "search",
                Property::data_descriptor(symbol_search.into(), attribute),
            );
            symbol_object.insert_property(
                "species",
                Property::data_descriptor(symbol_species.into(), attribute),
            );
            symbol_object.insert_property(
                "split",
                Property::data_descriptor(symbol_split.into(), attribute),
            );
            symbol_object.insert_property(
                "toPrimitive",
                Property::data_descriptor(symbol_to_primitive.into(), attribute),
            );
            symbol_object.insert_property(
                "toStringTag",
                Property::data_descriptor(symbol_to_string_tag.into(), attribute),
            );
            symbol_object.insert_property(
                "unscopables",
                Property::data_descriptor(symbol_unscopables.into(), attribute),
            );
        }

        (Self::NAME, symbol_object)
    }
}