Skip to content

Commit

Permalink
support default values in object/array destructuring
Browse files Browse the repository at this point in the history
  • Loading branch information
y21 committed Nov 7, 2024
1 parent e3fe579 commit f9c2008
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 32 deletions.
25 changes: 20 additions & 5 deletions crates/dash_compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2035,11 +2035,11 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {
match var.binding.name {
VariableDeclarationName::Identifier(Binding { ident, .. }) => it.push(ident),
VariableDeclarationName::Pattern(Pattern::Array { ref fields, rest }) => {
it.extend(fields.iter().flatten().map(|b| b.ident));
it.extend(fields.iter().flatten().map(|(b, _)| b.ident));
it.extend(rest.map(|b| b.ident));
}
VariableDeclarationName::Pattern(Pattern::Object { ref fields, rest }) => {
it.extend(fields.iter().map(|&(_, name, ident)| ident.unwrap_or(name)));
it.extend(fields.iter().map(|&(_, name, ident, _)| ident.unwrap_or(name)));
it.extend(rest.map(|b| b.ident));
}
}
Expand Down Expand Up @@ -2479,11 +2479,17 @@ fn compile_destructuring_pattern(
.try_into()
.map_err(|_| Error::DestructureLimitExceeded(at))?;

for (.., default) in fields.iter().rev() {
if let Some(default) = default {
ib.accept_expr(default.clone())?;
}
}

ib.accept_expr(from)?;

ib.build_objdestruct(field_count, rest_id);

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

Expand All @@ -2497,6 +2503,7 @@ fn compile_destructuring_pattern(
.cp
.add_symbol(name)
.map_err(|_| Error::ConstantPoolLimitExceeded(at))?;
ib.write_bool(default.is_some());
ib.writew(var_id);
ib.writew(ident_id);
}
Expand All @@ -2511,13 +2518,20 @@ fn compile_destructuring_pattern(
.try_into()
.map_err(|_| Error::DestructureLimitExceeded(at))?;

#[expect(clippy::manual_flatten, reason = "pattern contains an inner Some()")]
for field in fields.iter().rev() {
if let Some((_, Some(default))) = field {
ib.accept_expr(default.clone())?;
}
}

ib.accept_expr(from)?;

ib.build_arraydestruct(field_count);

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

let NumberConstant(id) = ib
Expand All @@ -2526,6 +2540,7 @@ fn compile_destructuring_pattern(
.add_number(id as f64)
.map_err(|_| Error::ConstantPoolLimitExceeded(at))?;

ib.write_bool(default.is_some());
ib.writew(id);
}
}
Expand Down
31 changes: 19 additions & 12 deletions crates/dash_middle/src/parser/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -691,28 +691,28 @@ pub enum VariableDeclarationKind {
Unnameable,
}

#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub enum Pattern {
/// Object destructuring: { a } = { a: 1 }
Object {
/// Fields to destructure
///
/// Destructured fields can also be aliased with ` { a: b } = { a: 3 } `
fields: Vec<(LocalId, Symbol, Option<Symbol>)>,
/// Destructured fields can also be aliased with ` { a: b } = { a: 3 }` and have default values
fields: Vec<(LocalId, Symbol, Option<Symbol>, Option<Expr>)>,
/// The rest element, if present
rest: Option<Binding>,
},
/// Array destructuring: [ a ] = [ 1 ]
Array {
/// Elements to destructure.
/// For `[a,,b]` this stores `[Some(a), None, Some(b)]`
fields: Vec<Option<Binding>>,
/// For `[a,,b = default]` this stores `[Some((a, None)), None, Some((b, Some(default)))]`
fields: Vec<Option<(Binding, Option<Expr>)>>,
/// The rest element, if present
rest: Option<Binding>,
},
}

#[derive(Debug, Clone, PartialEq, Display)]
#[derive(Debug, Clone, Display)]
pub enum VariableDeclarationName {
/// Normal identifier
Identifier(Binding),
Expand All @@ -725,15 +725,17 @@ impl fmt::Display for Pattern {
Pattern::Object { fields, rest } => {
write!(f, "{{ ")?;

for (i, (_, name, alias)) in fields.iter().enumerate() {
for (i, (_, name, alias, default)) in fields.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}

write!(f, "{name}")?;
if let Some(alias) = alias {
write!(f, "{name}: {alias}")?;
} else {
write!(f, "{name}")?;
write!(f, ": {alias}")?;
}
if let Some(default) = default {
write!(f, " = {default}")?;
}
}

Expand All @@ -752,7 +754,12 @@ impl fmt::Display for Pattern {
}

match name {
Some(name) => write!(f, "{name}")?,
Some((name, default)) => {
write!(f, "{name}")?;
if let Some(default) = default {
write!(f, " = {default}")?;
}
}
None => f.write_char(',')?,
}
}
Expand Down Expand Up @@ -785,7 +792,7 @@ impl From<TokenType> for VariableDeclarationKind {
}

/// A variable binding
#[derive(Debug, Clone, Display, PartialEq)]
#[derive(Debug, Clone, Display)]
#[display(fmt = "{kind} {name}")]
pub struct VariableBinding {
/// The name/identifier of this variable
Expand Down
6 changes: 4 additions & 2 deletions crates/dash_optimizer/src/type_infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,17 +318,19 @@ impl<'s> TypeInferCtx<'s> {
fn visit_pattern(&mut self, kind: VariableDeclarationKind, pat: &Pattern) {
match *pat {
Pattern::Object { ref fields, rest } => {
for &(id, field, alias) in fields {
for &(id, field, alias, ref default) in fields {
let name = alias.unwrap_or(field);
self.add_local(Binding { id, ident: name }, kind, None);
self.visit_maybe_expr(default.as_ref());
}
if let Some(rest) = rest {
self.add_local(rest, kind, None);
}
}
Pattern::Array { ref fields, rest } => {
for field in fields.iter().flatten().copied() {
for &(field, ref default) in fields.iter().flatten() {
self.add_local(field, kind, None);
self.visit_maybe_expr(default.as_ref());
}
if let Some(rest) = rest {
self.add_local(rest, kind, None);
Expand Down
16 changes: 14 additions & 2 deletions crates/dash_parser/src/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,14 @@ impl<'a, 'interner> Parser<'a, 'interner> {
} else {
None
};
fields.push((self.local_count.inc(), name, alias));

let default = if self.eat(TokenType::Assignment, false).is_some() {
Some(self.parse_expression_no_comma()?)
} else {
None
};

fields.push((self.local_count.inc(), name, alias, default));
}
_ => {
self.error(Error::unexpected_token(cur, TokenType::DUMMY_IDENTIFIER));
Expand Down Expand Up @@ -671,7 +678,12 @@ impl<'a, 'interner> Parser<'a, 'interner> {
other if other.is_identifier() => {
let name = other.as_identifier().unwrap();
self.advance();
fields.push(Some(self.create_binding(name)));
let default = if self.eat(TokenType::Assignment, false).is_some() {
Some(self.parse_expression_no_comma()?)
} else {
None
};
fields.push(Some((self.create_binding(name), default)));
}
_ => {
self.error(Error::unexpected_token(cur, TokenType::DUMMY_IDENTIFIER));
Expand Down
59 changes: 48 additions & 11 deletions crates/dash_vm/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,14 @@ mod extract {
}
}

impl ExtractBack for bool {
type Exception = Infallible;

fn extract(cx: &mut DispatchContext<'_>) -> Result<Self, Self::Exception> {
Ok(cx.fetch_and_inc_ip() == 1)
}
}

/// Convenience function for infallibly extracting a `T`
pub fn extract<T: ExtractBack<Exception = Infallible>>(cx: &mut DispatchContext<'_>) -> T {
match T::extract(cx) {
Expand All @@ -366,12 +374,26 @@ mod extract {
}
}

impl<E, A: ExtractBack<Exception = E>, B: ExtractBack<Exception = E>> ExtractBack for (A, B) {
type Exception = E;
macro_rules! tupl_impl {
($($($param:ident)*),*) => {
$(
impl<E $(, $param : ExtractBack<Exception = E>)*> ExtractBack for ($($param),*) {
type Exception = E;

fn extract(cx: &mut DispatchContext<'_>) -> Result<Self, Self::Exception> {
Ok((A::extract(cx)?, B::extract(cx)?))
}
fn extract(cx: &mut DispatchContext<'_>) -> Result<Self, Self::Exception> {
Ok((
$(
<$param>::extract(cx)?
),*
))
}
}
)*
};
}
tupl_impl! {
A B,
A B C
}

impl ExtractBack for ObjectProperty {
Expand Down Expand Up @@ -2061,13 +2083,20 @@ mod handlers {

let mut idents = Vec::new();

let mut iter = BackwardSequence::<(NumberWConstant, IdentW)>::new_u16(&mut cx);
while let Some((NumberWConstant(id), IdentW(ident))) = iter.next_infallible(&mut cx) {
let mut iter = BackwardSequence::<(bool, NumberWConstant, IdentW)>::new_u16(&mut cx);
while let Some((has_default, NumberWConstant(id), IdentW(ident))) = iter.next_infallible(&mut cx) {
if rest_id.is_some() {
idents.push(ident);
}

let prop = obj.get_property(&mut cx, ident.into())?;
let mut prop = obj.get_property(&mut cx, ident.into())?;
if has_default {
// NB: we need to at least pop it from the stack even if the property exists
let default = cx.pop_stack();
if prop.is_undefined() {
prop = default;
}
}
cx.set_local(id as usize, prop);
}

Expand Down Expand Up @@ -2097,13 +2126,21 @@ mod handlers {
pub fn arraydestruct(mut cx: DispatchContext<'_>) -> Result<Option<HandleResult>, Unrooted> {
let array = cx.pop_stack_rooted();

let mut iter = BackwardSequence::<Option<NumberWConstant>>::new_u16(&mut cx).enumerate();
let mut iter = BackwardSequence::<Option<(bool, NumberWConstant)>>::new_u16(&mut cx).enumerate();

while let Some((i, id)) = iter.next_infallible(&mut cx) {
if let Some(NumberWConstant(id)) = id {
if let Some((has_default, NumberWConstant(id))) = id {
let id = id as usize;
let key = cx.scope.intern_usize(i);
let prop = array.get_property(&mut cx.scope, key.into())?;
let mut prop = array.get_property(&mut cx.scope, key.into())?;

if has_default {
// NB: we need to at least pop it from the stack even if the property exists
let default = cx.pop_stack();
if prop.is_undefined() {
prop = default;
}
}
cx.set_local(id, prop);
}
}
Expand Down
6 changes: 6 additions & 0 deletions crates/dash_vm/src/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,12 @@ impl Unrooted {
Self { value }
}

/// Checks if this value is undefined.
/// As this does not require access to any garbage collected resources, this operation is safe without rooting.
pub fn is_undefined(self) -> bool {
matches!(self.value.unpack(), ValueKind::Undefined(_))
}

/// Returns an unprotected, unrooted reference to the value.
///
/// # Safety
Expand Down

0 comments on commit f9c2008

Please sign in to comment.