diff --git a/crates/dash_compiler/src/lib.rs b/crates/dash_compiler/src/lib.rs index 5a612eda..34e41c42 100644 --- a/crates/dash_compiler/src/lib.rs +++ b/crates/dash_compiler/src/lib.rs @@ -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; @@ -767,66 +767,9 @@ impl<'interner> Visitor> 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)?; } } } @@ -1642,14 +1585,14 @@ impl<'interner> Visitor> 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); } @@ -1667,6 +1610,18 @@ impl<'interner> Visitor> 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); @@ -1692,7 +1647,7 @@ impl<'interner> Visitor> 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(), @@ -1984,11 +1939,11 @@ impl<'interner> Visitor> 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)); } @@ -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, diff --git a/crates/dash_middle/src/parser/statement.rs b/crates/dash_middle/src/parser/statement.rs index 4ae537ad..6e290726 100644 --- a/crates/dash_middle/src/parser/statement.rs +++ b/crates/dash_middle/src/parser/statement.rs @@ -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 } ` @@ -705,7 +703,7 @@ pub enum VariableDeclarationName { rest: Option, }, /// Array destructuring: [ a ] = [ 1 ] - ArrayDestructuring { + Array { /// Elements to destructure. /// For `[a,,b]` this stores `[Some(a), None, Some(b)]` fields: Vec>, @@ -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() { @@ -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() { @@ -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), } diff --git a/crates/dash_optimizer/src/type_infer.rs b/crates/dash_optimizer/src/type_infer.rs index 1263b39d..7a4e44b3 100644 --- a/crates/dash_optimizer/src/type_infer.rs +++ b/crates/dash_optimizer/src/type_infer.rs @@ -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, @@ -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, }; @@ -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()); @@ -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, + ); } } @@ -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); } diff --git a/crates/dash_parser/src/expr.rs b/crates/dash_parser/src/expr.rs index 2f43daad..74e5a4ac 100644 --- a/crates/dash_parser/src/expr.rs +++ b/crates/dash_parser/src/expr.rs @@ -894,7 +894,7 @@ impl<'a, 'interner> Parser<'a, 'interner> { } if let Some(ident) = rest_binding { - list.push((Parameter::Spread(self.create_binding(ident)), None, None)); + list.push((Parameter::SpreadIdentifier(self.create_binding(ident)), None, None)); } let is_statement = self.eat(TokenType::LeftBrace, false).is_some(); diff --git a/crates/dash_parser/src/stmt.rs b/crates/dash_parser/src/stmt.rs index 41e9b3fa..9e5dab76 100644 --- a/crates/dash_parser/src/stmt.rs +++ b/crates/dash_parser/src/stmt.rs @@ -5,7 +5,7 @@ use dash_middle::parser::expr::{Expr, ExprKind}; use dash_middle::parser::statement::{ Asyncness, BlockStatement, Catch, 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, VariableBinding, VariableDeclaration, VariableDeclarationKind, VariableDeclarationName, VariableDeclarations, WhileLoop, }; @@ -557,68 +557,8 @@ impl<'a, 'interner> Parser<'a, 'interner> { Some(IfStatement::new(condition, then, branches, el)) } - /// Parses a list of parameters (identifier, followed by optional type segment) delimited by comma, - /// assuming that the ( has already been consumed - pub fn parse_parameter_list(&mut self) -> ParameterList { - let mut parameters = Vec::new(); - - while self.eat(TokenType::RightParen, false).is_none() { - let tok = self.next().cloned()?; - - let parameter = match tok.ty { - TokenType::Dot => { - // Begin of spread operator - for _ in 0..2 { - self.eat(TokenType::Dot, true)?; - } - - let ident = self.expect_identifier(true)?; - - Parameter::Spread(self.create_binding(ident)) - } - TokenType::Comma => continue, - // TODO: refactor to if let guards once stable - other if other.is_identifier() => { - Parameter::Identifier(self.create_binding(other.as_identifier().unwrap())) - } - _ => { - self.error(Error::unexpected_token(tok, TokenType::Comma)); - return None; - } - }; - - // Parse type param - let ty = if self.eat(TokenType::Colon, false).is_some() { - Some(self.parse_type_segment()?) - } else { - None - }; - - // Parse default value - let default = if self.eat(TokenType::Assignment, false).is_some() { - Some(self.parse_expression()?) - } else { - None - }; - - let is_spread = matches!(parameter, Parameter::Spread(..)); - - parameters.push((parameter, default, ty)); - - if is_spread { - // Must be followed by ) - self.eat(TokenType::RightParen, true)?; - - break; - } - } - - Some(parameters) - } - - /// Parses the `x` in `let x = 1`, `[x, y]` in `let [x, y] = [1, 2]`, etc. - fn parse_variable_binding_with_kind(&mut self, kind: VariableDeclarationKind) -> Option { - let name = if self.eat(TokenType::LeftBrace, false).is_some() { + pub fn parse_pattern(&mut self) -> Option { + if self.eat(TokenType::LeftBrace, false).is_some() { // Object destructuring let mut fields = Vec::new(); let mut rest = None; @@ -683,9 +623,10 @@ impl<'a, 'interner> Parser<'a, 'interner> { } } - VariableDeclarationName::ObjectDestructuring { fields, rest } - } else if self.eat(TokenType::LeftSquareBrace, false).is_some() { + Some(Pattern::Object { fields, rest }) + } else { // Array destructuring + self.eat(TokenType::LeftSquareBrace, true)?; let mut fields = Vec::new(); let mut rest = None; @@ -739,11 +680,80 @@ impl<'a, 'interner> Parser<'a, 'interner> { } } - VariableDeclarationName::ArrayDestructuring { fields, rest } + Some(Pattern::Array { fields, rest }) + } + } + + /// Parses a list of parameters (identifier, followed by optional type segment) delimited by comma, + /// assuming that the ( has already been consumed + pub fn parse_parameter_list(&mut self) -> ParameterList { + let mut parameters = Vec::new(); + + while self.eat(TokenType::RightParen, false).is_none() { + let tok = self.next().cloned()?; + + let parameter = match tok.ty { + TokenType::Dot => { + // Begin of spread operator + for _ in 0..2 { + self.eat(TokenType::Dot, true)?; + } + + if let Some(ident) = self.expect_identifier(false) { + Parameter::SpreadIdentifier(self.create_binding(ident)) + } else { + Parameter::SpreadPattern(self.local_count.inc(), self.parse_pattern()?) + } + } + TokenType::Comma => continue, + other => { + if let Some(ident) = other.as_identifier() { + Parameter::Identifier(self.create_binding(ident)) + } else { + self.advance_back(); + Parameter::Pattern(self.local_count.inc(), self.parse_pattern()?) + } + } + }; + + // Parse type param + let ty = if self.eat(TokenType::Colon, false).is_some() { + Some(self.parse_type_segment()?) + } else { + None + }; + + // Parse default value + let default = if self.eat(TokenType::Assignment, false).is_some() { + Some(self.parse_expression()?) + } else { + None + }; + + let is_spread = matches!( + parameter, + Parameter::SpreadIdentifier(..) | Parameter::SpreadPattern(..) + ); + + parameters.push((parameter, default, ty)); + + if is_spread { + // Must be followed by ) + self.eat(TokenType::RightParen, true)?; + + break; + } + } + + Some(parameters) + } + + /// Parses the `x` in `let x = 1`, `[x, y]` in `let [x, y] = [1, 2]`, etc. + fn parse_variable_binding_with_kind(&mut self, kind: VariableDeclarationKind) -> Option { + let name = if let Some(ident) = self.expect_identifier(false) { + VariableDeclarationName::Identifier(self.create_binding(ident)) } else { - // Identifier - let name = self.expect_identifier(true)?; - VariableDeclarationName::Identifier(self.create_binding(name)) + VariableDeclarationName::Pattern(self.parse_pattern()?) }; let ty = if self.eat(TokenType::Colon, false).is_some() {