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

            
38
332
pub(super) fn let_star(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
39
332
    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
332
    }
44
332
    let bindings = parse_bindings("LET*", &args[0])?;
45
332
    let mut local_symbols = symbols.clone();
46
498
    for (name, init) in bindings {
47
498
        let val = match init {
48
498
            Some(expr) => eval_value(&mut local_symbols, &expr)?,
49
            None => Expr::Nil,
50
        };
51
498
        local_symbols.define(Symbol::new(&name, SymbolKind::Variable).with_value(val));
52
    }
53
332
    eval_body(&mut local_symbols, &args[1..])
54
332
}
55

            
56
11703
pub(in crate::compiler) fn eval_body(symbols: &mut SymbolTable, body: &[Expr]) -> Result<Expr> {
57
11703
    let mut has_runtime = false;
58
11703
    for expr in &body[..body.len() - 1] {
59
5644
        let val = eval_value(symbols, expr)?;
60
5644
        if val.is_wasm_runtime() {
61
1328
            has_runtime = true;
62
4316
        }
63
    }
64
11703
    let result_expr = body.last().unwrap();
65
11703
    let result = eval_value(symbols, result_expr)?;
66
11703
    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
830
        let ty: WasmType = infer_runtime_type_with_body(&result, result_expr, body, symbols);
76
830
        return Ok(Expr::WasmRuntime(ty));
77
10873
    }
78
10873
    Ok(result)
79
11703
}