typst_eval/
binding.rs

1use ecow::eco_format;
2use rustc_hash::FxHashSet;
3use typst_library::diag::{At, SourceDiagnostic, SourceResult, bail, error};
4use typst_library::foundations::{Array, Dict, Value};
5use typst_syntax::ast::{self, AstNode};
6
7use crate::{Access, Eval, Vm};
8
9impl Eval for ast::LetBinding<'_> {
10    type Output = Value;
11
12    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
13        let value = match self.init() {
14            Some(expr) => expr.eval(vm)?,
15            None => Value::None,
16        };
17        if vm.flow.is_some() {
18            return Ok(Value::None);
19        }
20
21        match self.kind() {
22            ast::LetBindingKind::Normal(pattern) => destructure(vm, pattern, value)?,
23            ast::LetBindingKind::Closure(ident) => vm.define(ident, value),
24        }
25
26        Ok(Value::None)
27    }
28}
29
30impl Eval for ast::DestructAssignment<'_> {
31    type Output = Value;
32
33    fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
34        let value = self.value().eval(vm)?;
35        destructure_impl(vm, self.pattern(), value, &mut |vm, expr, value| {
36            let location = expr.access(vm)?;
37            *location = value;
38            Ok(())
39        })?;
40        Ok(Value::None)
41    }
42}
43
44/// Destructures a value into a pattern.
45pub(crate) fn destructure(
46    vm: &mut Vm,
47    pattern: ast::Pattern,
48    value: Value,
49) -> SourceResult<()> {
50    destructure_impl(vm, pattern, value, &mut |vm, expr, value| match expr {
51        ast::Expr::Ident(ident) => {
52            vm.define(ident, value);
53            Ok(())
54        }
55        _ => bail!(expr.span(), "cannot assign to this expression"),
56    })
57}
58
59/// Destruct the given value into the pattern and apply the function to each binding.
60fn destructure_impl<F>(
61    vm: &mut Vm,
62    pattern: ast::Pattern,
63    value: Value,
64    f: &mut F,
65) -> SourceResult<()>
66where
67    F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
68{
69    match pattern {
70        ast::Pattern::Normal(expr) => f(vm, expr, value)?,
71        ast::Pattern::Placeholder(_) => {}
72        ast::Pattern::Parenthesized(parenthesized) => {
73            destructure_impl(vm, parenthesized.pattern(), value, f)?
74        }
75        ast::Pattern::Destructuring(destruct) => match value {
76            Value::Array(value) => destructure_array(vm, destruct, value, f)?,
77            Value::Dict(value) => destructure_dict(vm, destruct, value, f)?,
78            _ => bail!(pattern.span(), "cannot destructure {}", value.ty()),
79        },
80    }
81    Ok(())
82}
83
84fn destructure_array<F>(
85    vm: &mut Vm,
86    destruct: ast::Destructuring,
87    value: Array,
88    f: &mut F,
89) -> SourceResult<()>
90where
91    F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
92{
93    let len = value.as_slice().len();
94    let mut i = 0;
95
96    for p in destruct.items() {
97        match p {
98            ast::DestructuringItem::Pattern(pattern) => {
99                let Ok(v) = value.at(i as i64, None) else {
100                    bail!(wrong_number_of_elements(destruct, len));
101                };
102                destructure_impl(vm, pattern, v, f)?;
103                i += 1;
104            }
105            ast::DestructuringItem::Spread(spread) => {
106                let sink_size = (1 + len).checked_sub(destruct.items().count());
107                let sink = sink_size.and_then(|s| value.as_slice().get(i..i + s));
108                let (Some(sink_size), Some(sink)) = (sink_size, sink) else {
109                    bail!(wrong_number_of_elements(destruct, len));
110                };
111                if let Some(expr) = spread.sink_expr() {
112                    f(vm, expr, Value::Array(sink.into()))?;
113                }
114                i += sink_size;
115            }
116            ast::DestructuringItem::Named(named) => {
117                bail!(named.span(), "cannot destructure named pattern from an array")
118            }
119        }
120    }
121
122    if i < len {
123        bail!(wrong_number_of_elements(destruct, len));
124    }
125
126    Ok(())
127}
128
129fn destructure_dict<F>(
130    vm: &mut Vm,
131    destruct: ast::Destructuring,
132    dict: Dict,
133    f: &mut F,
134) -> SourceResult<()>
135where
136    F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
137{
138    let mut sink = None;
139    let mut used = FxHashSet::default();
140
141    for p in destruct.items() {
142        match p {
143            // Shorthand for a direct identifier.
144            ast::DestructuringItem::Pattern(ast::Pattern::Normal(ast::Expr::Ident(
145                ident,
146            ))) => {
147                let v = dict.get(&ident).at(ident.span())?;
148                f(vm, ast::Expr::Ident(ident), v.clone())?;
149                used.insert(ident.get().clone());
150            }
151            ast::DestructuringItem::Named(named) => {
152                let name = named.name();
153                let v = dict.get(&name).at(name.span())?;
154                destructure_impl(vm, named.pattern(), v.clone(), f)?;
155                used.insert(name.get().clone());
156            }
157            ast::DestructuringItem::Spread(spread) => sink = spread.sink_expr(),
158            ast::DestructuringItem::Pattern(expr) => {
159                bail!(expr.span(), "cannot destructure unnamed pattern from dictionary");
160            }
161        }
162    }
163
164    if let Some(expr) = sink {
165        let mut sink = Dict::new();
166        for (key, value) in dict {
167            if !used.contains(key.as_str()) {
168                sink.insert(key, value);
169            }
170        }
171        f(vm, expr, Value::Dict(sink))?;
172    }
173
174    Ok(())
175}
176
177/// The error message when the number of elements of the destructuring and the
178/// array is mismatched.
179#[cold]
180fn wrong_number_of_elements(
181    destruct: ast::Destructuring,
182    len: usize,
183) -> SourceDiagnostic {
184    let mut count = 0;
185    let mut spread = false;
186
187    for p in destruct.items() {
188        match p {
189            ast::DestructuringItem::Pattern(_) => count += 1,
190            ast::DestructuringItem::Spread(_) => spread = true,
191            ast::DestructuringItem::Named(_) => {}
192        }
193    }
194
195    let quantifier = if len > count { "too many" } else { "not enough" };
196    let expected = match (spread, count) {
197        (true, 1) => "at least 1 element".into(),
198        (true, c) => eco_format!("at least {c} elements"),
199        (false, 0) => "an empty array".into(),
200        (false, 1) => "a single element".into(),
201        (false, c) => eco_format!("{c} elements",),
202    };
203
204    error!(
205        destruct.span(), "{quantifier} elements to destructure";
206        hint: "the provided array has a length of {len}, \
207               but the pattern expects {expected}",
208    )
209}