diff --git a/boa/src/builtins/property/attribute/mod.rs b/boa/src/builtins/property/attribute/mod.rs new file mode 100644 index 00000000000..15404cd37bc --- /dev/null +++ b/boa/src/builtins/property/attribute/mod.rs @@ -0,0 +1,145 @@ +//! This module implements the `Attribute` struct which contains the attibutes for `Property`. + +use bitflags::bitflags; +use gc::{unsafe_empty_trace, Finalize, Trace}; + +#[cfg(test)] +mod tests; + +bitflags! { + /// This struct constains the property flags as describen in the [`ECMAScript specification`][spec]. + /// + /// It contains the following flags: + /// - `[[Writable]]` (`WRITABLE`) - If `false`, attempts by ECMAScript code to change the property's `[[Value]]` attribute using `[[Set]]` will not succeed. + /// - `[[Enumerable]]` (`ENUMERABLE`) - If the property will be enumerated by a for-in enumeration. + /// - `[[Configurable]]` (`CONFIGURABLE`) - If `false`, attempts to delete the property, change the property to be an `accessor property`, or change its attributes (other than `[[Value]]`, or changing `[[Writable]]` to `false`) will fail. + /// + /// Additionaly there are flags for when the flags are defined. + #[derive(Finalize)] + pub struct Attribute: u8 { + /// None of the flags are present. + const NONE = 0b0000_0000; + + /// The `Writable` attribute decides whether the value associated with the property can be changed or not, from its initial value. + const WRITABLE = 0b0000_0011; + + /// The property is not writable. + const READONLY = 0b0000_0010; + + /// Is the `Writable` flag defined. + const HAS_WRITABLE = 0b0000_0010; + + /// If the property can be enumerated by a `for-in` loop. + const ENUMERABLE = 0b0000_1100; + + /// The property can not be enumerated in a `for-in`. + const NON_ENUMERABLE = 0b0000_1000; + + /// Is the `Enumerable` flag defined. + const HAS_ENUMERABLE = 0b0000_1000; + + /// If the property descriptor can be changed later. + const CONFIGURABLE = 0b0011_0000; + + /// The property descriptor cannot be changed. + const PERMANENT = 0b0010_0000; + + /// Is the `Configurable` flag defined. + const HAS_CONFIGURABLE = 0b0010_0000; + } +} + +// We implement `Trace` manualy rather that wih derive, beacuse `rust-gc`, +// derive `Trace` does not allow `Copy` and `Trace` to be both implemented. +// +// SAFETY: The `Attribute` struct only contains an `u8` +// and therefore it should be safe to implement an empty trace. +unsafe impl Trace for Attribute { + unsafe_empty_trace!(); +} + +impl Attribute { + /// Clear all flags. + #[inline] + pub fn clear(&mut self) { + self.bits = 0; + } + + /// Is the `writable` flag defined. + #[inline] + pub fn has_writable(self) -> bool { + self.contains(Self::HAS_WRITABLE) + } + + /// Sets the `writable` flag. + #[inline] + pub fn set_writable(&mut self, value: bool) { + if value { + *self |= Self::WRITABLE; + } else { + *self |= *self & !Self::WRITABLE; + } + } + + /// Gets the `writable` flag. + #[inline] + pub fn writable(self) -> bool { + debug_assert!(self.has_writable()); + self.contains(Self::WRITABLE) + } + + /// Is the `enumerable` flag defined. + #[inline] + pub fn has_enumerable(self) -> bool { + self.contains(Self::HAS_ENUMERABLE) + } + + /// Sets the `enumerable` flag. + #[inline] + pub fn set_enumerable(&mut self, value: bool) { + if value { + *self |= Self::ENUMERABLE; + } else { + *self |= *self & !Self::ENUMERABLE; + } + } + + /// Gets the `enumerable` flag. + #[inline] + pub fn enumerable(self) -> bool { + debug_assert!(self.has_enumerable()); + self.contains(Self::ENUMERABLE) + } + + /// Is the `configurable` flag defined. + #[inline] + pub fn has_configurable(self) -> bool { + self.contains(Self::HAS_CONFIGURABLE) + } + + /// Sets the `configurable` flag. + #[inline] + pub fn set_configurable(&mut self, value: bool) { + if value { + *self |= Self::CONFIGURABLE; + } else { + *self |= *self & !Self::CONFIGURABLE; + } + } + + /// Gets the `configurable` flag. + #[inline] + pub fn configurable(self) -> bool { + debug_assert!(self.has_configurable()); + self.contains(Self::CONFIGURABLE) + } +} + +impl Default for Attribute { + /// Returns the default flags according to the [ECMAScript specification][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#table-default-attribute-values + fn default() -> Self { + Self::READONLY | Self::NON_ENUMERABLE | Self::PERMANENT + } +} diff --git a/boa/src/builtins/property/attribute/tests.rs b/boa/src/builtins/property/attribute/tests.rs new file mode 100644 index 00000000000..0426a4337d5 --- /dev/null +++ b/boa/src/builtins/property/attribute/tests.rs @@ -0,0 +1,168 @@ + +use super::Attribute; + +#[test] +fn writable() { + let attribute = Attribute::WRITABLE; + + assert!(attribute.has_writable()); + assert!(attribute.writable()); +} + +#[test] +fn enumerable() { + let attribute = Attribute::ENUMERABLE; + + assert!(attribute.has_enumerable()); + assert!(attribute.enumerable()); +} + +#[test] +fn configurable() { + let attribute = Attribute::CONFIGURABLE; + + assert!(attribute.has_configurable()); + assert!(attribute.configurable()); +} + +#[test] +fn writable_and_enumerable() { + let attribute = Attribute::WRITABLE | Attribute::ENUMERABLE; + + assert!(attribute.has_writable()); + assert!(attribute.writable()); + assert!(attribute.has_enumerable()); + assert!(attribute.enumerable()); + + assert!(!attribute.has_configurable()); +} + +#[test] +fn enumerable_configurable() { + let attribute = Attribute::ENUMERABLE | Attribute::CONFIGURABLE; + + assert!(!attribute.has_writable()); + + assert!(attribute.has_enumerable()); + assert!(attribute.enumerable()); + assert!(attribute.has_configurable()); + assert!(attribute.configurable()); +} + +#[test] +fn writable_enumerable_configurable() { + let attribute = Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE; + + assert!(attribute.has_writable()); + assert!(attribute.writable()); + assert!(attribute.has_enumerable()); + assert!(attribute.enumerable()); + assert!(attribute.has_configurable()); + assert!(attribute.configurable()); +} + +#[test] +fn default() { + let attribute = Attribute::default(); + + assert!(attribute.has_writable()); + assert!(attribute.has_enumerable()); + assert!(attribute.has_configurable()); +} + +#[test] +fn clear() { + let mut attribute = Attribute::default(); + + attribute.clear(); + + assert!(!attribute.has_writable()); + assert!(!attribute.has_enumerable()); + assert!(!attribute.has_configurable()); + + assert!(attribute.is_empty()); +} + +#[test] +fn set_writable_to_true() { + let mut attribute = Attribute::default(); + + attribute.set_writable(true); + + assert!(attribute.has_writable()); + assert!(attribute.writable()); + assert!(attribute.has_enumerable()); + assert!(!attribute.enumerable()); + assert!(attribute.has_configurable()); + assert!(!attribute.configurable()); +} + +#[test] +fn set_writable_to_false() { + let mut attribute = Attribute::default(); + + attribute.set_writable(false); + + assert!(attribute.has_writable()); + assert!(!attribute.writable()); + assert!(attribute.has_enumerable()); + assert!(!attribute.enumerable()); + assert!(attribute.has_configurable()); + assert!(!attribute.configurable()); +} + +#[test] +fn set_enumerable_to_true() { + let mut attribute = Attribute::default(); + + attribute.set_enumerable(true); + + assert!(attribute.has_writable()); + assert!(!attribute.writable()); + assert!(attribute.has_enumerable()); + assert!(attribute.enumerable()); + assert!(attribute.has_configurable()); + assert!(!attribute.configurable()); +} + +#[test] +fn set_enumerable_to_false() { + let mut attribute = Attribute::default(); + + attribute.set_enumerable(false); + + assert!(attribute.has_writable()); + assert!(!attribute.writable()); + assert!(attribute.has_enumerable()); + assert!(!attribute.enumerable()); + assert!(attribute.has_configurable()); + assert!(!attribute.configurable()); +} + +#[test] +fn set_configurable_to_true() { + let mut attribute = Attribute::default(); + + attribute.set_configurable(true); + + assert!(attribute.has_writable()); + assert!(!attribute.writable()); + assert!(attribute.has_enumerable()); + assert!(!attribute.enumerable()); + assert!(attribute.has_configurable()); + assert!(attribute.configurable()); +} + +#[test] +fn set_configurable_to_false() { + let mut attribute = Attribute::default(); + + attribute.set_configurable(false); + + assert!(attribute.has_writable()); + assert!(!attribute.writable()); + assert!(attribute.has_enumerable()); + assert!(!attribute.enumerable()); + assert!(attribute.has_configurable()); + assert!(!attribute.configurable()); +} diff --git a/boa/src/builtins/property/mod.rs b/boa/src/builtins/property/mod.rs index 855eccc6a31..2cb07213022 100644 --- a/boa/src/builtins/property/mod.rs +++ b/boa/src/builtins/property/mod.rs @@ -17,6 +17,9 @@ use crate::builtins::value::Value; use gc::{Finalize, Trace}; +pub mod attribute; +pub use attribute::Attribute; + #[cfg(test)] mod tests; @@ -57,7 +60,7 @@ pub struct Property { impl Property { /// Checks if the provided Value can be used as a property key. pub fn is_property_key(value: &Value) -> bool { - value.is_string() || value.is_symbol() // Uncomment this when we are handeling symbols. + value.is_string() || value.is_symbol() } /// Make a new property with the given value