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
45pub(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
60fn 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 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#[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}