Skip to main content

typst_eval/
call.rs

1use comemo::{Tracked, TrackedMut};
2use ecow::{EcoString, EcoVec, eco_format};
3use typst_library::diag::{
4    At, HintedStrResult, HintedString, SourceResult, Trace, Tracepoint, bail, error,
5};
6use typst_library::engine::{Engine, Sink, Traced};
7use typst_library::foundations::{
8    Arg, Args, Binding, Capturer, Closure, ClosureNode, Content, Context, Func,
9    NativeElement, Scope, Scopes, SequenceElem, SymbolElem, Value,
10};
11use typst_library::introspection::Introspector;
12use typst_library::math::LrElem;
13use typst_library::{Library, World};
14use typst_syntax::ast::{self, AstNode};
15use typst_syntax::{Span, Spanned, SyntaxNode};
16use typst_utils::{LazyHash, Protected};
17
18use crate::{
19    Access, Eval, FlowEvent, Route, Vm, call_method_mut, hint_if_shadowed_std,
20    is_dict_mutating_method, is_mutating_method,
21};
22
23impl Eval for ast::FuncCall<'_> {
24    type Output = Value;
25
26    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
27        let span = self.span();
28        let callee = self.callee();
29
30        vm.engine.route.check_call_depth().at(span)?;
31
32        // Try to evaluate as a call to an associated function or field.
33        if let ast::Expr::FieldAccess(access) = callee {
34            let target_expr = access.target();
35            let field = access.field();
36            let (target, maybe_args) = if is_mutating_method(field.as_str()) {
37                match maybe_resolve_mutating(vm, target_expr, field, self.args(), span)? {
38                    Ok(value) => return Ok(value),
39                    Err((target, args)) => (target, Some(args)),
40                }
41            } else {
42                (target_expr.eval(vm)?, None)
43            };
44            match eval_field_callee(
45                vm,
46                access.to_untyped(),
47                field.as_str(),
48                field.span(),
49                target,
50                false,
51            )? {
52                FieldCallee::Func(func) => {
53                    let args = match maybe_args {
54                        Some(args) => args,
55                        None => self.args().eval(vm)?.spanned(span),
56                    };
57                    call_func(vm, func, args, span)
58                }
59                FieldCallee::Method(func, target) => {
60                    let mut args = match maybe_args {
61                        Some(args) => args,
62                        None => self.args().eval(vm)?.spanned(span),
63                    };
64                    // Method calls pass the target as the first argument.
65                    args.insert(0, target_expr.span(), target);
66                    call_func(vm, func, args, span)
67                }
68                FieldCallee::NonFunc(_, err) => Err(err).at(callee.span()),
69            }
70        } else {
71            // Function call order: we evaluate the callee before the arguments.
72            let func = callee
73                .eval(vm)?
74                .cast::<Func>()
75                .map_err(|err| hint_if_shadowed_std(vm, &callee, err))
76                .at(callee.span())?;
77            let args = self.args().eval(vm)?.spanned(span);
78            call_func(vm, func, args, span)
79        }
80    }
81}
82
83impl Eval for ast::MathCall<'_> {
84    type Output = Value;
85
86    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
87        eval_math_call(vm, self)
88    }
89}
90
91/// Evaluate a function call in math.
92fn eval_math_call(vm: &mut Vm, math_call: ast::MathCall) -> SourceResult<Value> {
93    let span = math_call.span();
94    let callee = math_call.callee();
95    let mut target_span = Span::detached();
96
97    vm.engine.route.check_call_depth().at(span)?;
98
99    let math_call_result = match callee {
100        ast::MathAccess::MathIdent(ident) => {
101            let callee_value = ident.eval(vm)?;
102            // We need to call `trace_at` for the callee manually because we did
103            // not evaluate via `ast::Expr::eval()`.
104            vm.trace_at(ident.span(), &callee_value);
105            match callee_value.clone().cast::<Func>() {
106                Ok(func) => FieldCallee::Func(func),
107                Err(err) => FieldCallee::NonFunc(callee_value, err),
108            }
109        }
110        ast::MathAccess::MathFieldAccess(access) => {
111            let target_expr = access.target();
112            target_span = target_expr.span();
113            let field = access.field();
114            let target = target_expr.eval(vm)?;
115            if is_mutating_method(field.as_str())
116                && matches!(target, Value::Array(_) | Value::Dict(_))
117            {
118                // FUTURE: This is probably worth allowing once we nail down
119                // mutable method semantics.
120                //
121                // Mutable methods have always produced an error in math because
122                // `Access` was never implemented for `MathIdent`, so this
123                // explicit error is just nicer. And while we could start to
124                // implement `Access`, making mutable methods work in math still
125                // requires deeper changes because math mode needs to know
126                // whether the target is actually a function before evaluating
127                // arguments.
128                bail!(
129                    span,
130                    "cannot call mutating methods in math";
131                    hint: "try using code mode to call the method: `#{}`",
132                        math_call.to_untyped().full_text();
133                );
134            }
135            eval_field_callee(
136                vm,
137                access.to_untyped(),
138                field.as_str(),
139                field.span(),
140                target,
141                true,
142            )?
143        }
144    };
145
146    let args = math_call.args();
147    match math_call_result {
148        FieldCallee::Func(func) => {
149            let args = args.eval(vm)?.spanned(span);
150            call_func(vm, func, args, span)
151        }
152        FieldCallee::Method(func, target) => {
153            let mut args = args.eval(vm)?.spanned(span);
154            // Method calls pass the target as the first argument.
155            args.insert(0, target_span, target);
156            call_func(vm, func, args, span)
157        }
158        FieldCallee::NonFunc(callee_value, _) => {
159            let parens = unparse_math_args(vm, args, callee)?;
160            Ok(Value::Content(callee_value.display().spanned(callee.span()) + parens))
161        }
162    }
163}
164
165/// Call a function.
166fn call_func(vm: &mut Vm, func: Func, args: Args, span: Span) -> SourceResult<Value> {
167    let func = func.spanned(span);
168    let point = || Tracepoint::Call(func.name().map(Into::into));
169    let f = || {
170        func.call(&mut vm.engine, vm.context, args)
171            .trace(vm.world(), point, span)
172    };
173
174    // Stacker is broken on WASM.
175    #[cfg(target_arch = "wasm32")]
176    return f();
177
178    #[cfg(not(target_arch = "wasm32"))]
179    stacker::maybe_grow(32 * 1024, 2 * 1024 * 1024, f)
180}
181
182/// Attempt to resolve a mutating method call by evaluating args and then
183/// attempting to access the target mutably. If the target's type doesn't
184/// support mutating methods (only Array/Dict actually do), returns the
185/// evaluated value and arguments.
186///
187/// This currently causes a number of bad errors due to limitations of the
188/// [`Access`] trait used for mutation.
189fn maybe_resolve_mutating(
190    vm: &mut Vm,
191    target: ast::Expr,
192    field: ast::Ident,
193    args: ast::Args,
194    span: Span,
195) -> SourceResult<Result<Value, (Value, Args)>> {
196    // We evaluate the arguments first because `target_expr.access(vm)` mutably
197    // borrows `vm`, so we won't be able to call `args.eval(vm)` afterwards.
198    let args = args.eval(vm)?.spanned(span);
199    match target.access(vm)? {
200        // Skip methods that aren't actually mutating for dictionaries.
201        target @ Value::Dict(_) if !is_dict_mutating_method(field.as_str()) => {
202            Ok(Err((target.clone(), args)))
203        }
204        // Only arrays and dictionaries have mutable methods.
205        target @ (Value::Array(_) | Value::Dict(_)) => {
206            let value = call_method_mut(target, &field, args, span);
207            let point = || Tracepoint::Call(Some(field.get().clone()));
208            Ok(Ok(value.trace(vm.world(), point, span)?))
209        }
210        target => Ok(Err((target.clone(), args))),
211    }
212}
213
214/// The kind of callee in a field-access function call.
215enum FieldCallee {
216    /// A method on a type or on content, with the target value to be added as
217    /// the first argument of the call.
218    Method(Func, Value),
219    /// A plain function to call.
220    Func(Func),
221    /// The field access doesn't actually produce a function. This will error in
222    /// code, but not in math.
223    NonFunc(Value, HintedString),
224}
225
226/// Evaluate a field-access callee, prioritizing associated functions of the
227/// value's type, "methods", over fields on the specific value.
228///
229/// Calls to fields of a value are only allowed for functions (`assert.eq`),
230/// types (`str.to-unicode`, `table.cell`), modules (`pdf.attach`), and symbols
231/// (`arrow.l`).
232///
233/// In particular, calls to a field function are not allowed for dictionaries
234/// because it would be ambiguous. If we did allow it, we would either have to
235/// prioritize methods or field functions, but both choices are bad:
236/// - Prioritizing methods would make all new method additions breaking changes.
237/// - Prioritizing field functions would break methods for certain dictionaries,
238///   e.g. `(at: x => ...).at(key)`.
239fn eval_field_callee<'a, 'b>(
240    vm: &'a mut Vm<'b>,
241    access: &SyntaxNode,
242    field: &str,
243    field_span: Span,
244    target: Value,
245    in_math: bool,
246) -> SourceResult<FieldCallee> {
247    let sink = (&mut vm.engine, field_span);
248
249    let mut is_method_call = false;
250    let callee_value = if let Some(method) = target.ty().scope().get(field) {
251        is_method_call = true;
252        method.read_checked(sink).clone()
253    } else if let Value::Content(content) = &target
254        && let Some(method) = content.elem().scope().get(field)
255    {
256        is_method_call = true;
257        method.read_checked(sink).clone()
258    } else if matches!(
259        target,
260        Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_)
261    ) {
262        // Only these types are allowed to use field call syntax on non-methods.
263        target.field(field, sink).at(field_span)?
264    } else {
265        // Otherwise we cannot call this field and produce an error.
266        match target.field(field, sink) {
267            // The field does exist.
268            Ok(callee_value) => {
269                // Aside from Dict, named Args, and Content, only a few other
270                // types have accessible fields which could produce these
271                // errors. As of June 2026, they are:
272                // - Alignment (.x, .y)
273                // - Length (.abs, .em)
274                // - Relative Length (.ratio, .length)
275                // - Stroke (.cap, .dash, .join, .miter-limit, .paint, .thickness)
276                // - Version (.major, .minor, .patch)
277                // The other types with fields (Symbol, Func, Type, Module) are
278                // handled above.
279                let is_dict = matches!(target, Value::Dict(_));
280                let is_named = matches!(target, Value::Args(_));
281                let mut err = if is_dict {
282                    // Dictionaries get a specific error & hint because they're
283                    // the easiest to attempt this with, and users need to be
284                    // told directly why it's not allowed.
285                    error!(
286                        access.span(),
287                        "cannot directly call dictionary keys as functions";
288                    )
289                } else if is_named {
290                    // Also give the custom error & hint for named arguments.
291                    error!(
292                        access.span(),
293                        "cannot directly call named argument fields as functions";
294                    )
295                } else {
296                    let (kind, name) = element_or_type_with_name(&target);
297                    error!(
298                        access.span(),
299                        "`{field}` is not a valid method for {kind} `{name}`";
300                    )
301                };
302                if callee_value.clone().cast::<Func>().is_ok() {
303                    err.hint(eco_format!(
304                        "to call the stored function, {}wrap the field access \
305                            in parentheses: `{}({})(..)`",
306                        if in_math { "use code mode and " } else { "" },
307                        if in_math { "#" } else { "" },
308                        access.full_text(),
309                    ));
310                } else if in_math {
311                    err.hint("try adding a space before the parentheses");
312                } else {
313                    err.hint(eco_format!(
314                        "to access the `{field}` {}, remove the function arguments: `{}`",
315                        if is_dict {
316                            "key"
317                        } else if is_named {
318                            "argument"
319                        } else {
320                            "field"
321                        },
322                        access.full_text(),
323                    ));
324                }
325                if is_dict {
326                    err.hint(
327                        "dictionary keys cannot be used with method syntax as keys \
328                            could conflict with built-in method names",
329                    );
330                } else if is_named {
331                    err.hint(
332                        "named arguments cannot be used with method syntax as argument \
333                            names could conflict with built-in method names",
334                    );
335                }
336
337                bail!(err)
338            }
339            // The field does not exist. We don't try as hard on the error here
340            // to avoid assuming the user's intent.
341            Err(_) => {
342                let (kind, name) = element_or_type_with_name(&target);
343                bail!(access.span(), "{kind} {name} has no method `{field}`")
344            }
345        }
346    };
347
348    vm.trace_at(access.span(), &callee_value);
349
350    match callee_value.clone().cast::<Func>() {
351        Ok(func) if is_method_call => Ok(FieldCallee::Method(func, target)),
352        Ok(func) => Ok(FieldCallee::Func(func)),
353        Err(err) => Ok(FieldCallee::NonFunc(callee_value, err)),
354    }
355}
356
357/// If the value is content, the string "element" and the name of its element
358/// function, or the string "type" and the name of the value's type.
359fn element_or_type_with_name(value: &Value) -> (&'static str, &'static str) {
360    if let Value::Content(content) = value {
361        ("element", content.elem().name())
362    } else {
363        ("type", value.ty().long_name())
364    }
365}
366
367impl Eval for ast::Args<'_> {
368    type Output = Args;
369
370    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
371        let mut items = EcoVec::with_capacity(self.items().count());
372
373        for arg in self.items() {
374            let span = arg.span();
375            match arg {
376                ast::Arg::Pos(expr) => {
377                    items.push(Arg {
378                        span,
379                        name: None,
380                        value: Spanned::new(expr.eval(vm)?, expr.span()),
381                    });
382                }
383                ast::Arg::Named(named) => {
384                    let expr = named.expr();
385                    items.push(Arg {
386                        span,
387                        name: Some(named.name().get().clone().into()),
388                        value: Spanned::new(expr.eval(vm)?, expr.span()),
389                    });
390                }
391                ast::Arg::Spread(spread) => match spread.expr().eval(vm)? {
392                    Value::None => {}
393                    Value::Array(array) => {
394                        items.extend(array.into_iter().map(|value| Arg {
395                            span,
396                            name: None,
397                            value: Spanned::new(value, span),
398                        }));
399                    }
400                    Value::Dict(dict) => {
401                        items.extend(dict.into_iter().map(|(key, value)| Arg {
402                            span,
403                            name: Some(key),
404                            value: Spanned::new(value, span),
405                        }));
406                    }
407                    Value::Args(args) => items.extend(args.items),
408                    v => bail!(spread.span(), "cannot spread {}", v.ty()),
409                },
410            }
411        }
412
413        // We do *not* use the `self.span()` here because we want the callsite
414        // span to be one level higher (the whole function call).
415        Ok(Args { span: Span::detached(), items })
416    }
417}
418
419impl Eval for ast::MathArgs<'_> {
420    type Output = Args;
421
422    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
423        // Math args need to fully separate named/pos to handle two-dimensional
424        // args correctly, for example: `mat(a, delim:"[", b; c, d)`.
425        let mut named = EcoVec::new();
426        let mut pos = Vec::new();
427        let mut two_dim_start: Option<usize> = None;
428
429        /// Optimize two-dimensional args by using `pos` as the sole container
430        /// while iterating and only group into an array when we encounter a
431        /// semicolon.
432        fn drain_into_array(pos: &mut Vec<Arg>, start: usize, span: Span) {
433            let array = pos.drain(start..).map(|arg| arg.value.v).collect();
434            pos.push(Arg {
435                span,
436                name: None,
437                value: Spanned::new(Value::Array(array), span),
438            });
439        }
440
441        for ast::MathArg { arg, ends_in_semicolon } in self.arg_items() {
442            let span = arg.span();
443            match arg {
444                ast::Arg::Pos(expr) => {
445                    pos.push(Arg {
446                        span,
447                        name: None,
448                        value: Spanned::new(expr.eval(vm)?, expr.span()),
449                    });
450                }
451                ast::Arg::Named(named_arg) => {
452                    let expr = named_arg.expr();
453                    named.push(Arg {
454                        span,
455                        name: Some(named_arg.name().get().clone().into()),
456                        value: Spanned::new(expr.eval(vm)?, expr.span()),
457                    });
458                }
459                ast::Arg::Spread(spread) => match spread.expr().eval(vm)? {
460                    Value::None => {}
461                    Value::Array(array) => {
462                        pos.extend(array.into_iter().map(|value| Arg {
463                            span,
464                            name: None,
465                            value: Spanned::new(value, span),
466                        }));
467                    }
468                    Value::Dict(dict) => {
469                        named.extend(dict.into_iter().map(|(key, value)| Arg {
470                            span,
471                            name: Some(key),
472                            value: Spanned::new(value, span),
473                        }));
474                    }
475                    Value::Args(args) => {
476                        for arg in args.items {
477                            if arg.name.is_none() {
478                                pos.push(arg);
479                            } else {
480                                named.push(arg);
481                            }
482                        }
483                    }
484                    v => bail!(spread.span(), "cannot spread {}", v.ty()),
485                },
486            }
487            if ends_in_semicolon {
488                let start = two_dim_start.unwrap_or(0);
489                // There's not really a better span to use :/
490                drain_into_array(&mut pos, start, self.span());
491                two_dim_start = Some(pos.len());
492            }
493        }
494
495        if let Some(start) = two_dim_start
496            && start != pos.len()
497        {
498            drain_into_array(&mut pos, start, self.span());
499        }
500
501        named.extend(pos);
502        Ok(Args { span: Span::detached(), items: named })
503    }
504}
505
506/// For non-functions in math, we evaluate the arguments and punctuation as
507/// content and wrap in an [`LrElem`].
508fn unparse_math_args(
509    vm: &mut Vm,
510    args: ast::MathArgs,
511    callee: ast::MathAccess,
512) -> SourceResult<Content> {
513    let mut body = Vec::new();
514    let mut errors = EcoVec::new();
515    for item in args.content_items() {
516        match item {
517            ast::MathArgItem::Space(space) => {
518                body.push(space.eval(vm)?.spanned(space.span()));
519            }
520            ast::MathArgItem::Comma(c, node)
521            | ast::MathArgItem::Semicolon(c, node)
522            | ast::MathArgItem::LeftParen(c, node)
523            | ast::MathArgItem::RightParen(c, node) => {
524                body.push(SymbolElem::packed(c).spanned(node.span()));
525            }
526            ast::MathArgItem::Arg(ast::Arg::Pos(expr)) => {
527                // We use `Value::display` to convert argument expressions into
528                // content instead of `Content::from_value`. This makes it so we
529                // don't error on `$sin(#1)$` because we don't error on `$#1$`.
530                body.push(expr.eval(vm)?.display().spanned(expr.span()));
531            }
532            ast::MathArgItem::Arg(ast::Arg::Named(named)) => {
533                let name = callee.to_untyped().full_text();
534                let fixed = named.to_untyped().full_text().replacen(":", "\\:", 1);
535                errors.push(error!(
536                    named.span(), "named-argument syntax can only be used with functions";
537                    hint[callee.span()]: "`{name}` is not a function";
538                    hint: "to render the colon as text, escape it: `{fixed}`";
539                ));
540            }
541            ast::MathArgItem::Arg(ast::Arg::Spread(spread)) => {
542                let name = callee.to_untyped().full_text();
543                let fixed = spread.to_untyped().full_text().replacen("..", ".. ", 1);
544                errors.push(error!(
545                    spread.span(), "spread-argument syntax can only be used with functions";
546                    hint[callee.span()]: "`{name}` is not a function";
547                    hint: "to render the dots as text, add a space: `{fixed}`";
548                ));
549            }
550        }
551    }
552
553    if !errors.is_empty() {
554        return Err(errors);
555    }
556
557    Ok(LrElem::new(SequenceElem::new(body).pack())
558        .pack()
559        .spanned(args.span()))
560}
561
562impl Eval for ast::Closure<'_> {
563    type Output = Value;
564
565    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
566        // Evaluate default values of named parameters.
567        let mut defaults = Vec::new();
568        for param in self.params().children() {
569            if let ast::Param::Named(named) = param {
570                defaults.push(named.expr().eval(vm)?);
571            }
572        }
573
574        // Collect captured variables.
575        let captured = {
576            let mut visitor = CapturesVisitor::new(Some(&vm.scopes), Capturer::Function);
577            visitor.visit(self.to_untyped());
578            visitor.finish()
579        };
580
581        // Define the closure.
582        let closure = Closure {
583            node: ClosureNode::Closure(self.to_untyped().clone()),
584            defaults,
585            captured,
586            num_pos_params: self
587                .params()
588                .children()
589                .filter(|p| matches!(p, ast::Param::Pos(_)))
590                .count(),
591        };
592
593        Ok(Value::Func(Func::from(closure).spanned(self.params().span())))
594    }
595}
596
597/// Call the function in the context with the arguments.
598#[comemo::memoize]
599#[allow(clippy::too_many_arguments)]
600pub fn eval_closure(
601    func: &Func,
602    closure: &LazyHash<Closure>,
603    world: Tracked<dyn World + '_>,
604    library: &LazyHash<Library>,
605    introspector: Tracked<dyn Introspector + '_>,
606    traced: Tracked<Traced>,
607    sink: TrackedMut<Sink>,
608    route: Tracked<Route>,
609    context: Tracked<Context>,
610    mut args: Args,
611) -> SourceResult<Value> {
612    let (name, params, body) = match closure.node {
613        ClosureNode::Closure(ref node) => {
614            let closure =
615                node.cast::<ast::Closure>().expect("node to be an `ast::Closure`");
616            (closure.name(), closure.params(), closure.body())
617        }
618        ClosureNode::Context(ref node) => {
619            (None, ast::Params::placeholder(), node.cast().unwrap())
620        }
621    };
622
623    // Don't leak the scopes from the call site. Instead, we use the scope
624    // of captured variables we collected earlier.
625    let mut scopes = Scopes::new(None);
626    scopes.top = closure.captured.clone();
627
628    // Prepare the engine.
629    let introspector = Protected::from_raw(introspector);
630    let engine = Engine {
631        library,
632        world,
633        introspector,
634        traced,
635        sink,
636        route: Route::extend(route),
637    };
638
639    // Prepare VM.
640    let mut vm = Vm::new(engine, context, scopes, body.span());
641
642    // Provide the closure itself for recursive calls.
643    if let Some(name) = name {
644        vm.define(name, func.clone());
645    }
646
647    let num_pos_args = args.to_pos().len();
648    let sink_size = num_pos_args.checked_sub(closure.num_pos_params);
649
650    let mut sink = None;
651    let mut sink_pos_values = None;
652    let mut defaults = closure.defaults.iter();
653    for p in params.children() {
654        match p {
655            ast::Param::Pos(pattern) => match pattern {
656                ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
657                    vm.define(ident, args.expect::<Value>(&ident)?)
658                }
659                pattern => {
660                    crate::destructure(
661                        &mut vm,
662                        pattern,
663                        args.expect::<Value>("pattern parameter")?,
664                    )?;
665                }
666            },
667            ast::Param::Spread(spread) => {
668                sink = Some(spread.sink_ident());
669                if let Some(sink_size) = sink_size {
670                    sink_pos_values = Some(args.consume(sink_size)?);
671                }
672            }
673            ast::Param::Named(named) => {
674                let name = named.name();
675                let default = defaults.next().unwrap();
676                let value =
677                    args.named::<Value>(&name)?.unwrap_or_else(|| default.clone());
678                vm.define(name, value);
679            }
680        }
681    }
682
683    if let Some(sink) = sink {
684        // Remaining args are captured regardless of whether the sink is named.
685        let mut remaining_args = args.take();
686        if let Some(sink_name) = sink {
687            if let Some(sink_pos_values) = sink_pos_values {
688                remaining_args.items.extend(sink_pos_values);
689            }
690            vm.define(sink_name, remaining_args);
691        }
692    }
693
694    // Ensure all arguments have been used.
695    args.finish()?;
696
697    // Handle control flow.
698    let output = body.eval(&mut vm)?;
699    match vm.flow {
700        Some(FlowEvent::Return(_, Some(explicit), _)) => return Ok(explicit),
701        Some(FlowEvent::Return(_, None, _)) => {}
702        Some(flow) => bail!(flow.forbidden()),
703        None => {}
704    }
705
706    Ok(output)
707}
708
709/// A visitor that determines which variables to capture for a closure.
710pub struct CapturesVisitor<'a> {
711    external: Option<&'a Scopes<'a>>,
712    internal: Scopes<'a>,
713    captures: Scope,
714    capturer: Capturer,
715}
716
717impl<'a> CapturesVisitor<'a> {
718    /// Create a new visitor for the given external scopes.
719    pub fn new(external: Option<&'a Scopes<'a>>, capturer: Capturer) -> Self {
720        Self {
721            external,
722            internal: Scopes::new(None),
723            captures: Scope::new(),
724            capturer,
725        }
726    }
727
728    /// Return the scope of captured variables.
729    pub fn finish(self) -> Scope {
730        self.captures
731    }
732
733    /// Visit any node and collect all captured variables.
734    pub fn visit(&mut self, node: &SyntaxNode) {
735        match node.cast() {
736            // Every identifier is a potential variable that we need to capture.
737            // Identifiers that shouldn't count as captures because they
738            // actually bind a new name are handled below (individually through
739            // the expressions that contain them).
740            Some(ast::Expr::Ident(ident)) => self.capture(ident.get(), Scopes::get),
741            Some(ast::Expr::MathIdent(ident)) => {
742                self.capture(ident.get(), Scopes::get_in_math)
743            }
744
745            // Code and content blocks create a scope.
746            Some(ast::Expr::CodeBlock(_) | ast::Expr::ContentBlock(_)) => {
747                self.internal.enter();
748                for child in node.children() {
749                    self.visit(child);
750                }
751                self.internal.exit();
752            }
753
754            // Don't capture the field of a field access.
755            Some(ast::Expr::FieldAccess(access)) => {
756                self.visit(access.target().to_untyped());
757            }
758            Some(ast::Expr::MathFieldAccess(access)) => {
759                self.visit(access.target().to_untyped());
760            }
761
762            // A closure contains parameter bindings, which are bound before the
763            // body is evaluated. Care must be taken so that the default values
764            // of named parameters cannot access previous parameter bindings.
765            Some(ast::Expr::Closure(expr)) => {
766                for param in expr.params().children() {
767                    if let ast::Param::Named(named) = param {
768                        self.visit(named.expr().to_untyped());
769                    }
770                }
771
772                self.internal.enter();
773                if let Some(name) = expr.name() {
774                    self.bind(name);
775                }
776
777                for param in expr.params().children() {
778                    match param {
779                        ast::Param::Pos(pattern) => {
780                            for ident in pattern.bindings() {
781                                self.bind(ident);
782                            }
783                        }
784                        ast::Param::Named(named) => self.bind(named.name()),
785                        ast::Param::Spread(spread) => {
786                            if let Some(ident) = spread.sink_ident() {
787                                self.bind(ident);
788                            }
789                        }
790                    }
791                }
792
793                self.visit(expr.body().to_untyped());
794                self.internal.exit();
795            }
796
797            // A let expression contains a binding, but that binding is only
798            // active after the body is evaluated.
799            Some(ast::Expr::LetBinding(expr)) => {
800                if let Some(init) = expr.init() {
801                    self.visit(init.to_untyped());
802                }
803
804                for ident in expr.kind().bindings() {
805                    self.bind(ident);
806                }
807            }
808
809            // A for loop contains one or two bindings in its pattern. These are
810            // active after the iterable is evaluated but before the body is
811            // evaluated.
812            Some(ast::Expr::ForLoop(expr)) => {
813                self.visit(expr.iterable().to_untyped());
814                self.internal.enter();
815
816                let pattern = expr.pattern();
817                for ident in pattern.bindings() {
818                    self.bind(ident);
819                }
820
821                self.visit(expr.body().to_untyped());
822                self.internal.exit();
823            }
824
825            // An import contains items, but these are active only after the
826            // path is evaluated.
827            Some(ast::Expr::ModuleImport(expr)) => {
828                self.visit(expr.source().to_untyped());
829                if let Some(ast::Imports::Items(items)) = expr.imports() {
830                    for item in items.iter() {
831                        self.bind(item.bound_name());
832                    }
833                }
834            }
835
836            _ => {
837                // Never capture the name part of a named pair.
838                if let Some(named) = node.cast::<ast::Named>() {
839                    self.visit(named.expr().to_untyped());
840                    return;
841                }
842
843                // Everything else is traversed from left to right.
844                for child in node.children() {
845                    self.visit(child);
846                }
847            }
848        }
849    }
850
851    /// Bind a new internal variable.
852    fn bind(&mut self, ident: ast::Ident) {
853        // The concrete value does not matter as we only use the scoping
854        // mechanism of `Scopes`, not the values themselves.
855        self.internal
856            .top
857            .bind(ident.get().clone(), Binding::detached(Value::None));
858    }
859
860    /// Capture a variable if it isn't internal.
861    fn capture(
862        &mut self,
863        ident: &EcoString,
864        getter: impl FnOnce(&'a Scopes<'a>, &str) -> HintedStrResult<&'a Binding>,
865    ) {
866        if self.internal.get(ident).is_ok() {
867            return;
868        }
869
870        let binding = match self.external {
871            Some(external) => match getter(external, ident) {
872                Ok(binding) => binding.capture(self.capturer),
873                Err(_) => return,
874            },
875            // The external scopes are only `None` when we are doing IDE capture
876            // analysis, in which case the concrete value doesn't matter.
877            None => Binding::detached(Value::None),
878        };
879
880        self.captures.bind(ident.clone(), binding);
881    }
882}
883
884#[cfg(test)]
885mod tests {
886    use typst_syntax::parse;
887
888    use super::*;
889
890    #[track_caller]
891    fn test(scopes: &Scopes, text: &str, result: &[&str]) {
892        let mut visitor = CapturesVisitor::new(Some(scopes), Capturer::Function);
893        let root = parse(text);
894        visitor.visit(&root);
895
896        let captures = visitor.finish();
897        let mut names: Vec<_> = captures.iter().map(|(k, ..)| k).collect();
898        names.sort();
899
900        assert_eq!(names, result);
901    }
902
903    #[test]
904    fn test_captures() {
905        let mut scopes = Scopes::new(None);
906        scopes.top.define("f", 0);
907        scopes.top.define("x", 0);
908        scopes.top.define("y", 0);
909        scopes.top.define("z", 0);
910        let s = &scopes;
911
912        // Let binding and function definition.
913        test(s, "#let x = x", &["x"]);
914        test(s, "#let x; #(x + y)", &["y"]);
915        test(s, "#let f(x, y) = x + y", &[]);
916        test(s, "#let f(x, y) = f", &[]);
917        test(s, "#let f = (x, y) => f", &["f"]);
918
919        // Closure with different kinds of params.
920        test(s, "#((x, y) => x + z)", &["z"]);
921        test(s, "#((x: y, z) => x + z)", &["y"]);
922        test(s, "#((..x) => x + y)", &["y"]);
923        test(s, "#((x, y: x + z) => x + y)", &["x", "z"]);
924        test(s, "#{x => x; x}", &["x"]);
925
926        // Show rule.
927        test(s, "#show y: x => x", &["y"]);
928        test(s, "#show y: x => x + z", &["y", "z"]);
929        test(s, "#show x: x => x", &["x"]);
930
931        // For loop.
932        test(s, "#for x in y { x + z }", &["y", "z"]);
933        test(s, "#for (x, y) in y { x + y }", &["y"]);
934        test(s, "#for x in y {} #x", &["x", "y"]);
935
936        // Import.
937        test(s, "#import z: x, y", &["z"]);
938        test(s, "#import x + y: x, y, z", &["x", "y"]);
939
940        // Blocks.
941        test(s, "#{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
942        test(s, "#[#let x = 1]#x", &["x"]);
943
944        // Field access.
945        test(s, "#x.y.f(z)", &["x", "z"]);
946
947        // Parenthesized expressions.
948        test(s, "#f(x: 1)", &["f"]);
949        test(s, "#(x: 1)", &[]);
950        test(s, "#(x = 1)", &["x"]);
951        test(s, "#(x += y)", &["x", "y"]);
952        test(s, "#{ (x, z) = (y, 1) }", &["x", "y", "z"]);
953        test(s, "#(x.at(y) = 5)", &["x", "y"]);
954    }
955
956    #[test]
957    fn test_captures_in_math() {
958        let mut scopes = Scopes::new(None);
959        scopes.top.define("f", 0);
960        scopes.top.define("x", 0);
961        scopes.top.define("y", 0);
962        scopes.top.define("z", 0);
963        // Multi-letter variables are required for math.
964        scopes.top.define("foo", 0);
965        scopes.top.define("bar", 0);
966        scopes.top.define("x-bar", 0);
967        scopes.top.define("x_bar", 0);
968        let s = &scopes;
969
970        // Basic math identifier differences.
971        test(s, "$ x f(z) $", &[]); // single letters not captured.
972        test(s, "$ #x #f(z) $", &["f", "x", "z"]);
973        test(s, "$ foo f(bar) $", &["bar", "foo"]);
974        test(s, "$ #foo[#$bar$] $", &["bar", "foo"]);
975        test(s, "$ #let foo = x; foo $", &["x"]);
976
977        // Math idents don't have dashes/underscores
978        test(s, "$ x-y x_y foo-x x_bar $", &["bar", "foo"]);
979        test(s, "$ #x-bar #x_bar $", &["x-bar", "x_bar"]);
980
981        // Named-params.
982        test(s, "$ foo(bar: y) $", &["foo"]);
983        test(s, "$ foo(x-y: 1, bar-z: 2) $", &["foo"]);
984
985        // Field access in math.
986        test(s, "$ foo.bar $", &["foo"]);
987        test(s, "$ foo.x $", &["foo"]);
988        test(s, "$ x.foo $", &["foo"]);
989        test(s, "$ foo . bar $", &["bar", "foo"]);
990        test(s, "$ foo.x.y.bar(z) $", &["foo"]);
991        test(s, "$ foo.x-bar $", &["bar", "foo"]);
992        test(s, "$ foo.x_bar $", &["bar", "foo"]);
993        test(s, "$ #x_bar.x-bar $", &["x_bar"]);
994    }
995}