1use comemo::{Tracked, TrackedMut};
2use ecow::{eco_format, EcoString, EcoVec};
3use typst_library::diag::{
4 bail, error, At, HintedStrResult, HintedString, SourceDiagnostic, SourceResult,
5 Trace, Tracepoint,
6};
7use typst_library::engine::{Engine, Sink, Traced};
8use typst_library::foundations::{
9 Arg, Args, Binding, Capturer, Closure, Content, Context, Func, NativeElement, Scope,
10 Scopes, SymbolElem, Value,
11};
12use typst_library::introspection::Introspector;
13use typst_library::math::LrElem;
14use typst_library::routines::Routines;
15use typst_library::World;
16use typst_syntax::ast::{self, AstNode, Ident};
17use typst_syntax::{Span, Spanned, SyntaxNode};
18use typst_utils::LazyHash;
19
20use crate::{call_method_mut, is_mutating_method, Access, Eval, FlowEvent, Route, Vm};
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 in_math = in_math(callee);
29 let callee_span = callee.span();
30 let args = self.args();
31 let trailing_comma = args.trailing_comma();
32
33 vm.engine.route.check_call_depth().at(span)?;
34
35 let (callee, args) = if let ast::Expr::FieldAccess(access) = callee {
37 let target = access.target();
38 let field = access.field();
39 match eval_field_call(target, field, args, span, vm)? {
40 FieldCall::Normal(callee, args) => (callee, args),
41 FieldCall::Resolved(value) => return Ok(value),
42 }
43 } else {
44 (callee.eval(vm)?, args.eval(vm)?.spanned(span))
46 };
47
48 let func_result = callee.clone().cast::<Func>();
49 if in_math && func_result.is_err() {
50 return wrap_args_in_math(callee, callee_span, args, trailing_comma);
51 }
52
53 let func = func_result
54 .map_err(|err| hint_if_shadowed_std(vm, &self.callee(), err))
55 .at(callee_span)?;
56
57 let point = || Tracepoint::Call(func.name().map(Into::into));
58 let f = || {
59 func.call(&mut vm.engine, vm.context, args)
60 .trace(vm.world(), point, span)
61 };
62
63 #[cfg(target_arch = "wasm32")]
65 return f();
66
67 #[cfg(not(target_arch = "wasm32"))]
68 stacker::maybe_grow(32 * 1024, 2 * 1024 * 1024, f)
69 }
70}
71
72impl Eval for ast::Args<'_> {
73 type Output = Args;
74
75 fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
76 let mut items = EcoVec::with_capacity(self.items().count());
77
78 for arg in self.items() {
79 let span = arg.span();
80 match arg {
81 ast::Arg::Pos(expr) => {
82 items.push(Arg {
83 span,
84 name: None,
85 value: Spanned::new(expr.eval(vm)?, expr.span()),
86 });
87 }
88 ast::Arg::Named(named) => {
89 let expr = named.expr();
90 items.push(Arg {
91 span,
92 name: Some(named.name().get().clone().into()),
93 value: Spanned::new(expr.eval(vm)?, expr.span()),
94 });
95 }
96 ast::Arg::Spread(spread) => match spread.expr().eval(vm)? {
97 Value::None => {}
98 Value::Array(array) => {
99 items.extend(array.into_iter().map(|value| Arg {
100 span,
101 name: None,
102 value: Spanned::new(value, span),
103 }));
104 }
105 Value::Dict(dict) => {
106 items.extend(dict.into_iter().map(|(key, value)| Arg {
107 span,
108 name: Some(key),
109 value: Spanned::new(value, span),
110 }));
111 }
112 Value::Args(args) => items.extend(args.items),
113 v => bail!(spread.span(), "cannot spread {}", v.ty()),
114 },
115 }
116 }
117
118 Ok(Args { span: Span::detached(), items })
121 }
122}
123
124impl Eval for ast::Closure<'_> {
125 type Output = Value;
126
127 fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
128 let mut defaults = Vec::new();
130 for param in self.params().children() {
131 if let ast::Param::Named(named) = param {
132 defaults.push(named.expr().eval(vm)?);
133 }
134 }
135
136 let captured = {
138 let mut visitor = CapturesVisitor::new(Some(&vm.scopes), Capturer::Function);
139 visitor.visit(self.to_untyped());
140 visitor.finish()
141 };
142
143 let closure = Closure {
145 node: self.to_untyped().clone(),
146 defaults,
147 captured,
148 num_pos_params: self
149 .params()
150 .children()
151 .filter(|p| matches!(p, ast::Param::Pos(_)))
152 .count(),
153 };
154
155 Ok(Value::Func(Func::from(closure).spanned(self.params().span())))
156 }
157}
158
159#[comemo::memoize]
161#[allow(clippy::too_many_arguments)]
162pub fn eval_closure(
163 func: &Func,
164 closure: &LazyHash<Closure>,
165 routines: &Routines,
166 world: Tracked<dyn World + '_>,
167 introspector: Tracked<Introspector>,
168 traced: Tracked<Traced>,
169 sink: TrackedMut<Sink>,
170 route: Tracked<Route>,
171 context: Tracked<Context>,
172 mut args: Args,
173) -> SourceResult<Value> {
174 let (name, params, body) = match closure.node.cast::<ast::Closure>() {
175 Some(node) => (node.name(), node.params(), node.body()),
176 None => (None, ast::Params::default(), closure.node.cast().unwrap()),
177 };
178
179 let mut scopes = Scopes::new(None);
182 scopes.top = closure.captured.clone();
183
184 let engine = Engine {
186 routines,
187 world,
188 introspector,
189 traced,
190 sink,
191 route: Route::extend(route),
192 };
193
194 let mut vm = Vm::new(engine, context, scopes, body.span());
196
197 if let Some(name) = name {
199 vm.define(name, func.clone());
200 }
201
202 let num_pos_args = args.to_pos().len();
203 let sink_size = num_pos_args.checked_sub(closure.num_pos_params);
204
205 let mut sink = None;
206 let mut sink_pos_values = None;
207 let mut defaults = closure.defaults.iter();
208 for p in params.children() {
209 match p {
210 ast::Param::Pos(pattern) => match pattern {
211 ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
212 vm.define(ident, args.expect::<Value>(&ident)?)
213 }
214 pattern => {
215 crate::destructure(
216 &mut vm,
217 pattern,
218 args.expect::<Value>("pattern parameter")?,
219 )?;
220 }
221 },
222 ast::Param::Spread(spread) => {
223 sink = Some(spread.sink_ident());
224 if let Some(sink_size) = sink_size {
225 sink_pos_values = Some(args.consume(sink_size)?);
226 }
227 }
228 ast::Param::Named(named) => {
229 let name = named.name();
230 let default = defaults.next().unwrap();
231 let value =
232 args.named::<Value>(&name)?.unwrap_or_else(|| default.clone());
233 vm.define(name, value);
234 }
235 }
236 }
237
238 if let Some(sink) = sink {
239 let mut remaining_args = args.take();
241 if let Some(sink_name) = sink {
242 if let Some(sink_pos_values) = sink_pos_values {
243 remaining_args.items.extend(sink_pos_values);
244 }
245 vm.define(sink_name, remaining_args);
246 }
247 }
248
249 args.finish()?;
251
252 let output = body.eval(&mut vm)?;
254 match vm.flow {
255 Some(FlowEvent::Return(_, Some(explicit), _)) => return Ok(explicit),
256 Some(FlowEvent::Return(_, None, _)) => {}
257 Some(flow) => bail!(flow.forbidden()),
258 None => {}
259 }
260
261 Ok(output)
262}
263
264enum FieldCall {
268 Normal(Value, Args),
269 Resolved(Value),
270}
271
272fn eval_field_call(
286 target_expr: ast::Expr,
287 field: Ident,
288 args: ast::Args,
289 span: Span,
290 vm: &mut Vm,
291) -> SourceResult<FieldCall> {
292 let (target, mut args) = if is_mutating_method(&field) {
294 let args = args.eval(vm)?.spanned(span);
298 match target_expr.access(vm)? {
304 target @ (Value::Array(_) | Value::Dict(_)) => {
306 let value = call_method_mut(target, &field, args, span);
307 let point = || Tracepoint::Call(Some(field.get().clone()));
308 return Ok(FieldCall::Resolved(value.trace(vm.world(), point, span)?));
309 }
310 target => (target.clone(), args),
311 }
312 } else {
313 let target = target_expr.eval(vm)?;
314 let args = args.eval(vm)?.spanned(span);
315 (target, args)
316 };
317
318 let field_span = field.span();
319 let sink = (&mut vm.engine, field_span);
320 if let Some(callee) = target.ty().scope().get(&field) {
321 args.insert(0, target_expr.span(), target);
322 Ok(FieldCall::Normal(callee.read_checked(sink).clone(), args))
323 } else if let Value::Content(content) = &target {
324 if let Some(callee) = content.elem().scope().get(&field) {
325 args.insert(0, target_expr.span(), target);
326 Ok(FieldCall::Normal(callee.read_checked(sink).clone(), args))
327 } else {
328 bail!(missing_field_call_error(target, field))
329 }
330 } else if matches!(
331 target,
332 Value::Symbol(_) | Value::Func(_) | Value::Type(_) | Value::Module(_)
333 ) {
334 let value = target.field(&field, sink).at(field_span)?;
337 Ok(FieldCall::Normal(value, args))
338 } else {
339 bail!(missing_field_call_error(target, field))
341 }
342}
343
344fn missing_field_call_error(target: Value, field: Ident) -> SourceDiagnostic {
346 let mut error = match &target {
347 Value::Content(content) => error!(
348 field.span(),
349 "element {} has no method `{}`",
350 content.elem().name(),
351 field.as_str(),
352 ),
353 _ => error!(
354 field.span(),
355 "type {} has no method `{}`",
356 target.ty(),
357 field.as_str()
358 ),
359 };
360
361 match target {
362 Value::Dict(ref dict) if matches!(dict.get(&field), Ok(Value::Func(_))) => {
363 error.hint(eco_format!(
364 "to call the function stored in the dictionary, surround \
365 the field access with parentheses, e.g. `(dict.{})(..)`",
366 field.as_str(),
367 ));
368 }
369 _ if target.field(&field, ()).is_ok() => {
370 error.hint(eco_format!(
371 "did you mean to access the field `{}`?",
372 field.as_str(),
373 ));
374 }
375 _ => {}
376 }
377
378 error
379}
380
381fn in_math(expr: ast::Expr) -> bool {
383 match expr {
384 ast::Expr::MathIdent(_) => true,
385 ast::Expr::FieldAccess(access) => in_math(access.target()),
386 _ => false,
387 }
388}
389
390fn wrap_args_in_math(
392 callee: Value,
393 callee_span: Span,
394 mut args: Args,
395 trailing_comma: bool,
396) -> SourceResult<Value> {
397 let mut body = Content::empty();
398 for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
399 if i > 0 {
400 body += SymbolElem::packed(',');
401 }
402 body += arg;
403 }
404 if trailing_comma {
405 body += SymbolElem::packed(',');
406 }
407 Ok(Value::Content(
408 callee.display().spanned(callee_span)
409 + LrElem::new(SymbolElem::packed('(') + body + SymbolElem::packed(')'))
410 .pack()
411 .spanned(args.span),
412 ))
413}
414
415fn hint_if_shadowed_std(
417 vm: &mut Vm,
418 callee: &ast::Expr,
419 mut err: HintedString,
420) -> HintedString {
421 if let ast::Expr::Ident(ident) = callee {
422 let ident = ident.get();
423 if vm.scopes.check_std_shadowed(ident) {
424 err.hint(eco_format!(
425 "use `std.{ident}` to access the shadowed standard library function",
426 ));
427 }
428 }
429 err
430}
431
432pub struct CapturesVisitor<'a> {
434 external: Option<&'a Scopes<'a>>,
435 internal: Scopes<'a>,
436 captures: Scope,
437 capturer: Capturer,
438}
439
440impl<'a> CapturesVisitor<'a> {
441 pub fn new(external: Option<&'a Scopes<'a>>, capturer: Capturer) -> Self {
443 Self {
444 external,
445 internal: Scopes::new(None),
446 captures: Scope::new(),
447 capturer,
448 }
449 }
450
451 pub fn finish(self) -> Scope {
453 self.captures
454 }
455
456 pub fn visit(&mut self, node: &SyntaxNode) {
458 match node.cast() {
459 Some(ast::Expr::Ident(ident)) => self.capture(ident.get(), Scopes::get),
464 Some(ast::Expr::MathIdent(ident)) => {
465 self.capture(ident.get(), Scopes::get_in_math)
466 }
467
468 Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
470 self.internal.enter();
471 for child in node.children() {
472 self.visit(child);
473 }
474 self.internal.exit();
475 }
476
477 Some(ast::Expr::FieldAccess(access)) => {
479 self.visit(access.target().to_untyped());
480 }
481
482 Some(ast::Expr::Closure(expr)) => {
486 for param in expr.params().children() {
487 if let ast::Param::Named(named) = param {
488 self.visit(named.expr().to_untyped());
489 }
490 }
491
492 self.internal.enter();
493 if let Some(name) = expr.name() {
494 self.bind(name);
495 }
496
497 for param in expr.params().children() {
498 match param {
499 ast::Param::Pos(pattern) => {
500 for ident in pattern.bindings() {
501 self.bind(ident);
502 }
503 }
504 ast::Param::Named(named) => self.bind(named.name()),
505 ast::Param::Spread(spread) => {
506 if let Some(ident) = spread.sink_ident() {
507 self.bind(ident);
508 }
509 }
510 }
511 }
512
513 self.visit(expr.body().to_untyped());
514 self.internal.exit();
515 }
516
517 Some(ast::Expr::Let(expr)) => {
520 if let Some(init) = expr.init() {
521 self.visit(init.to_untyped());
522 }
523
524 for ident in expr.kind().bindings() {
525 self.bind(ident);
526 }
527 }
528
529 Some(ast::Expr::For(expr)) => {
533 self.visit(expr.iterable().to_untyped());
534 self.internal.enter();
535
536 let pattern = expr.pattern();
537 for ident in pattern.bindings() {
538 self.bind(ident);
539 }
540
541 self.visit(expr.body().to_untyped());
542 self.internal.exit();
543 }
544
545 Some(ast::Expr::Import(expr)) => {
548 self.visit(expr.source().to_untyped());
549 if let Some(ast::Imports::Items(items)) = expr.imports() {
550 for item in items.iter() {
551 self.bind(item.bound_name());
552 }
553 }
554 }
555
556 _ => {
557 if let Some(named) = node.cast::<ast::Named>() {
559 self.visit(named.expr().to_untyped());
560 return;
561 }
562
563 for child in node.children() {
565 self.visit(child);
566 }
567 }
568 }
569 }
570
571 fn bind(&mut self, ident: ast::Ident) {
573 self.internal
576 .top
577 .bind(ident.get().clone(), Binding::detached(Value::None));
578 }
579
580 fn capture(
582 &mut self,
583 ident: &EcoString,
584 getter: impl FnOnce(&'a Scopes<'a>, &str) -> HintedStrResult<&'a Binding>,
585 ) {
586 if self.internal.get(ident).is_ok() {
587 return;
588 }
589
590 let binding = match self.external {
591 Some(external) => match getter(external, ident) {
592 Ok(binding) => binding.capture(self.capturer),
593 Err(_) => return,
594 },
595 None => Binding::detached(Value::None),
598 };
599
600 self.captures.bind(ident.clone(), binding);
601 }
602}
603
604#[cfg(test)]
605mod tests {
606 use typst_syntax::parse;
607
608 use super::*;
609
610 #[track_caller]
611 fn test(scopes: &Scopes, text: &str, result: &[&str]) {
612 let mut visitor = CapturesVisitor::new(Some(scopes), Capturer::Function);
613 let root = parse(text);
614 visitor.visit(&root);
615
616 let captures = visitor.finish();
617 let mut names: Vec<_> = captures.iter().map(|(k, ..)| k).collect();
618 names.sort();
619
620 assert_eq!(names, result);
621 }
622
623 #[test]
624 fn test_captures() {
625 let mut scopes = Scopes::new(None);
626 scopes.top.define("f", 0);
627 scopes.top.define("x", 0);
628 scopes.top.define("y", 0);
629 scopes.top.define("z", 0);
630 let s = &scopes;
631
632 test(s, "#let x = x", &["x"]);
634 test(s, "#let x; #(x + y)", &["y"]);
635 test(s, "#let f(x, y) = x + y", &[]);
636 test(s, "#let f(x, y) = f", &[]);
637 test(s, "#let f = (x, y) => f", &["f"]);
638
639 test(s, "#((x, y) => x + z)", &["z"]);
641 test(s, "#((x: y, z) => x + z)", &["y"]);
642 test(s, "#((..x) => x + y)", &["y"]);
643 test(s, "#((x, y: x + z) => x + y)", &["x", "z"]);
644 test(s, "#{x => x; x}", &["x"]);
645
646 test(s, "#show y: x => x", &["y"]);
648 test(s, "#show y: x => x + z", &["y", "z"]);
649 test(s, "#show x: x => x", &["x"]);
650
651 test(s, "#for x in y { x + z }", &["y", "z"]);
653 test(s, "#for (x, y) in y { x + y }", &["y"]);
654 test(s, "#for x in y {} #x", &["x", "y"]);
655
656 test(s, "#import z: x, y", &["z"]);
658 test(s, "#import x + y: x, y, z", &["x", "y"]);
659
660 test(s, "#{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
662 test(s, "#[#let x = 1]#x", &["x"]);
663
664 test(s, "#x.y.f(z)", &["x", "z"]);
666
667 test(s, "#f(x: 1)", &["f"]);
669 test(s, "#(x: 1)", &[]);
670 test(s, "#(x = 1)", &["x"]);
671 test(s, "#(x += y)", &["x", "y"]);
672 test(s, "#{ (x, z) = (y, 1) }", &["x", "y", "z"]);
673 test(s, "#(x.at(y) = 5)", &["x", "y"]);
674 }
675
676 #[test]
677 fn test_captures_in_math() {
678 let mut scopes = Scopes::new(None);
679 scopes.top.define("f", 0);
680 scopes.top.define("x", 0);
681 scopes.top.define("y", 0);
682 scopes.top.define("z", 0);
683 scopes.top.define("foo", 0);
685 scopes.top.define("bar", 0);
686 scopes.top.define("x-bar", 0);
687 scopes.top.define("x_bar", 0);
688 let s = &scopes;
689
690 test(s, "$ x f(z) $", &[]); test(s, "$ #x #f(z) $", &["f", "x", "z"]);
693 test(s, "$ foo f(bar) $", &["bar", "foo"]);
694 test(s, "$ #foo[#$bar$] $", &["bar", "foo"]);
695 test(s, "$ #let foo = x; foo $", &["x"]);
696
697 test(s, "$ x-y x_y foo-x x_bar $", &["bar", "foo"]);
699 test(s, "$ #x-bar #x_bar $", &["x-bar", "x_bar"]);
700
701 test(s, "$ foo(bar: y) $", &["foo"]);
703 test(s, "$ foo(x-y: 1, bar-z: 2) $", &["foo"]);
704
705 test(s, "$ foo.bar $", &["foo"]);
707 test(s, "$ foo.x $", &["foo"]);
708 test(s, "$ x.foo $", &["foo"]);
709 test(s, "$ foo . bar $", &["bar", "foo"]);
710 test(s, "$ foo.x.y.bar(z) $", &["foo"]);
711 test(s, "$ foo.x-bar $", &["bar", "foo"]);
712 test(s, "$ foo.x_bar $", &["bar", "foo"]);
713 test(s, "$ #x_bar.x-bar $", &["x_bar"]);
714 }
715}