Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Implement destructing assignments for assignment expressions #1895

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 90 additions & 14 deletions boa_engine/src/bytecompiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
declaration::{BindingPatternTypeArray, BindingPatternTypeObject, DeclarationPattern},
iteration::IterableLoopInitializer,
object::{MethodDefinition, PropertyDefinition, PropertyName},
operator::assign::AssignTarget,
template::TemplateElement,
Declaration, GetConstField, GetField,
},
Expand Down Expand Up @@ -169,6 +170,11 @@ impl<'b> ByteCompiler<'b> {
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitConst, &[index]);
}
BindingOpcode::SetName => {
let binding = self.context.set_mutable_binding(name);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::SetName, &[index]);
}
}
}

Expand Down Expand Up @@ -928,18 +934,26 @@ impl<'b> ByteCompiler<'b> {
let access = Access::Variable { name: name.sym() };
self.access_get(access, use_expr)?;
}
Node::Assign(assign) => {
// Implement destructing assignments like here: https://tc39.es/ecma262/#sec-destructuring-assignment
if let Node::Object(_) = assign.lhs() {
self.emit_opcode(Opcode::PushUndefined);
} else {
let access = Self::compile_access(assign.lhs()).ok_or_else(|| {
self.context
.construct_syntax_error("Invalid left-hand side in assignment")
})?;
self.access_set(access, Some(assign.rhs()), use_expr)?;
Node::Assign(assign) => match assign.lhs() {
AssignTarget::Identifier(name) => self.access_set(
Access::Variable { name: name.sym() },
Some(assign.rhs()),
use_expr,
)?,
AssignTarget::GetConstField(node) => {
self.access_set(Access::ByName { node }, Some(assign.rhs()), use_expr)?;
}
AssignTarget::GetField(node) => {
self.access_set(Access::ByValue { node }, Some(assign.rhs()), use_expr)?;
}
AssignTarget::DeclarationPattern(pattern) => {
self.compile_expr(assign.rhs(), true)?;
if use_expr {
self.emit_opcode(Opcode::Dup);
}
self.compile_declaration_pattern(pattern, BindingOpcode::SetName)?;
}
}
},
Node::GetConstField(node) => {
let access = Access::ByName { node };
self.access_get(access, use_expr)?;
Expand Down Expand Up @@ -1324,6 +1338,12 @@ impl<'b> ByteCompiler<'b> {
self.compile_declaration_pattern(pattern, BindingOpcode::InitConst)?;
}
},
IterableLoopInitializer::DeclarationPattern(pattern) => {
for ident in pattern.idents() {
self.context.create_mutable_binding(ident, true, true)?;
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?;
}
}

self.compile_stmt(for_in_loop.body(), false)?;
Expand Down Expand Up @@ -1401,6 +1421,12 @@ impl<'b> ByteCompiler<'b> {
self.compile_declaration_pattern(pattern, BindingOpcode::InitConst)?;
}
},
IterableLoopInitializer::DeclarationPattern(pattern) => {
for ident in pattern.idents() {
self.context.create_mutable_binding(ident, true, true)?;
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?;
}
}

