1
//! `LET` / `LET*` eval-only handlers plus `eval_body` — the body
2
//! sequencing shared by every binding form's tail. `eval_body` is the
3
//! piece that walks the body for accumulator setf chains so the
4
//! promoted runtime type matches the codegen path's `PairElement`.
5

            
6
use crate::ast::{Expr, WasmType};
7
use crate::compiler::expr::eval_value;
8
use crate::error::{Error, Result};
9
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
10

            
11
use super::infer::infer_runtime_type_with_body;
12
use super::parse::parse_bindings;
13

            
14
6784
pub(super) fn let_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
15
6784
    if args.len() < 2 {
16
68
        return Err(Error::Compile(
17
68
            "LET requires a bindings list and at least one body form".to_string(),
18
68
        ));
19
6716
    }
20
6716
    let bindings = parse_bindings("LET", &args[0])?;
21
6716
    let resolved: Vec<(String, Expr)> = bindings
22
6716
        .into_iter()
23
6852
        .map(|(name, init)| {
24
6852
            let val = match init {
25
6852
                Some(expr) => eval_value(symbols, &expr)?,
26
                None => Expr::Nil,
27
            };
28
6852
            Ok((name, val))
29
6852
        })
30
6716
        .collect::<Result<_>>()?;
31
6716
    let mut local_symbols = symbols.clone();
32
6852
    for (name, val) in resolved {
33
6852
        local_symbols.define(Symbol::new(&name, SymbolKind::Variable).with_value(val));
34
6852
    }
35
6716
    eval_body(&mut local_symbols, &args[1..])
36
6784
}
37

            
38
340
pub(super) fn let_star(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
39
340
    if args.len() < 2 {
40
        return Err(Error::Compile(
41
            "LET* requires a bindings list and at least one body form".to_string(),
42
        ));
43
340
    }
44
340
    let bindings = parse_bindings("LET*", &args[0])?;
45
340
    let mut local_symbols = symbols.clone();
46
476
    for (name, init) in bindings {
47
476
        let val = match init {
48
476
            Some(expr) => eval_value(&mut local_symbols, &expr)?,
49
            None => Expr::Nil,
50
        };
51
476
        local_symbols.define(Symbol::new(&name, SymbolKind::Variable).with_value(val));
52
    }
53
340
    eval_body(&mut local_symbols, &args[1..])
54
340
}
55

            
56
9504
pub(in crate::compiler) fn eval_body(symbols: &mut SymbolTable, body: &[Expr]) -> Result<Expr> {
57
9504
    let mut has_runtime = false;
58
9504
    for expr in &body[..body.len() - 1] {
59
4580
        let val = eval_value(symbols, expr)?;
60
4580
        if val.is_wasm_runtime() {
61
1020
            has_runtime = true;
62
3560
        }
63
    }
64
9504
    let result_expr = body.last().unwrap();
65
9504
    let result = eval_value(symbols, result_expr)?;
66
9504
    if has_runtime && !result.is_wasm_runtime() {
67
        // When the body's last form returns an accumulator (a let-bound
68
        // Symbol the body sets via `(setf <name> (cons V <name>))`),
69
        // walk the body for that setf and infer the PairElement from
70
        // the cons car so the promoted runtime type matches the
71
        // PairRef shape the codegen path actually emits. Without this,
72
        // the fallback `PairRef(I32)` disagrees with codegen when the
73
        // car is a Ratio/Commodity, leading to a ref.cast mismatch in
74
        // downstream consumers.
75
612
        let ty: WasmType = infer_runtime_type_with_body(&result, result_expr, body, symbols);
76
612
        return Ok(Expr::WasmRuntime(ty));
77
8892
    }
78
8892
    Ok(result)
79
9504
}