1
//! `LET` / `LET*` codegen (effect, stack, value-position variants).
2
//!
3
//! Six entry points share the same three-phase shape: parse the
4
//! bindings list, resolve each init (parallel for LET, sequential
5
//! for LET*), bind via `bind_runtime_or_const` to either a constant
6
//! or a freshly-allocated wasm local, then compile the body. The
7
//! only difference is what the body-compile target is.
8

            
9
use crate::ast::{Expr, WasmType};
10
use crate::compiler::context::CompileContext;
11
use crate::compiler::emit::FunctionEmitter;
12
use crate::compiler::expr::{
13
    compile_body, compile_body_for_stack, compile_for_effect, compile_for_stack,
14
    compile_for_stack_as, emit_nil_default, eval_value,
15
};
16
use crate::compiler::special::try_emit_lambda_for_value;
17
use crate::error::{Error, Result};
18
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
19

            
20
use super::infer::assigned_var_runtime_type;
21
use super::parse::parse_bindings;
22

            
23
4047
pub(super) fn compile_let(
24
4047
    ctx: &mut CompileContext,
25
4047
    emit: &mut FunctionEmitter,
26
4047
    symbols: &mut SymbolTable,
27
4047
    args: &[Expr],
28
4047
) -> Result<()> {
29
4047
    if args.len() < 2 {
30
142
        return Err(Error::Compile(
31
142
            "LET requires a bindings list and at least one body form".to_string(),
32
142
        ));
33
3905
    }
34
3905
    let bindings = parse_bindings("LET", &args[0])?;
35
3834
    let resolved = resolve_let_bindings(symbols, bindings)?;
36
3834
    let mut local = symbols.clone();
37
4260
    for (name, init_expr, val) in resolved {
38
4260
        let bound = bind_runtime_or_const(
39
4260
            ctx,
40
4260
            emit,
41
4260
            symbols,
42
4260
            &name,
43
4260
            init_expr.as_ref(),
44
4260
            &val,
45
4260
            &args[1..],
46
213
        )?;
47
4047
        record_closure_result(&mut local, ctx, &bound);
48
4047
        local.define(Symbol::new(&name, SymbolKind::Variable).with_value(bound));
49
    }
50
3621
    compile_body(ctx, emit, &mut local, &args[1..])
51
4047
}
52

            
53
3408
pub(super) fn compile_let_for_stack(
54
3408
    ctx: &mut CompileContext,
55
3408
    emit: &mut FunctionEmitter,
56
3408
    symbols: &mut SymbolTable,
57
3408
    args: &[Expr],
58
3408
) -> Result<WasmType> {
59
3408
    if args.len() < 2 {
60
        return Err(Error::Compile(
61
            "LET requires a bindings list and at least one body form".to_string(),
62
        ));
63
3408
    }
64
3408
    let bindings = parse_bindings("LET", &args[0])?;
65
3408
    let resolved = resolve_let_bindings(symbols, bindings)?;
66
3408
    let mut local = symbols.clone();
67
3479
    for (name, init_expr, val) in resolved {
68
3479
        let bound = bind_runtime_or_const(
69
3479
            ctx,
70
3479
            emit,
71
3479
            symbols,
72
3479
            &name,
73
3479
            init_expr.as_ref(),
74
3479
            &val,
75
3479
            &args[1..],
76
        )?;
77
3479
        record_closure_result(&mut local, ctx, &bound);
78
3479
        local.define(Symbol::new(&name, SymbolKind::Variable).with_value(bound));
79
    }
80
3408
    compile_body_for_stack(ctx, emit, &mut local, &args[1..])
81
3408
}
82

            
83
15833
pub(super) fn compile_let_star(
84
15833
    ctx: &mut CompileContext,
85
15833
    emit: &mut FunctionEmitter,
86
15833
    symbols: &mut SymbolTable,
87
15833
    args: &[Expr],
88
15833
) -> Result<()> {
89
15833
    if args.len() < 2 {
90
142
        return Err(Error::Compile(
91
142
            "LET* requires a bindings list and at least one body form".to_string(),
92
142
        ));
93
15691
    }
94
15691
    let bindings = parse_bindings("LET*", &args[0])?;
95
15691
    let mut local = symbols.clone();
96
18389
    for (name, init) in bindings {
97
18389
        let (init_expr, val) = resolve_sequential_binding(&mut local, init)?;
98
18389
        let bound = bind_runtime_or_const(
99
18389
            ctx,
100
18389
            emit,
101
18389
            &mut local,
102
18389
            &name,
103
18389
            init_expr.as_ref(),
104
18389
            &val,
105
18389
            &args[1..],
106
        )?;
107
18389
        record_closure_result(&mut local, ctx, &bound);
108
18389
        local.define(Symbol::new(&name, SymbolKind::Variable).with_value(bound));
109
    }
110
15691
    compile_body(ctx, emit, &mut local, &args[1..])
111
15833
}
112

            
113
213
pub(super) fn compile_let_star_for_stack(
114
213
    ctx: &mut CompileContext,
115
213
    emit: &mut FunctionEmitter,
116
213
    symbols: &mut SymbolTable,
117
213
    args: &[Expr],
118
213
) -> Result<WasmType> {
119
213
    if args.len() < 2 {
120
        return Err(Error::Compile(
121
            "LET* requires a bindings list and at least one body form".to_string(),
122
        ));
123
213
    }
124
213
    let bindings = parse_bindings("LET*", &args[0])?;
125
213
    let mut local = symbols.clone();
126
284
    for (name, init) in bindings {
127
284
        let (init_expr, val) = resolve_sequential_binding(&mut local, init)?;
128
284
        let bound = bind_runtime_or_const(
129
284
            ctx,
130
284
            emit,
131
284
            &mut local,
132
284
            &name,
133
284
            init_expr.as_ref(),
134
284
            &val,
135
284
            &args[1..],
136
        )?;
137
284
        record_closure_result(&mut local, ctx, &bound);
138
284
        local.define(Symbol::new(&name, SymbolKind::Variable).with_value(bound));
139
    }
140
213
    compile_body_for_stack(ctx, emit, &mut local, &args[1..])
141
213
}
142

            
143
710
pub(super) fn compile_let_for_effect(
144
710
    ctx: &mut CompileContext,
145
710
    emit: &mut FunctionEmitter,
146
710
    symbols: &mut SymbolTable,
147
710
    args: &[Expr],
148
710
) -> Result<()> {
149
710
    if args.len() < 2 {
150
        return Err(Error::Compile(
151
            "LET requires a bindings list and at least one body form".to_string(),
152
        ));
153
710
    }
154
710
    let bindings = parse_bindings("LET", &args[0])?;
155
710
    let resolved = resolve_let_bindings(symbols, bindings)?;
156
710
    let mut local = symbols.clone();
157
781
    for (name, init_expr, val) in resolved {
158
781
        let bound = bind_runtime_or_const(
159
781
            ctx,
160
781
            emit,
161
781
            symbols,
162
781
            &name,
163
781
            init_expr.as_ref(),
164
781
            &val,
165
781
            &args[1..],
166
        )?;
167
781
        record_closure_result(&mut local, ctx, &bound);
168
781
        local.define(Symbol::new(&name, SymbolKind::Variable).with_value(bound));
169
    }
170
994
    for arg in &args[1..] {
171
994
        compile_for_effect(ctx, emit, &mut local, arg)?;
172
    }
173
710
    Ok(())
174
710
}
175

            
176
710
pub(super) fn compile_let_star_for_effect(
177
710
    ctx: &mut CompileContext,
178
710
    emit: &mut FunctionEmitter,
179
710
    symbols: &mut SymbolTable,
180
710
    args: &[Expr],
181
710
) -> Result<()> {
182
710
    if args.len() < 2 {
183
        return Err(Error::Compile(
184
            "LET* requires a bindings list and at least one body form".to_string(),
185
        ));
186
710
    }
187
710
    let bindings = parse_bindings("LET*", &args[0])?;
188
710
    let mut local = symbols.clone();
189
2059
    for (name, init) in bindings {
190
2059
        let (init_expr, val) = resolve_sequential_binding(&mut local, init)?;
191
2059
        let bound = bind_runtime_or_const(
192
2059
            ctx,
193
2059
            emit,
194
2059
            &mut local,
195
2059
            &name,
196
2059
            init_expr.as_ref(),
197
2059
            &val,
198
2059
            &args[1..],
199
        )?;
200
2059
        record_closure_result(&mut local, ctx, &bound);
201
2059
        local.define(Symbol::new(&name, SymbolKind::Variable).with_value(bound));
202
    }
203
710
    for arg in &args[1..] {
204
710
        compile_for_effect(ctx, emit, &mut local, arg)?;
205
    }
206
710
    Ok(())
207
710
}
208

            
209
/// When a binding's emitted value is a closure, record its signature's result
210
/// type into the body's symbol table so the ctx-less eval surface (FOLD's
211
/// accumulator probe, binding-local sizing) can predict a HOF result type that
212
/// agrees with codegen — see `SymbolTable::closure_results`.
213
29039
fn record_closure_result(local: &mut SymbolTable, ctx: &CompileContext, bound: &Expr) {
214
26057
    if let Expr::WasmLocal(_, WasmType::Closure(sig)) = bound {
215
2556
        local.record_closure_result(*sig, ctx.closure_sig(*sig).result);
216
26483
    }
217
29039
}
218

            
219
7952
fn resolve_let_bindings(
220
7952
    symbols: &mut SymbolTable,
221
7952
    bindings: Vec<(String, Option<Expr>)>,
222
7952
) -> Result<Vec<(String, Option<Expr>, Expr)>> {
223
7952
    bindings
224
7952
        .into_iter()
225
8520
        .map(|(name, init)| {
226
8520
            let (orig, val) = match init {
227
8449
                Some(expr) => {
228
8449
                    let v = eval_value(symbols, &expr)?;
229
8449
                    (Some(expr), v)
230
                }
231
71
                None => (None, Expr::Nil),
232
            };
233
8520
            Ok((name, orig, val))
234
8520
        })
235
7952
        .collect()
236
7952
}
237

            
238
20732
fn resolve_sequential_binding(
239
20732
    local: &mut SymbolTable,
240
20732
    init: Option<Expr>,
241
20732
) -> Result<(Option<Expr>, Expr)> {
242
20732
    match init {
243
20732
        Some(expr) => {
244
20732
            let v = eval_value(local, &expr)?;
245
20732
            Ok((Some(expr), v))
246
        }
247
        None => Ok((None, Expr::Nil)),
248
    }
249
20732
}
250

            
251
/// If val is `WasmRuntime`, compile the init expression to the WASM stack,
252
/// allocate a local, and return `WasmLocal`. If val is an `Expr::Lambda`
253
/// that fits the Tier 1.5 v1 emit slice, lift it to a real wasm closure
254
/// value: emit the closure construction sequence, allocate a
255
/// `Closure(sig)` local, and return the corresponding `WasmLocal`.
256
/// Otherwise return the constant value untouched (the body's
257
/// const-fold path keeps working).
258
29252
fn bind_runtime_or_const(
259
29252
    ctx: &mut CompileContext,
260
29252
    emit: &mut FunctionEmitter,
261
29252
    symbols: &mut SymbolTable,
262
29252
    name: &str,
263
29252
    init_expr: Option<&Expr>,
264
29252
    val: &Expr,
265
29252
    body: &[Expr],
266
29252
) -> Result<Expr> {
267
    // A let-var the body mutates via `setf`/`set!` must be a runtime local even
268
    // when its init is a const: `setf` only emits a runtime store for a
269
    // `WasmLocal` place; a const-bound var instead takes the eval-rebind path,
270
    // which emits no wasm and is lost across loop scopes — so a
271
    // `(let ((acc 0)) … (setf acc …))` accumulator inside a DO/dolist would
272
    // silently never update. Allocate the local up front from the (compiled)
273
    // init value.
274
10153
    if !matches!(
275
29252
        val,
276
        Expr::WasmRuntime(_) | Expr::WasmLocal(_, _) | Expr::Lambda(_, _)
277
10153
    ) && let Some(ty) = assigned_var_runtime_type(body, name, val, symbols)
278
    {
279
7171
        match init_expr {
280
7171
            Some(expr) => {
281
7171
                compile_for_stack_as(ctx, emit, symbols, expr, ty)?;
282
            }
283
            None => emit_nil_default(ctx, emit, ty)?,
284
        }
285
6958
        let idx = ctx.alloc_local(ty)?;
286
6958
        emit.local_set(idx);
287
6958
        return Ok(Expr::WasmLocal(idx, ty));
288
22081
    }
289
22081
    match val {
290
        Expr::WasmRuntime(_) => {
291
16401
            let expr = init_expr.ok_or_else(|| {
292
                Error::Compile("runtime binding requires an init expression".to_string())
293
            })?;
294
            // Size the local from the type codegen actually pushes, not the
295
            // eval-time placeholder: the two can disagree (e.g. a fold over a
296
            // runtime closure — eval can't see the closure sig and falls back to
297
            // the literal seed's type, while codegen uses the sig result). The
298
            // compiled type is authoritative; trusting the placeholder would
299
            // local.set a value of one type into a local of another (invalid wasm).
300
16401
            let actual_ty = compile_for_stack(ctx, emit, symbols, expr)?;
301
16401
            let idx = ctx.alloc_local(actual_ty)?;
302
16401
            emit.local_set(idx);
303
16401
            Ok(Expr::WasmLocal(idx, actual_ty))
304
        }
305
142
        Expr::WasmLocal(_, _) => Ok(val.clone()),
306
2556
        Expr::Lambda(params, body) => {
307
2556
            if let Some(sig) = try_emit_lambda_for_value(ctx, emit, symbols, params, body)? {
308
2556
                let ty = WasmType::Closure(sig);
309
2556
                let idx = ctx.alloc_local(ty)?;
310
2556
                emit.local_set(idx);
311
                // Record the source so a higher-order native can inline this
312
                // closure per element with the actual element type, rather than
313
                // `call_ref`ing its fixed (Ratio-default) signature. ONLY for a
314
                // capture-free closure — inlining a capturing one at the HOF call
315
                // site would resolve its free vars there (possibly shadowed /
316
                // mutated) instead of at the closure's creation site (wrong
317
                // value); a capturing closure keeps the `call_ref` path.
318
2556
                if crate::compiler::special::is_capture_free(symbols, params, body) {
319
2059
                    ctx.record_closure_body(idx, params.clone(), (**body).clone());
320
2059
                }
321
2556
                return Ok(Expr::WasmLocal(idx, ty));
322
            }
323
            Ok(val.clone())
324
        }
325
2982
        _ => Ok(val.clone()),
326
    }
327
29252
}