1
//! DOLIST codegen on runtime pair-chains. Exercises the runtime
2
//! variant of `compile_dolist` that the static-eval path can't reach.
3

            
4
use super::common::compile_and_validate;
5

            
6
#[test]
7
1
fn dolist_over_static_integer_list() {
8
1
    compile_and_validate("(let* ((sum 0)) (dolist (i '(1 2 3)) (setf sum (+ sum i))) sum)");
9
1
}
10

            
11
#[test]
12
1
fn dolist_over_runtime_pair_chain() {
13
    // `(get-input-entities)` returns a runtime `pair<i32>` chain. The
14
    // dolist body sees `i` as a runtime i32 local.
15
1
    compile_and_validate(
16
1
        "(let* ((acc 0)) (dolist (i (get-input-entities)) (setf acc (+ acc 1))) acc)",
17
    );
18
1
}
19

            
20
#[test]
21
1
fn dolist_with_explicit_result_form() {
22
1
    compile_and_validate("(let* ((acc 0)) (dolist (i '(1 2)) (setf acc (+ acc i))) acc)");
23
1
}
24

            
25
#[test]
26
1
fn dolist_over_cons_ratio_chain() {
27
1
    compile_and_validate(
28
1
        "(let* ((sum 0)) \
29
1
           (dolist (r (cons (/ 1 2) (cons (/ 3 4) nil))) \
30
1
             (setf sum (+ sum r))) \
31
1
           sum)",
32
    );
33
1
}
34

            
35
#[test]
36
1
fn dolist_over_string_chain() {
37
1
    compile_and_validate(
38
1
        "(let* ((count 0)) \
39
1
           (dolist (s (cons \"a\" (cons \"b\" nil))) \
40
1
             (setf count (+ count 1))) \
41
1
           count)",
42
    );
43
1
}
44

            
45
/// A DOLIST as the value of a `defun` body (value/stack position). The form
46
/// used to carry `stack: None`, so this errored with "special form 'DOLIST'
47
/// cannot produce stack value". No result form ≡ nil. Locks in the stack path.
48
#[test]
49
1
fn dolist_in_value_position_no_result_form() {
50
1
    compile_and_validate("(defun walk (xs) (dolist (x xs) x)) (walk '(1 2 3))");
51
1
}
52

            
53
/// Same, with an explicit result form that the stack path must leave typed
54
/// on the operand stack.
55
#[test]
56
1
fn dolist_in_value_position_with_result_form() {
57
1
    compile_and_validate("(let* ((acc 0)) (dolist (x '(1 2 3) acc) (setf acc (+ acc x))))");
58
1
}
59

            
60
/// A CONSTANT-list dolist whose body `setf`s an OUTER variable to a runtime
61
/// value. The const-fold path must run the same setf-target promotion the
62
/// runtime path does — without it, `out` is left a bare `WasmRuntime`
63
/// placeholder with no stack producer and the wasm is malformed.
64
#[test]
65
1
fn const_dolist_body_setf_outer_to_runtime_compiles() {
66
1
    compile_and_validate("(let ((out nil)) (dolist (x '(1)) (setf out (get-input-entities))) out)");
67
1
}
68

            
69
/// A nil-initialized let local whose only `setf` store comes from a USER
70
/// function returning a ref type. The local must be sized from the function's
71
/// result type (PairRef), not the falsy `Bool` of its `nil` init — otherwise
72
/// the ref store fails wasm validation. Locks in that setf-local inference
73
/// types a user-fn rhs via its body, not just pure-native rhs.
74
#[test]
75
1
fn setf_local_sized_from_user_fn_ref_return_compiles() {
76
1
    compile_and_validate("(defun f () (get-input-entities)) (let ((out nil)) (setf out (f)) out)");
77
1
}
78

            
79
/// A let-bound runtime closure used as a FOLD accumulator function. Its result
80
/// type (Ratio, from its Ratio-default arithmetic body) is recorded at the
81
/// closure-emit site, so the eval-side fold result type AGREES with codegen and
82
/// the nil-init `out` local is sized correctly. Before the closure-result
83
/// recording the eval mirror sized `out` from the integer seed (Index) while
84
/// codegen emitted Ratio → invalid wasm.
85
#[test]
86
1
fn fold_runtime_closure_scalar_into_setf_local_compiles() {
87
1
    compile_and_validate(
88
1
        "(let ((f (lambda (acc x) (+ acc x)))) \
89
1
           (let ((out nil)) (setf out (fold f 0 (list 1 2 3))) out))",
90
    );
91
1
}
92

            
93
/// A let-bound runtime closure that BUILDS a list (`(cons x acc)`). Param-usage
94
/// inference types `acc` as a `PairRef` (a list accumulator) instead of the
95
/// default Ratio — otherwise `(cons x acc)` rejects `acc` as a non-pair cdr.
96
#[test]
97
1
fn fold_runtime_closure_list_builder_compiles() {
98
1
    compile_and_validate("(let ((f (lambda (acc x) (cons x acc)))) (fold f nil (list 1 2 3)))");
99
1
}
100

            
101
/// MAP / FILTER over let-bound runtime closures with scalar results compile
102
/// (the closure's Ratio-default params match the Ratio-cell literal list).
103
#[test]
104
1
fn map_filter_runtime_closures_compile() {
105
1
    compile_and_validate("(let ((g (lambda (x) (* x 2)))) (map g (list 1 2 3)))");
106
1
    compile_and_validate("(let ((p (lambda (x) (< 1 x)))) (filter p (list 1 2 3)))");
107
1
}
108

            
109
/// MAP result element is the closure's RESULT type, not the input element: a
110
/// closure returning a StringRef, mapped over a list and stored into a nil-init
111
/// local, must size that local as `pair<string>` (eval uses the recorded
112
/// closure result, matching codegen) rather than `pair<ratio>` → invalid wasm.
113
#[test]
114
1
fn map_runtime_closure_result_sizes_setf_local() {
115
1
    compile_and_validate(
116
1
        "(let ((g (lambda (x) (tag-name 0)))) \
117
1
           (let ((out nil)) (setf out (map g (list 1 2))) out))",
118
    );
