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
12070
fn eval_lambda(_symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
56
12070
    lambda(args)
57
12070
}
58

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

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

            
78
284
pub(super) fn compile_function_form(
79
284
    ctx: &mut CompileContext,
80
284
    emit: &mut FunctionEmitter,
81
284
    symbols: &mut SymbolTable,
82
284
    args: &[Expr],
83
284
) -> Result<()> {
84
284
    let result = function_form(symbols, args)?;
85
142
    emit_closure_or_stringify(ctx, emit, symbols, &result)
86
284
}
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
568
fn emit_closure_or_stringify(
95
568
    ctx: &mut CompileContext,
96
568
    emit: &mut FunctionEmitter,
97
568
    symbols: &SymbolTable,
98
568
    result: &Expr,
99
568
) -> Result<()> {
100
568
    if let Expr::Lambda(params, body) = result
101
568
        && let Some(sig) = try_emit_lambda_for_value(ctx, emit, symbols, params, body)?
102
    {
103
426
        return serialize_stack_to_output(ctx, emit, crate::ast::WasmType::Closure(sig));
104
142
    }
105
142
    compile_string(ctx, emit, &format_expr(result))
106
568
}
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
3203
pub(in crate::compiler) fn is_capture_free(
135
3203
    symbols: &SymbolTable,
136
3203
    params: &LambdaParams,
137
3203
    body: &Expr,
138
3203
) -> bool {
139
3203
    if !params.optional.is_empty()
140
3202
        || params.rest.is_some()
141
3202
        || !params.key.is_empty()
142
3202
        || !params.aux.is_empty()
143
    {
144
1
        return false;
145
3202
    }
146
3202
    references_no_free_variable(symbols, &params.required, body)
147
3203
}
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
13303
fn references_no_free_variable(symbols: &SymbolTable, bound: &[String], expr: &Expr) -> bool {
155
3556
    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
7964
        Expr::Symbol(name) => {
166
10804
            bound.iter().any(|p| p == name)
167
3986
                || (!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
3556
        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
3556
        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
3555
        Expr::List(elems) => elems
195
3555
            .iter()
196
10095
            .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
1776
        _ => true,
204
    }
205
13303
}
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
3986
fn resolves_to_variable(symbols: &SymbolTable, name: &str) -> bool {
219
3414
    matches!(
220
3986
        symbols.lookup(name).map(|sym| sym.kind()),
221
        Some(SymbolKind::Variable)
222
    )
223
3986
}
224

            
225
3556
fn resolves_to_macro(symbols: &SymbolTable, name: &str) -> bool {
226
3555
    matches!(
227
3556
        symbols.lookup(name).map(|sym| sym.kind()),
228
        Some(SymbolKind::Macro)
229
    )
230
3556
}
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
3414
fn has_function_binding(symbols: &SymbolTable, name: &str) -> bool {
236
3414
    symbols
237
3414
        .lookup(name)
238
3414
        .is_some_and(|sym| sym.function().is_some())
239
3414
}
240

            
241
3124
pub(in crate::compiler) fn try_emit_lambda_for_value(
242
3124
    ctx: &mut CompileContext,
243
3124
    emit: &mut FunctionEmitter,
244
3124
    symbols: &SymbolTable,
245
3124
    params: &LambdaParams,
246
3124
    body: &Expr,
247
3124
) -> Result<Option<crate::ast::ClosureSigId>> {
248
3124
    if !emit::is_v1_eligible(symbols, params, body) {
249
142
        return Ok(None);
250
2982
    }
251
2982
    let sig = emit::emit_lambda_value(ctx, emit, symbols, params, body)?;
252
2982
    Ok(Some(sig))
253
3124
}
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
1136
pub(in crate::compiler) fn try_emit_lambda_for_host_iter(
263
1136
    ctx: &mut CompileContext,
264
1136
    emit: &mut FunctionEmitter,
265
1136
    symbols: &SymbolTable,
266
1136
    params: &LambdaParams,
267
1136
    body: &Expr,
268
1136
    user_param_types: &[WasmType],
269
1136
) -> Result<Option<crate::ast::ClosureSigId>> {
270
1136
    if !emit::is_v1_eligible(symbols, params, body) {
271
        return Ok(None);
272
1136
    }
273
1136
    let sig = emit::emit_lambda_value_typed(
274
1136
        ctx,
275
1136
        emit,
276
1136
        symbols,
277
1136
        params,
278
1136
        body,
279
1136
        user_param_types,
280
1136
        emit::CallingConvention::HostAnyref,
281
497
    )?;
282
639
    Ok(Some(sig))
283
1136
}
284

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

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

            
313
852
pub(super) fn compile_apply_form(
314
852
    ctx: &mut CompileContext,
315
852
    emit: &mut FunctionEmitter,
316
852
    symbols: &mut SymbolTable,
317
852
    args: &[Expr],
318
852
) -> Result<()> {
319
852
    let all_args = build_apply_call(symbols, args)?;
320
426
    compile_call(ctx, emit, symbols, &all_args)
321
852
}
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
852
fn build_apply_call(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Vec<Expr>> {
334
852
    if args.len() < 2 {
335
142
        return Err(Error::Compile(
336
142
            "apply requires at least a function and one argument".to_string(),
337
142
        ));
338
710
    }
339
710
    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
710
    let mut all_args: Vec<Expr> = Vec::with_capacity(leading.len() + 1);
344
710
    all_args.push(resolve_apply_designator(symbols, &leading[0])?);
345
710
    for arg in &leading[1..] {
346
213
        all_args.push(eval_value(symbols, arg)?);
347
    }
348
710
    all_args.extend(spread_apply_last_arg(symbols, &args[args.len() - 1])?);
349
426
    Ok(all_args)
350
852
}
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
1136
fn spread_apply_last_arg(symbols: &mut SymbolTable, arg: &Expr) -> Result<Vec<Expr>> {
361
1136
    if let Expr::List(elems) = arg
362
426
        && let Some(Expr::Symbol(head)) = elems.first()
363
426
        && head.eq_ignore_ascii_case("LIST")
364
426
        && resolves_to_native_list(symbols, head)
365
    {
366
284
        return Ok(elems[1..].to_vec());
367
852
    }
368
852
    match eval_value(symbols, arg)? {
369
639
        Expr::Quote(inner) => match *inner {
370
568
            Expr::List(elems) => Ok(elems),
371
            Expr::Nil => Ok(vec![]),
372
71
            _ => Err(apply_last_arg_error()),
373
        },
374
        Expr::Nil => Ok(vec![]),
375
213
        _ => Err(apply_last_arg_error()),
376
    }
377
1136
}
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
426
fn resolves_to_native_list(symbols: &SymbolTable, head: &str) -> bool {
386
426
    match symbols.lookup(head) {
387
426
        Some(sym) => sym.function().is_none() && sym.value().is_none(),
388
        None => false,
389
    }
390
426
}
391

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

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

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

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

            
453
426
pub(super) fn apply(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
454
426
    if args.len() < 2 {
455
        return Err(Error::Compile(
456
            "apply requires at least a function and one argument".to_string(),
457
        ));
458
426
    }
459
426
    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
426
    let mut all_args: Vec<Expr> = Vec::with_capacity(leading.len() + 1);
464
426
    all_args.push(resolve_apply_designator(symbols, &leading[0])?);
465
1065
    for arg in &leading[1..] {
466
1065
        all_args.push(eval_value(symbols, arg)?);
467
    }
468
426
    all_args.extend(spread_apply_last_arg(symbols, &args[args.len() - 1])?);
469
426
    super::super::expr::call(symbols, &all_args)
470
426
}
471

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