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

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

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

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

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

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

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

            
238
19874
fn resolve_sequential_binding(
239
19874
    local: &mut SymbolTable,
240
19874
    init: Option<Expr>,
241
19874
) -> Result<(Option<Expr>, Expr)> {
242
19874
    match init {
243
19874
        Some(expr) => {
244
19874
            let v = eval_value(local, &expr)?;
245
19874
            Ok((Some(expr), v))
246
        }
247
        None => Ok((None, Expr::Nil)),
248
    }
249
19874
}
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
28058
fn bind_runtime_or_const(
259
28058
    ctx: &mut CompileContext,
260
28058
    emit: &mut FunctionEmitter,
261
28058
    symbols: &mut SymbolTable,
262
28058
    name: &str,
263
28058
    init_expr: Option<&Expr>,
264
28058
    val: &Expr,
265
28058
    body: &[Expr],
266
28058
) -> 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
9748
    if !matches!(
275
28058
        val,
276
        Expr::WasmRuntime(_) | Expr::WasmLocal(_, _) | Expr::Lambda(_, _)
277
9748
    ) && let Some(ty) = assigned_var_runtime_type(body, name, val, symbols)
278
    {
279
6892
        match init_expr {
280
6892
            Some(expr) => {
281
6892
                compile_for_stack_as(ctx, emit, symbols, expr, ty)?;
282
            }
283
            None => emit_nil_default(ctx, emit, ty)?,
284
        }
285
6688
        let idx = ctx.alloc_local(ty)?;
286
6688
        emit.local_set(idx);
287
6688
        return Ok(Expr::WasmLocal(idx, ty));
288
21166
    }
289
21166
    match val {
290
        Expr::WasmRuntime(_) => {
291
15726
            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
15726
            let actual_ty = compile_for_stack(ctx, emit, symbols, expr)?;
301
15726
            let idx = ctx.alloc_local(actual_ty)?;
302
15726
            emit.local_set(idx);
303
15726
            Ok(Expr::WasmLocal(idx, actual_ty))
304
        }
305
136
        Expr::WasmLocal(_, _) => Ok(val.clone()),
306
2448
        Expr::Lambda(params, body) => {
307
2448
            if let Some(sig) = try_emit_lambda_for_value(ctx, emit, symbols, params, body)? {
308
2448
                let ty = WasmType::Closure(sig);
309
2448
                let idx = ctx.alloc_local(ty)?;
310
2448
                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
2448
                if crate::compiler::special::is_capture_free(symbols, params, body) {
319
1972
                    ctx.record_closure_body(idx, params.clone(), (**body).clone());
320
1972
                }
321
2448
                return Ok(Expr::WasmLocal(idx, ty));
322
            }
323
            Ok(val.clone())
324
        }
325
2856
        _ => Ok(val.clone()),
326
    }
327
28058
}