self.compile_stmt(for_of_loop.body(), false)?;
Expand Down Expand Up @@ -2010,7 +2036,7 @@ impl<'b> ByteCompiler<'b> {

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

match binding {
Expand Down Expand Up @@ -2050,6 +2076,26 @@ impl<'b> ByteCompiler<'b> {
self.emit(Opcode::CopyDataProperties, &[excluded_keys.len() as u32]);
self.emit_binding(def, *ident);
}
RestGetConstField {
get_const_field,
excluded_keys,
} => {
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::PushEmptyObject);
for key in excluded_keys {
self.emit_push_literal(Literal::String(
self.interner().resolve_expect(*key).into(),
));
}
self.emit(Opcode::CopyDataProperties, &[excluded_keys.len() as u32]);
self.access_set(
Access::ByName {
node: get_const_field,
},
None,
false,
)?;
}
BindingPattern {
ident,
pattern,
Expand Down Expand Up @@ -2085,8 +2131,8 @@ impl<'b> ByteCompiler<'b> {

for (i, binding) in pattern.bindings().iter().enumerate() {
use BindingPatternTypeArray::{
BindingPattern, BindingPatternRest, Elision, Empty, SingleName,
SingleNameRest,
BindingPattern, BindingPatternRest, Elision, Empty, GetConstField,
GetConstFieldRest, GetField, GetFieldRest, SingleName, SingleNameRest,
};

let next = if i == pattern.bindings().len() - 1 {
Expand Down Expand Up @@ -2118,6 +2164,20 @@ impl<'b> ByteCompiler<'b> {
}
self.emit_binding(def, *ident);
}
GetField { get_field } => {
self.emit_opcode(next);
self.access_set(Access::ByValue { node: get_field }, None, false)?;
}
GetConstField { get_const_field } => {
self.emit_opcode(next);
self.access_set(
Access::ByName {
node: get_const_field,
},
None,
false,
)?;
}
// BindingElement : BindingPattern Initializer[opt]
BindingPattern { pattern } => {
self.emit_opcode(next);
Expand All @@ -2129,6 +2189,22 @@ impl<'b> ByteCompiler<'b> {
self.emit_binding(def, *ident);
self.emit_opcode(Opcode::PushTrue);
}
GetFieldRest { get_field } => {
self.emit_opcode(Opcode::IteratorToArray);
self.access_set(Access::ByValue { node: get_field }, None, false)?;
self.emit_opcode(Opcode::PushTrue);
}
GetConstFieldRest { get_const_field } => {
self.emit_opcode(Opcode::IteratorToArray);
self.access_set(
Access::ByName {
node: get_const_field,
},
None,
false,
)?;
self.emit_opcode(Opcode::PushTrue);
}
// BindingRestElement : ... BindingPattern
BindingPatternRest { pattern } => {
self.emit_opcode(Opcode::IteratorToArray);
Expand Down
25 changes: 24 additions & 1 deletion boa_engine/src/syntax/ast/node/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,26 @@ mod tests;
pub struct ArrayDecl {
#[cfg_attr(feature = "deser", serde(flatten))]
arr: Box<[Node]>,
has_trailing_comma_spread: bool,
}

impl ArrayDecl {
/// Crate a new array declaration.
pub(crate) fn new<A>(array: A, has_trailing_comma_spread: bool) -> Self
where
A: Into<Box<[Node]>>,
{
Self {
arr: array.into(),
has_trailing_comma_spread,
}
}

/// Indicates if a spread operator in the array literal has a trailing comma.
/// This is a syntax error in some cases.
pub(crate) fn has_trailing_comma_spread(&self) -> bool {
self.has_trailing_comma_spread
}
}

impl AsRef<[Node]> for ArrayDecl {
Expand All @@ -44,7 +64,10 @@ where
T: Into<Box<[Node]>>,
{
fn from(decl: T) -> Self {
Self { arr: decl.into() }
Self {
arr: decl.into(),
has_trailing_comma_spread: false,
}
}
}

Expand Down
100 changes: 93 additions & 7 deletions boa_engine/src/syntax/ast/node/declaration/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
//! Declaration nodes
use crate::syntax::ast::node::{join_nodes, Identifier, Node};
use crate::syntax::ast::node::{
field::{GetConstField, GetField},
join_nodes,
statement_list::StatementList,
Identifier, Node,
};
use boa_gc::{Finalize, Trace};
use boa_interner::{Interner, Sym, ToInternedString};

Expand All @@ -23,8 +28,6 @@ pub use self::{
function_expr::FunctionExpr,
};

use super::StatementList;

#[cfg(test)]
mod tests;

Expand Down Expand Up @@ -337,10 +340,12 @@ impl DeclarationPatternObject {
let mut idents = Vec::new();

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

match binding {
Empty => {}
Empty | RestGetConstField { .. } => {}
SingleName {
ident,
property_name: _,
Expand Down Expand Up @@ -437,11 +442,17 @@ impl DeclarationPatternArray {

for binding in &self.bindings {
use BindingPatternTypeArray::{
BindingPattern, BindingPatternRest, Elision, Empty, SingleName, SingleNameRest,
BindingPattern, BindingPatternRest, Elision, Empty, GetConstField,
GetConstFieldRest, GetField, GetFieldRest, SingleName, SingleNameRest,
};

match binding {
Empty | Elision => {}
Empty
| Elision
| GetField { .. }
| GetConstField { .. }
| GetFieldRest { .. }
| GetConstFieldRest { .. } => {}
SingleName {
ident,
default_init: _,
Expand Down Expand Up @@ -500,6 +511,20 @@ 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.
///
/// 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.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
RestGetConstField {
get_const_field: GetConstField,
excluded_keys: Vec<Sym>,
},

/// 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 @@ -545,6 +570,11 @@ impl ToInternedString for BindingPatternTypeObject {
} => {
format!(" ... {}", interner.resolve_expect(*property_name))
}
BindingPatternTypeObject::RestGetConstField {
get_const_field, ..
} => {
format!(" ... {}", get_const_field.to_interned_string(interner))
}
BindingPatternTypeObject::BindingPattern {
ident: property_name,
pattern,
Expand Down Expand Up @@ -606,6 +636,28 @@ pub enum BindingPatternTypeArray {
default_init: Option<Node>,
},

/// GetField represents a binding with a property accessor.
///
/// Note: According to the spec this is not part of an ArrayBindingPattern.
/// This is only used when a array literal is used as the left-hand-side of an assignment expression.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
GetField { get_field: GetField },

/// GetConstField represents a binding with a property accessor.
///
/// Note: According to the spec this is not part of an ArrayBindingPattern.
/// This is only used when a array literal is used as the left-hand-side of an assignment expression.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
GetConstField { get_const_field: GetConstField },

/// BindingPattern represents a `BindingPattern` in a `BindingElement` of an array binding pattern.
///
/// The pattern and the optional default initializer are both stored in the DeclarationPattern.
Expand All @@ -624,6 +676,28 @@ pub enum BindingPatternTypeArray {
/// [spec1]: https://tc39.es/ecma262/#prod-BindingRestElement
SingleNameRest { ident: Sym },

/// GetFieldRest represents a rest binding (spread operator) with a property accessor.
///
/// Note: According to the spec this is not part of an ArrayBindingPattern.
/// This is only used when a array literal is used as the left-hand-side of an assignment expression.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
GetFieldRest { get_field: GetField },

/// GetConstFieldRest represents a rest binding (spread operator) with a property accessor.
///
/// Note: According to the spec this is not part of an ArrayBindingPattern.
/// This is only used when a array literal is used as the left-hand-side of an assignment expression.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression
GetConstFieldRest { get_const_field: GetConstField },

/// SingleNameRest represents a `BindingPattern` in a `BindingRestElement` of an array binding pattern.
///
/// More information:
Expand All @@ -648,12 +722,24 @@ impl ToInternedString for BindingPatternTypeArray {
}
buf
}
BindingPatternTypeArray::GetField { get_field } => {
format!(" {}", get_field.to_interned_string(interner))
}
BindingPatternTypeArray::GetConstField { get_const_field } => {
format!(" {}", get_const_field.to_interned_string(interner))
}
BindingPatternTypeArray::BindingPattern { pattern } => {
format!(" {}", pattern.to_interned_string(interner))
}
BindingPatternTypeArray::SingleNameRest { ident } => {
format!(" ... {}", interner.resolve_expect(*ident))
}
BindingPatternTypeArray::GetFieldRest { get_field } => {
format!(" ... {}", get_field.to_interned_string(interner))
}
BindingPatternTypeArray::GetConstFieldRest { get_const_field } => {
format!(" ... {}", get_const_field.to_interned_string(interner))
}
BindingPatternTypeArray::BindingPatternRest { pattern } => {
format!(" ... {}", pattern.to_interned_string(interner))
}
Expand Down
Loading