typst_eval/
binding.rs

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