diff --git a/src/eval/func.rs b/src/eval/func.rs index e3b6a99ce..a6e0de84d 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -276,13 +276,11 @@ pub(super) struct Closure { #[derive(Hash)] pub enum Param { /// A positional parameter: `x`. - Pos(Ident), + Pos(ast::Pattern), /// A named parameter with a default value: `draw: false`. Named(Ident, Value), /// An argument sink: `..args`. Sink(Option), - /// A placeholder: `_`. - Placeholder, } impl Closure { @@ -330,9 +328,18 @@ impl Closure { let mut sink_pos_values = None; for p in &closure.params { match p { - Param::Pos(ident) => { - vm.define(ident.clone(), args.expect::(ident)?); - } + Param::Pos(pattern) => match pattern { + ast::Pattern::Normal(ast::Expr::Ident(ident)) => { + vm.define(ident.clone(), args.expect::(ident)?) + } + ast::Pattern::Normal(_) => unreachable!(), + _ => { + pattern.define( + &mut vm, + args.expect::("pattern parameter")?, + )?; + } + }, Param::Sink(ident) => { sink = ident.clone(); if let Some(sink_size) = sink_size { @@ -344,9 +351,6 @@ impl Closure { args.named::(ident)?.unwrap_or_else(|| default.clone()); vm.define(ident.clone(), value); } - Param::Placeholder => { - args.eat::()?; - } } } @@ -443,12 +447,15 @@ impl<'a> CapturesVisitor<'a> { for param in expr.params().children() { match param { - ast::Param::Pos(ident) => self.bind(ident), + ast::Param::Pos(pattern) => { + for ident in pattern.idents() { + self.bind(ident); + } + } ast::Param::Named(named) => self.bind(named.name()), ast::Param::Sink(spread) => { self.bind(spread.name().unwrap_or_default()) } - _ => {} } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index d2ca0e74e..e8e1af51b 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1207,14 +1207,11 @@ impl Eval for ast::Closure { let mut params = Vec::new(); for param in self.params().children() { match param { - ast::Param::Pos(name) => { - params.push(Param::Pos(name)); - } + ast::Param::Pos(pattern) => params.push(Param::Pos(pattern)), ast::Param::Named(named) => { params.push(Param::Named(named.name(), named.expr().eval(vm)?)); } ast::Param::Sink(spread) => params.push(Param::Sink(spread.name())), - ast::Param::Placeholder(_) => params.push(Param::Placeholder), } } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 1fa6c89fb..53e92949b 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -1594,23 +1594,19 @@ node! { #[derive(Debug, Clone, Hash)] pub enum Param { /// A positional parameter: `x`. - Pos(Ident), + Pos(Pattern), /// A named parameter with a default value: `draw: false`. Named(Named), /// An argument sink: `..args`. Sink(Spread), - /// A placeholder: `_`. - Placeholder(Underscore), } impl AstNode for Param { fn from_untyped(node: &SyntaxNode) -> Option { match node.kind() { - SyntaxKind::Ident => node.cast().map(Self::Pos), SyntaxKind::Named => node.cast().map(Self::Named), SyntaxKind::Spread => node.cast().map(Self::Sink), - SyntaxKind::Underscore => node.cast().map(Self::Placeholder), - _ => Option::None, + _ => node.cast().map(Self::Pos), } } @@ -1619,7 +1615,6 @@ impl AstNode for Param { Self::Pos(v) => v.as_untyped(), Self::Named(v) => v.as_untyped(), Self::Sink(v) => v.as_untyped(), - Self::Placeholder(v) => v.as_untyped(), } } } diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 0bbf77e24..ab73479e9 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -523,7 +523,11 @@ fn code(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { } fn code_expr(p: &mut Parser) { - code_expr_prec(p, false, 0) + code_expr_prec(p, false, 0, false) +} + +fn code_expr_or_pattern(p: &mut Parser) { + code_expr_prec(p, false, 0, true) } fn embedded_code_expr(p: &mut Parser) { @@ -542,7 +546,7 @@ fn embedded_code_expr(p: &mut Parser) { ); let prev = p.prev_end(); - code_expr_prec(p, true, 0); + code_expr_prec(p, true, 0, false); // Consume error for things like `#12p` or `#"abc\"`. if !p.progress(prev) { @@ -560,14 +564,19 @@ fn embedded_code_expr(p: &mut Parser) { p.unstop(); } -fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) { +fn code_expr_prec( + p: &mut Parser, + atomic: bool, + min_prec: usize, + allow_destructuring: bool, +) { let m = p.marker(); if let (false, Some(op)) = (atomic, ast::UnOp::from_kind(p.current())) { p.eat(); - code_expr_prec(p, atomic, op.precedence()); + code_expr_prec(p, atomic, op.precedence(), false); p.wrap(m, SyntaxKind::Unary); } else { - code_primary(p, atomic); + code_primary(p, atomic, allow_destructuring); } loop { @@ -615,7 +624,7 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) { } p.eat(); - code_expr_prec(p, false, prec); + code_expr_prec(p, false, prec, false); p.wrap(m, SyntaxKind::Binary); continue; } @@ -624,7 +633,7 @@ fn code_expr_prec(p: &mut Parser, atomic: bool, min_prec: usize) { } } -fn code_primary(p: &mut Parser, atomic: bool) { +fn code_primary(p: &mut Parser, atomic: bool, allow_destructuring: bool) { let m = p.marker(); match p.current() { SyntaxKind::Ident => { @@ -650,7 +659,7 @@ fn code_primary(p: &mut Parser, atomic: bool) { SyntaxKind::LeftBrace => code_block(p), SyntaxKind::LeftBracket => content_block(p), - SyntaxKind::LeftParen => with_paren(p), + SyntaxKind::LeftParen => with_paren(p, allow_destructuring), SyntaxKind::Dollar => equation(p), SyntaxKind::Let => let_binding(p), SyntaxKind::Set => set_rule(p), @@ -716,7 +725,7 @@ fn content_block(p: &mut Parser) { p.wrap(m, SyntaxKind::ContentBlock); } -fn with_paren(p: &mut Parser) { +fn with_paren(p: &mut Parser, allow_destructuring: bool) { let m = p.marker(); let mut kind = collection(p, true); if p.at(SyntaxKind::Arrow) { @@ -739,11 +748,37 @@ fn with_paren(p: &mut Parser) { SyntaxKind::Array => validate_array(p, m), SyntaxKind::Dict => validate_dict(p, m), SyntaxKind::Parenthesized => validate_parenthesized(p, m), + SyntaxKind::Destructuring if !allow_destructuring => { + invalidate_destructuring(p, m) + } _ => {} } p.wrap(m, kind); } +fn invalidate_destructuring(p: &mut Parser, m: Marker) { + let mut collection_kind = Option::None; + for child in p.post_process(m) { + match child.kind() { + SyntaxKind::Named | SyntaxKind::Keyed => match collection_kind { + Some(SyntaxKind::Array) => child.convert_to_error(eco_format!( + "expected expression, found {}", + child.kind().name() + )), + _ => collection_kind = Some(SyntaxKind::Dict), + }, + SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {} + kind => match collection_kind { + Some(SyntaxKind::Dict) => child.convert_to_error(eco_format!( + "expected named or keyed pair, found {}", + kind.name() + )), + _ => collection_kind = Some(SyntaxKind::Array), + }, + } + } +} + fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind { p.stop_at_newline(false); p.assert(SyntaxKind::LeftParen); @@ -760,10 +795,17 @@ fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind { let prev = p.prev_end(); match item(p, keyed) { SyntaxKind::Spread => parenthesized = false, - SyntaxKind::Named | SyntaxKind::Keyed if kind.is_none() => { - kind = Some(SyntaxKind::Dict); + SyntaxKind::Named | SyntaxKind::Keyed => { + match kind { + Some(SyntaxKind::Array) => kind = Some(SyntaxKind::Destructuring), + _ => kind = Some(SyntaxKind::Dict), + } parenthesized = false; } + SyntaxKind::Int => match kind { + Some(SyntaxKind::Array) | None => kind = Some(SyntaxKind::Array), + Some(_) => kind = Some(SyntaxKind::Destructuring), + }, _ if kind.is_none() => kind = Some(SyntaxKind::Array), _ => {} } @@ -809,7 +851,7 @@ fn item(p: &mut Parser, keyed: bool) -> SyntaxKind { } if !p.eat_if(SyntaxKind::Underscore) { - code_expr(p); + code_expr_or_pattern(p); } else { return SyntaxKind::Underscore; } @@ -1161,6 +1203,10 @@ fn validate_params(p: &mut Parser, m: Marker) { child.make_erroneous(); } } + SyntaxKind::Array | SyntaxKind::Dict | SyntaxKind::Destructuring => { + validate_pattern(child.children_mut().iter_mut(), &mut used, false); + child.convert_to_kind(SyntaxKind::Destructuring); + } SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma @@ -1194,9 +1240,17 @@ fn validate_args(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) { + validate_pattern(p.post_process(m), &mut used, forbid_expressions); +} + +fn validate_pattern<'a>( + children: impl Iterator, + used: &mut HashSet, + forbid_expressions: bool, +) { + let mut used_spread = false; + for child in children { match child.kind() { SyntaxKind::Ident => { if !used.insert(child.text().clone()) { diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ index 2d12a2bc1..0f6809b5b 100644 --- a/tests/typ/compiler/closure.typ +++ b/tests/typ/compiler/closure.typ @@ -145,6 +145,29 @@ test(greet("Typst", whatever: 10)) } +--- +// Parameter unpacking. +#let f((a, b), ..c) = (a, b, c) +#test(f((1, 2), 3, 4), (1, 2, (3, 4))) + +#let f((k: a, b), c: 3, (d,)) = (a, b, c, d) +#test(f((k: 1, b: 2), (4,)), (1, 2, 3, 4)) + +// Error: 22-23 duplicate parameter: a +#let f((a: b), (c,), a) = none + +// Error: 8-14 expected identifier, found array +#let f((a, b): 0) = none + +// Error: 10-19 expected identifier, found destructuring pattern +#let f(..(a, b: c)) = none + +// Error: 10-16 expected identifier, found array +#let f(..(a, b)) = none + +// Error: 10-19 expected identifier, found destructuring pattern +#let f(..(a, b: c)) = none + --- // Error: 11-12 duplicate parameter: x #let f(x, x) = none