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
44pub(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
59fn 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 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#[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}