Skip to content

Commit 0105eb7

Browse files
authored
Fix function sinks (typst#638)
1 parent d1cd814 commit 0105eb7

File tree

7 files changed

+111
-36
lines changed

7 files changed

+111
-36
lines changed

src/eval/args.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ impl Args {
6161
Ok(None)
6262
}
6363

64+
/// Consume n positional arguments if possible.
65+
pub fn consume(&mut self, n: usize) -> SourceResult<EcoVec<Arg>> {
66+
if n > self.items.len() {
67+
bail!(self.span, "not enough arguments");
68+
}
69+
let vec = self.items.to_vec();
70+
let (left, right) = vec.split_at(n);
71+
self.items = right.into();
72+
return Ok(left.into());
73+
}
74+
6475
/// Consume and cast the first positional argument.
6576
///
6677
/// Returns a `missing argument: {what}` error if no positional argument is

src/eval/func.rs

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -260,15 +260,22 @@ pub(super) struct Closure {
260260
pub name: Option<Ident>,
261261
/// Captured values from outer scopes.
262262
pub captured: Scope,
263-
/// The parameter names and default values. Parameters with default value
264-
/// are named parameters.
265-
pub params: Vec<(Ident, Option<Value>)>,
266-
/// The name of an argument sink where remaining arguments are placed.
267-
pub sink: Option<Ident>,
263+
/// The list of parameters.
264+
pub params: Vec<Param>,
268265
/// The expression the closure should evaluate to.
269266
pub body: Expr,
270267
}
271268

269+
#[derive(Hash)]
270+
pub enum Param {
271+
/// A positional parameter: `x`.
272+
Pos(Ident),
273+
/// A named parameter with a default value: `draw: false`.
274+
Named(Ident, Value),
275+
/// An argument sink: `..args`.
276+
Sink(Option<Ident>),
277+
}
278+
272279
impl Closure {
273280
/// Call the function in the context with the arguments.
274281
#[allow(clippy::too_many_arguments)]
@@ -304,21 +311,38 @@ impl Closure {
304311
}
305312

306313
// Parse the arguments according to the parameter list.
307-
for (param, default) in &closure.params {
308-
vm.define(
309-
param.clone(),
310-
match default {
311-
Some(default) => {
312-
args.named::<Value>(param)?.unwrap_or_else(|| default.clone())
314+
let num_pos_params =
315+
closure.params.iter().filter(|p| matches!(p, Param::Pos(_))).count();
316+
let num_pos_args = args.to_pos().len() as usize;
317+
let sink_size = num_pos_args.checked_sub(num_pos_params);
318+
319+
let mut sink = None;
320+
let mut sink_pos_values = None;
321+
for p in &closure.params {
322+
match p {
323+
Param::Pos(ident) => {
324+
vm.define(ident.clone(), args.expect::<Value>(ident)?);
325+
}
326+
Param::Sink(ident) => {
327+
sink = ident.clone();
328+
if let Some(sink_size) = sink_size {
329+
sink_pos_values = Some(args.consume(sink_size)?);
313330
}
314-
None => args.expect::<Value>(param)?,
315-
},
316-
);
331+
}
332+
Param::Named(ident, default) => {
333+
let value =
334+
args.named::<Value>(ident)?.unwrap_or_else(|| default.clone());
335+
vm.define(ident.clone(), value);
336+
}
337+
}
317338
}
318339

319-
// Put the remaining arguments into the sink.
320-
if let Some(sink) = &closure.sink {
321-
vm.define(sink.clone(), args.take());
340+
if let Some(sink) = sink {
341+
let mut remaining_args = args.take();
342+
if let Some(sink_pos_values) = sink_pos_values {
343+
remaining_args.items.extend(sink_pos_values);
344+
}
345+
vm.define(sink, remaining_args);
322346
}
323347

324348
// Ensure all arguments have been used.
@@ -407,7 +431,8 @@ impl<'a> CapturesVisitor<'a> {
407431
match param {
408432
ast::Param::Pos(ident) => self.bind(ident),
409433
ast::Param::Named(named) => self.bind(named.name()),
410-
ast::Param::Sink(ident) => self.bind(ident),
434+
ast::Param::Sink(Some(ident)) => self.bind(ident),
435+
_ => {}
411436
}
412437
}
413438

src/eval/mod.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,24 +1149,17 @@ impl Eval for ast::Closure {
11491149
visitor.finish()
11501150
};
11511151

1152-
let mut params = Vec::new();
1153-
let mut sink = None;
1154-
11551152
// Collect parameters and an optional sink parameter.
1153+
let mut params = Vec::new();
11561154
for param in self.params().children() {
11571155
match param {
11581156
ast::Param::Pos(name) => {
1159-
params.push((name, None));
1157+
params.push(Param::Pos(name));
11601158
}
11611159
ast::Param::Named(named) => {
1162-
params.push((named.name(), Some(named.expr().eval(vm)?)));
1163-
}
1164-
ast::Param::Sink(name) => {
1165-
if sink.is_some() {
1166-
bail!(name.span(), "only one argument sink is allowed");
1167-
}
1168-
sink = Some(name);
1160+
params.push(Param::Named(named.name(), named.expr().eval(vm)?));
11691161
}
1162+
ast::Param::Sink(name) => params.push(Param::Sink(name)),
11701163
}
11711164
}
11721165

@@ -1176,7 +1169,6 @@ impl Eval for ast::Closure {
11761169
name,
11771170
captured,
11781171
params,
1179-
sink,
11801172
body: self.body(),
11811173
};
11821174

src/syntax/ast.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,15 +1570,15 @@ pub enum Param {
15701570
/// A named parameter with a default value: `draw: false`.
15711571
Named(Named),
15721572
/// An argument sink: `..args`.
1573-
Sink(Ident),
1573+
Sink(Option<Ident>),
15741574
}
15751575

15761576
impl AstNode for Param {
15771577
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
15781578
match node.kind() {
15791579
SyntaxKind::Ident => node.cast().map(Self::Pos),
15801580
SyntaxKind::Named => node.cast().map(Self::Named),
1581-
SyntaxKind::Spread => node.cast_first_match().map(Self::Sink),
1581+
SyntaxKind::Spread => Some(Self::Sink(node.cast_first_match())),
15821582
_ => Option::None,
15831583
}
15841584
}
@@ -1587,7 +1587,7 @@ impl AstNode for Param {
15871587
match self {
15881588
Self::Pos(v) => v.as_untyped(),
15891589
Self::Named(v) => v.as_untyped(),
1590-
Self::Sink(v) => v.as_untyped(),
1590+
Self::Sink(_) => self.as_untyped(),
15911591
}
15921592
}
15931593
}

src/syntax/parser.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,7 @@ fn validate_dict(p: &mut Parser, m: Marker) {
10691069
}
10701070

10711071
fn validate_params(p: &mut Parser, m: Marker) {
1072+
let mut used_spread = false;
10721073
let mut used = HashSet::new();
10731074
for child in p.post_process(m) {
10741075
match child.kind() {
@@ -1086,12 +1087,24 @@ fn validate_params(p: &mut Parser, m: Marker) {
10861087
}
10871088
SyntaxKind::Spread => {
10881089
let Some(within) = child.children_mut().last_mut() else { continue };
1089-
if within.kind() != SyntaxKind::Ident {
1090+
if used_spread {
1091+
child.convert_to_error("only one argument sink is allowed");
1092+
continue;
1093+
}
1094+
used_spread = true;
1095+
if within.kind() == SyntaxKind::Dots {
1096+
continue;
1097+
} else if within.kind() != SyntaxKind::Ident {
10901098
within.convert_to_error(eco_format!(
10911099
"expected identifier, found {}",
10921100
within.kind().name(),
10931101
));
10941102
child.make_erroneous();
1103+
continue;
1104+
}
1105+
if !used.insert(within.text().clone()) {
1106+
within.convert_to_error("duplicate parameter");
1107+
child.make_erroneous();
10951108
}
10961109
}
10971110
SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {}

tests/typ/compiler/closure.typ

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@
155155
// Error: 35-36 duplicate parameter
156156
#let f(a, b, a: none, b: none, c, b) = none
157157

158+
---
159+
// Error: 13-14 duplicate parameter
160+
#let f(a, ..a) = none
161+
158162
---
159163
// Error: 7-17 expected identifier, named pair or argument sink, found keyed pair
160164
#((a, "named": b) => none)

tests/typ/compiler/spread.typ

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
#{
2828
let save(..args) = {
2929
test(type(args), "arguments")
30-
test(repr(args), "(1, 2, three: true)")
30+
test(repr(args), "(three: true, 1, 2)")
3131
}
3232

3333
save(1, 2, three: true)
@@ -55,6 +55,11 @@
5555
#f(..if false {})
5656
#f(..for x in () [])
5757

58+
---
59+
// unnamed spread
60+
#let f(.., a) = a
61+
#test(f(1, 2, 3), 3)
62+
5863
---
5964
// Error: 13-19 cannot spread string
6065
#calc.min(.."nope")
@@ -64,7 +69,7 @@
6469
#let f(..true) = none
6570

6671
---
67-
// Error: 15-16 only one argument sink is allowed
72+
// Error: 13-16 only one argument sink is allowed
6873
#let f(..a, ..b) = none
6974

7075
---
@@ -91,3 +96,28 @@
9196
---
9297
// Error: 5-11 cannot spread array into dictionary
9398
#(..(1, 2), a: 1)
99+
100+
---
101+
// Spread at beginning.
102+
#{
103+
let f(..a, b) = (a, b)
104+
test(repr(f(1)), "((), 1)")
105+
test(repr(f(1, 2, 3)), "((1, 2), 3)")
106+
test(repr(f(1, 2, 3, 4, 5)), "((1, 2, 3, 4), 5)")
107+
}
108+
109+
---
110+
// Spread in the middle.
111+
#{
112+
let f(a, ..b, c) = (a, b, c)
113+
test(repr(f(1, 2)), "(1, (), 2)")
114+
test(repr(f(1, 2, 3, 4, 5)), "(1, (2, 3, 4), 5)")
115+
}
116+
117+
---
118+
#{
119+
let f(..a, b, c, d) = none
120+
121+
// Error: 4-10 missing argument: d
122+
f(1, 2)
123+
}

0 commit comments

Comments
 (0)