Skip to content

Commit

Permalink
Add field accessors to destructing assignment (#2213)
Browse files Browse the repository at this point in the history
This Pull Request changes the following:

- Implement parsing/cover-conversion for field accessor member expressions in [DestructuringAssignmentTarget](https://tc39.es/ecma262/#prod-DestructuringAssignmentTarget).
- Modify the `CopyDataProperties` opcode to account for excluded keys that are only known at runtime.
  • Loading branch information
raskad committed Aug 14, 2022
1 parent 492d843 commit 2dcdf51
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 47 deletions.
111 changes: 103 additions & 8 deletions boa_engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -989,7 +989,7 @@ impl<'b> ByteCompiler<'b> {
PropertyDefinition::SpreadObject(expr) => {
self.compile_expr(expr, true)?;
self.emit_opcode(Opcode::Swap);
self.emit(Opcode::CopyDataProperties, &[0]);
self.emit(Opcode::CopyDataProperties, &[0, 0]);
self.emit_opcode(Opcode::Pop);
}
PropertyDefinition::CoverInitializedName(_, _) => {
Expand Down Expand Up @@ -2195,9 +2195,13 @@ impl<'b> ByteCompiler<'b> {

self.emit_opcode(Opcode::RequireObjectCoercible);

let mut additional_excluded_keys_count = 0;
let rest_exits = pattern.has_rest();

for binding in pattern.bindings() {
use BindingPatternTypeObject::{
BindingPattern, Empty, RestGetConstField, RestProperty, SingleName,
AssignmentGetConstField, AssignmentGetField, AssignmentRestProperty,
BindingPattern, Empty, RestProperty, SingleName,
};

match binding {
Expand All @@ -2218,7 +2222,11 @@ impl<'b> ByteCompiler<'b> {
PropertyName::Computed(node) => {
self.compile_expr(node, true)?;
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::GetPropertyByValue);
if rest_exits {
self.emit_opcode(Opcode::GetPropertyByValuePush);
} else {
self.emit_opcode(Opcode::GetPropertyByValue);
}
}
}

Expand All @@ -2229,13 +2237,17 @@ impl<'b> ByteCompiler<'b> {
self.patch_jump(skip);
}
self.emit_binding(def, *ident);

if rest_exits && property_name.computed().is_some() {
self.emit_opcode(Opcode::Swap);
additional_excluded_keys_count += 1;
}
}
// BindingRestProperty : ... BindingIdentifier
RestProperty {
ident,
excluded_keys,
} => {
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::PushEmptyObject);

for key in excluded_keys {
Expand All @@ -2244,10 +2256,13 @@ impl<'b> ByteCompiler<'b> {
));
}

self.emit(Opcode::CopyDataProperties, &[excluded_keys.len() as u32]);
self.emit(
Opcode::CopyDataProperties,
&[excluded_keys.len() as u32, additional_excluded_keys_count],
);
self.emit_binding(def, *ident);
}
RestGetConstField {
AssignmentRestProperty {
get_const_field,
excluded_keys,
} => {
Expand All @@ -2258,14 +2273,92 @@ impl<'b> ByteCompiler<'b> {
self.interner().resolve_expect(*key).into(),
));
}
self.emit(Opcode::CopyDataProperties, &[excluded_keys.len() as u32]);
self.emit(Opcode::CopyDataProperties, &[excluded_keys.len() as u32, 0]);
self.access_set(
Access::ByName {
node: get_const_field,
},
None,
false,
)?;
}
AssignmentGetConstField {
property_name,
get_const_field,
default_init,
} => {
self.emit_opcode(Opcode::Dup);
match property_name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name(*name);
self.emit(Opcode::GetPropertyByName, &[index]);
}
PropertyName::Computed(node) => {
self.compile_expr(node, true)?;
self.emit_opcode(Opcode::Swap);
if rest_exits {
self.emit_opcode(Opcode::GetPropertyByValuePush);
} else {
self.emit_opcode(Opcode::GetPropertyByValue);
}
}
}

if let Some(init) = default_init {
let skip =
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true)?;
self.patch_jump(skip);
}

self.access_set(
Access::ByName {
node: get_const_field,
},
None,
false,
)?;

if rest_exits && property_name.computed().is_some() {
self.emit_opcode(Opcode::Swap);
additional_excluded_keys_count += 1;
}
}
AssignmentGetField {
property_name,
get_field,
default_init,
} => {
self.emit_opcode(Opcode::Dup);
match property_name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name(*name);
self.emit(Opcode::GetPropertyByName, &[index]);
}
PropertyName::Computed(node) => {
self.compile_expr(node, true)?;
self.emit_opcode(Opcode::Swap);
if rest_exits {
self.emit_opcode(Opcode::GetPropertyByValuePush);
} else {
self.emit_opcode(Opcode::GetPropertyByValue);
}
}
}

if let Some(init) = default_init {
let skip =
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true)?;
self.patch_jump(skip);
}

self.access_set(Access::ByValue { node: get_field }, None, false)?;

if rest_exits && property_name.computed().is_some() {
self.emit_opcode(Opcode::Swap);
additional_excluded_keys_count += 1;
}
}
BindingPattern {
ident,
Expand Down Expand Up @@ -2297,7 +2390,9 @@ impl<'b> ByteCompiler<'b> {
}
}

