Skip to content

Commit

Permalink
support object/array destructuring patterns in parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
y21 committed Nov 5, 2024
1 parent 86b4e99 commit 4d30119
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 169 deletions.
164 changes: 95 additions & 69 deletions crates/dash_compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use dash_middle::parser::expr::{
use dash_middle::parser::statement::{
Asyncness, Binding, BlockStatement, Class, ClassMember, ClassMemberKey, ClassMemberValue, DoWhileLoop, ExportKind,
ForInLoop, ForLoop, ForOfLoop, FunctionDeclaration, FunctionKind, IfStatement, ImportKind, Loop, Parameter,
ReturnStatement, ScopeId, SpecifierKind, Statement, StatementKind, SwitchCase, SwitchStatement, TryCatch,
Pattern, ReturnStatement, ScopeId, SpecifierKind, Statement, StatementKind, SwitchCase, SwitchStatement, TryCatch,
VariableDeclaration, VariableDeclarationKind, VariableDeclarationName, VariableDeclarations, WhileLoop,
};
use dash_middle::sourcemap::Span;
Expand Down Expand Up @@ -767,66 +767,9 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {
ib.build_pop();
}
}
VariableDeclarationName::ObjectDestructuring { fields, rest } => {
let rest_id = rest.map(|rest| ib.find_local_from_binding(rest));

let field_count = fields
.len()
.try_into()
.map_err(|_| Error::DestructureLimitExceeded(span))?;

VariableDeclarationName::Pattern(ref pat) => {
let value = value.ok_or(Error::MissingInitializerInDestructuring(span))?;
ib.accept_expr(value)?;

ib.build_objdestruct(field_count, rest_id);

for (local, name, alias) in fields {
let name = alias.unwrap_or(name);
let id = ib.find_local_from_binding(Binding { id: local, ident: name });

let NumberConstant(var_id) = ib
.current_function_mut()
.cp
.add_number(id as f64)
.map_err(|_| Error::ConstantPoolLimitExceeded(span))?;
let SymbolConstant(ident_id) = ib
.current_function_mut()
.cp
.add_symbol(name)
.map_err(|_| Error::ConstantPoolLimitExceeded(span))?;
ib.writew(var_id);
ib.writew(ident_id);
}
}
VariableDeclarationName::ArrayDestructuring { fields, rest } => {
if rest.is_some() {
unimplementedc!(span, "rest operator in array destructuring");
}

let field_count = fields
.len()
.try_into()
.map_err(|_| Error::DestructureLimitExceeded(span))?;

let value = value.ok_or(Error::MissingInitializerInDestructuring(span))?;
ib.accept_expr(value)?;

ib.build_arraydestruct(field_count);

for name in fields {
ib.write_bool(name.is_some());
if let Some(name) = name {
let id = ib.find_local_from_binding(name);

let NumberConstant(id) = ib
.current_function_mut()
.cp
.add_number(id as f64)
.map_err(|_| Error::ConstantPoolLimitExceeded(span))?;

ib.writew(id);
}
}
compile_destructuring_pattern(&mut ib, value, pat, span)?;
}
}
}
Expand Down Expand Up @@ -1642,14 +1585,14 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {
let mut rest_local = None;

for (param, default, _ty) in &arguments {
let name = match *param {
Parameter::Identifier(ident) => ident,
Parameter::Spread(ident) => ident,
let id = match *param {
Parameter::Identifier(binding) | Parameter::SpreadIdentifier(binding) => {
ib.find_local_from_binding(binding)
}
Parameter::Pattern(id, _) | Parameter::SpreadPattern(id, _) => ib.decl_to_slot.slot_from_local(id),
};

let id = ib.find_local_from_binding(name);

if let Parameter::Spread(..) = param {
if let Parameter::SpreadIdentifier(_) | Parameter::SpreadPattern(..) = param {
rest_local = Some(id);
}

Expand All @@ -1667,6 +1610,18 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {

sub_ib.add_local_label(Label::FinishParamDefaultValueInit);
}

if let Parameter::Pattern(_, pat) | Parameter::SpreadPattern(_, pat) = param {
compile_destructuring_pattern(
ib,
Expr {
span,
kind: ExprKind::Compiled(compile_local_load(id, false)),
},
pat,
span,
)?;
}
}

transformations::hoist_declarations(id, &mut ib.inner.scope_counter, &mut ib.inner.scopes, &mut statements);
Expand All @@ -1692,7 +1647,7 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {
name: name.map(|binding| binding.ident),
ty,
params: match arguments.last() {
Some((Parameter::Spread(..), ..)) => arguments.len() - 1,
Some((Parameter::SpreadPattern(..) | Parameter::SpreadIdentifier(_), ..)) => arguments.len() - 1,
_ => arguments.len(),
},
externals: cmp.externals.into(),
Expand Down Expand Up @@ -1984,11 +1939,11 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {
for var in &vars {
match var.binding.name {
VariableDeclarationName::Identifier(Binding { ident, .. }) => it.push(ident),
VariableDeclarationName::ArrayDestructuring { ref fields, rest } => {
VariableDeclarationName::Pattern(Pattern::Array { ref fields, rest }) => {
it.extend(fields.iter().flatten().map(|b| b.ident));
it.extend(rest.map(|b| b.ident));
}
VariableDeclarationName::ObjectDestructuring { ref fields, rest } => {
VariableDeclarationName::Pattern(Pattern::Object { ref fields, rest }) => {
it.extend(fields.iter().map(|&(_, name, ident)| ident.unwrap_or(name)));
it.extend(rest.map(|b| b.ident));
}
Expand Down Expand Up @@ -2414,6 +2369,77 @@ fn compile_object_members(
Ok(members)
}

fn compile_destructuring_pattern(
ib: &mut InstructionBuilder<'_, '_>,
from: Expr,
pat: &Pattern,
at: Span,
) -> Result<(), Error> {
match pat {
Pattern::Object { fields, rest } => {
let rest_id = rest.map(|rest| ib.find_local_from_binding(rest));

let field_count = fields
.len()
.try_into()
.map_err(|_| Error::DestructureLimitExceeded(at))?;

ib.accept_expr(from)?;

ib.build_objdestruct(field_count, rest_id);

for &(local, name, alias) in fields {
let name = alias.unwrap_or(name);
let id = ib.find_local_from_binding(Binding { id: local, ident: name });

let NumberConstant(var_id) = ib
.current_function_mut()
.cp
.add_number(id as f64)
.map_err(|_| Error::ConstantPoolLimitExceeded(at))?;
let SymbolConstant(ident_id) = ib
.current_function_mut()
.cp
.add_symbol(name)
.map_err(|_| Error::ConstantPoolLimitExceeded(at))?;
ib.writew(var_id);
ib.writew(ident_id);
}
}
Pattern::Array { fields, rest } => {
if rest.is_some() {
unimplementedc!(at, "rest operator in array destructuring");
}

let field_count = fields
.len()
.try_into()
.map_err(|_| Error::DestructureLimitExceeded(at))?;

ib.accept_expr(from)?;

ib.build_arraydestruct(field_count);

for &name in fields {
ib.write_bool(name.is_some());
if let Some(name) = name {
let id = ib.find_local_from_binding(name);

let NumberConstant(id) = ib
.current_function_mut()
.cp
.add_number(id as f64)
.map_err(|_| Error::ConstantPoolLimitExceeded(at))?;

ib.writew(id);
}
}
}
}

Ok(())
}

fn compile_class_members(
ib: &mut InstructionBuilder<'_, '_>,
span: Span,
Expand Down
28 changes: 18 additions & 10 deletions crates/dash_middle/src/parser/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -692,11 +692,9 @@ pub enum VariableDeclarationKind {
}

#[derive(Debug, Clone, PartialEq)]
pub enum VariableDeclarationName {
/// Normal identifier
Identifier(Binding),
pub enum Pattern {
/// Object destructuring: { a } = { a: 1 }
ObjectDestructuring {
Object {
/// Fields to destructure
///
/// Destructured fields can also be aliased with ` { a: b } = { a: 3 } `
Expand All @@ -705,7 +703,7 @@ pub enum VariableDeclarationName {
rest: Option<Binding>,
},
/// Array destructuring: [ a ] = [ 1 ]
ArrayDestructuring {
Array {
/// Elements to destructure.
/// For `[a,,b]` this stores `[Some(a), None, Some(b)]`
fields: Vec<Option<Binding>>,
Expand All @@ -714,11 +712,17 @@ pub enum VariableDeclarationName {
},
}

impl fmt::Display for VariableDeclarationName {
#[derive(Debug, Clone, PartialEq, Display)]
pub enum VariableDeclarationName {
/// Normal identifier
Identifier(Binding),
Pattern(Pattern),
}

impl fmt::Display for Pattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VariableDeclarationName::Identifier(name) => write!(f, "{name}"),
VariableDeclarationName::ObjectDestructuring { fields, rest } => {
Pattern::Object { fields, rest } => {
write!(f, "{{ ")?;

for (i, (_, name, alias)) in fields.iter().enumerate() {
Expand All @@ -739,7 +743,7 @@ impl fmt::Display for VariableDeclarationName {

write!(f, " }}")
}
VariableDeclarationName::ArrayDestructuring { fields, rest } => {
Pattern::Array { fields, rest } => {
write!(f, "[ ")?;

for (i, name) in fields.iter().enumerate() {
Expand Down Expand Up @@ -940,5 +944,9 @@ pub enum ClassMemberValue {
#[derive(Debug, Clone, Display)]
pub enum Parameter {
Identifier(Binding),
Spread(Binding),
SpreadIdentifier(Binding),
#[display(fmt = "{_1}")]
Pattern(LocalId, Pattern),
#[display(fmt = "{_1}")]
SpreadPattern(LocalId, Pattern),
}
63 changes: 43 additions & 20 deletions crates/dash_optimizer/src/type_infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::cell::RefCell;

use dash_log::debug;
use dash_middle::compiler::scope::{BlockScope, CompileValueType, FunctionScope, Local, ScopeGraph, ScopeKind};
use dash_middle::interner::Symbol;
use dash_middle::interner::{sym, Symbol};
use dash_middle::lexer::token::TokenType;
use dash_middle::parser::expr::{
ArrayLiteral, ArrayMemberKind, AssignmentExpr, AssignmentTarget, BinaryExpr, CallArgumentKind, ConditionalExpr,
Expand All @@ -11,8 +11,8 @@ use dash_middle::parser::expr::{
};
use dash_middle::parser::statement::{
Binding, BlockStatement, Class, ClassMemberKey, ClassMemberValue, DoWhileLoop, ExportKind, ForInLoop, ForLoop,
ForOfLoop, FunctionDeclaration, IfStatement, ImportKind, LocalId, Loop, Parameter, ReturnStatement, ScopeId,
SpecifierKind, Statement, StatementKind, SwitchCase, SwitchStatement, TryCatch, VariableBinding,
ForOfLoop, FunctionDeclaration, IfStatement, ImportKind, LocalId, Loop, Parameter, Pattern, ReturnStatement,
ScopeId, SpecifierKind, Statement, StatementKind, SwitchCase, SwitchStatement, TryCatch, VariableBinding,
VariableDeclaration, VariableDeclarationKind, VariableDeclarationName, VariableDeclarations, WhileLoop,
};

Expand Down Expand Up @@ -315,35 +315,41 @@ impl<'s> TypeInferCtx<'s> {
}
}

fn visit_variable_binding(&mut self, binding: &VariableBinding, value: Option<&Expr>) {
let ty = match value {
Some(expr) => self.visit(expr),
None => Some(CompileValueType::Uninit),
};
debug!("discovered new variable(s) {binding:?} of type {:?}", ty);

match binding.name {
VariableDeclarationName::Identifier(name) => self.add_local(name, binding.kind, ty),
VariableDeclarationName::ObjectDestructuring { ref fields, rest } => {
fn visit_pattern(&mut self, kind: VariableDeclarationKind, pat: &Pattern) {
match *pat {
Pattern::Object { ref fields, rest } => {
for &(id, field, alias) in fields {
let name = alias.unwrap_or(field);
self.add_local(Binding { id, ident: name }, binding.kind, None);
self.add_local(Binding { id, ident: name }, kind, None);
}
if let Some(rest) = rest {
self.add_local(rest, binding.kind, None);
self.add_local(rest, kind, None);
}
}
VariableDeclarationName::ArrayDestructuring { ref fields, rest } => {
Pattern::Array { ref fields, rest } => {
for field in fields.iter().flatten().copied() {
self.add_local(field, binding.kind, None);
self.add_local(field, kind, None);
}
if let Some(rest) = rest {
self.add_local(rest, binding.kind, None);
self.add_local(rest, kind, None);
}
}
}
}

fn visit_variable_binding(&mut self, binding: &VariableBinding, value: Option<&Expr>) {
let ty = match value {
Some(expr) => self.visit(expr),
None => Some(CompileValueType::Uninit),
};
debug!("discovered new variable(s) {binding:?} of type {:?}", ty);

match binding.name {
VariableDeclarationName::Identifier(name) => self.add_local(name, binding.kind, ty),
VariableDeclarationName::Pattern(ref pat) => self.visit_pattern(binding.kind, pat),
}
}

pub fn visit_variable_declaration(&mut self, VariableDeclarations(declarations): &VariableDeclarations) {
for VariableDeclaration { binding, value } in declarations {
self.visit_variable_binding(binding, value.as_ref());
Expand Down Expand Up @@ -605,8 +611,19 @@ impl<'s> TypeInferCtx<'s> {
self.with_function_scope(sub_func_id, |this| {
for (param, expr, _) in parameters {
match *param {
Parameter::Identifier(ident) | Parameter::Spread(ident) => {
this.add_local(ident, VariableDeclarationKind::Var, None);
Parameter::Identifier(binding) | Parameter::SpreadIdentifier(binding) => {
this.add_local(binding, VariableDeclarationKind::Var, None)
}
Parameter::Pattern(local_id, _) | Parameter::SpreadPattern(local_id, _) => {
// Actual patterns bindings are visited in a second pass so that the actual parameters locals get their ids first
this.add_local(
Binding {
ident: sym::empty,
id: local_id,
},
VariableDeclarationKind::Unnameable,
None,
);
}
}

Expand All @@ -615,6 +632,12 @@ impl<'s> TypeInferCtx<'s> {
}
}

for (param, _, _) in parameters {
if let Parameter::Pattern(_, pat) | Parameter::SpreadPattern(_, pat) = param {
this.visit_pattern(VariableDeclarationKind::Var, pat);
}
}

for stmt in statements {
this.visit_statement(stmt);
}
Expand Down
Loading

0 comments on commit 4d30119

Please sign in to comment.