119
1
}
120

            
121
/// FILTER keeps INPUT elements: a closure predicate over a Ratio list, stored
122
/// into a nil-init local, sizes it as `pair<ratio>` (the input element) via the
123
/// eval path that now mirrors `compile_filter_literal` (input-element widening)
124
/// rather than the old `pair<ratio>` default that drifted for non-Ratio lists.
125
#[test]
126
1
fn filter_runtime_closure_into_setf_local_compiles() {
127
1
    compile_and_validate(
128
1
        "(let ((p (lambda (n) (< 0 n)))) \
129
1
           (let ((out nil)) (setf out (filter p (list 1 2 3))) out))",
130
    );
131
1
}
132

            
133
/// A body/program TAIL that is `(setf …)`: its value must be serialized to the
134
/// output buffer (the `compile_expr` contract), not stranded on the wasm stack
135
/// — otherwise the enclosing block ends non-empty (invalid wasm).
136
#[test]
137
1
fn tail_setf_serializes_value_not_left_on_stack() {
138
1
    compile_and_validate("(let ((x 0)) (setf x 5))");
139
1
}
140

            
141
/// `(setf <ratio-local> <integer-literal>)`: the rhs literal must coerce to the
142
/// local's declared type (Ratio), crossing the Index↔Scalar boundary — not be
143
/// stored as a raw I32 into a ref-typed local (invalid wasm).
144
#[test]
145
1
fn setf_coerces_literal_rhs_to_local_type() {
146
1
    compile_and_validate("(let ((x 1/2)) (setf x 1) x)");
147
1
}
148

            
149
/// SETF-promotion inference is scope-aware: a shadowed inner `(setf x 2)` must
150
/// NOT promote the OUTER `x` to a runtime local. Here the outer `x` stays the
151
/// const `1`, so `(+ x 1/2)` is `(+ 1 1/2)` (both Scalar) and compiles; if the
152
/// shadowed setf wrongly promoted the outer `x` to a runtime Index, the add
153
/// would be an Index/Scalar stratum mismatch.
154
#[test]
155
1
fn setf_promotion_respects_shadowing() {
156
1
    compile_and_validate("(let ((x 1)) (let ((x 0)) (setf x 2)) (+ x 1/2))");
157
1
}
158

            
159
/// FOLD with a let-bound closure over a RUNTIME I32 list (`get-input-entities`
160
/// yields a `pair<i32>`). The closure param `x` defaults to Ratio, so a
161
/// `call_ref` would reject the i32 element. The HOF recovers the closure's body
162
/// (`CompileContext::closure_body`) and INLINES it per element instead, binding
163
/// `x` to the actual i32 — so the closure works over a non-Ratio list.
164
#[test]
165
1
fn fold_let_closure_over_runtime_i32_list_inlines() {
166
1
    compile_and_validate("(let ((f (lambda (acc x) (+ acc 1)))) (fold f 0 (get-input-entities)))");
167
1
}
168

            
169
/// SETF must invalidate the recorded closure body only AFTER the rhs is
170
/// compiled: the rhs here FOLDS the closure being reassigned over a runtime i32
171
/// list, which requires the inline path (a Ratio-default `call_ref` would
172
/// mismatch the i32 element). Forgetting the body before compiling the rhs would
173
/// drop the inline and fail to compile.
174
#[test]
175
1
fn setf_rhs_may_fold_the_closure_being_reassigned() {
176
1
    compile_and_validate(
177
1
        "(let ((f (lambda (acc x) (+ acc 1)))) \
178
1
           (setf f (begin (fold f 0 (get-input-entities)) f)) \
179
1
           0)",
180
    );
181
1
}
182

            
183
/// A let-bound STRING predicate closure: param-usage inference pins `s` to
184
/// `StringRef` (it's a `string=` argument), so the closure emits with `s:
185
/// StringRef` — matching the string-list element — instead of the default
186
/// Ratio that `string=` would reject at emission.
187
#[test]
188
1
fn filter_let_closure_string_predicate_compiles() {
189
1
    compile_and_validate(
190
1
        "(let ((p (lambda (s) (string= s \"x\")))) (filter p (cons \"a\" (cons \"b\" nil))))",
191
    );
192
1
}