1
//! `CONS` constructor. Eval path constant-folds chains; compile path
2
//! pushes typed car/cdr onto the stack and calls `pair_new`.
3
//! `push_pair_car` boxes i32 values via `ref.i31`; `push_pair_cdr`
4
//! emits `ref.null pair` for terminal `nil` or validates a typed
5
//! `PairRef(elem)` matches the declared element.
6

            
7
use crate::ast::{Expr, PairElement, WasmType};
8
use crate::compiler::context::CompileContext;
9
use crate::compiler::emit::FunctionEmitter;
10
use crate::compiler::expr::{
11
    compile_expr, compile_for_effect, compile_for_stack, compile_for_stack_as, eval_value,
12
    serialize_stack_to_output,
13
};
14
use crate::error::{Error, Result};
15
use crate::runtime::SymbolTable;
16

            
17
use super::datum::{compile_folded_to_stack, is_datum_result};
18
use super::infer::infer_pair_element;
19

            
20
39831
pub(super) fn cons(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
21
39831
    if args.len() != 2 {
22
142
        return Err(Error::Arity {
23
142
            name: "CONS".to_string(),
24
142
            expected: 2,
25
142
            actual: args.len(),
26
142
        });
27
39689
    }
28
39689
    let car = eval_value(symbols, &args[0])?;
29
39476
    let cdr = eval_value(symbols, &args[1])?;
30
39476
    if car.is_wasm_runtime()
31
9372
        || cdr.is_wasm_runtime()
32
8094
        || matches!(cdr.wasm_type(), Some(WasmType::PairRef(_)))
33
    {
34
31382
        let elem = infer_pair_element(&car, &cdr)?;
35
31382
        return Ok(Expr::WasmRuntime(WasmType::PairRef(elem)));
36
8094
    }
37
8094
    match cdr {
38
4260
        Expr::Quote(inner) => match *inner {
39
4260
            Expr::List(mut elems) => {
40
4260
                elems.insert(0, car);
41
4260
                Ok(Expr::Quote(Box::new(Expr::List(elems))))
42
            }
43
            Expr::Nil => Ok(Expr::Quote(Box::new(Expr::List(vec![car])))),
44
            other => Ok(Expr::Quote(Box::new(Expr::cons(car, other)))),
45
        },
46
3692
        Expr::Nil => Ok(Expr::Quote(Box::new(Expr::List(vec![car])))),
47
142
        other => Ok(Expr::Quote(Box::new(Expr::cons(car, other)))),
48
    }
49
39831
}
50

            
51
1349
pub(super) fn compile_cons(
52
1349
    ctx: &mut CompileContext,
53
1349
    emit: &mut FunctionEmitter,
54
1349
    symbols: &mut SymbolTable,
55
1349
    args: &[Expr],
56
1349
) -> Result<()> {
57
1349
    let result = cons(symbols, args)?;
58
1207
    if matches!(result.wasm_type(), Some(WasmType::PairRef(_))) {
59
568
        let ty = compile_cons_to_stack(ctx, emit, symbols, args)?;
60
497
        serialize_stack_to_output(ctx, emit, ty)?;
61
497
        return Ok(());
62
639
    }
63
639
    compile_expr(ctx, emit, symbols, &result)
64
1349
}
65

            
66
7881
pub(super) fn compile_cons_to_stack(
67
7881
    ctx: &mut CompileContext,
68
7881
    emit: &mut FunctionEmitter,
69
7881
    symbols: &mut SymbolTable,
70
7881
    args: &[Expr],
71
7881
) -> Result<WasmType> {
72
    // A fully-constant cons folds to a quoted list datum (e.g. (cons 0 '(1 2 3))
73
    // → '(0 1 2 3)); render it as a datum so value position agrees with the
74
    // effect path instead of trapping when push_pair_cdr meets the quoted cdr.
75
    // Only a runtime car/cdr (PairRef result) takes the pair_new path below.
76
7881
    let folded = cons(symbols, args)?;
77
7881
    if !matches!(folded.wasm_type(), Some(WasmType::PairRef(_))) && is_datum_result(&folded) {
78
426
        return compile_folded_to_stack(ctx, emit, symbols, folded);
79
7455
    }
80
    // If an argument transfers control before the pair is built (a
81
    // `(return-from …)` / `(error …)` — args evaluate left-to-right before the
82
    // call), the rest is dead: the pair is never constructed, so a non-list cdr
83
    // must NOT be rejected. Emit the live prefix (which performs the exit) and
84
    // return a placeholder element. Classify divergence on a CLONE (the exit is
85
    // still recorded by the single emit below).
86
7455
    if crate::compiler::special::form_diverges_for_test(&mut symbols.clone(), &args[0])? {
87
71
        let ty = compile_for_stack(ctx, emit, symbols, &args[0])?;
88
71
        return Ok(WasmType::PairRef(
89
71
            PairElement::from_wasm_type(ty).unwrap_or(PairElement::AnyRef),
90
71
        ));
91
7384
    }
92
7384
    if crate::compiler::special::form_diverges_for_test(&mut symbols.clone(), &args[1])? {
93
        // Car is evaluated (for its effects) then the cdr exits before
94
        // `pair_new`; the car value is dead, so compile it for effect.
95
71
        compile_for_effect(ctx, emit, symbols, &args[0])?;
96
71
        let ty = compile_for_stack(ctx, emit, symbols, &args[1])?;
97
71
        return Ok(WasmType::PairRef(
98
71
            PairElement::from_wasm_type(ty).unwrap_or(PairElement::AnyRef),
99
71
        ));
100
7313
    }
101
    // Decide the element type up front via the eval pipeline so we can
102
    // reject heterogeneous mixing before emitting any wasm.
103
7313
    let car_resolved = eval_value(symbols, &args[0])?;
104
7313
    let cdr_resolved = eval_value(symbols, &args[1])?;
105
7313
    let elem = infer_pair_element(&car_resolved, &cdr_resolved)?;
106
7313
    push_pair_car(ctx, emit, symbols, &args[0], elem)?;
107
7242
    push_pair_cdr(ctx, emit, symbols, &args[1], elem, &cdr_resolved)?;
108
7171
    emit.call(ctx.ids.pair_new);
109
7171
    Ok(WasmType::PairRef(elem))
110
7881
}
111

            
112
/// Emit `elem_args` as a nul-terminated `$pair` chain. LIST uses this so it
113
/// never synthesizes a `(CONS …)` form — that would route through symbol
114
/// dispatch and could hit a user `(defun cons …)` shadow. `elem_args` are the
115
/// ORIGINAL element expressions (emitted once via `push_pair_car`); the
116
/// chain's element type is decided up front by folding their resolved values
117
/// through the eval `cons` builder (same pairwise widening CONS does), so
118
/// every cell is emitted homogeneously.
119
1562
pub(super) fn compile_pair_chain(
120
1562
    ctx: &mut CompileContext,
121
1562
    emit: &mut FunctionEmitter,
122
1562
    symbols: &mut SymbolTable,
123
1562
    elem_args: &[Expr],
124
1562
) -> Result<WasmType> {
125
1562
    let resolved: Vec<Expr> = elem_args
126
1562
        .iter()
127
2627
        .map(|a| eval_value(symbols, a))
128
1562
        .collect::<Result<_>>()?;
129
1562
    let elem = match fold_chain_value(symbols, &resolved)?.wasm_type() {
130
710
        Some(WasmType::PairRef(elem)) => elem,
131
852
        _ => PairElement::AnyRef,
132
    };
133
1562
    emit_chain_cells(ctx, emit, symbols, elem_args, elem)?;
134
1491
    Ok(WasmType::PairRef(elem))
135
1562
}
136

            
137
/// Fold resolved `elems` right-to-left through the eval `cons` builder to get
138
/// the chain's unified `PairRef(elem)` placeholder (or `Nil` when empty).
139
1562
fn fold_chain_value(symbols: &mut SymbolTable, elems: &[Expr]) -> Result<Expr> {
140
1562
    let mut chain = Expr::Nil;
141
2627
    for elem in elems.iter().rev() {
142
2627
        chain = cons(symbols, &[elem.clone(), chain])?;
143
    }
144
1562
    Ok(chain)
145
1562
}
146

            
147
/// Emit the nested `pair_new` cells for `elem_args` (original expressions),
148
/// every car at the unified `elem` slot. Innermost (`nil`) cdr first via
149
/// recursion, so the wasm stack order per cell is `[car, cdr_ref]` → `pair_new`.
150
4047
fn emit_chain_cells(
151
4047
    ctx: &mut CompileContext,
152
4047
    emit: &mut FunctionEmitter,
153
4047
    symbols: &mut SymbolTable,
154
4047
    elem_args: &[Expr],
155
4047
    elem: PairElement,
156
4047
) -> Result<()> {
157
4047
    let Some((car, rest)) = elem_args.split_first() else {
158
1491
        emit.ref_null(ctx.ids.ty_pair);
159
1491
        return Ok(());
160
    };
161
2556
    push_pair_car(ctx, emit, symbols, car, elem)?;
162
2485
    emit_chain_cells(ctx, emit, symbols, rest, elem)?;
163
2485
    emit.call(ctx.ids.pair_new);
164
2485
    Ok(())
165
4047
}
166

            
167
9869
fn push_pair_car(
168
9869
    ctx: &mut CompileContext,
169
9869
    emit: &mut FunctionEmitter,
170
9869
    symbols: &mut SymbolTable,
171
9869
    arg: &Expr,
172
9869
    elem: PairElement,
173
9869
) -> Result<()> {
174
9869
    if elem == PairElement::AnyRef {
175
        // Heterogeneous cell: widen any wasm type to the anyref car. The
176
        // i31-boxed value types (I32 / Bool) need `ref.i31`; reference-typed
177
        // values are anyref subtypes already.
178
2698
        let actual = compile_for_stack(ctx, emit, symbols, arg)?;
179
2627
        if matches!(actual, WasmType::I32 | WasmType::Bool) {
180
1420
            emit.ref_i31();
181
1420
        }
182
2627
        return Ok(());
183
7171
    }
184
7171
    match elem {
185
        // Value cells: a pair cell is NOT the Index stratum (CLAUDE.md), so a
186
        // dimension-flexible integer literal in a Ratio cell must coerce to
187
        // Ratio rather than lower as I32 and mismatch the slot. `nil` in these
188
        // cells lands on a real zero (`0` / `#f` / `0/1`), never a trapping
189
        // null — `as_wasm_type` is i31-boxed (I32/Bool) or a non-null ratio.
190
        PairElement::I32 | PairElement::Bool | PairElement::Ratio => {
191
6603
            compile_for_stack_as(ctx, emit, symbols, arg, elem.as_wasm_type())?;
192
6603
            if matches!(elem, PairElement::I32 | PairElement::Bool) {
193
3195
                emit.ref_i31();
194
3408
            }
195
6603
            Ok(())
196
        }
197
        // Reference cells (string / entity / commodity): keep the strict match
198
        // so a `nil` or wrong-typed car stays a COMPILE error rather than a
199
        // typed null that `CAR`'s non-null cast would trap on at runtime.
200
        _ => {
201
568
            let actual = compile_for_stack(ctx, emit, symbols, arg)?;
202
568
            if PairElement::from_wasm_type(actual) != Some(elem) {
203
71
                return Err(Error::Compile(format!(
204
71
                    "CONS car: expected {elem} to match the inferred pair element, got {actual}"
205
71
                )));
206
497
            }
207
497
            Ok(())
208
        }
209
    }
210
9869
}
211

            
212
7242
fn push_pair_cdr(
213
7242
    ctx: &mut CompileContext,
214
7242
    emit: &mut FunctionEmitter,
215
7242
    symbols: &mut SymbolTable,
216
7242
    arg: &Expr,
217
7242
    elem: PairElement,
218
7242
    resolved: &Expr,
219
7242
) -> Result<()> {
220
7242
    if matches!(resolved, Expr::Nil) {
221
2769
        emit.ref_null(ctx.ids.ty_pair);
222
2769
        return Ok(());
223
4473
    }
224
4473
    let actual = compile_for_stack(ctx, emit, symbols, arg)?;
225
710
    match actual {
226
4402
        WasmType::PairRef(actual_elem) if actual_elem == elem => Ok(()),
227
        // The widened-AnyRef case accepts any typed pair as the cdr —
228
        // `$pair` already carries `anyref` cars, so no per-element
229
        // adjustment is needed once we've decided to ride the
230
        // heterogeneous variant.
231
710
        WasmType::PairRef(_) if elem == PairElement::AnyRef => Ok(()),
232
        WasmType::PairRef(actual_elem) => Err(Error::Compile(format!(
233
            "CONS cdr element type mismatch — expected pair<{elem}>, got pair<{actual_elem}>"
234
        ))),
235
71
        other => Err(Error::Compile(format!(
236
71
            "CONS cdr must be a pair or nil, got {other}"
237
71
        ))),
238
    }
239
7242
}