1
//! `(catch-each items var body)` codegen — Tier 2's named recovery
2
//! primitive (ADR-0025).
3
//!
4
//! Lowering: compile `body` as `(lambda (var) body)` via the Tier 1.5
5
//! closure machinery to get a `$closure_<sig>` value on the stack;
6
//! extract its funcref + env, push the items pair-list, then call the
7
//! `__nomi_catch_each` host import. The host walks the chain in Rust,
8
//! invokes the funcref per item recovering per-call `wasmtime::Error`,
9
//! and returns a heterogeneous `pair<anyref>` of `(ok . v)` / `(err
10
//! . (code . msg))` cells. Engine traps (`OutOfFuel` /
11
//! `EpochInterrupt`) bypass capture and re-throw to the outer call site
12
//! — they're never catchable.
13

            
14
use crate::ast::{Expr, LambdaParams, PairElement, WasmType};
15
use crate::compiler::context::CompileContext;
16
use crate::compiler::emit::FunctionEmitter;
17
use crate::compiler::expr::{compile_for_stack, eval_value, serialize_stack_to_output};
18
use crate::compiler::special::try_emit_lambda_for_host_iter;
19
use crate::error::{Error, Result};
20
use crate::runtime::SymbolTable;
21

            
22
const RESULT_TY: WasmType = WasmType::PairRef(PairElement::AnyRef);
23

            
24
544
pub(super) fn compile_catch_each(
25
544
    ctx: &mut CompileContext,
26
544
    emit: &mut FunctionEmitter,
27
544
    symbols: &mut SymbolTable,
28
544
    args: &[Expr],
29
544
) -> Result<()> {
30
544
    let ty = compile_catch_each_for_stack(ctx, emit, symbols, args)?;
31
    serialize_stack_to_output(ctx, emit, ty)
32
544
}
33

            
34
pub(super) fn compile_catch_each_for_effect(
35
    ctx: &mut CompileContext,
36
    emit: &mut FunctionEmitter,
37
    symbols: &mut SymbolTable,
38
    args: &[Expr],
39
) -> Result<()> {
40
    compile_catch_each_for_stack(ctx, emit, symbols, args)?;
41
    emit.drop_value();
42
    Ok(())
43
}
44

            
45
1088
pub(super) fn compile_catch_each_for_stack(
46
1088
    ctx: &mut CompileContext,
47
1088
    emit: &mut FunctionEmitter,
48
1088
    symbols: &mut SymbolTable,
49
1088
    args: &[Expr],
50
1088
) -> Result<WasmType> {
51
1088
    let (items_expr, var_name, body) = parse_form(args)?;
52

            
53
1088
    let elem_ty = infer_items_elem_type(symbols, items_expr)?;
54
1088
    let params = LambdaParams::simple(vec![var_name.to_string()]);
55
1088
    let sig = try_emit_lambda_for_host_iter(ctx, emit, symbols, &params, body, &[elem_ty])?
56
612
        .ok_or_else(|| {
57
            Error::Compile(
58
                "catch-each: body lambda is out of v1 first-class-closure scope; \
59
                 rewrite the body to use only required parameters"
60
                    .to_string(),
61
            )
62
        })?;
63

            
64
612
    let closure_ty = WasmType::Closure(sig);
65
612
    let closure_local = ctx.alloc_local(closure_ty)?;
66
612
    emit.local_set(closure_local);
67

            
68
612
    let closure_type_idx = ctx.closure_sig(sig).closure_type_idx;
69
612
    emit.local_get(closure_local);
70
612
    emit.struct_get(closure_type_idx, 0);
71
612
    emit.local_get(closure_local);
72
612
    emit.struct_get(closure_type_idx, 1);
73

            
74
612
    let items_ty = compile_items_for_stack(ctx, emit, symbols, items_expr)?;
75
612
    enforce_items_pair(items_ty)?;
76

            
77
544
    let func_idx = ctx.ids.nomi_catch_each()?;
78
544
    emit.call(func_idx);
79
544
    emit.ref_cast_nullable(ctx.ids.ty_pair);
80
544
    Ok(RESULT_TY)
81
1088
}
82

            
83
/// Infers the user-visible element type of the items pair-list. The
84
/// closure body's iteration variable must bind at this type so the body
85
/// can use it in arithmetic / typed positions; the host fn passes raw
86
/// anyref per element and the closure's prologue downcasts to this type
87
/// before the body runs. Empty-list / heterogeneous-AnyRef shapes fall
88
/// back to AnyRef so the body can still reference the variable, just
89
/// untyped.
90
1088
fn infer_items_elem_type(symbols: &mut SymbolTable, items_expr: &Expr) -> Result<WasmType> {
91
1088
    let resolved = eval_value(symbols, items_expr)?;
92
1088
    Ok(match resolved.wasm_type() {
93
272
        Some(WasmType::PairRef(elem)) => elem.as_wasm_type(),
94
816
        _ => fallback_elem_type_from_literal(&resolved),
95
    })
96
1088
}
97

            
98
1564
fn fallback_elem_type_from_literal(expr: &Expr) -> WasmType {
99
1564
    match expr {
100
748
        Expr::Quote(inner) => fallback_elem_type_from_literal(inner),
101
748
        Expr::List(elems) => first_homogeneous_type(elems).unwrap_or(WasmType::AnyRef),
102
68
        _ => WasmType::AnyRef,
103
    }
104
1564
}
105

            
106
748
fn first_homogeneous_type(elems: &[Expr]) -> Option<WasmType> {
107
748
    let head = elems.first()?;
108
612
    let head_ty = literal_wasm_type(head)?;
109
1428
    if elems.iter().all(|e| literal_wasm_type(e) == Some(head_ty)) {
110
544
        Some(head_ty)
111
    } else {
112
68
        Some(WasmType::AnyRef)
113
    }
114
748
}
115

            
116
/// Mirrors the literal element classification in
117
/// `compiler::native::list::infer::literal_pair_element` so the closure
118
/// body's iteration variable binds at the same type CAR/CDR extract — a
119
/// numeric literal is a dimensionless `Ratio` (never a count), and bool
120
/// literals ride the `PairElement::Bool` slot (so the iteration var is `Bool`,
121
/// recovered as Nil/Bool not Number).
122
2040
fn literal_wasm_type(expr: &Expr) -> Option<WasmType> {
123
2040
    match expr {
124
1972
        Expr::Number(_) => Some(WasmType::Ratio),
125
68
        Expr::String(_) => Some(WasmType::StringRef),
126
        Expr::Bool(_) => Some(WasmType::Bool),
127
        Expr::WasmRuntime(t) | Expr::WasmLocal(_, t) => Some(*t),
128
        Expr::Quote(inner) => literal_wasm_type(inner),
129
        _ => None,
130
    }
131
2040
}
132

            
133
272
pub(super) fn catch_each_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
134
272
    let (items_expr, _, _) = parse_form(args)?;
