From 9b8f50ee58ad50a06c6f0d3ae2da35efec990446 Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Tue, 14 Sep 2021 19:01:07 +0200 Subject: [PATCH] Implement `ComputedPropertyName` for accessor properties in `ObjectLiteral` (#1526) --- boa/src/syntax/ast/node/mod.rs | 71 ++++++++--- boa/src/syntax/ast/node/object/mod.rs | 117 +++++++++--------- .../primary/object_initializer/mod.rs | 100 +++++++++++---- 3 files changed, 189 insertions(+), 99 deletions(-) diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 53420ee57b..9da219867f 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -482,17 +482,7 @@ pub enum PropertyDefinition { /// /// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions - Property(Box, Node), - - /// Binds a computed property name to a JavaScript value. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-ComputedPropertyName - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions - ComputedPropertyName(Node, Node), + Property(PropertyName, Node), /// A property of an object can also refer to a function or a getter or setter method. /// @@ -502,7 +492,7 @@ pub enum PropertyDefinition { /// /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Method_definitions - MethodDefinition(MethodDefinitionKind, Box, FunctionExpr), + MethodDefinition(MethodDefinitionKind, PropertyName, FunctionExpr), /// The Rest/Spread Properties for ECMAScript proposal (stage 4) adds spread properties to object literals. /// It copies own enumerable properties from a provided object onto a new object. @@ -530,7 +520,7 @@ impl PropertyDefinition { /// Creates a `Property` definition. pub fn property(name: N, value: V) -> Self where - N: Into>, + N: Into, V: Into, { Self::Property(name.into(), value.into()) @@ -539,7 +529,7 @@ impl PropertyDefinition { /// Creates a `MethodDefinition`. pub fn method_definition(kind: MethodDefinitionKind, name: N, body: FunctionExpr) -> Self where - N: Into>, + N: Into, { Self::MethodDefinition(kind, name.into(), body) } @@ -614,6 +604,59 @@ unsafe impl Trace for MethodDefinitionKind { empty_trace!(); } +/// PropertyName can be either a literal or computed. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-PropertyName +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, PartialEq, Finalize)] +pub enum PropertyName { + /// A `Literal` property name can be either an identifier, a string or a numeric literal. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#prod-LiteralPropertyName + Literal(Box), + /// A `Computed` property name is an expression that gets evaluated and converted into a property name. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#prod-ComputedPropertyName + Computed(Node), +} + +impl Display for PropertyName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PropertyName::Literal(key) => write!(f, "{}", key), + PropertyName::Computed(key) => write!(f, "{}", key), + } + } +} + +impl From for PropertyName +where + T: Into>, +{ + fn from(name: T) -> Self { + Self::Literal(name.into()) + } +} + +impl From for PropertyName { + fn from(name: Node) -> Self { + Self::Computed(name) + } +} + +unsafe impl Trace for PropertyName { + empty_trace!(); +} + /// This parses the given source code, and then makes sure that /// the resulting StatementList is formatted in the same manner /// as the source code. This is expected to have a preceding diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index 146d644104..b47e6ad078 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -4,7 +4,7 @@ use crate::{ exec::Executable, gc::{Finalize, Trace}, property::PropertyDescriptor, - syntax::ast::node::{join_nodes, MethodDefinitionKind, Node, PropertyDefinition}, + syntax::ast::node::{join_nodes, MethodDefinitionKind, Node, PropertyDefinition, PropertyName}, BoaProfiler, Context, JsResult, JsValue, }; use std::fmt; @@ -64,11 +64,6 @@ impl Object { value.display_no_indent(f, indent + 1)?; writeln!(f, ",")?; } - PropertyDefinition::ComputedPropertyName(key, value) => { - writeln!(f, "{}{},", indentation, key)?; - value.display_no_indent(f, indent + 1)?; - writeln!(f, ",")?; - } PropertyDefinition::SpreadObject(key) => { writeln!(f, "{}...{},", indentation, key)?; } @@ -99,19 +94,15 @@ impl Executable for Object { // TODO: Implement the rest of the property types. for property in self.properties().iter() { match property { - PropertyDefinition::Property(key, value) => { - obj.set_property( - key.clone(), - PropertyDescriptor::builder() - .value(value.run(context)?) - .writable(true) - .enumerable(true) - .configurable(true), - ); - } - PropertyDefinition::ComputedPropertyName(key, value) => { + PropertyDefinition::Property(name, value) => { + let name = match name { + PropertyName::Literal(name) => name.clone().into(), + PropertyName::Computed(node) => { + node.run(context)?.to_property_key(context)? + } + }; obj.set_property( - key.run(context)?.to_property_key(context)?, + name, PropertyDescriptor::builder() .value(value.run(context)?) .writable(true) @@ -119,48 +110,56 @@ impl Executable for Object { .configurable(true), ); } - PropertyDefinition::MethodDefinition(kind, name, func) => match kind { - MethodDefinitionKind::Ordinary => { - obj.set_property( - name.clone(), - PropertyDescriptor::builder() - .value(func.run(context)?) - .writable(true) - .enumerable(true) - .configurable(true), - ); - } - MethodDefinitionKind::Get => { - let set = obj - .get_property(name.clone()) - .as_ref() - .and_then(|a| a.set()) - .cloned(); - obj.set_property( - name.clone(), - PropertyDescriptor::builder() - .maybe_get(func.run(context)?.as_object()) - .maybe_set(set) - .enumerable(true) - .configurable(true), - ) + PropertyDefinition::MethodDefinition(kind, name, func) => { + let name = match name { + PropertyName::Literal(name) => name.clone().into(), + PropertyName::Computed(node) => { + node.run(context)?.to_property_key(context)? + } + }; + match kind { + MethodDefinitionKind::Ordinary => { + obj.set_property( + name, + PropertyDescriptor::builder() + .value(func.run(context)?) + .writable(true) + .enumerable(true) + .configurable(true), + ); + } + MethodDefinitionKind::Get => { + let set = obj + .get_property(name.clone()) + .as_ref() + .and_then(|a| a.set()) + .cloned(); + obj.set_property( + name, + PropertyDescriptor::builder() + .maybe_get(func.run(context)?.as_object()) + .maybe_set(set) + .enumerable(true) + .configurable(true), + ) + } + MethodDefinitionKind::Set => { + let get = obj + .get_property(name.clone()) + .as_ref() + .and_then(|a| a.get()) + .cloned(); + obj.set_property( + name, + PropertyDescriptor::builder() + .maybe_get(get) + .maybe_set(func.run(context)?.as_object()) + .enumerable(true) + .configurable(true), + ) + } } - MethodDefinitionKind::Set => { - let get = obj - .get_property(name.clone()) - .as_ref() - .and_then(|a| a.get()) - .cloned(); - obj.set_property( - name.clone(), - PropertyDescriptor::builder() - .maybe_get(get) - .maybe_set(func.run(context)?.as_object()) - .enumerable(true) - .configurable(true), - ) - } - }, + } // [spec]: https://tc39.es/ecma262/#sec-runtime-semantics-propertydefinitionevaluation PropertyDefinition::SpreadObject(node) => { let val = node.run(context)?; diff --git a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs index 03b07caf6e..b8b2dba8db 100644 --- a/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/boa/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -9,7 +9,7 @@ #[cfg(test)] mod tests; -use crate::syntax::ast::node::Identifier; +use crate::syntax::ast::node::{Identifier, PropertyName}; use crate::syntax::lexer::TokenKind; use crate::{ syntax::{ @@ -141,10 +141,24 @@ where let node = AssignmentExpression::new(false, self.allow_yield, self.allow_await) .parse(cursor)?; cursor.expect(Punctuator::CloseBracket, "expected token ']'")?; - cursor.expect(Punctuator::Colon, "expected token ':'")?; - let val = AssignmentExpression::new(false, self.allow_yield, self.allow_await) - .parse(cursor)?; - return Ok(node::PropertyDefinition::ComputedPropertyName(node, val)); + let next_token = cursor.next()?.ok_or(ParseError::AbruptEnd)?; + match next_token.kind() { + TokenKind::Punctuator(Punctuator::Colon) => { + let val = AssignmentExpression::new(false, self.allow_yield, self.allow_await) + .parse(cursor)?; + return Ok(node::PropertyDefinition::property(node, val)); + } + TokenKind::Punctuator(Punctuator::OpenParen) => { + return MethodDefinition::new(self.allow_yield, self.allow_await, node) + .parse(cursor); + } + _ => { + return Err(ParseError::unexpected( + next_token, + "expected AssignmentExpression or MethodDefinition", + )) + } + } } // Peek for '}' or ',' to indicate shorthand property name @@ -210,7 +224,7 @@ where struct MethodDefinition { allow_yield: AllowYield, allow_await: AllowAwait, - identifier: String, + identifier: PropertyName, } impl MethodDefinition { @@ -219,7 +233,7 @@ impl MethodDefinition { where Y: Into, A: Into, - I: Into, + I: Into, { Self { allow_yield: allow_yield.into(), @@ -238,16 +252,17 @@ where fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("MethodDefinition", "Parsing"); - let (methodkind, prop_name, params) = match self.identifier.as_str() { - idn @ "get" | idn @ "set" - if matches!( - cursor.peek(0)?.map(|t| t.kind()), - Some(&TokenKind::Identifier(_)) - | Some(&TokenKind::Keyword(_)) - | Some(&TokenKind::BooleanLiteral(_)) - | Some(&TokenKind::NullLiteral) - | Some(&TokenKind::NumericLiteral(_)) - ) => + let (method_kind, prop_name, params) = match self.identifier { + PropertyName::Literal(ident) + if ["get", "set"].contains(&ident.as_ref()) + && matches!( + cursor.peek(0)?.map(|t| t.kind()), + Some(&TokenKind::Identifier(_)) + | Some(&TokenKind::Keyword(_)) + | Some(&TokenKind::BooleanLiteral(_)) + | Some(&TokenKind::NullLiteral) + | Some(&TokenKind::NumericLiteral(_)) + ) => { let prop_name = cursor.next()?.ok_or(ParseError::AbruptEnd)?.to_string(); cursor.expect( @@ -257,14 +272,51 @@ where let first_param = cursor.peek(0)?.expect("current token disappeared").clone(); let params = FormalParameters::new(false, false).parse(cursor)?; cursor.expect(Punctuator::CloseParen, "method definition")?; - if idn == "get" { + if ident.as_ref() == "get" { + if !params.is_empty() { + return Err(ParseError::unexpected( + first_param, + "getter functions must have no arguments", + )); + } + (MethodDefinitionKind::Get, prop_name.into(), params) + } else { + if params.len() != 1 { + return Err(ParseError::unexpected( + first_param, + "setter functions must have one argument", + )); + } + (MethodDefinitionKind::Set, prop_name.into(), params) + } + } + PropertyName::Literal(ident) + if ["get", "set"].contains(&ident.as_ref()) + && matches!( + cursor.peek(0)?.map(|t| t.kind()), + Some(&TokenKind::Punctuator(Punctuator::OpenBracket)) + ) => + { + cursor.expect(Punctuator::OpenBracket, "token vanished")?; + let prop_name = + AssignmentExpression::new(false, self.allow_yield, self.allow_await) + .parse(cursor)?; + cursor.expect(Punctuator::CloseBracket, "expected token ']'")?; + cursor.expect( + TokenKind::Punctuator(Punctuator::OpenParen), + "property method definition", + )?; + let first_param = cursor.peek(0)?.expect("current token disappeared").clone(); + let params = FormalParameters::new(false, false).parse(cursor)?; + cursor.expect(Punctuator::CloseParen, "method definition")?; + if ident.as_ref() == "get" { if !params.is_empty() { return Err(ParseError::unexpected( first_param, "getter functions must have no arguments", )); } - (MethodDefinitionKind::Get, prop_name, params) + (MethodDefinitionKind::Get, prop_name.into(), params) } else { if params.len() != 1 { return Err(ParseError::unexpected( @@ -272,17 +324,13 @@ where "setter functions must have one argument", )); } - (MethodDefinitionKind::Set, prop_name, params) + (MethodDefinitionKind::Set, prop_name.into(), params) } } prop_name => { let params = FormalParameters::new(false, false).parse(cursor)?; cursor.expect(Punctuator::CloseParen, "method definition")?; - ( - MethodDefinitionKind::Ordinary, - prop_name.to_string(), - params, - ) + (MethodDefinitionKind::Ordinary, prop_name, params) } }; @@ -297,7 +345,7 @@ where )?; Ok(node::PropertyDefinition::method_definition( - methodkind, + method_kind, prop_name, FunctionExpr::new(None, params, body), ))