1
#[cfg(test)]
2
mod capture_free_tests;
3
mod captures;
4
mod emit;
5
pub(in crate::compiler) mod monomorph;
6
mod param_infer;
7

            
8
use crate::ast::Expr;
9
use crate::error::{Error, Result};
10
use crate::runtime::{SymbolKind, SymbolTable};
11

            
12
use super::super::context::CompileContext;
13
use super::super::emit::FunctionEmitter;
14
use crate::ast::WasmType;
15

            
16
use super::super::expr::{
17
    compile_call, compile_call_for_stack, compile_string, eval_value, format_expr,
18
    serialize_stack_to_output,
19
};
20
use super::SpecialFormSpec;
21
use super::binding::parse_lambda_params;
22
use crate::ast::LambdaParams;
23

            
24
pub(super) const FORMS: &[SpecialFormSpec] = &[
25
    SpecialFormSpec {
26
        name: "LAMBDA",
27
        eval: eval_lambda,
28
        compile: spec_compile_lambda,
29
        stack: None,
30
        effect: None,
31
    },
32
    SpecialFormSpec {
33
        name: "FUNCTION",
34
        eval: function_form,
35
        compile: compile_function_form,
36
        stack: None,
37
        effect: None,
38
    },
39
    SpecialFormSpec {
40
        name: "FUNCALL",
41
        eval: funcall,
42
        compile: compile_funcall,
43
        stack: Some(compile_funcall_for_stack),
44
        effect: None,
45
    },
46
    SpecialFormSpec {
47
        name: "APPLY",
48
        eval: apply,
49
        compile: compile_apply_form,
50
        stack: Some(compile_apply_form_for_stack),
51
        effect: None,
52
    },
53
];
54

            
55
11560
fn eval_lambda(_symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
56
11560
    lambda(args)
57
11560
}
58

            
59
544
fn spec_compile_lambda(
60
544
    ctx: &mut CompileContext,
61
544
    emit: &mut FunctionEmitter,
62
544
    symbols: &mut SymbolTable,
63
544
    args: &[Expr],
64
544
) -> Result<()> {
65
544
    compile_lambda_form(ctx, emit, symbols, args)
66
544
}
67

            
68
544
pub(super) fn compile_lambda_form(
69
544
    ctx: &mut CompileContext,
70
544
    emit: &mut FunctionEmitter,
71
544
    symbols: &SymbolTable,
72
544
    args: &[Expr],
73
544
) -> Result<()> {
74
544
    let result = lambda(args)?;
75
408
    emit_closure_or_stringify(ctx, emit, symbols, &result)
76
544
}
77

            
78
272
pub(super) fn compile_function_form(
79
272
    ctx: &mut CompileContext,
80
272
    emit: &mut FunctionEmitter,
81
272
    symbols: &mut SymbolTable,
82
272
    args: &[Expr],
83
272
) -> Result<()> {
84
272
    let result = function_form(symbols, args)?;
85
136
    emit_closure_or_stringify(ctx, emit, symbols, &result)
86
272
}
87

            
88
/// Lower a value-position lambda/function expression. If the result is
89
/// a `Lambda` form that fits the Tier 1.5 v1 emit slice, emit it as a
90
/// real wasm closure value and serialize via the closure debug writer.
91
/// Otherwise (out-of-scope params/captures, or non-`Lambda` body returned
92
/// by `function_form`) fall back to stringification so the existing
93
/// inline path keeps working.
94
544
fn emit_closure_or_stringify(
95
544
    ctx: &mut CompileContext,
96
544
    emit: &mut FunctionEmitter,
97
544
    symbols: &SymbolTable,
98
544
    result: &Expr,
99
544
) -> Result<()> {
100
544
    if let Expr::Lambda(params, body) = result
101
544
        && let Some(sig) = try_emit_lambda_for_value(ctx, emit, symbols, params, body)?
102
    {
103
408
        return serialize_stack_to_output(ctx, emit, crate::ast::WasmType::Closure(sig));
104
136
    }
105
136
    compile_string(ctx, emit, &format_expr(result))
106
544
}
107

            
108
/// Stack-position emit: if `params`/`body` fit the Tier 1.5 v1 slice
109
/// the closure value is constructed on the wasm stack and the
110
/// `ClosureSigId` returned. The caller decides whether to serialize it
111
/// (value position via `compile_expr`) or leave it on the stack
112
/// (stack position via `compile_for_stack`). Returns `Ok(None)` when
113
/// the lambda is out of v1 scope so the caller can fall back.
114
/// Whether `body` is safe to INLINE at a different site (a HOF call): it must
115
/// reference NO free outer variable. A captured free var — whether a runtime
116
/// `WasmLocal` OR a compile-time CONST (`(let ((x 1)) (lambda … x) …)`) — would
117
/// resolve to its CREATION-site value through the closure's env / const fold,
118
/// but an inline at the call site resolves it THERE (possibly shadowed or a
119
/// different const), producing a wrong value. So the check is stricter than
120
/// `compute_captures` (which only tracks WasmLocal captures for env building):
121
/// any free symbol that resolves to a `Variable` (let-/param-bound) OR carries a
122
/// `function` binding (a user `defun` / redefined builtin) makes the body
123
/// inline-unsafe — both can resolve differently at a later inline site. Bare
124
/// operators / natives (no `function`) and special forms resolve identically
125
/// everywhere and are fine. Re-running this at the inline site catches a global
126
/// the body uses that an inner binder has since shadowed or `defun`-redefined.
127
/// Conservative: a body that merely shadows an outer name with its own inner
128
/// binder, or calls a stable user function, may be rejected, costing precision
129
/// (not correctness).
130
///
131
/// Only a SIMPLE (required-params-only) lambda is an inline candidate — an
132
/// optional / key / aux default expr or a `&rest` binder would each need its
133
/// own capture analysis, so a lambda carrying any stays on the `call_ref` path.
134
3068
pub(in crate::compiler) fn is_capture_free(
135
3068
    symbols: &SymbolTable,
136
3068
    params: &LambdaParams,
137
3068
    body: &Expr,
138
3068
) -> bool {
139
3068
    if !params.optional.is_empty()
140
3067
        || params.rest.is_some()
141
3067
        || !params.key.is_empty()
142
3067
        || !params.aux.is_empty()
143
    {
144
1
        return false;
145
3067
    }
146
3067
    references_no_free_variable(symbols, &params.required, body)
147
3068
}
148

            
149
/// `bound` are names lexically bound by the closure (its required params) — a
150
/// reference to one is never a creation-site capture. The walk is conservative
151
/// for nested binders: it does NOT extend `bound` with an inner lambda's params,
152
/// so a shadowing inner name that collides with a creation-site `Variable` is
153
/// rejected (precision, not soundness).
154
12742
fn references_no_free_variable(symbols: &SymbolTable, bound: &[String], expr: &Expr) -> bool {
155
3406
    match expr {
156
        // A bound param is never a capture. A free symbol is inline-safe only if
157
        // it resolves to a STABLE global: not a `Variable` (a let/param binding
158
        // an inner scope could shadow) AND carrying no `function` binding. A
159
        // symbol with a `function` is a user `defun` — or a builtin operator a
160
        // `defun` has REDEFINED in place (kind stays `Operator`, `function` set).
161
        // Either way its meaning can differ between the closure's creation scope
162
        // and a later inline site, so reject it; the closure keeps `call_ref`,
163
        // which invokes the value compiled at creation. Bare operators / natives
164
        // (no `function`) resolve identically everywhere and stay inlinable.
165
7628
        Expr::Symbol(name) => {
166
10348
            bound.iter().any(|p| p == name)
167
3818
                || (!resolves_to_variable(symbols, name) && !has_function_binding(symbols, name))
168
        }
169
        // QUOTE's argument is data, not evaluated — it captures nothing.
170
1
        Expr::Quote(_) => true,
171
3406
        Expr::List(elems) if matches!(elems.first(), Some(Expr::Symbol(h)) if h.eq_ignore_ascii_case("quote")) => {
172
            true
173
        }
174
        // A macro call is opaque pre-expansion: it can expand to a creation-scope
175
        // variable, which an inline would re-expand (and re-resolve) at the call
176
        // site. Conservatively treat any macro-headed form as a capture.
177
3406
        Expr::List(elems) if matches!(elems.first(), Some(Expr::Symbol(h)) if resolves_to_macro(symbols, h)) => {
178
1
            false
179
        }
180
        // QUASIQUOTE evaluates its UNQUOTE subforms against the surrounding
181
        // scope, so it can capture; walk inner conservatively (template symbols
182
        // count as potential captures — precision cost, never a soundness hole).
183
2
        Expr::Quasiquote(inner) | Expr::Unquote(inner) | Expr::UnquoteSplicing(inner) => {
184
4
            references_no_free_variable(symbols, bound, inner)
185
        }
186
        // A nested lambda's body (and any param default) still evaluates against
187
        // the creation scope when the outer body runs, so it must be walked too;
188
        // never short-circuit it to capture-free.
189
1
        Expr::Lambda(inner_params, inner_body) => {
190
1
            param_default_exprs(inner_params)
191
1
                .all(|d| references_no_free_variable(symbols, bound, d))
192
1
                && references_no_free_variable(symbols, bound, inner_body)
193
        }
194
3405
        Expr::List(elems) => elems
195
3405
            .iter()
196
9669
            .all(|e| references_no_free_variable(symbols, bound, e)),
197
        // A dotted pair can hide an unquoted free symbol (`(,x . nil)`); both
198
        // arms evaluate when the surrounding quasiquote expands, so walk both.
199
1
        Expr::Cons(car, cdr) => {
200
1
            references_no_free_variable(symbols, bound, car)
201
                && references_no_free_variable(symbols, bound, cdr)
202
        }
203
1701
        _ => true,
204
    }
205
12742
}
206

            
207
/// The default-value expressions carried by a lambda's optional / key / aux
208
/// params (each evaluated at closure creation, so each can capture).
209
1
fn param_default_exprs(params: &LambdaParams) -> impl Iterator<Item = &Expr> {
210
1
    params
211
1
        .optional
212
1
        .iter()
213
1
        .chain(&params.key)
214
1
        .chain(&params.aux)
215
1
        .filter_map(|(_, default)| default.as_ref())
216
1
}
217

            
218
3818
fn resolves_to_variable(symbols: &SymbolTable, name: &str) -> bool {
219
3270
    matches!(
220
3818
        symbols.lookup(name).map(|sym| sym.kind()),
221
        Some(SymbolKind::Variable)
222
    )
223
3818
}
224

            
225
3406
fn resolves_to_macro(symbols: &SymbolTable, name: &str) -> bool {
226
3405
    matches!(
227
3406
        symbols.lookup(name).map(|sym| sym.kind()),
228
        Some(SymbolKind::Macro)
229
    )
230
3406
}
231

            
232
/// Whether `name` resolves to a symbol carrying a `function` body — a user
233
/// `defun` (or a builtin a `defun` redefined in place), whose meaning can change
234
/// between a closure's creation scope and a later inline site.
235
3270
fn has_function_binding(symbols: &SymbolTable, name: &str) -> bool {
236
3270
    symbols
237
3270
        .lookup(name)
238
3270
        .is_some_and(|sym| sym.function().is_some())
239
3270
}
240

            
241
2992
pub(in crate::compiler) fn try_emit_lambda_for_value(
242
2992
    ctx: &mut CompileContext,
243
2992
    emit: &mut FunctionEmitter,
244
2992
    symbols: &SymbolTable,
245
2992
    params: &LambdaParams,
246
2992
    body: &Expr,
247
2992
) -> Result<Option<crate::ast::ClosureSigId>> {
248
2992
    if !emit::is_v1_eligible(symbols, params, body) {
249
136
        return Ok(None);
250
2856
    }
251
2856
    let sig = emit::emit_lambda_value(ctx, emit, symbols, params, body)?;
252
2856
    Ok(Some(sig))
253
2992
}
254

            
255
/// Variant for host-iteration call sites (CATCH-EACH and similar). The
256
/// host fn passes per-element items as raw anyref, so the closure body
257
/// fn is wired with anyref params and downcasts to the declared user
258
/// types in its prologue. The resulting `ClosureSigId` describes the
259
/// wire-level signature `(anyref...) -> anyref` — the caller emits the
260
/// closure value, then funcref+env are extracted and handed to the
261
/// host fn for per-element invocation.
262
1088
pub(in crate::compiler) fn try_emit_lambda_for_host_iter(
263
1088
    ctx: &mut CompileContext,
264
1088
    emit: &mut FunctionEmitter,
265
1088
    symbols: &SymbolTable,
266
1088
    params: &LambdaParams,
267
1088
    body: &Expr,
268
1088
    user_param_types: &[WasmType],
269
1088
) -> Result<Option<crate::ast::ClosureSigId>> {
270
1088
    if !emit::is_v1_eligible(symbols, params, body) {
271
        return Ok(None);
272
1088
    }
273
1088
    let sig = emit::emit_lambda_value_typed(
274
1088
        ctx,
275
1088
        emit,
276
1088
        symbols,
277
1088
        params,
278
1088
        body,
279
1088
        user_param_types,
280
1088
        emit::CallingConvention::HostAnyref,
281
476
    )?;
282
612
    Ok(Some(sig))
283
1088
}
284

            
285
1088
pub(super) fn compile_funcall(
286
1088
    ctx: &mut CompileContext,
287
1088
    emit: &mut FunctionEmitter,
288
1088
    symbols: &mut SymbolTable,
289
1088
    args: &[Expr],
290
1088
) -> Result<()> {
291
1088
    if args.is_empty() {
292
68
        return Err(Error::Compile(
293
68
            "funcall requires at least a function argument".to_string(),
294
68
        ));
295
1020
    }
296
1020
    compile_call(ctx, emit, symbols, args)
297
1088
}
298

            
299
5848
pub(super) fn compile_funcall_for_stack(
300
5848
    ctx: &mut CompileContext,
301
5848
    emit: &mut FunctionEmitter,
302
5848
    symbols: &mut SymbolTable,
303
5848
    args: &[Expr],
304
5848
) -> Result<WasmType> {
305
5848
    if args.is_empty() {
306
        return Err(Error::Compile(
307
            "funcall requires at least a function argument".to_string(),
308
        ));
309
5848
    }
310
5848
    compile_call_for_stack(ctx, emit, symbols, args)
311
5848
}
312

            
313
816
pub(super) fn compile_apply_form(
314
816
    ctx: &mut CompileContext,
315
816
    emit: &mut FunctionEmitter,
316
816
    symbols: &mut SymbolTable,
317
816
    args: &[Expr],
318
816
) -> Result<()> {
319
816
    let all_args = build_apply_call(symbols, args)?;
320
408
    compile_call(ctx, emit, symbols, &all_args)
321
816
}
322

            
323
pub(super) fn compile_apply_form_for_stack(
324
    ctx: &mut CompileContext,
325
    emit: &mut FunctionEmitter,
326
    symbols: &mut SymbolTable,
327
    args: &[Expr],
328
) -> Result<WasmType> {
329
    let all_args = build_apply_call(symbols, args)?;
330
    compile_call_for_stack(ctx, emit, symbols, &all_args)
331
}
332

            
333
816
fn build_apply_call(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Vec<Expr>> {
334
816
    if args.len() < 2 {
335
136
        return Err(Error::Compile(
336
136
            "apply requires at least a function and one argument".to_string(),
337
136
        ));
338
680
    }
339
680
    let leading = &args[..args.len() - 1];
340
    // Evaluate the designator + leading args BEFORE deciding the last-arg
341
    // spread: a leading arg can rebind `LIST` (left-to-right eval order), and
342
    // `spread_apply_last_arg`'s builtin-`LIST` check must see that rebinding.
343
680
    let mut all_args: Vec<Expr> = Vec::with_capacity(leading.len() + 1);
344
680
    all_args.push(resolve_apply_designator(symbols, &leading[0])?);
345
680
    for arg in &leading[1..] {
346
204
        all_args.push(eval_value(symbols, arg)?);
347
    }
348
680
    all_args.extend(spread_apply_last_arg(symbols, &args[args.len() - 1])?);
349
408
    Ok(all_args)
350
816
}
351

            
352
/// Resolve APPLY's last argument into the list of argument expressions to
353
/// spread. A syntactic `(LIST e1 e2 …)` spreads its element EXPRESSIONS
354
/// directly (`(apply f (list a b c))` ≡ `(f a b c)`) — this is the only form
355
/// that carries genuinely runtime elements, since a runtime `(list …)` no
356
/// longer eval-folds to a quoted list (it builds a `$pair` chain). A quoted
357
/// literal list spreads its constant elements; `nil` spreads nothing.
358
/// Anything else (a bare runtime pair, a non-list value) is an error: APPLY
359
/// can't statically know a runtime list's arity.
360
1088
fn spread_apply_last_arg(symbols: &mut SymbolTable, arg: &Expr) -> Result<Vec<Expr>> {
361
1088
    if let Expr::List(elems) = arg
362
408
        && let Some(Expr::Symbol(head)) = elems.first()
363
408
        && head.eq_ignore_ascii_case("LIST")
364
408
        && resolves_to_native_list(symbols, head)
365
    {
366
272
        return Ok(elems[1..].to_vec());
367
816
    }
368
816
    match eval_value(symbols, arg)? {
369
612
        Expr::Quote(inner) => match *inner {
370
544
            Expr::List(elems) => Ok(elems),
371
            Expr::Nil => Ok(vec![]),
372
68
            _ => Err(apply_last_arg_error()),
373
        },
374
        Expr::Nil => Ok(vec![]),
375
204
        _ => Err(apply_last_arg_error()),
376
    }
377
1088
}
378

            
379
/// Whether `head` still names the builtin `LIST` constructor (not a user
380
/// `(defun list …)` / `(let ((list …)))` that shadows it). The call dispatcher
381
/// gives a user `function()` binding priority over the native, so the APPLY
382
/// fast-path must only fire when no such shadow exists — otherwise it would
383
/// spread element expressions while the program meant to call the user's
384
/// `list`.
385
408
fn resolves_to_native_list(symbols: &SymbolTable, head: &str) -> bool {
386
408
    match symbols.lookup(head) {
387
408
        Some(sym) => sym.function().is_none() && sym.value().is_none(),
388
        None => false,
389
    }
390
408
}
391

            
392
272
fn apply_last_arg_error() -> Error {
393
272
    Error::Compile(
394
272
        "apply: last argument must be a literal list — `(list …)`, a quoted \
395
272
         list, or nil (a runtime list's arity isn't known at compile time)"
396
272
            .to_string(),
397
272
    )
398
272
}
399

            
400
12240
pub(super) fn lambda(args: &[Expr]) -> Result<Expr> {
401
12240
    if args.len() < 2 {
402
136
        return Err(Error::Compile(
403
136
            "LAMBDA requires a parameter list and body".to_string(),
404
136
        ));
405
12104
    }
406
12104
    let params = parse_lambda_params("LAMBDA", &args[0])?;
407
12104
    let body = if args.len() == 2 {
408
11628
        args[1].clone()
409
    } else {
410
476
        let mut forms = Vec::with_capacity(args.len());
411
476
        forms.push(Expr::Symbol("BEGIN".to_string()));
412
476
        forms.extend_from_slice(&args[1..]);
413
476
        Expr::List(forms)
414
    };
415
12104
    Ok(Expr::Lambda(params, Box::new(body)))
416
12240
}
417

            
418
3060
pub(super) fn function_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
419
3060
    if args.len() != 1 {
420
        return Err(Error::Arity {
421
            name: "FUNCTION".to_string(),
422
            expected: 1,
423
            actual: args.len(),
424
        });
425
3060
    }
426
3060
    match &args[0] {
427
2856
        Expr::Symbol(name) => {
428
2856
            let func = symbols
429
2856
                .lookup(name)
430
2856
                .and_then(|s| s.function().cloned())
431
2856
                .ok_or_else(|| Error::Compile(format!("'{name}' has no function definition")))?;
432
2788
            Ok(func)
433
        }
434
136
        Expr::List(elems) if matches!(elems.first(), Some(Expr::Symbol(s)) if s == "LAMBDA") => {
435
136
            lambda(&elems[1..])
436
        }
437
68
        _ => Err(Error::Compile(
438
68
            "FUNCTION: argument must be a symbol or lambda expression".to_string(),
439
68
        )),
440
    }
441
3060
}
442

            
443
408
pub(super) fn funcall(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
444
408
    if args.is_empty() {
445
        return Err(Error::Compile(
446
            "funcall requires at least a function argument".to_string(),
447
        ));
448
408
    }
449
408
    tracing::debug!(args = args.len(), "compiling funcall");
450
408
    super::super::expr::call(symbols, args)
451
408
}
452

            
453
408
pub(super) fn apply(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
454
408
    if args.len() < 2 {
455
        return Err(Error::Compile(
456
            "apply requires at least a function and one argument".to_string(),
457
        ));
458
408
    }
459
408
    let leading = &args[..args.len() - 1];
460
    // Evaluate the designator + leading args before the last-arg spread so a
461
    // leading arg that rebinds `LIST` is visible to the builtin-`LIST` check
462
    // (matches `build_apply_call`).
463
408
    let mut all_args: Vec<Expr> = Vec::with_capacity(leading.len() + 1);
464
408
    all_args.push(resolve_apply_designator(symbols, &leading[0])?);
465
1020
    for arg in &leading[1..] {
466
1020
        all_args.push(eval_value(symbols, arg)?);
467
    }
468
408
    all_args.extend(spread_apply_last_arg(symbols, &args[args.len() - 1])?);
469
408
    super::super::expr::call(symbols, &all_args)
470
408
}
471

            
472
1088
pub(super) fn resolve_apply_designator(symbols: &mut SymbolTable, arg: &Expr) -> Result<Expr> {
473
1088
    match arg {
474
136
        Expr::Symbol(name) => {
475
136
            let sym = symbols
476
136
                .lookup(name)
477
136
                .ok_or_else(|| Error::UndefinedSymbol(name.clone()))?;
478
136
            if sym.value().is_some() {
479
                eval_value(symbols, arg)
480
            } else {
481
136
                Ok(Expr::Symbol(name.clone()))
482
            }
483
        }
484
952
        _ => eval_value(symbols, arg),
485
    }
486
1088
}