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
568
pub(super) fn compile_catch_each(
25
568
    ctx: &mut CompileContext,
26
568
    emit: &mut FunctionEmitter,
27
568
    symbols: &mut SymbolTable,
28
568
    args: &[Expr],
29
568
) -> Result<()> {
30
568
    let ty = compile_catch_each_for_stack(ctx, emit, symbols, args)?;
31
    serialize_stack_to_output(ctx, emit, ty)
32
568
}
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
1136
pub(super) fn compile_catch_each_for_stack(
46
1136
    ctx: &mut CompileContext,
47
1136
    emit: &mut FunctionEmitter,
48
1136
    symbols: &mut SymbolTable,
49
1136
    args: &[Expr],
50
1136
) -> Result<WasmType> {
51
1136
    let (items_expr, var_name, body) = parse_form(args)?;
52

            
53
1136
    let elem_ty = infer_items_elem_type(symbols, items_expr)?;
54
1136
    let params = LambdaParams::simple(vec![var_name.to_string()]);
55
1136
    let sig = try_emit_lambda_for_host_iter(ctx, emit, symbols, &params, body, &[elem_ty])?
56
639
        .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
639
    let closure_ty = WasmType::Closure(sig);
65
639
    let closure_local = ctx.alloc_local(closure_ty)?;
66
639
    emit.local_set(closure_local);
67

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

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

            
77
568
    let func_idx = ctx.ids.nomi_catch_each()?;
78
568
    emit.call(func_idx);
79
568
    emit.ref_cast_nullable(ctx.ids.ty_pair);
80
568
    Ok(RESULT_TY)
81
1136
}
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
1136
fn infer_items_elem_type(symbols: &mut SymbolTable, items_expr: &Expr) -> Result<WasmType> {
91
1136
    let resolved = eval_value(symbols, items_expr)?;
92
1136
    Ok(match resolved.wasm_type() {
93
284
        Some(WasmType::PairRef(elem)) => elem.as_wasm_type(),
94
852
        _ => fallback_elem_type_from_literal(&resolved),
95
    })
96
1136
}
97

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

            
106
781
fn first_homogeneous_type(elems: &[Expr]) -> Option<WasmType> {
107
781
    let head = elems.first()?;
108
639
    let head_ty = literal_wasm_type(head)?;
109
1491
    if elems.iter().all(|e| literal_wasm_type(e) == Some(head_ty)) {
110
568
        Some(head_ty)
111
    } else {
112
71
        Some(WasmType::AnyRef)
113
    }
114
781
}
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
2130
fn literal_wasm_type(expr: &Expr) -> Option<WasmType> {
123
2130
    match expr {
124
2059
        Expr::Number(_) => Some(WasmType::Ratio),
125
71
        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
2130
}
132

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

            
140
1420
fn parse_form(args: &[Expr]) -> Result<(&Expr, &str, &Expr)> {
141
1420
    if args.len() != 3 {
142
        return Err(Error::Arity {
143
            name: "catch-each".to_string(),
144
            expected: 3,
145
            actual: args.len(),
146
        });
147
1420
    }
148
1420
    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
1420
    Ok((&args[0], var_name, &args[2]))
155
1420
}
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
639
fn compile_items_for_stack(
163
639
    ctx: &mut CompileContext,
164
639
    emit: &mut FunctionEmitter,
165
639
    symbols: &mut SymbolTable,
166
639
    items_expr: &Expr,
167
639
) -> Result<WasmType> {
168
639
    let resolved = eval_value(symbols, items_expr)?;
169
639
    if is_empty_list(&resolved) {
170
71
        emit.ref_null(ctx.ids.ty_pair);
171
71
        return Ok(RESULT_TY);
172
568
    }
173
568
    compile_for_stack(ctx, emit, symbols, items_expr)
174
639
}
175

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

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

            
194
284
fn enforce_items_pair_value(value: &Expr) -> Result<()> {
195
284
    match value.wasm_type() {
196
284
        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
284
}