compose_eval/expression/
bindings.rs

1use crate::vm::{ErrorMode, Machine};
2use crate::{Eval, Evaluated};
3use compose_error_codes::E0301_ARRAY_DESTRUCTURING_WRONG_NUMBER_OF_ELEMENTS;
4use compose_library::diag::{bail, error, At, SourceDiagnostic, SourceResult, Spanned};
5use compose_library::{diag, ArrayValue, BindingKind, IntoValue, Value, Visibility, Vm};
6use compose_syntax::ast;
7use compose_syntax::ast::{AstNode, DestructuringItem, Expr, Pattern};
8use ecow::{eco_format, eco_vec};
9
10impl<'a> Eval for ast::Ident<'a> {
11    fn eval(self, vm: &mut Machine) -> SourceResult<Evaluated> {
12        let span = self.span();
13        let binding = vm.frames.top.scopes.get(&self).at(span)?;
14
15        let mutable = binding.is_mutable();
16
17        Ok(Evaluated::new(
18            binding.read_checked(span, &mut vm.engine.sink).clone(),
19            mutable,
20        )
21        .with_origin(binding.span()))
22    }
23}
24
25impl<'a> Eval for ast::LetBinding<'a> {
26    fn eval(self, vm: &mut Machine) -> SourceResult<Evaluated> {
27        let has_init = self.has_initial_value();
28        let init = self.initial_value();
29
30        let binding_kind = match (self.is_mut(), has_init) {
31            (true, true) => BindingKind::Mutable,
32            (false, true) => BindingKind::Immutable { first_assign: None },
33            (true, false) => BindingKind::UninitializedMutable,
34            (false, false) => BindingKind::Uninitialized,
35        };
36
37        let visibility = if self.is_public() {
38            Visibility::Public
39        } else {
40            Visibility::Private
41        };
42
43        let value = match init {
44            Some(expr) => {
45                vm.with_closure_capture_errors_mode(ErrorMode::Deferred, |vm| expr.eval(vm))?
46            }
47            None => Evaluated::unit(),
48        };
49
50        // handle control flow
51        if vm.flow.is_some() {
52            return Ok(Evaluated::unit());
53        }
54
55        destructure_pattern(vm, self.pattern(), value.value, binding_kind, visibility)?;
56
57        Ok(Evaluated::unit())
58    }
59}
60
61pub fn destructure_pattern(
62    vm: &mut Machine,
63    pattern: Pattern,
64    value: Value,
65    binding_kind: BindingKind,
66    visibility: Visibility,
67) -> SourceResult<()> {
68    destructure_impl(vm, pattern, value, &mut |vm, expr, value| match expr {
69        Expr::Ident(ident) => {
70            let name = ident.get().clone();
71            let spanned = value
72                .named(Spanned::new(name, ident.span()))
73                // Now that the names have been added, make sure any deferred errors are resolved
74                .resolved()?;
75
76            vm.define(ident, spanned, binding_kind, visibility)?;
77
78            Ok(())
79        }
80        _ => Err(eco_vec![diag::SourceDiagnostic::error(
81            expr.span(),
82            "cannot destructure pattern",
83        )]),
84    })
85}
86
87fn destructure_impl(
88    vm: &mut Machine,
89    pattern: Pattern,
90    value: Value,
91    bind: &mut impl Fn(&mut Machine, Expr, Value) -> SourceResult<()>,
92) -> SourceResult<()> {
93    match pattern {
94        Pattern::Single(expr) => bind(vm, expr, value)?,
95        Pattern::PlaceHolder(_) => {} // A placeholder means we discard the value, no need to bind
96        Pattern::Destructuring(destruct) => match value {
97            Value::Array(value) => destructure_array(vm, destruct, value, bind)?,
98            Value::Map(value) => destructure_map(vm, destruct, value, bind)?,
99            _ => bail!(pattern.span(), "cannot destructure {}", value.ty()),
100        }
101    };
102
103    Ok(())
104}
105
106fn destructure_array(vm: &mut Machine, destruct: ast::Destructuring, value: ArrayValue, bind: &mut impl Fn(&mut Machine, Expr, Value) -> SourceResult<()>) -> SourceResult<()> {
107    let arr = value.heap_ref().get_unwrap(&vm.heap).clone();
108
109    let len = arr.len();
110    let mut index = 0;
111
112    for p in destruct.items() {
113        match p {
114            DestructuringItem::Pattern(pat) => {
115                let Some(v) = arr.get(index) else {
116                    bail!(wrong_number_of_elements(destruct, len))
117                };
118
119                destructure_impl(vm, pat, v.clone(), bind)?;
120                index += 1;
121            }
122            DestructuringItem::Named(named) => {
123                bail!(named.span(), "cannot destructure a named pattern from an array")
124            }
125            DestructuringItem::Spread(spread) => {
126                // The number of elements that have not been bound by a destructuring item and will be bound by the spread
127                let sink_size = (1 + len).checked_sub(destruct.items().count());
128
129                // The items that will be bound by the spread
130                let sunk_items = sink_size.and_then(|n| arr.get(index..index + n));
131
132                let (Some(sink_size), Some(sunk_items)) = (sink_size, sunk_items) else {
133                    bail!(wrong_number_of_elements(destruct, len))
134                };
135
136                if let Some(expr) = spread.sink_expr() {
137                    let sunk_arr = ArrayValue::from(vm.heap_mut(), sunk_items.to_vec()).into_value();
138                    bind(vm, expr, sunk_arr)?;
139                }
140                index += sink_size;
141            }
142        }
143    }
144
145    // require all items to be bound
146    if index != len {
147        bail!(wrong_number_of_elements(destruct, len))
148    }
149
150    Ok(())
151}
152
153#[allow(unused)]
154fn destructure_map(vm: &mut Machine, destruct: ast::Destructuring, value: compose_library::MapValue, bind: &mut impl Fn(&mut Machine, Expr, Value) -> SourceResult<()>) -> SourceResult<()> {
155
156    unimplemented!("destructuring maps")
157}
158
159
160/// The error message when the number of elements of the destructuring and the
161/// array is mismatched.
162#[cold]
163fn wrong_number_of_elements(
164    destruct: ast::Destructuring,
165    len: usize,
166) -> SourceDiagnostic {
167    let mut count = 0;
168    let mut spread = false;
169
170    for p in destruct.items() {
171        match p {
172            DestructuringItem::Pattern(_) => count += 1,
173            DestructuringItem::Spread(_) => spread = true,
174            DestructuringItem::Named(_) => {}
175        }
176    }
177
178    let quantifier = if len > count { "too many" } else { "not enough" };
179    let expected = match (spread, count) {
180        (true, 1) => "at least 1 element".into(),
181        (true, c) => eco_format!("at least {c} elements"),
182        (false, 0) => "an empty array".into(),
183        (false, 1) => "a single element".into(),
184        (false, c) => eco_format!("{c} elements",),
185    };
186
187    let mut err = error!(
188        destruct.span(), "{quantifier} elements to destructure";
189        hint: "the provided array has a length of {len}, \
190               but the pattern expects {expected}";
191        code: &E0301_ARRAY_DESTRUCTURING_WRONG_NUMBER_OF_ELEMENTS;
192    );
193
194    if len > count {
195        err.hint("use `..` to ignore the remaining elements, or `..rest` to bind them");
196    }
197
198    err
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use crate::test::{assert_eval, assert_eval_with_vm, eval_code_with_vm, TestWorld};
205    use compose_error_codes::{E0004_MUTATE_IMMUTABLE_VARIABLE, W0001_USED_UNINITIALIZED_VARIABLE};
206    use compose_library::{BindingKind, UnitValue};
207
208    #[test]
209    fn test_let_binding() {
210        let world = TestWorld::new();
211        let mut vm = Machine::new(&world);
212        eval_code_with_vm(&mut vm, &world, "let a = 3")
213            .value
214            .expect("failed to evaluate");
215
216        let binding = vm.get("a").unwrap();
217
218        assert_eq!(binding.read(), &Value::Int(3));
219        assert_eq!(
220            binding.kind(),
221            BindingKind::Immutable { first_assign: None }
222        );
223    }
224
225    #[test]
226    fn test_let_mut_binding() {
227        let world = TestWorld::new();
228        let mut vm = Machine::new(&world);
229        assert_eval_with_vm(&mut vm, &world, "let mut a = 3");
230
231        let binding = vm.get("a").unwrap();
232
233        assert_eq!(binding.read(), &Value::Int(3));
234        assert_eq!(binding.kind(), BindingKind::Mutable);
235    }
236
237    #[test]
238    fn test_let_binding_without_value() {
239        let world = TestWorld::new();
240        let mut vm = Machine::new(&world);
241        assert_eval_with_vm(&mut vm, &world, "let a");
242
243        let binding = vm.get("a").unwrap();
244
245        assert_eq!(binding.read(), &Value::unit());
246        assert_eq!(binding.kind(), BindingKind::Uninitialized);
247    }
248
249    #[test]
250    fn test_let_mut_binding_without_value() {
251        let world = TestWorld::new();
252        let mut vm = Machine::new(&world);
253        assert_eval_with_vm(&mut vm, &world, "let mut a");
254
255        let binding = vm.get("a").unwrap();
256        assert_eq!(binding.read(), &Value::unit());
257        assert_eq!(binding.kind(), BindingKind::UninitializedMutable);
258    }
259
260    #[test]
261    fn test_read_ident() {
262        let world = TestWorld::new();
263        let mut vm = Machine::new(&world);
264        // define the variable
265        assert_eval_with_vm(&mut vm, &world, "let a = 3");
266        // read the variable
267        let result = assert_eval_with_vm(&mut vm, &world, "a");
268        assert_eq!(result, Value::Int(3));
269    }
270
271    #[test]
272    fn test_read_uninitialised_variable() {
273        let world = TestWorld::new();
274        let mut vm = Machine::new(&world);
275        // set up the variable
276        assert_eval_with_vm(&mut vm, &world, "let a");
277        // reading emits warning
278        let result = eval_code_with_vm(&mut vm, &world, "a")
279            .assert_warnings(&[W0001_USED_UNINITIALIZED_VARIABLE])
280            .assert_no_errors()
281            .get_value();
282
283        assert_eq!(result, Value::unit());
284    }
285
286    #[test]
287    fn integration() {
288        let result = assert_eval(
289            r#"
290            let a = 3;
291            let b = 4;
292            let c = a + b;
293            c * 2;
294        "#,
295        );
296
297        assert_eq!(result, Value::Int(14));
298    }
299
300    #[test]
301    fn assign_mut() {
302        let world = TestWorld::new();
303        let mut vm = Machine::new(&world);
304        assert_eval_with_vm(&mut vm, &world, "let mut a = 3");
305
306        let binding = vm.get("a").unwrap();
307        assert_eq!(binding.kind(), BindingKind::Mutable);
308
309        assert_eval_with_vm(&mut vm, &world, "a = 4");
310        let result = assert_eval_with_vm(&mut vm, &world, "a");
311        assert_eq!(result, Value::Int(4));
312
313        // should still be mutable
314        let binding = vm.get("a").unwrap();
315        assert_eq!(binding.kind(), BindingKind::Mutable);
316    }
317
318    #[test]
319    fn assign_mut_uninitialised() {
320        let world = TestWorld::new();
321        let mut vm = Machine::new(&world);
322        assert_eval_with_vm(&mut vm, &world, "let mut a");
323
324        let binding = vm.get("a").unwrap();
325        assert_eq!(binding.kind(), BindingKind::UninitializedMutable);
326        assert_eq!(binding.read(), &Value::Unit(UnitValue));
327
328        assert_eval_with_vm(&mut vm, &world, "a = 4");
329        let result = assert_eval_with_vm(&mut vm, &world, "a");
330        assert_eq!(result, Value::Int(4));
331
332        // should now be mutable
333        let binding = vm.get("a").unwrap();
334        assert_eq!(binding.kind(), BindingKind::Mutable);
335    }
336
337    #[test]
338    fn assign_uninitialised() {
339        let world = TestWorld::new();
340        let mut vm = Machine::new(&world);
341        assert_eval_with_vm(&mut vm, &world, "let a");
342
343        let binding = vm.get("a").unwrap();
344        assert_eq!(binding.kind(), BindingKind::Uninitialized);
345        assert_eq!(binding.read(), &Value::unit());
346
347        assert_eval_with_vm(&mut vm, &world, "a = 4");
348        let result = assert_eval_with_vm(&mut vm, &world, "a");
349        assert_eq!(result, Value::Int(4));
350
351        // should now be immutable
352        let binding = vm.get("a").unwrap();
353        assert!(matches!(binding.kind(), BindingKind::Immutable { .. }));
354    }
355
356    #[test]
357    fn assign_immut_error() {
358        let world = TestWorld::new();
359        let mut vm = Machine::new(&world);
360        assert_eval_with_vm(&mut vm, &world, "let a = 3");
361        let binding = vm.get("a").unwrap();
362        assert_eq!(
363            binding.kind(),
364            BindingKind::Immutable { first_assign: None }
365        );
366        assert_eq!(binding.read(), &Value::Int(3));
367
368        eval_code_with_vm(&mut vm, &world, "a = 4")
369            .assert_errors(&[E0004_MUTATE_IMMUTABLE_VARIABLE]);
370    }
371}