1
//! LAMBDA / FUNCTION / FUNCALL / APPLY codegen.
2

            
3
use super::common::{compile_and_validate, compile_expect_error, wrap_with_runtime_ratio};
4

            
5
#[test]
6
1
fn lambda_literal_compiles() {
7
1
    compile_and_validate("(lambda (x) (+ x 1))");
8
1
}
9

            
10
#[test]
11
1
fn lambda_with_rest_param() {
12
1
    compile_and_validate("(lambda (x &rest more) x)");
13
1
}
14

            
15
#[test]
16
1
fn lambda_with_optional_param() {
17
1
    compile_and_validate("(lambda (x &optional (y 10)) (+ x y))");
18
1
}
19

            
20
#[test]
21
1
fn function_form_quotes_symbol() {
22
1
    compile_and_validate("(defun greet (x) x) (function greet)");
23
1
}
24

            
25
#[test]
26
1
fn function_unknown_symbol_errors() {
27
1
    let err = compile_expect_error("(function no-such-fn)");
28
1
    assert!(
29
1
        err.contains("function definition")
30
            || err.contains("no-such-fn")
31
            || err.contains("Undefined")
32
            || err.contains("NO-SUCH-FN"),
33
        "got: {err}",
34
    );
35
1
}
36

            
37
#[test]
38
1
fn funcall_resolves_designator_and_calls() {
39
1
    compile_and_validate("(defun double (x) (* x 2)) (funcall (function double) 5)");
40
1
}
41

            
42
#[test]
43
1
fn funcall_with_runtime_arg() {
44
1
    compile_and_validate(&wrap_with_runtime_ratio(
45
1
        "(defun double (n) (* n 2)) (funcall (function double) X)",
46
1
    ));
47
1
}
48

            
49
#[test]
50
1
fn apply_with_list() {
51
1
    compile_and_validate("(defun sum3 (a b c) (+ a b c)) (apply (function sum3) (list 1 2 3))");
52
1
}
53

            
54
#[test]
55
1
fn apply_with_runtime_arg() {
56
1
    compile_and_validate(&wrap_with_runtime_ratio(
57
1
        "(defun id (n) n) (apply (function id) (list X))",
58
1
    ));
59
1
}
60

            
61
#[test]
62
1
fn funcall_empty_args_errors() {
63
1
    let err = compile_expect_error("(funcall)");
64
1
    assert!(
65
1
        err.contains("funcall") || err.contains("FUNCALL") || err.contains("function argument"),
66
        "got: {err}"
67
    );
68
1
}
69

            
70
#[test]
71
1
fn apply_too_few_args_errors() {
72
1
    let err = compile_expect_error("(apply)");
73
1
    assert!(err.contains("apply") || err.contains("APPLY"), "got: {err}");
74
1
}
75

            
76
#[test]
77
1
fn apply_one_arg_errors() {
78
1
    let err = compile_expect_error("(apply (function +))");
79
1
    assert!(err.contains("apply") || err.contains("APPLY"), "got: {err}");
80
1
}
81

            
82
#[test]
83
1
fn apply_with_non_list_last_arg_errors() {
84
1
    let err = compile_expect_error("(defun id (x) x) (apply (function id) 42)");
85
1
    assert!(
86
1
        err.contains("apply") || err.contains("APPLY") || err.contains("quoted list"),
87
        "got: {err}"
88
    );
89
1
}
90

            
91
#[test]
92
1
fn apply_with_quoted_non_list_errors() {
93
    // `'42` is a quoted number, not a quoted list — apply must
94
    // reject so the spread doesn't reinterpret an atom as args.
95
1
    let err = compile_expect_error("(defun id (x) x) (apply (function id) '42)");
96
1
    assert!(
97
1
        err.contains("apply") || err.contains("APPLY") || err.contains("quoted list"),
98
        "got: {err}"
99
    );
100
1
}
101

            
102
#[test]
103
1
fn apply_with_empty_quoted_list_compiles() {
104
    // `apply` with `'()` as the last arg should be a zero-arg call
105
    // — the empty spread is a real use case for variadic helpers.
106
1
    compile_and_validate("(defun zero () 0) (apply (function zero) '())");
107
1
}
108

            
109
#[test]
110
1
fn function_with_lambda_compiles() {
111
    // `(function (lambda ...))` route — exercises the inline lambda
112
    // case in function_form, distinct from the bare symbol case.
113
1
    compile_and_validate("(function (lambda (x) x))");
114
1
}
115

            
116
#[test]
117
1
fn function_with_invalid_arg_errors() {
118
    // Bare number isn't a symbol or lambda — function_form's
119
    // fallback error path.
120
1
    let err = compile_expect_error("(function 42)");
121
1
    assert!(
122
1
        err.contains("symbol or lambda") || err.contains("FUNCTION"),
123
        "got: {err}"
124
    );
125
1
}
126

            
127
#[test]
128
1
fn lambda_missing_body_errors() {
129
1
    let err = compile_expect_error("(lambda)");
130
1
    assert!(
131
1
        err.contains("LAMBDA") || err.contains("parameter list"),
132
        "got: {err}"
133
    );
134
1
}
135

            
136
#[test]
137
1
fn lambda_only_params_errors() {
138
1
    let err = compile_expect_error("(lambda (x))");
139
1
    assert!(
140
1
        err.contains("LAMBDA") || err.contains("parameter list"),
141
        "got: {err}"
142
    );
143
1
}
144

            
145
#[test]
146
1
fn lambda_multi_form_body_wraps_in_begin() {
147
    // Two body forms must be wrapped in BEGIN — exercises the
148
    // `forms.push(Symbol("BEGIN"))` branch in `lambda()`.
149
1
    compile_and_validate("(funcall (lambda (x) (+ x 1) (+ x 2)) 5)");
150
1
}
151

            
152
/// Tier 1.5 Gap A regression-lock: a FUNCALL whose return value is
153
/// consumed in arithmetic must drive the `stack` callback in
154
/// special::lambda::FORMS — without it, `compile_for_stack` errored
155
/// with "special form 'FUNCALL' cannot produce stack value".
156
#[test]
157
1
fn funcall_at_stack_position_compiles() {
158
1
    compile_and_validate("(defun double (x) (* x 2)) (+ (funcall (function double) 5) 1)");
159
1
}
160

            
161
/// Tier 1.5 Gap A regression-lock: same constraint for APPLY in
162
/// stack position.
163
#[test]
164
1
fn apply_at_stack_position_compiles() {
165
1
    compile_and_validate("(defun sum2 (a b) (+ a b)) (+ (apply (function sum2) (list 3 4)) 1)");
166
1
}
167

            
168
/// Tier 1.5 runtime-closure dispatch: a let-bound lambda used in
169
/// FUNCALL must lower through the `$closure` GC value + `call_ref`
170
/// path, not the inline lambda-call path. Locks the call-site
171
/// rewrite that promotes `Expr::Lambda` inits to `Expr::WasmLocal`
172
/// closures.
173
#[test]
174
1
fn let_bound_lambda_funcall_compiles_via_call_ref() {
175
1
    compile_and_validate("(let ((f (lambda (x) (* x 2)))) (funcall f 5))");
176
1
}
177

            
178
#[test]
179
1
fn let_star_bound_lambda_funcall_compiles_via_call_ref() {
180
1
    compile_and_validate("(let* ((f (lambda (x) (* x 2)))) (funcall f 5))");
181
1
}
182

            
183
/// Stack-position variant: the runtime-closure dispatch must serve
184
/// `compile_symbol_call_for_stack` too so the value flows into
185
/// surrounding arithmetic.
186
#[test]
187
1
fn let_bound_lambda_funcall_at_stack_position_compiles() {
188
1
    compile_and_validate("(let ((f (lambda (x) (* x 2)))) (+ (funcall f 5) 1))");
189
1
}
190

            
191
/// Arity mismatch on a runtime-closure call must be caught at compile
192
/// time by `compile_call_ref` — the `$closure_<sig>` carries the
193
/// expected param count and we can't dispatch through `call_ref` with
194
/// the wrong shape.
195
#[test]
196
1
fn let_bound_lambda_funcall_arity_mismatch_errors() {
197
1
    let err = compile_expect_error("(let ((f (lambda (x) (* x 2)))) (funcall f 5 6))");
198
1
    assert!(
199
1
        err.contains("closure") || err.contains("arity") || err.contains("expected"),
200
        "got: {err}"
201
    );
202
1
}
203

            
204
/// Tier 1.5 Gap B closure: a recursive defun called with a runtime
205
/// arg lowers through the monomorph cache + runtime-call path
206
/// (`__defun_<name>_<sig>` wasm fn) instead of overflowing the inline
207
/// const-fold walk. Validation is enough — the wasm's recursive
208
/// `call $idx` survives the validator's stack-typing pass only if the
209
/// fixpoint return-type inference converged on the right `Ratio`
210
/// signature.
211
#[test]
212
1
fn recursive_defun_with_runtime_arg_compiles_and_runs() {
213
1
    let src = "(defun fact (n) (if (<= n 1) 1 (* n (fact (- n 1)))))
214
1
               (let* ((X (transaction-post-date 0))) (fact X))";
215
1
    compile_and_validate(src);
216
1
}
217

            
218
/// A recursive runtime-call defun whose signature includes a `#f` / `nil`
219
/// literal argument must compile. Bool/nil literals lower to `WasmType::Bool`
220
/// on the stack, so `infer_arg_type` must classify them as `Bool` too —
221
/// otherwise the monomorph signature says `I32` while `emit_runtime_call`
222
/// pushes `Bool` and the exact-type check rejects the call. Regression for the
223
/// WasmType::Bool / runtime-call inference mismatch.
224
#[test]
225
1
fn recursive_defun_with_bool_literal_arg_compiles() {
226
1
    let src = "(defun bounce (x flag) (if flag (bounce x #f) x))
227
1
               (let* ((X (transaction-post-date 0))) (bounce X #t))";
228
1
    compile_and_validate(src);
229
1
}
230

            
231
/// A recursive runtime-arg defun nested as an ARGUMENT to a native (not in
232
/// head position) routes through the eval-side recursion guard
233
/// (`dispatch_symbol` re-entry → `recursive_runtime_call_type` placeholder)
234
/// rather than overflowing the const-fold walk, then the codegen monomorph
235
/// path emits the real call. `fact` has a base case so it's well-formed;
236
/// validation confirms the nested call lowered without a compiler overflow.
237
#[test]
238
1
fn recursive_runtime_arg_call_nested_in_native_compiles() {
239
1
    let src = "(defun fact (n) (if (<= n 1) 1 (* n (fact (- n 1)))))
240
1
               (let* ((X (transaction-post-date 0))) (+ (/ 1 1) (fact X)))";
241
1
    compile_and_validate(src);
242
1
}
243

            
244
/// A pass-through recursive runtime-arg call (`(loopx x)`) nested in a native
245
/// COMPILES: the eval-side re-entry guard hands it to the codegen monomorph
246
/// path (the compile-time walk terminates; the unbounded recursion becomes a
247
/// runtime trap, not a compiler overflow). Validation confirms the nested
248
/// `call $idx` lowered. The nms suite covers the runtime-trap behavior and the
249
/// depth-guard error path for the cases the monomorph path can't take over.
250
#[test]
251
1
fn passthrough_recursive_runtime_arg_nested_in_native_compiles() {
252
1
    compile_and_validate(
253
1
        "(defun loopx (x) (loopx x)) \
254
1
         (let* ((X (transaction-post-date 0))) (+ (/ 1 1) (loopx X)))",
255
    );
256
1
}
257

            
258
/// Two distinct call sites of the same defun with the same arg-type
259
/// signature share a single emitted monomorph — the cache key is
260
/// `(name, params)`. Compile success + validation is the contract the
261
/// test pins; emitting two helpers for an identical signature would
262
/// not change the wire result but would bloat the wasm and is the
263
/// regression we're guarding against.
264
#[test]
265
1
fn monomorph_cache_reuses_for_same_sig() {
266
1
    let src = "(defun double (n) (* n 2))
267
1
               (let* ((X (transaction-post-date 0))
268
1
                      (Y (transaction-post-date 1)))
269
1
                 (+ (double X) (double Y)))";
270
1
    compile_and_validate(src);
271
1
}
272

            
273
/// Same defun called with two different runtime arg types must emit
274
/// two distinct monomorphs because the cache is keyed on the param
275
/// signature, not the name. We exercise it with `Ratio` on one site
276
/// and a `Ratio`-bearing local on another — both routes hit the
277
/// runtime-call path.
278
#[test]
279
1
fn monomorph_cache_separates_for_different_sigs() {
280
1
    let src = "(defun id (n) n)
281
1
               (let* ((X (transaction-post-date 0))
282
1
                      (IDX (entity-count)))
283
1
                 (+ (id X) (id X)))";
284
1
    compile_and_validate(src);
285
1
}
286

            
287
/// Regression (AFL, compile-level): a lambda body that is a too-short binder
288
/// form — `(map (lambda (s) (do)) (list 1))` — drove closure param-type
289
/// inference to slice `&elems[2..]` out of range and PANIC. `compile_expect_error`
290
/// fails on a panic, so this pins the public contract that the malformed shape
291
/// surfaces a structured compile error through the real `compile()` path, not a
292
/// SIGABRT. (The `param_infer` unit test covers the helper in isolation.)
293
#[test]
294
1
fn lambda_body_short_binder_does_not_panic_compiler() {
295
4
    for body in ["(do)", "(let)", "(dolist)", "(do*)"] {
296
4
        let _ = compile_expect_error(&format!("(map (lambda (s) {body}) (list 1))"));
297
4
    }
298
1
}