typst_eval/
call.rs

1use comemo::{Tracked, TrackedMut};
2use ecow::{EcoString, EcoVec, eco_format};
3use typst_library::World;
4use typst_library::diag::{
5    At, HintedStrResult, HintedString, SourceDiagnostic, SourceResult, Trace, Tracepoint,
6    bail, error,
7};
8use typst_library::engine::{Engine, Sink, Traced};
9use typst_library::foundations::{
10    Arg, Args, Binding, Capturer, Closure, ClosureNode, Content, Context, Func,
11    NativeElement, Scope, Scopes, SymbolElem, Value,
12};
13use typst_library::introspection::Introspector;
14use typst_library::math::LrElem;
15use typst_library::routines::Routines;
16use typst_syntax::ast::{self, AstNode, Ident};
17use typst_syntax::{Span, Spanned, SyntaxNode};
18use typst_utils::LazyHash;
19
20use crate::{Access, Eval, FlowEvent, Route, Vm, call_method_mut, is_mutating_method};
21
22impl Eval for ast::FuncCall<'_> {
23    type Output = Value;
24
25    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
26        let span = self.span();
27        let callee = self.callee();
28        let callee_span = callee.span();
29        let args = self.args();
30
31        vm.engine.route.check_call_depth().at(span)?;
32
33        // Try to evaluate as a call to an associated function or field.
34        let (callee_value, args_value) = if let ast::Expr::FieldAccess(access) = callee {
35            let target = access.target();
36            let field = access.field();
37            match eval_field_call(target, field, args, span, vm)? {
38                FieldCall::Normal(callee, args) => {
39                    if vm.inspected == Some(callee_span) {
40                        vm.trace(callee.clone());
41                    }
42                    (callee, args)
43                }
44                FieldCall::Resolved(value) => return Ok(value),
45            }
46        } else {
47            // Function call order: we evaluate the callee before the arguments.
48            (callee.eval(vm)?, args.eval(vm)?.spanned(span))
49        };
50
51        let func_result = callee_value.clone().cast::<Func>();
52
53        if func_result.is_err() && in_math(callee) {
54            return wrap_args_in_math(
55                callee_value,
56                callee_span,
57                args_value,
58                args.trailing_comma(),
59            );
60        }
61
62        let func = func_result
63            .map_err(|err| hint_if_shadowed_std(vm, &self.callee(), err))
64            .at(callee_span)?;
65
66        let point = || Tracepoint::Call(func.name().map(Into::into));
67        let f = || {
68            func.call(&mut vm.engine, vm.context, args_value).trace(
69                vm.world(),
70                point,
71                span,
72            )
73        };
74
75        // Stacker is broken on WASM.
76        #[cfg(target_arch = "wasm32")]
77        return f();
78
79        #[cfg(not(target_arch = "wasm32"))]
80        stacker::maybe_grow(32 * 1024, 2 * 1024 * 1024, f)
81    }
82}
83
84impl Eval for ast::Args<'_> {
85    type Output = Args;
86
87    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
88        let mut items = EcoVec::with_capacity(self.items().count());
89
90        for arg in self.items() {
91            let span = arg.span();
92            match arg {
93                ast::Arg::Pos(expr) => {
94                    items.push(Arg {
95                        span,
96                        name: None,
97                        value: Spanned::new(expr.eval(vm)?, expr.span()),
98                    });
99                }
100                ast::Arg::Named(named) => {
101                    let expr = named.expr();
102                    items.push(Arg {
103                        span,
104                        name: Some(named.name().get().clone().into()),
105                        value: Spanned::new(expr.eval(vm)?, expr.span()),
106                    });
107                }
108                ast::Arg::Spread(spread) => match spread.expr().eval(vm)? {
109                    Value::None => {}
110                    Value::Array(array) => {
111                        items.extend(array.into_iter().map(|value| Arg {
112                            span,
113                            name: None,
114                            value: Spanned::new(value, span),
115                        }));
116                    }
117                    Value::Dict(dict) => {
118                        items.extend(dict.into_iter().map(|(key, value)| Arg {
119                            span,
120                            name: Some(key),
121                            value: Spanned::new(value, span),
122                        }));
123                    }
124                    Value::Args(args) => items.extend(args.items),
125                    v => bail!(spread.span(), "cannot spread {}", v.ty()),
126                },
127            }
128        }
129
130        // We do *not* use the `self.span()` here because we want the callsite
131        // span to be one level higher (the whole function call).
132        Ok(Args { span: Span::detached(), items })
133    }
134}
135
136impl Eval for ast::Closure<'_> {
137    type Output = Value;
138
139    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
140        // Evaluate default values of named parameters.
141        let mut defaults = Vec::new();
142        for param in self.params().children() {
143            if let ast::Param::Named(named) = param {
144                defaults.push(named.expr().eval(vm)?);
145            }
146        }
147
148        // Collect captured variables.
149        let captured = {
150            let mut visitor = CapturesVisitor::new(Some(&vm.scopes), Capturer::Function);
151            visitor.visit(self.to_untyped());
152            visitor.finish()
153        };
154
155        // Define the closure.
156        let closure = Closure {
157            node: ClosureNode::Closure(self.to_untyped().clone()),
158            defaults,
159            captured,
160            num_pos_params: self
161                .params()
162                .children()
163                .filter(|p| matches!(p, ast::Param::Pos(_)))
164                .count(),
165        };
166
167        Ok(Value::Func(Func::from(closure).spanned(self.params().span())))
168    }
169}
170
171/// Call the function in the context with the arguments.
172#[comemo::memoize]
173#[allow(clippy::too_many_arguments)]
174pub fn eval_closure(
175    func: &Func,
176    closure: &LazyHash<Closure>,
177    routines: &Routines,
178    world: Tracked<dyn World + '_>,
179    introspector: Tracked<Introspector>,
180    traced: Tracked<Traced>,
181    sink: TrackedMut<Sink>,
182    route: Tracked<Route>,
183    context: Tracked<Context>,
184    mut args: Args,
185) -> SourceResult<Value> {
186    let (name, params, body) = match closure.node {
187        ClosureNode::Closure(ref node) => {
188            let closure =
189                node.cast::<ast::Closure>().expect("node to be an `ast::Closure`");
190            (closure.name(), closure.params(), closure.body())
191        }
192        ClosureNode::Context(ref node) => {
193            (None, ast::Params::default(), node.cast().unwrap())
194        }
195    };
196
197    // Don't leak the scopes from the call site. Instead, we use the scope
198    // of captured variables we collected earlier.
199    let mut scopes = Scopes::new(None);
200    scopes.top = closure.captured.clone();
201
202    // Prepare the engine.
203    let engine = Engine {
204        routines,
205        world,
206        introspector,
207        traced,
208        sink,
209        route: Route::extend(route),
210    };
211
212    // Prepare VM.
213    let mut vm = Vm::new(engine, context, scopes, body.span());
214
215    // Provide the closure itself for recursive calls.
216    if let Some(name) = name {
217        vm.define(name, func.clone());
218    }
219
220    let num_pos_args = args.to_pos().len();
221    let sink_size = num_pos_args.checked_sub(closure.num_pos_params);
222
223    let mut sink = None;
224    let mut sink_pos_values = None;
225    let mut defaults = closure.defaults.iter();
226    for p in params.children() {
227        match p {
228            ast::Param::Pos(pattern) => match pattern {
229                ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
230                    vm.define(ident, args.expect::<Value>(&ident)?)
231                }
232                pattern => {
233                    crate::destructure(
234                        &mut vm,
235                        pattern,
236                        args.expect::<Value>("pattern parameter")?,
237                    )?;
238                }
239            },
240            ast::Param::Spread(spread) => {
241                sink = Some(spread.sink_ident());
242                if let Some(sink_size) = sink_size {
243                    sink_pos_values = Some(args.consume(sink_size)?);
244                }
245            }
246            ast::Param::Named(named) => {
247                let name = named.name();
248                let default = defaults.next().unwrap();
249                let value =
250                    args.named::<Value>(&name)?.unwrap_or_else(|| default.clone());
251                vm.define(name, value);
252            }
253        }
254    }
255
256    if let Some(sink) = sink {
257        // Remaining args are captured regardless of whether the sink is named.
258        let mut remaining_args = args.take();
259        if let Some(sink_name) = sink {
260            if let Some(sink_pos_values) = sink_pos_values {
261                remaining_args.items.extend(sink_pos_values);
262            }
263            vm.define(sink_name, remaining_args);
264        }
265    }
266
267    // Ensure all arguments have been used.
268    args.finish()?;
269
270    // Handle control flow.
271    let output = body.eval(&mut vm)?;
272    match vm.flow {
273        Some(FlowEvent::Return(_, Some(explicit), _)) => return Ok(explicit),
274        Some(FlowEvent::Return(_, None, _)) => {}
275        Some(flow) => bail!(flow.forbidden()),
276        None => {}
277    }
278
279    Ok(output)
280}
281
282/// This used only as the return value of `eval_field_call`.
283/// - `Normal` means that we have a function to call and the arguments to call it with.
284/// - `Resolved` means that we have already resolved the call and have the value.
285enum FieldCall {
286    Normal(Value, Args),
287    Resolved(Value),
288}
289
290/// Evaluate a field call's callee and arguments.
291///
292/// This follows the normal function call order: we evaluate the callee before the
293/// arguments.
294///
295/// Prioritize associated functions on the value's type (e.g., methods) over its fields.
296/// A function call on a field is only allowed for functions, types, modules (because
297/// they are scopes), and symbols (because they have modifiers or associated functions).
298///
299/// For dictionaries, it is not allowed because it would be ambiguous - prioritizing
300/// associated functions would make an addition of a new associated function a breaking
301/// change and prioritizing fields would break associated functions for certain
302/// dictionaries.
303fn eval_field_call(
304    target_expr: ast::Expr,
305    field: Ident,
306    args: ast::Args,
307    span: Span,
308    vm: &mut Vm,
309) -> SourceResult<FieldCall> {
310    // Evaluate the field-call's target and overall arguments.
311    let (target, mut args) = if is_mutating_method(&field) {
312        // If `field` looks like a mutating method, we evaluate the arguments first,
313        // because `target_expr.access(vm)` mutably borrows the `vm`, so that we can't
314        // evaluate the arguments after it.
315        let args = args.eval(vm)?.spanned(span);
316        // However, this difference from the normal call order is not observable because
317        // expressions like `(1, arr.len(), 2, 3).push(arr.pop())` evaluate the target to
318        // a temporary which we disallow mutation on (returning an error).
319        // Theoretically this could be observed if a method matching `is_mutating_method`
320        // was added to some type in the future and we didn't update this function.
321        match target_expr.access(vm)? {
322            // Only arrays and dictionaries have mutable methods.
323            target @ (Value::Array(_) | Value::Dict(_)) => {
324                let value = call_method_mut(target, &field, args, span);
325                let point = || Tracepoint::Call(Some(field.get().clone()));
326                return Ok(FieldCall::Resolved(value.trace(vm.world(), point, span)?));
327            }
328            target => (target.clone(), args),
329        }
330    } else {
331        let target = target_expr.eval(vm)?;
332        let args = args.eval(vm)?.spanned(span);
333        (target, args)
334    };
335
336    let field_span = field.span();
337    let sink = (&mut vm.engine, field_span);
338    if let Some(callee) = target.ty().scope().get(&field) {
339        args.insert(0, target_expr.span(), target);
340        Ok(FieldCall::Normal(callee.read_checked(sink).clone(), args))
341    } else if let Value::Content(content) = &target {
342        if let Some(callee) = content.elem().scope().get(&field) {
343            args.insert(0, target_expr.span(), target);
344            Ok(FieldCall::Normal(callee.read_checked(sink).clone(), args))
345        } else {
346            bail!(missing_field_call_error(target, field))
347        }
348    } else if matches!(
349        target,
350        Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_)
351    ) {
352        // Certain value types may have their own ways to access method fields.
353        // e.g. `$arrow.r(v)$`, `table.cell[..]`
354        let value = target.field(&field, sink).at(field_span)?;
355        Ok(FieldCall::Normal(value, args))
356    } else {
357        // Otherwise we cannot call this field.
358        bail!(missing_field_call_error(target, field))
359    }
360}
361
362/// Produce an error when we cannot call the field.
363fn missing_field_call_error(target: Value, field: Ident) -> SourceDiagnostic {
364    let mut error = match &target {
365        Value::Content(content) => error!(
366            field.span(),
367            "element {} has no method `{}`",
368            content.elem().name(),
369            field.as_str(),
370        ),
371        _ => error!(
372            field.span(),
373            "type {} has no method `{}`",
374            target.ty(),
375            field.as_str()
376        ),
377    };
378
379    match target {
380        Value::Dict(ref dict) if matches!(dict.get(&field), Ok(Value::Func(_))) => {
381            error.hint(eco_format!(
382                "to call the function stored in the dictionary, surround \
383                the field access with parentheses, e.g. `(dict.{})(..)`",
384                field.as_str(),
385            ));
386        }
387        _ if target.field(&field, ()).is_ok() => {
388            error.hint(eco_format!(
389                "did you mean to access the field `{}`?",
390                field.as_str(),
391            ));
392        }
393        _ => {}
394    }
395
396    error
397}
398
399/// Check if the expression is in a math context.
400fn in_math(expr: ast::Expr) -> bool {
401    match expr {
402        ast::Expr::MathIdent(_) => true,
403        ast::Expr::FieldAccess(access) => in_math(access.target()),
404        _ => false,
405    }
406}
407
408/// For non-functions in math, we wrap the arguments in parentheses.
409fn wrap_args_in_math(
410    callee: Value,
411    callee_span: Span,
412    mut args: Args,
413    trailing_comma: bool,
414) -> SourceResult<Value> {
415    let mut body = Content::empty();
416    for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
417        if i > 0 {
418            body += SymbolElem::packed(',');
419        }
420        body += arg;
421    }
422    if trailing_comma {
423        body += SymbolElem::packed(',');
424    }
425
426    let formatted = callee.display().spanned(callee_span)
427        + LrElem::new(SymbolElem::packed('(') + body + SymbolElem::packed(')'))
428            .pack()
429            .spanned(args.span);
430
431    args.finish()?;
432    Ok(Value::Content(formatted))
433}
434
435/// Provide a hint if the callee is a shadowed standard library function.
436fn hint_if_shadowed_std(
437    vm: &mut Vm,
438    callee: &ast::Expr,
439    mut err: HintedString,
440) -> HintedString {
441    if let ast::Expr::Ident(ident) = callee {
442        let ident = ident.get();
443        if vm.scopes.check_std_shadowed(ident) {
444            err.hint(eco_format!(
445                "use `std.{ident}` to access the shadowed standard library function",
446            ));
447        }
448    }
449    err
450}
451
452/// A visitor that determines which variables to capture for a closure.
453pub struct CapturesVisitor<'a> {
454    external: Option<&'a Scopes<'a>>,
455    internal: Scopes<'a>,
456    captures: Scope,
457    capturer: Capturer,
458}
459
460impl<'a> CapturesVisitor<'a> {
461    /// Create a new visitor for the given external scopes.
462    pub fn new(external: Option<&'a Scopes<'a>>, capturer: Capturer) -> Self {
463        Self {
464            external,
465            internal: Scopes::new(None),
466            captures: Scope::new(),
467            capturer,
468        }
469    }
470
471    /// Return the scope of captured variables.
472    pub fn finish(self) -> Scope {
473        self.captures
474    }
475
476    /// Visit any node and collect all captured variables.
477    pub fn visit(&mut self, node: &SyntaxNode) {
478        match node.cast() {
479            // Every identifier is a potential variable that we need to capture.
480            // Identifiers that shouldn't count as captures because they
481            // actually bind a new name are handled below (individually through
482            // the expressions that contain them).
483            Some(ast::Expr::Ident(ident)) => self.capture(ident.get(), Scopes::get),
484            Some(ast::Expr::MathIdent(ident)) => {
485                self.capture(ident.get(), Scopes::get_in_math)
486            }
487
488            // Code and content blocks create a scope.
489            Some(ast::Expr::CodeBlock(_) | ast::Expr::ContentBlock(_)) => {
490                self.internal.enter();
491                for child in node.children() {
492                    self.visit(child);
493                }
494                self.internal.exit();
495            }
496
497            // Don't capture the field of a field access.
498            Some(ast::Expr::FieldAccess(access)) => {
499                self.visit(access.target().to_untyped());
500            }
501
502            // A closure contains parameter bindings, which are bound before the
503            // body is evaluated. Care must be taken so that the default values
504            // of named parameters cannot access previous parameter bindings.
505            Some(ast::Expr::Closure(expr)) => {
506                for param in expr.params().children() {
507                    if let ast::Param::Named(named) = param {
508                        self.visit(named.expr().to_untyped());
509                    }
510                }
511
512                self.internal.enter();
513                if let Some(name) = expr.name() {
514                    self.bind(name);
515                }
516
517                for param in expr.params().children() {
518                    match param {
519                        ast::Param::Pos(pattern) => {
520                            for ident in pattern.bindings() {
521                                self.bind(ident);
522                            }
523                        }
524                        ast::Param::Named(named) => self.bind(named.name()),
525                        ast::Param::Spread(spread) => {
526                            if let Some(ident) = spread.sink_ident() {
527                                self.bind(ident);
528                            }
529                        }
530                    }
531                }
532
533                self.visit(expr.body().to_untyped());
534                self.internal.exit();
535            }
536
537            // A let expression contains a binding, but that binding is only
538            // active after the body is evaluated.
539            Some(ast::Expr::LetBinding(expr)) => {
540                if let Some(init) = expr.init() {
541                    self.visit(init.to_untyped());
542                }
543
544                for ident in expr.kind().bindings() {
545                    self.bind(ident);
546                }
547            }
548
549            // A for loop contains one or two bindings in its pattern. These are
550            // active after the iterable is evaluated but before the body is
551            // evaluated.
552            Some(ast::Expr::ForLoop(expr)) => {
553                self.visit(expr.iterable().to_untyped());
554                self.internal.enter();
555
556                let pattern = expr.pattern();
557                for ident in pattern.bindings() {
558                    self.bind(ident);
559                }
560
561                self.visit(expr.body().to_untyped());
562                self.internal.exit();
563            }
564
565            // An import contains items, but these are active only after the
566            // path is evaluated.
567            Some(ast::Expr::ModuleImport(expr)) => {
568                self.visit(expr.source().to_untyped());
569                if let Some(ast::Imports::Items(items)) = expr.imports() {
570                    for item in items.iter() {
571                        self.bind(item.bound_name());
572                    }
573                }
574            }
575
576            _ => {
577                // Never capture the name part of a named pair.
578                if let Some(named) = node.cast::<ast::Named>() {
579                    self.visit(named.expr().to_untyped());
580                    return;
581                }
582
583                // Everything else is traversed from left to right.
584                for child in node.children() {
585                    self.visit(child);
586                }
587            }
588        }
589    }
590
591    /// Bind a new internal variable.
592    fn bind(&mut self, ident: ast::Ident) {
593        // The concrete value does not matter as we only use the scoping
594        // mechanism of `Scopes`, not the values themselves.
595        self.internal
596            .top
597            .bind(ident.get().clone(), Binding::detached(Value::None));
598    }
599
600    /// Capture a variable if it isn't internal.
601    fn capture(
602        &mut self,
603        ident: &EcoString,
604        getter: impl FnOnce(&'a Scopes<'a>, &str) -> HintedStrResult<&'a Binding>,
605    ) {
606        if self.internal.get(ident).is_ok() {
607            return;
608        }
609
610        let binding = match self.external {
611            Some(external) => match getter(external, ident) {
612                Ok(binding) => binding.capture(self.capturer),
613                Err(_) => return,
614            },
615            // The external scopes are only `None` when we are doing IDE capture
616            // analysis, in which case the concrete value doesn't matter.
617            None => Binding::detached(Value::None),
618        };
619
620        self.captures.bind(ident.clone(), binding);
621    }
622}
623
624#[cfg(test)]
625mod tests {
626    use typst_syntax::parse;
627
628    use super::*;
629
630    #[track_caller]
631    fn test(scopes: &Scopes, text: &str, result: &[&str]) {
632        let mut visitor = CapturesVisitor::new(Some(scopes), Capturer::Function);
633        let root = parse(text);
634        visitor.visit(&root);
635
636        let captures = visitor.finish();
637        let mut names: Vec<_> = captures.iter().map(|(k, ..)| k).collect();
638        names.sort();
639
640        assert_eq!(names, result);
641    }
642
643    #[test]
644    fn test_captures() {
645        let mut scopes = Scopes::new(None);
646        scopes.top.define("f", 0);
647        scopes.top.define("x", 0);
648        scopes.top.define("y", 0);
649        scopes.top.define("z", 0);
650        let s = &scopes;
651
652        // Let binding and function definition.
653        test(s, "#let x = x", &["x"]);
654        test(s, "#let x; #(x + y)", &["y"]);
655        test(s, "#let f(x, y) = x + y", &[]);
656        test(s, "#let f(x, y) = f", &[]);
657        test(s, "#let f = (x, y) => f", &["f"]);
658
659        // Closure with different kinds of params.
660        test(s, "#((x, y) => x + z)", &["z"]);
661        test(s, "#((x: y, z) => x + z)", &["y"]);
662        test(s, "#((..x) => x + y)", &["y"]);
663        test(s, "#((x, y: x + z) => x + y)", &["x", "z"]);
664        test(s, "#{x => x; x}", &["x"]);
665
666        // Show rule.
667        test(s, "#show y: x => x", &["y"]);
668        test(s, "#show y: x => x + z", &["y", "z"]);
669        test(s, "#show x: x => x", &["x"]);
670
671        // For loop.
672        test(s, "#for x in y { x + z }", &["y", "z"]);
673        test(s, "#for (x, y) in y { x + y }", &["y"]);
674        test(s, "#for x in y {} #x", &["x", "y"]);
675
676        // Import.
677        test(s, "#import z: x, y", &["z"]);
678        test(s, "#import x + y: x, y, z", &["x", "y"]);
679
680        // Blocks.
681        test(s, "#{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
682        test(s, "#[#let x = 1]#x", &["x"]);
683
684        // Field access.
685        test(s, "#x.y.f(z)", &["x", "z"]);
686
687        // Parenthesized expressions.
688        test(s, "#f(x: 1)", &["f"]);
689        test(s, "#(x: 1)", &[]);
690        test(s, "#(x = 1)", &["x"]);
691        test(s, "#(x += y)", &["x", "y"]);
692        test(s, "#{ (x, z) = (y, 1) }", &["x", "y", "z"]);
693        test(s, "#(x.at(y) = 5)", &["x", "y"]);
694    }
695
696    #[test]
697    fn test_captures_in_math() {
698        let mut scopes = Scopes::new(None);
699        scopes.top.define("f", 0);
700        scopes.top.define("x", 0);
701        scopes.top.define("y", 0);
702        scopes.top.define("z", 0);
703        // Multi-letter variables are required for math.
704        scopes.top.define("foo", 0);
705        scopes.top.define("bar", 0);
706        scopes.top.define("x-bar", 0);
707        scopes.top.define("x_bar", 0);
708        let s = &scopes;
709
710        // Basic math identifier differences.
711        test(s, "$ x f(z) $", &[]); // single letters not captured.
712        test(s, "$ #x #f(z) $", &["f", "x", "z"]);
713        test(s, "$ foo f(bar) $", &["bar", "foo"]);
714        test(s, "$ #foo[#$bar$] $", &["bar", "foo"]);
715        test(s, "$ #let foo = x; foo $", &["x"]);
716
717        // Math idents don't have dashes/underscores
718        test(s, "$ x-y x_y foo-x x_bar $", &["bar", "foo"]);
719        test(s, "$ #x-bar #x_bar $", &["x-bar", "x_bar"]);
720
721        // Named-params.
722        test(s, "$ foo(bar: y) $", &["foo"]);
723        test(s, "$ foo(x-y: 1, bar-z: 2) $", &["foo"]);
724
725        // Field access in math.
726        test(s, "$ foo.bar $", &["foo"]);
727        test(s, "$ foo.x $", &["foo"]);
728        test(s, "$ x.foo $", &["foo"]);
729        test(s, "$ foo . bar $", &["bar", "foo"]);
730        test(s, "$ foo.x.y.bar(z) $", &["foo"]);
731        test(s, "$ foo.x-bar $", &["bar", "foo"]);
732        test(s, "$ foo.x_bar $", &["bar", "foo"]);
733        test(s, "$ #x_bar.x-bar $", &["x_bar"]);
734    }
735}