Browse Source

Implement `ComputedPropertyName` for accessor properties in `ObjectLiteral` (#1526)

pull/1577/head
raskad 3 years ago committed by GitHub
parent
commit
9b8f50ee58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 71
      boa/src/syntax/ast/node/mod.rs
  2. 117
      boa/src/syntax/ast/node/object/mod.rs
  3. 100
      boa/src/syntax/parser/expression/primary/object_initializer/mod.rs

71
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<str>, 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<str>, 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<N, V>(name: N, value: V) -> Self
where
N: Into<Box<str>>,
N: Into<PropertyName>,
V: Into<Node>,
{
Self::Property(name.into(), value.into())
@ -539,7 +529,7 @@ impl PropertyDefinition {
/// Creates a `MethodDefinition`.
pub fn method_definition<N>(kind: MethodDefinitionKind, name: N, body: FunctionExpr) -> Self
where
N: Into<Box<str>>,
N: Into<PropertyName>,
{
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<str>),
/// 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<T> From<T> for PropertyName
where
T: Into<Box<str>>,
{
fn from(name: T) -> Self {
Self::Literal(name.into())
}
}
impl From<Node> 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

117
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)?;

100
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<AllowYield>,
A: Into<AllowAwait>,
I: Into<String>,
I: Into<PropertyName>,
{
Self {
allow_yield: allow_yield.into(),
@ -238,16 +252,17 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
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),
))

Loading…
Cancel
Save