self.emit_opcode(Opcode::Pop);
if !rest_exits {
self.emit_opcode(Opcode::Pop);
}
}
DeclarationPattern::Array(pattern) => {
let skip_init = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
Expand Down
121 changes: 110 additions & 11 deletions boa_engine/src/syntax/ast/node/declaration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,9 @@ impl DeclarationPattern {
}
}
}
BindingPatternTypeObject::RestGetConstField {
get_const_field, ..
BindingPatternTypeObject::AssignmentRestProperty {
get_const_field,
..
} => {
if get_const_field.obj().contains_arguments() {
return true;
Expand Down Expand Up @@ -432,8 +433,9 @@ impl DeclarationPattern {
return true;
}
}
BindingPatternTypeObject::RestGetConstField {
get_const_field, ..
BindingPatternTypeObject::AssignmentRestProperty {
get_const_field,
..
} => {
if get_const_field.obj().contains(symbol) {
return true;
Expand Down Expand Up @@ -560,18 +562,27 @@ impl DeclarationPatternObject {
&self.bindings
}

// Returns if the object binding pattern has a rest element.
#[inline]
pub(crate) fn has_rest(&self) -> bool {
matches!(
self.bindings.last(),
Some(BindingPatternTypeObject::RestProperty { .. })
)
}

/// Gets the list of identifiers declared by the object binding pattern.
#[inline]
pub(crate) fn idents(&self) -> Vec<Sym> {
let mut idents = Vec::new();

for binding in &self.bindings {
use BindingPatternTypeObject::{
BindingPattern, Empty, RestGetConstField, RestProperty, SingleName,
AssignmentRestProperty, BindingPattern, Empty, RestProperty, SingleName,
};

match binding {
Empty | RestGetConstField { .. } => {}
Empty | AssignmentRestProperty { .. } => {}
SingleName {
ident,
property_name: _,
Expand All @@ -585,6 +596,12 @@ impl DeclarationPatternObject {
} => {
idents.push(*property_name);
}
BindingPatternTypeObject::AssignmentGetConstField { property_name, .. }
| BindingPatternTypeObject::AssignmentGetField { property_name, .. } => {
if let Some(name) = property_name.literal() {
idents.push(name);
}
}
BindingPattern {
ident: _,
pattern,
Expand Down Expand Up @@ -737,20 +754,50 @@ pub enum BindingPatternTypeObject {
/// [spec1]: https://tc39.es/ecma262/#prod-BindingRestProperty
RestProperty { ident: Sym, excluded_keys: Vec<Sym> },

/// RestGetConstField represents a rest property (spread operator) with a property accessor.
/// AssignmentRestProperty represents a rest property with a DestructuringAssignmentTarget.
///
/// Note: According to the spec this is not part of an ObjectBindingPattern.
/// This is only used when a object literal is used as the left-hand-side of an assignment expression.
/// This is only used when a object literal is used to cover an AssignmentPattern.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
RestGetConstField {
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentRestProperty
AssignmentRestProperty {
get_const_field: GetConstField,
excluded_keys: Vec<Sym>,
},

/// AssignmentGetConstField represents an AssignmentProperty with a cost field member expression AssignmentElement.
///
/// Note: According to the spec this is not part of an ObjectBindingPattern.
/// This is only used when a object literal is used to cover an AssignmentPattern.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentProperty
AssignmentGetConstField {
property_name: PropertyName,
get_const_field: GetConstField,
default_init: Option<Node>,
},

/// AssignmentGetField represents an AssignmentProperty with an expression field member expression AssignmentElement.
///
/// Note: According to the spec this is not part of an ObjectBindingPattern.
/// This is only used when a object literal is used to cover an AssignmentPattern.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentProperty
AssignmentGetField {
property_name: PropertyName,
get_field: GetField,
default_init: Option<Node>,
},

/// BindingPattern represents a `BindingProperty` with a `BindingPattern` as the `BindingElement`.
///
/// Additionally to the identifier of the new property and the nested binding pattern,
Expand Down Expand Up @@ -806,11 +853,63 @@ impl ToInternedString for BindingPatternTypeObject {
} => {
format!(" ... {}", interner.resolve_expect(*property_name))
}
Self::RestGetConstField {
Self::AssignmentRestProperty {
get_const_field, ..
} => {
format!(" ... {}", get_const_field.to_interned_string(interner))
}
Self::AssignmentGetConstField {
property_name,
get_const_field,
default_init,
} => {
let mut buf = match property_name {
PropertyName::Literal(name) => {
format!(
" {} : {}",
interner.resolve_expect(*name),
get_const_field.to_interned_string(interner)
)
}
PropertyName::Computed(node) => {
format!(
" [{}] : {}",
node.to_interned_string(interner),
get_const_field.to_interned_string(interner)
)
}
};
if let Some(init) = &default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
Self::AssignmentGetField {
property_name,
get_field,
default_init,
} => {
let mut buf = match property_name {
PropertyName::Literal(name) => {
format!(
" {} : {}",
interner.resolve_expect(*name),
get_field.to_interned_string(interner)
)
}
PropertyName::Computed(node) => {
format!(
" [{}] : {}",
node.to_interned_string(interner),
get_field.to_interned_string(interner)
)
}
};
if let Some(init) = &default_init {
buf.push_str(&format!(" = {}", init.to_interned_string(interner)));
}
buf
}
Self::BindingPattern {
ident: property_name,
pattern,
Expand Down
4 changes: 2 additions & 2 deletions boa_engine/src/syntax/ast/node/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ pub enum PropertyName {

impl PropertyName {
/// Returns the literal property name if it exists.
pub(in crate::syntax) fn literal(&self) -> Option<Sym> {
pub(crate) fn literal(&self) -> Option<Sym> {
if let Self::Literal(sym) = self {
Some(*sym)
} else {
Expand All @@ -399,7 +399,7 @@ impl PropertyName {
}

/// Returns the expression node if the property name is computed.
pub(in crate::syntax) fn computed(&self) -> Option<&Node> {
pub(crate) fn computed(&self) -> Option<&Node> {
if let Self::Computed(node) = self {
Some(node)
} else {
Expand Down

0 comments on commit 2dcdf51

Please sign in to comment.