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::native::{chain_pair_element, compile_constant_list};
19
use crate::compiler::special::try_emit_lambda_for_host_iter;
20
use crate::error::{Error, Result};
21
use crate::runtime::SymbolTable;
22

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

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

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

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

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

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

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

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

            
78
913
    let func_idx = ctx.ids.nomi_catch_each()?;
79
913
    emit.call(func_idx);
80
913
    emit.ref_cast_nullable(ctx.ids.ty_pair);
81
913
    Ok(RESULT_TY)
82
1577
}
83

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

            
99
/// A constant items list binds the iteration variable at the element the LIST
100
/// chain materializes with, so the body sees the same type CAR extracts.
101
2241
fn fallback_elem_type_from_literal(expr: &Expr) -> WasmType {
102
2241
    match expr {
103
996
        Expr::Quote(inner) => fallback_elem_type_from_literal(inner),
104
996
        Expr::List(elems) => {
105
996
            chain_pair_element(elems).map_or(WasmType::AnyRef, PairElement::as_wasm_type)
106
        }
107
249
        _ => WasmType::AnyRef,
108
    }
109
2241
}
110

            
111
498
pub(super) fn catch_each_form(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
112
498
    let (items_expr, _, _) = parse_form(args)?;
113
498
    let items_value = eval_value(symbols, items_expr)?;
114
498
    enforce_items_pair_value(&items_value)?;
115
498
    Ok(Expr::WasmRuntime(RESULT_TY))
116
498
}
117

            
118
2075
fn parse_form(args: &[Expr]) -> Result<(&Expr, &str, &Expr)> {
119
2075
    if args.len() != 3 {
120
        return Err(Error::Arity {
121
            name: "catch-each".to_string(),
122
            expected: 3,
123
            actual: args.len(),
124
        });
125
2075
    }
126
2075
    let var_name = args[1].as_symbol().ok_or_else(|| {
127
        Error::Compile(format!(
128
            "catch-each: variable must be a symbol, got {:?}",
129
            args[1]
130
        ))
131
    })?;
132
2075
    Ok((&args[0], var_name, &args[2]))
133
2075
}
134

            
135
/// Materializes `items_expr` as a `pair<...>` value on the stack.
136
/// `(list)` resolves to `Expr::Nil` / `Expr::List([])`, which
137
/// `compile_for_stack` lowers to `i32_const(0)` (legacy `Nil` slot).
138
/// `catch-each` needs a *typed* pair-null, so detect empties up front
139
/// and emit `ref.null $pair` directly.
140
996
fn compile_items_for_stack(
141
996
    ctx: &mut CompileContext,
142
996
    emit: &mut FunctionEmitter,
143
996
    symbols: &mut SymbolTable,
144
996
    items_expr: &Expr,
145
996
) -> Result<WasmType> {
146
996
    let resolved = eval_value(symbols, items_expr)?;
147
996
    if is_empty_list(&resolved) {
148
83
        emit.ref_null(ctx.ids.ty_pair);
149
83
        return Ok(RESULT_TY);
150
913
    }
151
    // A symbol / quote naming a constant list has no stack lowering of its
152
    // own; materialize it by the same chain rule `infer_items_elem_type` uses.
153
913
    if !matches!(items_expr, Expr::List(_))
154
166
        && let Expr::Quote(inner) = &resolved
155
83
        && let Expr::List(members) = inner.as_ref()
156
    {
157
83
        let elem = chain_pair_element(members)?;
158
83
        return compile_constant_list(ctx, emit, symbols, members.clone(), elem);
159
830
    }
160
830
    compile_for_stack(ctx, emit, symbols, items_expr)
161
996
}
162

            
163
1660
fn is_empty_list(expr: &Expr) -> bool {
164
1660
    match expr {
165
83
        Expr::Nil => true,
166
664
        Expr::List(elems) => elems.is_empty(),
167
664
        Expr::Quote(inner) => is_empty_list(inner),
168
249
        _ => false,
169
    }
170
1660
}
171

            
172
996
fn enforce_items_pair(ty: WasmType) -> Result<()> {
173
996
    match ty {
174
913
        WasmType::PairRef(_) => Ok(()),
175
83
        other => Err(Error::Compile(format!(
176
83
            "catch-each: items expression must be a pair list, got {other}"
177
83
        ))),
178
    }
179
996
}
180

            
181
498
fn enforce_items_pair_value(value: &Expr) -> Result<()> {
182
498
    match value.wasm_type() {
183
498
        Some(WasmType::PairRef(_)) | None => Ok(()),
184
        Some(other) => Err(Error::Compile(format!(
185
            "catch-each: items expression must be a pair list, got {other}"
186
        ))),
187
    }
188
498
}