//! Property access expressions, as defined by the [spec]. //! //! [Property access expressions][access] provide two ways to access properties of an object: *dot notation* //! and *bracket notation*. //! - *Dot notation* is mostly used when the name of the property is static, and a valid Javascript //! identifier e.g. `obj.prop`, `arr.$val`. //! - *Bracket notation* is used when the name of the property is either variable, not a valid //! identifier or a symbol e.g. `arr[var]`, `arr[5]`, `arr[Symbol.iterator]`. //! //! A property access expression can be represented by a [`SimplePropertyAccess`] (`x.y`), a //! [`PrivatePropertyAccess`] (`x.#y`) or a [`SuperPropertyAccess`] (`super["y"]`), each of them with //! slightly different semantics overall. //! //! [spec]: https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-property-accessors //! [access]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors use crate::expression::Expression; use crate::function::PrivateName; use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use boa_interner::{Interner, Sym, ToInternedString}; use core::ops::ControlFlow; /// A property access field. /// /// See the [module level documentation][self] for more information. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub enum PropertyAccessField { /// A constant property field, such as `x.prop`. Const(Sym), /// An expression property field, such as `x["val"]`. Expr(Box), } impl From for PropertyAccessField { #[inline] fn from(id: Sym) -> Self { Self::Const(id) } } impl From for PropertyAccessField { #[inline] fn from(expr: Expression) -> Self { Self::Expr(Box::new(expr)) } } impl VisitWith for PropertyAccessField { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { match self { Self::Const(sym) => visitor.visit_sym(sym), Self::Expr(expr) => visitor.visit_expression(expr), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::Const(sym) => visitor.visit_sym_mut(sym), Self::Expr(expr) => visitor.visit_expression_mut(&mut *expr), } } } /// A property access expression. /// /// See the [module level documentation][self] for more information. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub enum PropertyAccess { /// A simple property access (`x.prop`). Simple(SimplePropertyAccess), /// A property access of a private property (`x.#priv`). Private(PrivatePropertyAccess), /// A property access of a `super` reference. (`super["prop"]`). Super(SuperPropertyAccess), } impl ToInternedString for PropertyAccess { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { match self { Self::Simple(s) => s.to_interned_string(interner), Self::Private(p) => p.to_interned_string(interner), Self::Super(s) => s.to_interned_string(interner), } } } impl From for Expression { #[inline] fn from(access: PropertyAccess) -> Self { Self::PropertyAccess(access) } } impl VisitWith for PropertyAccess { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { match self { Self::Simple(spa) => visitor.visit_simple_property_access(spa), Self::Private(ppa) => visitor.visit_private_property_access(ppa), Self::Super(supa) => visitor.visit_super_property_access(supa), } } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { match self { Self::Simple(spa) => visitor.visit_simple_property_access_mut(spa), Self::Private(ppa) => visitor.visit_private_property_access_mut(ppa), Self::Super(supa) => visitor.visit_super_property_access_mut(supa), } } } /// A simple property access, where the target object is an [`Expression`]. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct SimplePropertyAccess { target: Box, field: PropertyAccessField, } impl SimplePropertyAccess { /// Gets the target object of the property access. #[inline] #[must_use] pub const fn target(&self) -> &Expression { &self.target } /// Gets the accessed field of the target object. #[inline] #[must_use] pub const fn field(&self) -> &PropertyAccessField { &self.field } /// Creates a `PropertyAccess` AST Expression. pub fn new(target: Expression, field: F) -> Self where F: Into, { Self { target: target.into(), field: field.into(), } } } impl ToInternedString for SimplePropertyAccess { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { let target = self.target.to_interned_string(interner); match self.field { PropertyAccessField::Const(sym) => format!("{target}.{}", interner.resolve_expect(sym)), PropertyAccessField::Expr(ref expr) => { format!("{target}[{}]", expr.to_interned_string(interner)) } } } } impl From for PropertyAccess { #[inline] fn from(access: SimplePropertyAccess) -> Self { Self::Simple(access) } } impl VisitWith for SimplePropertyAccess { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { try_break!(visitor.visit_expression(&self.target)); visitor.visit_property_access_field(&self.field) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { try_break!(visitor.visit_expression_mut(&mut self.target)); visitor.visit_property_access_field_mut(&mut self.field) } } /// An access expression to a class object's [private fields][mdn]. /// /// Private property accesses differ slightly from plain property accesses, since the accessed /// property must be prefixed by `#`, and the bracket notation is not allowed. For example, /// `this.#a` is a valid private property access. /// /// This expression corresponds to the [`MemberExpression.PrivateIdentifier`][spec] production. /// /// [spec]: https://tc39.es/ecma262/#prod-MemberExpression /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct PrivatePropertyAccess { target: Box, field: PrivateName, } impl PrivatePropertyAccess { /// Creates a `GetPrivateField` AST Expression. #[inline] #[must_use] pub fn new(value: Expression, field: PrivateName) -> Self { Self { target: value.into(), field, } } /// Gets the original object from where to get the field from. #[inline] #[must_use] pub const fn target(&self) -> &Expression { &self.target } /// Gets the name of the field to retrieve. #[inline] #[must_use] pub const fn field(&self) -> PrivateName { self.field } } impl ToInternedString for PrivatePropertyAccess { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { format!( "{}.#{}", self.target.to_interned_string(interner), interner.resolve_expect(self.field.description()) ) } } impl From for PropertyAccess { #[inline] fn from(access: PrivatePropertyAccess) -> Self { Self::Private(access) } } impl VisitWith for PrivatePropertyAccess { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { try_break!(visitor.visit_expression(&self.target)); visitor.visit_private_name(&self.field) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { try_break!(visitor.visit_expression_mut(&mut self.target)); visitor.visit_private_name_mut(&mut self.field) } } /// A property access of an object's parent, as defined by the [spec]. /// /// A `SuperPropertyAccess` is much like a regular [`PropertyAccess`], but where its `target` object /// is not a regular object, but a reference to the parent object of the current object ([`super`][mdn]). /// /// [spec]: https://tc39.es/ecma262/#prod-SuperProperty /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, PartialEq)] pub struct SuperPropertyAccess { field: PropertyAccessField, } impl SuperPropertyAccess { /// Creates a new property access field node. #[must_use] pub const fn new(field: PropertyAccessField) -> Self { Self { field } } /// Gets the name of the field to retrieve. #[inline] #[must_use] pub const fn field(&self) -> &PropertyAccessField { &self.field } } impl ToInternedString for SuperPropertyAccess { #[inline] fn to_interned_string(&self, interner: &Interner) -> String { match &self.field { PropertyAccessField::Const(field) => { format!("super.{}", interner.resolve_expect(*field)) } PropertyAccessField::Expr(field) => { format!("super[{}]", field.to_interned_string(interner)) } } } } impl From for PropertyAccess { #[inline] fn from(access: SuperPropertyAccess) -> Self { Self::Super(access) } } impl VisitWith for SuperPropertyAccess { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { visitor.visit_property_access_field(&self.field) } fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { visitor.visit_property_access_field_mut(&mut self.field) } }