//! Boa's implementation of ECMAScript's Property Descriptor. //! //! The Property Descriptor type is used to explain the manipulation and reification of `Object` //! property attributes. Values of the Property Descriptor type are Records. Each field's name is //! an attribute name and its value is a corresponding attribute value as specified in //! [6.1.7.1][section]. In addition, any field may be present or absent. The schema name used //! within this specification to tag literal descriptions of Property Descriptor records is //! `PropertyDescriptor`. //! //! More information: //! - [MDN documentation][mdn] //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty //! [section]: https://tc39.es/ecma262/#sec-property-attributes mod attribute; mod nonmaxu32; use crate::{js_string, object::shape::slot::SlotAttributes, JsString, JsSymbol, JsValue}; use boa_gc::{Finalize, Trace}; use std::{fmt, iter::FusedIterator}; pub use {attribute::Attribute, nonmaxu32::NonMaxU32}; /// This represents an ECMAScript Property AKA The Property Descriptor. /// /// Property descriptors present in objects come in three main flavors: /// - data descriptors /// - accessor descriptors /// - generic descriptor /// /// A data Property Descriptor is one that includes any fields named either /// \[\[Value\]\] or \[\[Writable\]\]. /// /// An accessor Property Descriptor is one that includes any fields named either /// \[\[Get\]\] or \[\[Set\]\]. /// /// A generic Property Descriptor is a Property Descriptor value that is neither /// a data Property Descriptor nor an accessor Property Descriptor. /// /// More information: /// - [MDN documentation][mdn] /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-property-descriptor-specification-type /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty #[derive(Default, Debug, Clone, Trace, Finalize)] pub struct PropertyDescriptor { enumerable: Option, configurable: Option, kind: DescriptorKind, } /// `DescriptorKind` represents the different kinds of property descriptors. #[derive(Debug, Clone, Trace, Finalize)] pub enum DescriptorKind { /// A data property descriptor. Data { /// The value of the property. value: Option, /// Whether the property is writable. writable: Option, }, /// An accessor property descriptor. Accessor { /// The getter of the property. get: Option, /// The setter of the property. set: Option, }, /// A generic property descriptor. Generic, } impl Default for DescriptorKind { fn default() -> Self { Self::Generic } } impl PropertyDescriptor { /// An accessor property descriptor is one that includes any fields named either `[[Get]]` or `[[Set]]`. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-isaccessordescriptor #[inline] #[must_use] pub const fn is_accessor_descriptor(&self) -> bool { matches!(self.kind, DescriptorKind::Accessor { .. }) } /// A data property descriptor is one that includes any fields named either `[[Value]]` or `[[Writable]]`. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-isdatadescriptor #[inline] #[must_use] pub const fn is_data_descriptor(&self) -> bool { matches!(self.kind, DescriptorKind::Data { .. }) } /// A generic property descriptor is one that is neither a data descriptor nor an accessor descriptor. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-isgenericdescriptor #[inline] #[must_use] pub const fn is_generic_descriptor(&self) -> bool { matches!(self.kind, DescriptorKind::Generic) } /// Returns if the property descriptor is empty. #[inline] #[must_use] pub const fn is_empty(&self) -> bool { self.is_generic_descriptor() && self.enumerable.is_none() && self.configurable.is_none() } /// Returns if the property descriptor is enumerable. /// Returns `None` if the `enumerable` field is not set. #[inline] #[must_use] pub const fn enumerable(&self) -> Option { self.enumerable } /// Returns if the property descriptor is configurable. /// Returns `None` if the `configurable` field is not set. #[inline] #[must_use] pub const fn configurable(&self) -> Option { self.configurable } /// Returns if the property descriptor is writable. /// Returns `None` if the `writable` field is not set or the property descriptor is not a data descriptor. #[inline] #[must_use] pub const fn writable(&self) -> Option { match self.kind { DescriptorKind::Data { writable, .. } => writable, _ => None, } } /// Returns the value of the property descriptor. /// Returns `None` if the value is not set or the property descriptor is not a data descriptor. #[inline] #[must_use] pub const fn value(&self) -> Option<&JsValue> { match &self.kind { DescriptorKind::Data { value, .. } => value.as_ref(), _ => None, } } /// Returns the getter of the property descriptor. /// Returns `None` if the getter is not set or the property descriptor is not an accessor descriptor. #[inline] #[must_use] pub const fn get(&self) -> Option<&JsValue> { match &self.kind { DescriptorKind::Accessor { get, .. } => get.as_ref(), _ => None, } } /// Returns the setter of the property descriptor. /// Returns `None` if the setter is not set or the property descriptor is not an accessor descriptor. #[inline] #[must_use] pub const fn set(&self) -> Option<&JsValue> { match &self.kind { DescriptorKind::Accessor { set, .. } => set.as_ref(), _ => None, } } /// Returns if the property descriptor is enumerable. /// /// # Panics /// /// Panics if the `enumerable` field is not set. #[inline] #[must_use] pub fn expect_enumerable(&self) -> bool { self.enumerable .expect("[[enumerable]] field not in property descriptor") } /// Returns if the property descriptor is configurable. /// /// # Panics /// /// Panics if the `configurable` field is not set. #[inline] #[must_use] pub fn expect_configurable(&self) -> bool { self.configurable .expect("[[configurable]] field not in property descriptor") } /// Returns if the property descriptor is writable. /// /// # Panics /// /// Panics if the `writable` field is not set. #[inline] #[must_use] pub fn expect_writable(&self) -> bool { self.writable() .expect("[[writable]] field not in property descriptor") } /// Returns the value of the property descriptor. /// /// # Panics /// /// Panics if the `value` field is not set. #[inline] #[must_use] pub fn expect_value(&self) -> &JsValue { self.value() .expect("[[value]] field not in property descriptor") } /// Returns the getter of the property descriptor. /// /// # Panics /// /// Panics if the `getter` field is not set. #[inline] #[must_use] pub fn expect_get(&self) -> &JsValue { self.get() .expect("[[get]] field not in property descriptor") } /// Returns the setter of the property descriptor. /// /// # Panics /// /// Panics if the `setter` field is not set. #[inline] #[must_use] pub fn expect_set(&self) -> &JsValue { self.set() .expect("[[set]] field not in property descriptor") } /// Returns the kind of the property descriptor. #[inline] #[must_use] pub const fn kind(&self) -> &DescriptorKind { &self.kind } /// Creates a new [`PropertyDescriptorBuilder`]. #[inline] #[must_use] pub fn builder() -> PropertyDescriptorBuilder { PropertyDescriptorBuilder::new() } /// Creates an accessor property descriptor with default values. #[inline] #[must_use] pub fn into_accessor_defaulted(mut self) -> Self { self.kind = DescriptorKind::Accessor { get: self.get().cloned(), set: self.set().cloned(), }; PropertyDescriptorBuilder { inner: self } .complete_with_defaults() .build() } /// Creates a data property descriptor with default values. #[must_use] pub fn into_data_defaulted(mut self) -> Self { self.kind = DescriptorKind::Data { value: self.value().cloned(), writable: self.writable(), }; PropertyDescriptorBuilder { inner: self } .complete_with_defaults() .build() } /// Creates an generic property descriptor with default values. #[inline] #[must_use] pub fn complete_property_descriptor(self) -> Self { PropertyDescriptorBuilder { inner: self } .complete_with_defaults() .build() } /// Fills the fields of the `PropertyDescriptor` that are not set /// with fields from the given `PropertyDescriptor`. /// /// # Panics /// /// Panics if the given `PropertyDescriptor` is not compatible with this one. #[inline] pub fn fill_with(&mut self, desc: &Self) { match (&mut self.kind, &desc.kind) { ( DescriptorKind::Data { value, writable }, DescriptorKind::Data { value: desc_value, writable: desc_writable, }, ) => { if let Some(desc_value) = desc_value { *value = Some(desc_value.clone()); } if let Some(desc_writable) = desc_writable { *writable = Some(*desc_writable); } } ( DescriptorKind::Accessor { get, set }, DescriptorKind::Accessor { get: desc_get, set: desc_set, }, ) => { if let Some(desc_get) = desc_get { *get = Some(desc_get.clone()); } if let Some(desc_set) = desc_set { *set = Some(desc_set.clone()); } } (_, DescriptorKind::Generic) => {} _ => panic!("Tried to fill a descriptor with an incompatible descriptor"), } if let Some(enumerable) = desc.enumerable { self.enumerable = Some(enumerable); } if let Some(configurable) = desc.configurable { self.configurable = Some(configurable); } } pub(crate) fn to_slot_attributes(&self) -> SlotAttributes { let mut attributes = SlotAttributes::empty(); attributes.set(SlotAttributes::CONFIGURABLE, self.expect_configurable()); attributes.set(SlotAttributes::ENUMERABLE, self.expect_enumerable()); if self.is_data_descriptor() { attributes.set(SlotAttributes::WRITABLE, self.expect_writable()); } else { attributes.set(SlotAttributes::GET, self.get().is_some()); attributes.set(SlotAttributes::SET, self.set().is_some()); } attributes } } /// A builder for [`PropertyDescriptor`]. #[derive(Default, Debug, Clone)] pub struct PropertyDescriptorBuilder { inner: PropertyDescriptor, } impl PropertyDescriptorBuilder { /// Creates a new [`PropertyDescriptorBuilder`]. #[must_use] pub fn new() -> Self { Self::default() } /// Sets the `value` field of the property descriptor. #[must_use] pub fn value>(mut self, value: V) -> Self { match self.inner.kind { DescriptorKind::Data { value: ref mut v, .. } => *v = Some(value.into()), // TODO: maybe panic when trying to convert accessor to data? _ => { self.inner.kind = DescriptorKind::Data { value: Some(value.into()), writable: None, } } } self } /// Sets the `writable` field of the property descriptor. #[must_use] pub fn writable(mut self, writable: bool) -> Self { match self.inner.kind { DescriptorKind::Data { writable: ref mut w, .. } => *w = Some(writable), // TODO: maybe panic when trying to convert accessor to data? _ => { self.inner.kind = DescriptorKind::Data { value: None, writable: Some(writable), } } } self } /// Sets the `get` field of the property descriptor. #[must_use] pub fn get>(mut self, get: V) -> Self { match self.inner.kind { DescriptorKind::Accessor { get: ref mut g, .. } => *g = Some(get.into()), // TODO: maybe panic when trying to convert data to accessor? _ => { self.inner.kind = DescriptorKind::Accessor { get: Some(get.into()), set: None, } } } self } /// Sets the `set` field of the property descriptor. #[must_use] pub fn set>(mut self, set: V) -> Self { match self.inner.kind { DescriptorKind::Accessor { set: ref mut s, .. } => *s = Some(set.into()), // TODO: maybe panic when trying to convert data to accessor? _ => { self.inner.kind = DescriptorKind::Accessor { set: Some(set.into()), get: None, } } } self } /// Optionally sets the `enumerable` field of the property descriptor. #[must_use] pub const fn maybe_enumerable(mut self, enumerable: Option) -> Self { if let Some(enumerable) = enumerable { self = self.enumerable(enumerable); } self } /// Optionally sets the `configurable` field of the property descriptor. #[must_use] pub const fn maybe_configurable(mut self, configurable: Option) -> Self { if let Some(configurable) = configurable { self = self.configurable(configurable); } self } /// Optionally sets the `value` field of the property descriptor. #[must_use] pub fn maybe_value>(mut self, value: Option) -> Self { if let Some(value) = value { self = self.value(value); } self } /// Optionally sets the `writable` field of the property descriptor. #[must_use] pub fn maybe_writable(mut self, writable: Option) -> Self { if let Some(writable) = writable { self = self.writable(writable); } self } /// Optionally sets the `get` field of the property descriptor. #[must_use] pub fn maybe_get>(mut self, get: Option) -> Self { if let Some(get) = get { self = self.get(get); } self } /// Optionally sets the `set` field of the property descriptor. #[must_use] pub fn maybe_set>(mut self, set: Option) -> Self { if let Some(set) = set { self = self.set(set); } self } /// Sets the `enumerable` field of the property descriptor. #[must_use] pub const fn enumerable(mut self, enumerable: bool) -> Self { self.inner.enumerable = Some(enumerable); self } /// Sets the `configurable` field of the property descriptor. #[must_use] pub const fn configurable(mut self, configurable: bool) -> Self { self.inner.configurable = Some(configurable); self } /// Fill any missing fields in the property descriptor. #[must_use] pub fn complete_with_defaults(mut self) -> Self { match self.inner.kind { DescriptorKind::Generic => { self.inner.kind = DescriptorKind::Data { value: Some(JsValue::undefined()), writable: Some(false), } } DescriptorKind::Data { ref mut value, ref mut writable, } => { if value.is_none() { *value = Some(JsValue::undefined()); } if writable.is_none() { *writable = Some(false); } } DescriptorKind::Accessor { ref mut set, ref mut get, } => { if set.is_none() { *set = Some(JsValue::undefined()); } if get.is_none() { *get = Some(JsValue::undefined()); } } } if self.inner.configurable.is_none() { self.inner.configurable = Some(false); } if self.inner.enumerable.is_none() { self.inner.enumerable = Some(false); } self } /// Returns a reference to the currently built [`PropertyDescriptor`]. #[must_use] pub const fn inner(&self) -> &PropertyDescriptor { &self.inner } /// Consumes the builder and returns the [`PropertyDescriptor`]. #[must_use] #[allow(clippy::missing_const_for_fn)] pub fn build(self) -> PropertyDescriptor { self.inner } } impl From for PropertyDescriptor { fn from(builder: PropertyDescriptorBuilder) -> Self { builder.build() } } /// This abstracts away the need for `IsPropertyKey` by transforming the `PropertyKey` /// values into an enum with both valid types: String and Symbol /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ispropertykey #[derive(Finalize, PartialEq, Debug, Clone, Eq, Hash)] pub enum PropertyKey { /// A string property key. String(JsString), /// A symbol property key. Symbol(JsSymbol), /// A numeric property key. Index(NonMaxU32), } /// Utility function for parsing [`PropertyKey`]. fn parse_u32_index(mut input: I) -> Option where I: Iterator + ExactSizeIterator + FusedIterator, T: Into, { // min: 0 --> 1 char // max: 4_294_967_296 --> 10 chars // // Max char range: [1, 10] inclusive. const MAX_CHAR_COUNT: usize = 10; const CHAR_ZERO: u16 = b'0' as u16; const CHAR_NINE: u16 = b'9' as u16; // Eliminate any string if it's greater than the max char count. let len = input.len(); if len > MAX_CHAR_COUNT { return None; } // Helper function, for converting character to digit [0, 9]. let to_digit = |c: u16| -> Option { if matches!(c, CHAR_ZERO..=CHAR_NINE) { Some(u32::from(c - CHAR_ZERO)) } else { None } }; let byte = input.next()?.into(); if byte == CHAR_ZERO { if len == 1 { // SAFETY: `0` is not `u32::MAX`. return unsafe { Some(NonMaxU32::new_unchecked(0)) }; } // String "012345" is not a valid index. return None; } let mut result = to_digit(byte)?; // If the len is equal to max chars, then we need to do checked operations, // in case of overflows. If less use unchecked versions. if len == MAX_CHAR_COUNT { for c in input { result = result.checked_mul(10)?.checked_add(to_digit(c.into())?)?; } NonMaxU32::new(result) } else { for c in input { result = result * 10 + to_digit(c.into())?; } // SAFETY: `result` cannot be `u32::MAX`, // because the length of the input is smaller than `MAX_CHAR_COUNT`. unsafe { Some(NonMaxU32::new_unchecked(result)) } } } impl From<&[u16]> for PropertyKey { #[inline] fn from(string: &[u16]) -> Self { debug_assert!(parse_u32_index( String::from_utf16(string) .expect("should be ascii string") .bytes() ) .is_none()); Self::String(string.into()) } } impl From for PropertyKey { #[inline] fn from(string: JsString) -> Self { parse_u32_index(string.as_slice().iter().copied()).map_or(Self::String(string), Self::Index) } } impl From for PropertyKey { #[inline] fn from(symbol: JsSymbol) -> Self { Self::Symbol(symbol) } } impl fmt::Display for PropertyKey { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::String(ref string) => string.to_std_string_escaped().fmt(f), Self::Symbol(ref symbol) => symbol.descriptive_string().to_std_string_escaped().fmt(f), Self::Index(index) => index.get().fmt(f), } } } impl From<&PropertyKey> for JsValue { #[inline] fn from(property_key: &PropertyKey) -> Self { match property_key { PropertyKey::String(ref string) => string.clone().into(), PropertyKey::Symbol(ref symbol) => symbol.clone().into(), PropertyKey::Index(index) => { i32::try_from(index.get()).map_or_else(|_| Self::new(index.get()), Self::new) } } } } impl From for JsValue { #[inline] fn from(property_key: PropertyKey) -> Self { match property_key { PropertyKey::String(ref string) => string.clone().into(), PropertyKey::Symbol(ref symbol) => symbol.clone().into(), PropertyKey::Index(index) => js_string!(index.get().to_string()).into(), } } } impl From for PropertyKey { fn from(value: u8) -> Self { // SAFETY: `u8` can never be `u32::MAX`. unsafe { Self::Index(NonMaxU32::new_unchecked(value.into())) } } } impl From for PropertyKey { fn from(value: u16) -> Self { // SAFETY: `u16` can never be `u32::MAX`. unsafe { Self::Index(NonMaxU32::new_unchecked(value.into())) } } } impl From for PropertyKey { fn from(value: u32) -> Self { NonMaxU32::new(value) .map_or_else(|| Self::String(js_string!(value.to_string())), Self::Index) } } impl From for PropertyKey { fn from(value: usize) -> Self { u32::try_from(value) .ok() .and_then(NonMaxU32::new) .map_or_else(|| Self::String(js_string!(value.to_string())), Self::Index) } } impl From for PropertyKey { fn from(value: i64) -> Self { u32::try_from(value) .ok() .and_then(NonMaxU32::new) .map_or_else(|| Self::String(js_string!(value.to_string())), Self::Index) } } impl From for PropertyKey { fn from(value: u64) -> Self { u32::try_from(value) .ok() .and_then(NonMaxU32::new) .map_or_else(|| Self::String(js_string!(value.to_string())), Self::Index) } } impl From for PropertyKey { fn from(value: isize) -> Self { u32::try_from(value) .ok() .and_then(NonMaxU32::new) .map_or_else(|| Self::String(js_string!(value.to_string())), Self::Index) } } impl From for PropertyKey { fn from(value: i32) -> Self { if !value.is_negative() { // Safety: A positive i32 value fits in 31 bits, so it can never be u32::MAX. return Self::Index(unsafe { NonMaxU32::new_unchecked(value as u32) }); } Self::String(js_string!(value.to_string())) } } impl From for PropertyKey { fn from(value: f64) -> Self { use num_traits::cast::FromPrimitive; u32::from_f64(value).and_then(NonMaxU32::new).map_or_else( || Self::String(ryu_js::Buffer::new().format(value).into()), Self::Index, ) } } impl PartialEq<[u16]> for PropertyKey { fn eq(&self, other: &[u16]) -> bool { match self { Self::String(ref string) => string == other, _ => false, } } } #[derive(Debug, Clone, Copy)] pub(crate) enum PropertyNameKind { Key, Value, KeyAndValue, }