From d5d98b67a83944d72a5c0f8e8e2f43aeee667122 Mon Sep 17 00:00:00 2001 From: Marmare314 <49279081+Marmare314@users.noreply.github.com> Date: Tue, 25 Apr 2023 11:22:12 +0200 Subject: [PATCH] Destructuring assign (#703) --- src/eval/func.rs | 4 +- src/eval/mod.rs | 233 ++++++++++++++++++----------- src/ide/highlight.rs | 1 + src/syntax/ast.rs | 167 +++++++++++++-------- src/syntax/kind.rs | 3 + src/syntax/parser.rs | 45 ++++-- tests/typ/compiler/let.typ | 10 +- tests/typ/compiler/ops-invalid.typ | 4 + tests/typ/compiler/ops.typ | 42 ++++++ 9 files changed, 342 insertions(+), 167 deletions(-) diff --git a/src/eval/func.rs b/src/eval/func.rs index 489527efb..e3b6a99ce 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -445,7 +445,9 @@ impl<'a> CapturesVisitor<'a> { match param { ast::Param::Pos(ident) => self.bind(ident), ast::Param::Named(named) => self.bind(named.name()), - ast::Param::Sink(Some(ident)) => self.bind(ident), + ast::Param::Sink(spread) => { + self.bind(spread.name().unwrap_or_default()) + } _ => {} } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 850a3d32f..d8f49d661 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -456,6 +456,7 @@ impl Eval for ast::Expr { Self::Unary(v) => v.eval(vm), Self::Binary(v) => v.eval(vm), Self::Let(v) => v.eval(vm), + Self::DestructAssign(v) => v.eval(vm), Self::Set(_) => bail!(forbidden("set")), Self::Show(_) => bail!(forbidden("show")), Self::Conditional(v) => v.eval(vm), @@ -1212,8 +1213,8 @@ impl Eval for ast::Closure { ast::Param::Named(named) => { params.push(Param::Named(named.name(), named.expr().eval(vm)?)); } - ast::Param::Sink(name) => params.push(Param::Sink(name)), - ast::Param::Placeholder => params.push(Param::Placeholder), + ast::Param::Sink(spread) => params.push(Param::Sink(spread.name())), + ast::Param::Placeholder(_) => params.push(Param::Placeholder), } } @@ -1231,97 +1232,141 @@ impl Eval for ast::Closure { } impl ast::Pattern { - // Destruct the given value into the pattern. - #[tracing::instrument(skip_all)] - pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult { - match self { - ast::Pattern::Ident(ident) => { - vm.define(ident.clone(), value); - Ok(Value::None) - } - ast::Pattern::Placeholder => Ok(Value::None), - ast::Pattern::Destructuring(destruct) => { - match value { - Value::Array(value) => { - let mut i = 0; - for p in destruct.bindings() { - match p { - ast::DestructuringKind::Ident(ident) => { - let Ok(v) = value.at(i) else { - bail!(ident.span(), "not enough elements to destructure"); - }; - vm.define(ident.clone(), v.clone()); - i += 1; - } - ast::DestructuringKind::Sink(ident) => { - (1 + value.len() as usize).checked_sub(destruct.bindings().count()).and_then(|sink_size| { - let Ok(sink) = value.slice(i, Some(i + sink_size as i64)) else { - return None; - }; - if let Some(ident) = ident { - vm.define(ident, sink); - } - i += sink_size as i64; - Some(()) - }).ok_or("not enough elements to destructure").at(self.span())?; - } - ast::DestructuringKind::Named(key, _) => { - bail!( - key.span(), - "cannot destructure named elements from an array" - ) - } - ast::DestructuringKind::Placeholder => i += 1, - } - } - if i < value.len() { - bail!(self.span(), "too many elements to destructure"); - } - } - Value::Dict(value) => { - let mut sink = None; - let mut used = HashSet::new(); - for p in destruct.bindings() { - match p { - ast::DestructuringKind::Ident(ident) => { - let Ok(v) = value.at(&ident) else { - bail!(ident.span(), "destructuring key not found in dictionary"); - }; - vm.define(ident.clone(), v.clone()); - used.insert(ident.clone().take()); - } - ast::DestructuringKind::Sink(ident) => { - sink = ident.clone() - } - ast::DestructuringKind::Named(key, ident) => { - let Ok(v) = value.at(&key) else { - bail!(ident.span(), "destructuring key not found in dictionary"); - }; - vm.define(ident.clone(), v.clone()); - used.insert(key.clone().take()); - } - ast::DestructuringKind::Placeholder => {} - } - } + fn destruct_array( + &self, + vm: &mut Vm, + value: Array, + f: T, + destruct: &ast::Destructuring, + ) -> SourceResult + where + T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult, + { + let mut i = 0; + for p in destruct.bindings() { + match p { + ast::DestructuringKind::Normal(expr) => { + let Ok(v) = value.at(i) else { + bail!(expr.span(), "not enough elements to destructure"); + }; + f(vm, expr, v.clone())?; + i += 1; + } + ast::DestructuringKind::Sink(spread) => { + let sink_size = (1 + value.len() as usize) + .checked_sub(destruct.bindings().count()); + let sink = + sink_size.and_then(|s| value.slice(i, Some(i + s as i64)).ok()); - if let Some(ident) = sink { - let mut sink = Dict::new(); - for (key, value) in value { - if !used.contains(key.as_str()) { - sink.insert(key, value); - } - } - vm.define(ident, Value::Dict(sink)); + if let (Some(sink_size), Some(sink)) = (sink_size, sink) { + if let Some(expr) = spread.expr() { + f(vm, expr, Value::Array(sink.clone()))?; } - } - _ => { - bail!(self.span(), "cannot destructure {}", value.type_name()); + i += sink_size as i64; + } else { + bail!(self.span(), "not enough elements to destructure") } } - - Ok(Value::None) + ast::DestructuringKind::Named(named) => { + bail!(named.span(), "cannot destructure named elements from an array") + } + ast::DestructuringKind::Placeholder(_) => i += 1, } } + if i < value.len() { + bail!(self.span(), "too many elements to destructure"); + } + + Ok(Value::None) + } + + fn destruct_dict( + &self, + vm: &mut Vm, + value: Dict, + f: T, + destruct: &ast::Destructuring, + ) -> SourceResult + where + T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult, + { + let mut sink = None; + let mut used = HashSet::new(); + for p in destruct.bindings() { + match p { + ast::DestructuringKind::Normal(ast::Expr::Ident(ident)) => { + let Ok(v) = value.at(&ident) else { + bail!(ident.span(), "destructuring key not found in dictionary"); + }; + f(vm, ast::Expr::Ident(ident.clone()), v.clone())?; + used.insert(ident.take()); + } + ast::DestructuringKind::Sink(spread) => sink = spread.expr(), + ast::DestructuringKind::Named(named) => { + let Ok(v) = value.at(named.name().as_str()) else { + bail!(named.name().span(), "destructuring key not found in dictionary"); + }; + f(vm, named.expr(), v.clone())?; + used.insert(named.name().take()); + } + ast::DestructuringKind::Placeholder(_) => {} + ast::DestructuringKind::Normal(expr) => { + bail!(expr.span(), "expected key, found expression"); + } + } + } + + if let Some(expr) = sink { + let mut sink = Dict::new(); + for (key, value) in value { + if !used.contains(key.as_str()) { + sink.insert(key, value); + } + } + f(vm, expr, Value::Dict(sink))?; + } + + Ok(Value::None) + } + + /// Destruct the given value into the pattern and apply the function to each binding. + #[tracing::instrument(skip_all)] + fn apply(&self, vm: &mut Vm, value: Value, f: T) -> SourceResult + where + T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult, + { + match self { + ast::Pattern::Normal(expr) => { + f(vm, expr.clone(), value)?; + Ok(Value::None) + } + ast::Pattern::Placeholder(_) => Ok(Value::None), + ast::Pattern::Destructuring(destruct) => match value { + Value::Array(value) => self.destruct_array(vm, value, f, destruct), + Value::Dict(value) => self.destruct_dict(vm, value, f, destruct), + _ => bail!(self.span(), "cannot destructure {}", value.type_name()), + }, + } + } + + /// Destruct the value into the pattern by binding. + pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult { + self.apply(vm, value, |vm, expr, value| match expr { + ast::Expr::Ident(ident) => { + vm.define(ident, value); + Ok(Value::None) + } + _ => unreachable!(), + }) + } + + /// Destruct the value into the pattern by assignment. + pub fn assign(&self, vm: &mut Vm, value: Value) -> SourceResult { + self.apply(vm, value, |vm, expr, value| { + let location = expr.access(vm)?; + *location = value; + Ok(Value::None) + }) } } @@ -1345,6 +1390,16 @@ impl Eval for ast::LetBinding { } } +impl Eval for ast::DestructAssignment { + type Output = Value; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + let value = self.value().eval(vm)?; + self.pattern().assign(vm, value)?; + Ok(Value::None) + } +} + impl Eval for ast::SetRule { type Output = Styles; @@ -1514,7 +1569,7 @@ impl Eval for ast::ForLoop { let pattern = self.pattern(); match (&pattern, iter.clone()) { - (ast::Pattern::Ident(_), Value::Str(string)) => { + (ast::Pattern::Normal(_), Value::Str(string)) => { // Iterate over graphemes of string. iter!(for pattern in string.as_str().graphemes(true)); } @@ -1526,7 +1581,7 @@ impl Eval for ast::ForLoop { // Iterate over values of array. iter!(for pattern in array); } - (ast::Pattern::Ident(_), _) => { + (ast::Pattern::Normal(_), _) => { bail!(self.iter().span(), "cannot loop over {}", iter.type_name()); } (_, _) => { diff --git a/src/ide/highlight.rs b/src/ide/highlight.rs index 259d34c36..b7b063a6e 100644 --- a/src/ide/highlight.rs +++ b/src/ide/highlight.rs @@ -246,6 +246,7 @@ pub fn highlight(node: &LinkedNode) -> Option { SyntaxKind::LoopContinue => None, SyntaxKind::FuncReturn => None, SyntaxKind::Destructuring => None, + SyntaxKind::DestructAssignment => None, SyntaxKind::LineComment => Some(Tag::Comment), SyntaxKind::BlockComment => Some(Tag::Comment), diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index f22508e82..1fa6c89fb 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -164,6 +164,8 @@ pub enum Expr { Closure(Closure), /// A let binding: `let x = 1`. Let(LetBinding), + //// A destructuring assignment: `(x, y) = (1, 2)`. + DestructAssign(DestructAssignment), /// A set rule: `set text(...)`. Set(SetRule), /// A show rule: `show heading: it => emph(it.body)`. @@ -240,6 +242,7 @@ impl AstNode for Expr { SyntaxKind::FuncCall => node.cast().map(Self::FuncCall), SyntaxKind::Closure => node.cast().map(Self::Closure), SyntaxKind::LetBinding => node.cast().map(Self::Let), + SyntaxKind::DestructAssignment => node.cast().map(Self::DestructAssign), SyntaxKind::SetRule => node.cast().map(Self::Set), SyntaxKind::ShowRule => node.cast().map(Self::Show), SyntaxKind::Conditional => node.cast().map(Self::Conditional), @@ -299,6 +302,7 @@ impl AstNode for Expr { Self::FuncCall(v) => v.as_untyped(), Self::Closure(v) => v.as_untyped(), Self::Let(v) => v.as_untyped(), + Self::DestructAssign(v) => v.as_untyped(), Self::Set(v) => v.as_untyped(), Self::Show(v) => v.as_untyped(), Self::Conditional(v) => v.as_untyped(), @@ -1179,6 +1183,11 @@ impl Named { pub fn expr(&self) -> Expr { self.0.cast_last_match().unwrap_or_default() } + + /// The right-hand side of the pair as an identifier. + pub fn expr_ident(&self) -> Option { + self.0.cast_last_match() + } } node! { @@ -1559,6 +1568,28 @@ impl Params { } } +node! { + /// A spread: `..x` or `..x.at(0)`. + Spread +} + +impl Spread { + /// Try to get an identifier. + pub fn name(&self) -> Option { + self.0.cast_first_match() + } + + /// Try to get an expression. + pub fn expr(&self) -> Option { + self.0.cast_first_match() + } +} + +node! { + /// An underscore: `_` + Underscore +} + /// A parameter to a closure. #[derive(Debug, Clone, Hash)] pub enum Param { @@ -1567,9 +1598,9 @@ pub enum Param { /// A named parameter with a default value: `draw: false`. Named(Named), /// An argument sink: `..args`. - Sink(Option), + Sink(Spread), /// A placeholder: `_`. - Placeholder, + Placeholder(Underscore), } impl AstNode for Param { @@ -1577,8 +1608,8 @@ impl AstNode for Param { match node.kind() { SyntaxKind::Ident => node.cast().map(Self::Pos), SyntaxKind::Named => node.cast().map(Self::Named), - SyntaxKind::Spread => Some(Self::Sink(node.cast_first_match())), - SyntaxKind::Underscore => Some(Self::Placeholder), + SyntaxKind::Spread => node.cast().map(Self::Sink), + SyntaxKind::Underscore => node.cast().map(Self::Placeholder), _ => Option::None, } } @@ -1587,8 +1618,8 @@ impl AstNode for Param { match self { Self::Pos(v) => v.as_untyped(), Self::Named(v) => v.as_untyped(), - Self::Sink(_) => self.as_untyped(), - Self::Placeholder => self.as_untyped(), + Self::Sink(v) => v.as_untyped(), + Self::Placeholder(v) => v.as_untyped(), } } } @@ -1598,56 +1629,63 @@ node! { Destructuring } -/// The kind of an element in a destructuring pattern. -#[derive(Debug, Clone, Hash)] -pub enum DestructuringKind { - /// An identifier: `x`. - Ident(Ident), - /// An argument sink: `..y`. - Sink(Option), - /// Named arguments: `x: 1`. - Named(Ident, Ident), - /// A placeholder: `_`. - Placeholder, -} - impl Destructuring { /// The bindings of the destructuring. pub fn bindings(&self) -> impl Iterator + '_ { - self.0.children().filter_map(|child| match child.kind() { - SyntaxKind::Ident => { - Some(DestructuringKind::Ident(child.cast().unwrap_or_default())) - } - SyntaxKind::Spread => Some(DestructuringKind::Sink(child.cast_first_match())), - SyntaxKind::Named => { - let mut filtered = child.children().filter_map(SyntaxNode::cast); - let key = filtered.next().unwrap_or_default(); - let ident = filtered.next().unwrap_or_default(); - Some(DestructuringKind::Named(key, ident)) - } - SyntaxKind::Underscore => Some(DestructuringKind::Placeholder), - _ => Option::None, - }) + self.0.children().filter_map(SyntaxNode::cast) } // Returns a list of all identifiers in the pattern. pub fn idents(&self) -> impl Iterator + '_ { self.bindings().filter_map(|binding| match binding { - DestructuringKind::Ident(ident) => Some(ident), - DestructuringKind::Sink(ident) => ident, - DestructuringKind::Named(_, ident) => Some(ident), - DestructuringKind::Placeholder => Option::None, + DestructuringKind::Normal(Expr::Ident(ident)) => Some(ident), + DestructuringKind::Sink(spread) => spread.name(), + DestructuringKind::Named(named) => named.expr_ident(), + _ => Option::None, }) } } +/// The kind of an element in a destructuring pattern. +#[derive(Debug, Clone, Hash)] +pub enum DestructuringKind { + /// An expression: `x`. + Normal(Expr), + /// An argument sink: `..y`. + Sink(Spread), + /// Named arguments: `x: 1`. + Named(Named), + /// A placeholder: `_`. + Placeholder(Underscore), +} + +impl AstNode for DestructuringKind { + fn from_untyped(node: &SyntaxNode) -> Option { + match node.kind() { + SyntaxKind::Named => node.cast().map(Self::Named), + SyntaxKind::Spread => node.cast().map(Self::Sink), + SyntaxKind::Underscore => node.cast().map(Self::Placeholder), + _ => node.cast().map(Self::Normal), + } + } + + fn as_untyped(&self) -> &SyntaxNode { + match self { + Self::Normal(v) => v.as_untyped(), + Self::Named(v) => v.as_untyped(), + Self::Sink(v) => v.as_untyped(), + Self::Placeholder(v) => v.as_untyped(), + } + } +} + /// The kind of a pattern. #[derive(Debug, Clone, Hash)] pub enum Pattern { - /// A single identifier: `x`. - Ident(Ident), + /// A single expression: `x`. + Normal(Expr), /// A placeholder: `_`. - Placeholder, + Placeholder(Underscore), /// A destructuring pattern: `(x, _, ..y)`. Destructuring(Destructuring), } @@ -1655,18 +1693,17 @@ pub enum Pattern { impl AstNode for Pattern { fn from_untyped(node: &SyntaxNode) -> Option { match node.kind() { - SyntaxKind::Ident => node.cast().map(Self::Ident), SyntaxKind::Destructuring => node.cast().map(Self::Destructuring), - SyntaxKind::Underscore => Some(Self::Placeholder), - _ => Option::None, + SyntaxKind::Underscore => node.cast().map(Self::Placeholder), + _ => node.cast().map(Self::Normal), } } fn as_untyped(&self) -> &SyntaxNode { match self { - Self::Ident(v) => v.as_untyped(), + Self::Normal(v) => v.as_untyped(), Self::Destructuring(v) => v.as_untyped(), - Self::Placeholder => self.as_untyped(), + Self::Placeholder(v) => v.as_untyped(), } } } @@ -1675,16 +1712,16 @@ impl Pattern { // Returns a list of all identifiers in the pattern. pub fn idents(&self) -> Vec { match self { - Pattern::Ident(ident) => vec![ident.clone()], + Pattern::Normal(Expr::Ident(ident)) => vec![ident.clone()], Pattern::Destructuring(destruct) => destruct.idents().collect(), - Pattern::Placeholder => vec![], + _ => vec![], } } } impl Default for Pattern { fn default() -> Self { - Self::Ident(Ident::default()) + Self::Normal(Expr::default()) } } @@ -1716,23 +1753,18 @@ impl LetBindingKind { impl LetBinding { /// The kind of the let binding. pub fn kind(&self) -> LetBindingKind { - if let Some(pattern) = self.0.cast_first_match::() { - LetBindingKind::Normal(pattern) - } else { - LetBindingKind::Closure( - self.0 - .cast_first_match::() - .unwrap_or_default() - .name() - .unwrap_or_default(), - ) + match self.0.cast_first_match::() { + Some(Pattern::Normal(Expr::Closure(closure))) => { + LetBindingKind::Closure(closure.name().unwrap_or_default()) + } + pattern => LetBindingKind::Normal(pattern.unwrap_or_default()), } } /// The expression the binding is initialized with. pub fn init(&self) -> Option { match self.kind() { - LetBindingKind::Normal(Pattern::Ident(_)) => { + LetBindingKind::Normal(Pattern::Normal(_)) => { self.0.children().filter_map(SyntaxNode::cast).nth(1) } LetBindingKind::Normal(_) => self.0.cast_first_match(), @@ -1741,6 +1773,23 @@ impl LetBinding { } } +node! { + /// An assignment expression `(x, y) = (1, 2)`. + DestructAssignment +} + +impl DestructAssignment { + /// The pattern of the assignment. + pub fn pattern(&self) -> Pattern { + self.0.cast_first_match::().unwrap_or_default() + } + + /// The expression that is assigned. + pub fn value(&self) -> Expr { + self.0.cast_last_match().unwrap_or_default() + } +} + node! { /// A set rule: `set text(...)`. SetRule diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index d35901b09..0717e16c4 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -244,6 +244,8 @@ pub enum SyntaxKind { FuncReturn, /// A destructuring pattern: `(x, _, ..y)`. Destructuring, + /// A destructuring assignment expression: `(x, y) = (1, 2)`. + DestructAssignment, /// A line comment: `// ...`. LineComment, @@ -430,6 +432,7 @@ impl SyntaxKind { Self::LoopContinue => "`continue` expression", Self::FuncReturn => "`return` expression", Self::Destructuring => "destructuring pattern", + Self::DestructAssignment => "destructuring assignment expression", Self::LineComment => "line comment", Self::BlockComment => "block comment", Self::Error => "syntax error", diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 389cf0269..0bbf77e24 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -725,7 +725,16 @@ fn with_paren(p: &mut Parser) { p.assert(SyntaxKind::Arrow); code_expr(p); kind = SyntaxKind::Closure; + } else if p.at(SyntaxKind::Eq) && kind != SyntaxKind::Parenthesized { + // TODO: add warning if p.at(SyntaxKind::Eq) && kind == SyntaxKind::Parenthesized + + validate_destruct_pattern(p, m, false); + p.wrap(m, SyntaxKind::Destructuring); + p.assert(SyntaxKind::Eq); + code_expr(p); + kind = SyntaxKind::DestructAssignment; } + match kind { SyntaxKind::Array => validate_array(p, m), SyntaxKind::Dict => validate_dict(p, m), @@ -866,7 +875,7 @@ fn pattern(p: &mut Parser) -> PatternKind { let m = p.marker(); if p.at(SyntaxKind::LeftParen) { let kind = collection(p, false); - validate_destruct_pattern(p, m); + validate_destruct_pattern(p, m, true); if kind == SyntaxKind::Parenthesized { PatternKind::Ident @@ -1184,7 +1193,7 @@ fn validate_args(p: &mut Parser, m: Marker) { } } -fn validate_destruct_pattern(p: &mut Parser, m: Marker) { +fn validate_destruct_pattern(p: &mut Parser, m: Marker, forbid_expressions: bool) { let mut used_spread = false; let mut used = HashSet::new(); for child in p.post_process(m) { @@ -1206,7 +1215,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) { if within.kind() == SyntaxKind::Dots { continue; - } else if within.kind() != SyntaxKind::Ident { + } else if forbid_expressions && within.kind() != SyntaxKind::Ident { within.convert_to_error(eco_format!( "expected identifier, found {}", within.kind().name(), @@ -1231,15 +1240,17 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) { child.make_erroneous(); } - let Some(within) = child.children_mut().last_mut() else { return }; - if within.kind() != SyntaxKind::Ident - && within.kind() != SyntaxKind::Underscore - { - within.convert_to_error(eco_format!( - "expected identifier, found {}", - within.kind().name(), - )); - child.make_erroneous(); + if forbid_expressions { + let Some(within) = child.children_mut().last_mut() else { return }; + if within.kind() != SyntaxKind::Ident + && within.kind() != SyntaxKind::Underscore + { + within.convert_to_error(eco_format!( + "expected identifier, found {}", + within.kind().name(), + )); + child.make_erroneous(); + } } } SyntaxKind::LeftParen @@ -1247,10 +1258,12 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) { | SyntaxKind::Comma | SyntaxKind::Underscore => {} kind => { - child.convert_to_error(eco_format!( - "expected identifier or destructuring sink, found {}", - kind.name() - )); + if forbid_expressions { + child.convert_to_error(eco_format!( + "expected identifier or destructuring sink, found {}", + kind.name() + )); + } } } } diff --git a/tests/typ/compiler/let.typ b/tests/typ/compiler/let.typ index 160d21479..7081bcf05 100644 --- a/tests/typ/compiler/let.typ +++ b/tests/typ/compiler/let.typ @@ -121,6 +121,12 @@ Three // Error: 13-14 at most one binding per identifier is allowed #let (a: a, a) = (a: 1, b: 2) +// Error: 13-20 expected identifier, found function call +#let (a, b: b.at(0)) = (a: 1, b: 2) + +// Error: 7-14 expected identifier or destructuring sink, found function call +#let (a.at(0),) = (1,) + --- // Error: 13-14 not enough elements to destructure #let (a, b, c) = (1, 2) @@ -181,11 +187,11 @@ Three #let (a, b) = (a: 1) --- -// Error: 13-14 destructuring key not found in dictionary +// Error: 10-11 destructuring key not found in dictionary #let (a, b: b) = (a: 1) --- -// Error: 7-8 cannot destructure named elements from an array +// Error: 7-11 cannot destructure named elements from an array #let (a: a, b) = (1, 2, 3) --- diff --git a/tests/typ/compiler/ops-invalid.typ b/tests/typ/compiler/ops-invalid.typ index 3ab629296..2af3b767e 100644 --- a/tests/typ/compiler/ops-invalid.typ +++ b/tests/typ/compiler/ops-invalid.typ @@ -100,6 +100,10 @@ // Error: 4-5 unknown variable: x #((x) = "") +--- +// Error: 4-5 unknown variable: x +#((x,) = (1,)) + --- // Error: 3-8 cannot mutate a temporary value #(1 + 2 += 3) diff --git a/tests/typ/compiler/ops.typ b/tests/typ/compiler/ops.typ index a6c64cbd6..2bb06e4d3 100644 --- a/tests/typ/compiler/ops.typ +++ b/tests/typ/compiler/ops.typ @@ -200,6 +200,48 @@ #(x = "some") #test(x, "some") #(x += "thing") #test(x, "something") +--- +// Test destructuring assignments. + +#let a = none +#let b = none +#let c = none +#((a,) = (1,)) +#test(a, 1) + +#((_, a, b, _) = (1, 2, 3, 4)) +#test(a, 2) +#test(b, 3) + +#((a, b, ..c) = (1, 2, 3, 4, 5, 6)) +#test(a, 1) +#test(b, 2) +#test(c, (3, 4, 5, 6)) + +#((a: a, b, x: c) = (a: 1, b: 2, x: 3)) +#test(a, 1) +#test(b, 2) +#test(c, 3) + +#let a = (1, 2) +#((a: a.at(0), b) = (a: 3, b: 4)) +#test(a, (3, 2)) +#test(b, 4) + +#let a = (1, 2) +#((a.at(0), b) = (3, 4)) +#test(a, (3, 2)) +#test(b, 4) + +#((a, ..b) = (1, 2, 3, 4)) +#test(a, 1) +#test(b, (2, 3, 4)) + +#let a = (1, 2) +#((b, ..a.at(0)) = (1, 2, 3, 4)) +#test(a, ((2, 3, 4), 2)) +#test(b, 1) + --- // Error: 3-6 cannot mutate a constant: box #(box = 1)