135
272
    let items_value = eval_value(symbols, items_expr)?;
136
272
    enforce_items_pair_value(&items_value)?;
137
272
    Ok(Expr::WasmRuntime(RESULT_TY))
138
272
}
139

            
140
1360
fn parse_form(args: &[Expr]) -> Result<(&Expr, &str, &Expr)> {
141
1360
    if args.len() != 3 {
142
        return Err(Error::Arity {
143
            name: "catch-each".to_string(),
144
            expected: 3,
145
            actual: args.len(),
146
        });
147
1360
    }
148
1360
    let var_name = args[1].as_symbol().ok_or_else(|| {
149
        Error::Compile(format!(
150
            "catch-each: variable must be a symbol, got {:?}",
151
            args[1]
152
        ))
153
    })?;
154
1360
    Ok((&args[0], var_name, &args[2]))
155
1360
}
156

            
157
/// Materializes `items_expr` as a `pair<...>` value on the stack.
158
/// `(list)` resolves to `Expr::Nil` / `Expr::List([])`, which
159
/// `compile_for_stack` lowers to `i32_const(0)` (legacy `Nil` slot).
160
/// `catch-each` needs a *typed* pair-null, so detect empties up front
161
/// and emit `ref.null $pair` directly.
162
612
fn compile_items_for_stack(
163
612
    ctx: &mut CompileContext,
164
612
    emit: &mut FunctionEmitter,
165
612
    symbols: &mut SymbolTable,
166
612
    items_expr: &Expr,
167
612
) -> Result<WasmType> {
168
612
    let resolved = eval_value(symbols, items_expr)?;
169
612
    if is_empty_list(&resolved) {
170
68
        emit.ref_null(ctx.ids.ty_pair);
171
68
        return Ok(RESULT_TY);
172
544
    }
173
544
    compile_for_stack(ctx, emit, symbols, items_expr)
174
612
}
175

            
176
1020
fn is_empty_list(expr: &Expr) -> bool {
177
1020
    match expr {
178
        Expr::Nil => true,
179
408
        Expr::List(elems) => elems.is_empty(),
180
408
        Expr::Quote(inner) => is_empty_list(inner),
181
204
        _ => false,
182
    }
183
1020
}
184

            
185
612
fn enforce_items_pair(ty: WasmType) -> Result<()> {
186
612
    match ty {
187
544
        WasmType::PairRef(_) => Ok(()),
188
68
        other => Err(Error::Compile(format!(
189
68
            "catch-each: items expression must be a pair list, got {other}"
190
68
        ))),
191
    }
192
612
}
193

            
194
272
fn enforce_items_pair_value(value: &Expr) -> Result<()> {
195
272
    match value.wasm_type() {
196
272
        Some(WasmType::PairRef(_)) | None => Ok(()),
197
        Some(other) => Err(Error::Compile(format!(
198
            "catch-each: items expression must be a pair list, got {other}"
199
        ))),
200
    }
201
272
}