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

            
53
5727
pub(super) fn compile_let_for_stack(
54
5727
    ctx: &mut CompileContext,
55
5727
    emit: &mut FunctionEmitter,
56
5727
    symbols: &mut SymbolTable,
57
5727
    args: &[Expr],
58
5727
) -> Result<WasmType> {
59
5727
    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
5727
    }
64
5727
    let bindings = parse_bindings("LET", &args[0])?;
65
5727
    let resolved = resolve_let_bindings(symbols, bindings)?;
66
5727
    let mut local = symbols.clone();
67
5727
    for (name, init_expr, val) in resolved {
68
5727
        let bound = bind_runtime_or_const(
69
5727
            ctx,
70
5727
            emit,
71
5727
            symbols,
72
5727
            &name,
73
5727
            init_expr.as_ref(),
74
5727
            &val,
75
5727
            &args[1..],
76
        )?;
77
5727
        record_closure_result(&mut local, ctx, &bound);
78
5727
        local.define(Symbol::new(&name, SymbolKind::Variable).with_value(bound));
79
    }
80
5727
    compile_body_for_stack(ctx, emit, &mut local, &args[1..])
81
5727
}
82

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

            
113
332
pub(super) fn compile_let_star_for_stack(
114
332
    ctx: &mut CompileContext,
115
332
    emit: &mut FunctionEmitter,
116
332
    symbols: &mut SymbolTable,
117
332
    args: &[Expr],
118
332
) -> Result<WasmType> {
119
332
    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
332
    }
124
332
    let bindings = parse_bindings("LET*", &args[0])?;
125
332
    let mut local = symbols.clone();
126
332
    for (name, init) in bindings {
127
332
        let (init_expr, val) = resolve_sequential_binding(&mut local, init)?;
128
332
        let bound = bind_runtime_or_const(
129
332
            ctx,
130
332
            emit,
131
332
            &mut local,
132
332
            &name,
133
332
            init_expr.as_ref(),
134
332
            &val,
135
332
            &args[1..],
136
        )?;
137
332
        record_closure_result(&mut local, ctx, &bound);
138
332
        local.define(Symbol::new(&name, SymbolKind::Variable).with_value(bound));
139
    }
140
332
    compile_body_for_stack(ctx, emit, &mut local, &args[1..])
141
332
}
142

            
143
830
pub(super) fn compile_let_for_effect(
144
830
    ctx: &mut CompileContext,
145
830
    emit: &mut FunctionEmitter,
146
830
    symbols: &mut SymbolTable,
147
830
    args: &[Expr],
148
830
) -> Result<()> {
149
830
    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
830
    }
154
830
    let bindings = parse_bindings("LET", &args[0])?;
155
830
    let resolved = resolve_let_bindings(symbols, bindings)?;
156
830
    let mut local = symbols.clone();
157
913
    for (name, init_expr, val) in resolved {
158
913
        let bound = bind_runtime_or_const(
159
913
            ctx,
160
913
            emit,
161
913
            symbols,
162
913
            &name,
163
913
            init_expr.as_ref(),
164
913
            &val,
165
913
            &args[1..],
166
        )?;
167
913
        record_closure_result(&mut local, ctx, &bound);
168
913
        local.define(Symbol::new(&name, SymbolKind::Variable).with_value(bound));
169
    }
170
1162
    for arg in &args[1..] {
171
1162
        compile_for_effect(ctx, emit, &mut local, arg)?;
172
    }
173
830
    Ok(())
174
830
}
175

            
176
830
pub(super) fn compile_let_star_for_effect(
177
830
    ctx: &mut CompileContext,
178
830
    emit: &mut FunctionEmitter,
179
830
    symbols: &mut SymbolTable,
180
830
    args: &[Expr],
181
830
) -> Result<()> {
182
830
    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
830
    }
187
830
    let bindings = parse_bindings("LET*", &args[0])?;
188
830
    let mut local = symbols.clone();
189
2407
    for (name, init) in bindings {
190
2407
        let (init_expr, val) = resolve_sequential_binding(&mut local, init)?;
191
2407
        let bound = bind_runtime_or_const(
192
2407
            ctx,
193
2407
            emit,
194
2407
            &mut local,
195
2407
            &name,
196
2407
            init_expr.as_ref(),
197
2407
            &val,
198
2407
            &args[1..],
199
        )?;
200
2407
        record_closure_result(&mut local, ctx, &bound);
201
2407
        local.define(Symbol::new(&name, SymbolKind::Variable).with_value(bound));
202
    }
203
830
    for arg in &args[1..] {
204
830
        compile_for_effect(ctx, emit, &mut local, arg)?;
205
    }
206
830
    Ok(())
207
830
}
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
35607
fn record_closure_result(local: &mut SymbolTable, ctx: &CompileContext, bound: &Expr) {
214
31623
    if let Expr::WasmLocal(_, WasmType::Closure(sig)) = bound {
215
3569
        let shape = ctx.closure_sig(*sig);
216
3569
        local.record_closure_result(*sig, shape.result);
217
3569
        if let Some(param) = shape.params.first() {
218
3569
            local.record_closure_param(*sig, *param);
219
3569
        }
220
32038
    }
221
35607
}
222

            
223
11039
fn resolve_let_bindings(
224
11039
    symbols: &mut SymbolTable,
225
11039
    bindings: Vec<(String, Option<Expr>)>,
226
11039
) -> Result<Vec<(String, Option<Expr>, Expr)>> {
227
11039
    bindings
228
11039
        .into_iter()
229
11620
        .map(|(name, init)| {
230
11620
            let (orig, val) = match init {
231
11537
                Some(expr) => {
232
11537
                    let v = eval_value(symbols, &expr)?;
233
11537
                    (Some(expr), v)
234
                }
235
83
                None => (None, Expr::Nil),
236
            };
237
11620
            Ok((name, orig, val))
238
11620
        })
239
11039
        .collect()
240
11039
}
241

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

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