1
//! `(handler-case)` — pattern-match recovery from a raised condition
2
//! (Tier 3.3, ADR-0026).
3
//!
4
//! ```lisp
5
//! (handler-case <body>
6
//!   (no-such-account (e) (log (error-message e)))
7
//!   (commodity-mismatch (e) (recover))
8
//!   (t (e) (error-code e)))            ; t = catch-all (optional, last)
9
//! ```
10
//!
11
//! Lowering (single-value `Catch::One`, the same shape as the Tier 3.2
12
//! boundary wrapper):
13
//! ```text
14
//! block $outer (result T)
15
//!   block $handler (result (ref null $nomi_condition))
16
//!     try_table (result T) (catch $nomi_error → $handler)
17
//!       <body → T>
18
//!     end
19
//!     br $outer                       ; normal completion carries T out
20
//!   end                               ; catch edge: condition ref on stack
21
//!   local.set $cond
22
//!   <flat dispatch>                   ; each clause an independent `if` that
23
//!                                     ; `br $outer`s on match; t = uncond.;
24
//!                                     ; no match → re-throw same condition
25
//! end
26
//! ```
27
//!
28
//! Result type `T` is discovered at emit time and unified across the body
29
//! and every reachable clause body (mirrors BLOCK's emit-time discovery);
30
//! a diverging arm contributes no type. Re-raise on no-match re-`throw`s a
31
//! fresh `$nomi_error` carrying the SAME condition struct — nomiscript
32
//! conditions have no observable identity, so this is equivalent to
33
//! re-raising the original and avoids needing an exnref.
34

            
35
use wasm_encoder::{BlockType, Catch, HeapType, RefType, ValType};
36

            
37
use crate::ast::{EntityKind, Expr, WasmType};
38
use crate::compiler::context::CompileContext;
39
use crate::compiler::emit::FunctionEmitter;
40
use crate::compiler::expr::{eval_value, serialize_stack_to_output};
41
use crate::error::{Error, Result};
42
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
43

            
44
use super::arms::{Arm, OnConflict, arm_of_form, compile_arm, finish_arm, reconcile_arms};
45
use super::block_exits::form_diverges;
46

            
47
/// The body and every reachable clause must agree once literal / `nil` arms
48
/// reconcile; a genuine conflict stays a compile error.
49
const ON_CONFLICT: OnConflict = OnConflict::Reject("HANDLER-CASE");
50

            
51
const HANDLER_CASE: &str = "handler-case";
52
const CATCH_ALL: &str = "T";
53

            
54
/// One parsed clause: `code` is `None` for the `t` catch-all, else the
55
/// (reader-upcased) condition-code symbol; `var` is the condition binding;
56
/// `body` is the clause's body forms.
57
struct Clause<'a> {
58
    code: Option<&'a str>,
59
    var: &'a str,
60
    body: &'a [Expr],
61
}
62

            
63
581
pub(super) fn eval_handler_case(symbols: &mut SymbolTable, args: &[Expr]) -> Result<Expr> {
64
581
    let (body, clauses) = parse(args)?;
65
    // If the body can't throw and folds to a constant, that's the value.
66
581
    let body_val = eval_value(symbols, body)?;
67
581
    let body_diverges = form_diverges(&mut symbols.clone(), body)?;
68
581
    if !body_val.is_wasm_runtime() && !body_diverges {
69
        return Ok(body_val);
70
581
    }
71
    // Otherwise the form has a runtime result. Its type must match what
72
    // `compile_handler_case_for_stack` emits: the unified type of the body
73
    // (when it can fall through) plus every reachable clause body — NOT just
74
    // the body, which is wrong when the body always throws (e.g. an `(error)`
75
    // body whose value comes from a clause). Callers that materialise a
76
    // runtime value (let/defvar binding, lambda arg) trust this type to size
77
    // the local, so a body-only type yields an invalid module.
78
581
    let mut arms: Vec<Arm> = Vec::new();
79
581
    if !body_diverges {
80
249
        arms.push(arm_of_form(body, &body_val)?);
81
332
    }
82
581
    for clause in &clauses {
83
581
        let mut clause_syms = symbols.clone();
84
581
        clause_syms.define(Symbol::new(clause.var, SymbolKind::Variable).with_value(
85
581
            Expr::WasmLocal(0, WasmType::EntityRef(EntityKind::Condition)),
86
        ));
87
581
        let (cval, cdiverges) = eval_clause_value(&mut clause_syms, clause)?;
88
581
        if !cdiverges {
89
581
            let value_form = clause.body.last().unwrap_or(&EMPTY_CLAUSE_VALUE);
90
581
            arms.push(arm_of_form(value_form, &cval)?);
91
        }
92
    }
93
581
    Ok(Expr::WasmRuntime(reconcile_arms(&arms, ON_CONFLICT)?))
94
581
}
95

            
96
/// Eval-time value + divergence of a clause body, mirroring the
97
/// compile-side `compile_clause_body` walk (last form is the value; an
98
/// earlier diverging form makes the rest dead).
99
581
fn eval_clause_value(symbols: &mut SymbolTable, clause: &Clause<'_>) -> Result<(Expr, bool)> {
100
581
    if clause.body.is_empty() {
101
        return Ok((Expr::Nil, false));
102
581
    }
103
581
    let last = clause.body.len() - 1;
104
581
    for (idx, form) in clause.body.iter().enumerate() {
105
581
        if idx == last {
106
581
            let diverges = form_diverges(&mut symbols.clone(), form)?;
107
581
            return Ok((eval_value(symbols, form)?, diverges));
108
        }
109
        if form_diverges(&mut symbols.clone(), form)? {
110
            eval_value(symbols, form)?;
111
            return Ok((Expr::Nil, true));
112
        }
113
        eval_value(symbols, form)?;
114
    }
115
    Ok((Expr::Nil, false))
116
581
}
117

            
118
2075
pub(super) fn compile_handler_case(
119
2075
    ctx: &mut CompileContext,
120
2075
    emit: &mut FunctionEmitter,
121
2075
    symbols: &mut SymbolTable,
122
2075
    args: &[Expr],
123
2075
) -> Result<()> {
124
2075
    let ty = compile_handler_case_for_stack(ctx, emit, symbols, args)?;
125
1826
    serialize_stack_to_output(ctx, emit, ty)
126
2075
}
127

            
128
332
pub(super) fn compile_handler_case_for_effect(
129
332
    ctx: &mut CompileContext,
130
332
    emit: &mut FunctionEmitter,
131
332
    symbols: &mut SymbolTable,
132
332
    args: &[Expr],
133
332
) -> Result<()> {
134
332
    compile_handler_case_for_stack(ctx, emit, symbols, args)?;
135
332
    emit.drop_value();
136
332
    Ok(())
137
332
}
138

            
139
4233
pub(super) fn compile_handler_case_for_stack(
140
4233
    ctx: &mut CompileContext,
141
4233
    emit: &mut FunctionEmitter,
142
4233
    symbols: &mut SymbolTable,
143
4233
    args: &[Expr],
144
4233
) -> Result<WasmType> {
145
4233
    let (body, clauses) = parse(args)?;
146

            
147
    // Stash local for the caught condition, allocated before the body so the
148
    // body's own locals stack above it (matches the boundary wrapper).
149
4067
    let cond_local = ctx.alloc_local(WasmType::AnyRef)?;
150

            
151
    // --- Phase 1: compile every arm into its own scratch, collect types. ---
152
    // Each scratch is seeded at the depth it will actually be spliced into,
153
    // so relative `br` targets of a `(return-from)` / `(go)` inside the arm
154
    // stay correct after splicing:
155
    //   - body: inside $outer/$handler/try_table → parent + 3.
156
    //   - a non-`t` clause: inside $outer + its own guard `if` → parent + 2.
157
    //   - the `t` clause: inside $outer only (no guard `if`) → parent + 1.
158
4067
    let parent_depth = emit.block_depth();
159
4067
    let body_depth = parent_depth + 3;
160

            
161
4067
    let mut body_scratch = FunctionEmitter::new_seeded(body_depth);
162
4067
    let body_arm = compile_arm(ctx, &mut body_scratch, symbols, body)?;
163
4067
    let body_diverges = body_arm.is_none();
164

            
165
4067
    let mut clause_arms: Vec<ClauseArm<'_>> = Vec::with_capacity(clauses.len());
166
4150
    for clause in &clauses {
167
4150
        let clause_depth = if clause.code.is_some() {
168
2822
            parent_depth + 2
169
        } else {
170
1328
            parent_depth + 1
171
        };
172
4150
        let mut scratch = FunctionEmitter::new_seeded(clause_depth);
173
4150
        let mut clause_syms = symbols.clone();
174
4150
        clause_syms.define(Symbol::new(clause.var, SymbolKind::Variable).with_value(
175
4150
            Expr::WasmLocal(cond_local, WasmType::EntityRef(EntityKind::Condition)),
176
        ));
177
4150
        let (value_form, arm) = compile_clause_body(ctx, &mut scratch, &mut clause_syms, clause)?;
178
4150
        clause_arms.push(ClauseArm {
179
4150
            scratch,
180
4150
            symbols: clause_syms,
181
4150
            value_form,
182
4150
            arm,
183
4150
        });
184
    }
185

            
186
4067
    let arms: Vec<Arm> = body_arm
187
4067
        .into_iter()
188
4067
        .chain(clause_arms.iter().filter_map(|clause| clause.arm))
189
4067
        .collect();
190
4067
    let result_ty = reconcile_arms(&arms, ON_CONFLICT)?;
191
3818
    if let Some(arm) = body_arm {
192
1577
        finish_arm(ctx, &mut body_scratch, symbols, body, arm, result_ty)?;
193
2241
    }
194
3818
    let mut clause_scratches: Vec<(FunctionEmitter, bool)> = Vec::with_capacity(clauses.len());
195
3901
    for mut clause in clause_arms {
196
3901
        if let Some(arm) = clause.arm {
197
3735
            finish_arm(
198
3735
                ctx,
199
3735
                &mut clause.scratch,
200
3735
                &mut clause.symbols,
201
3735
                clause.value_form,
202
3735
                arm,
203
3735
                result_ty,
204
            )?;
205
166
        }
206
3901
        clause_scratches.push((clause.scratch, clause.arm.is_none()));
207
    }
208

            
209
    // --- Phase 2: emit the real structure, splicing the scratch bodies. ---
210
3818
    let cond_ref = ValType::Ref(RefType {
211
3818
        nullable: true,
212
3818
        heap_type: HeapType::Concrete(ctx.condition_type_idx()),
213
3818
    });
214
3818
    let result_vt = ctx.wasm_val_type(result_ty);
215
3818
    let tag = ctx.nomi_error_tag();
216

            
217
3818
    emit.block_start_typed(BlockType::Result(result_vt)); // $outer
218
3818
    emit.block_start_typed(BlockType::Result(cond_ref)); // $handler
219
3818
    emit.try_table(
220
3818
        BlockType::Result(result_vt),
221
3818
        &[Catch::One { tag, label: 0 }],
222
    );
223
3818
    emit.splice(&body_scratch.take_bytes());
224
3818
    if body_diverges {
225
2241
        // Dead tail value after a diverging body; reset to polymorphic so
226
2241
        // the try_table's declared result type is satisfied vacuously.
227
2241
        emit.unreachable();
228
2241
    }
229
3818
    emit.block_end(); // close try_table
230
3818
    emit.br(1); // normal completion → $outer, skip handler
231
3818
    emit.block_end(); // close $handler — catch lands here, condition on stack
232

            
233
3818
    emit.local_set(cond_local);
234
3818
    emit_dispatch(
235
3818
        ctx,
236
3818
        emit,
237
3818
        &clauses,
238
3818
        &clause_scratches,
239
3818
        cond_local,
240
3818
        result_ty,
241
    )?;
242
3818
    emit.block_end(); // close $outer
243
3818
    Ok(result_ty)
244
4233
}
245

            
246
/// A compiled clause awaiting the unified result type: its scratch, the clause
247
/// scope, the value form and its arm (`None` when the clause diverges).
248
struct ClauseArm<'a> {
249
    scratch: FunctionEmitter,
250
    symbols: SymbolTable,
251
    value_form: &'a Expr,
252
    arm: Option<Arm>,
253
}
254

            
255
static EMPTY_CLAUSE_VALUE: Expr = Expr::Nil;
256

            
257
/// Compiles a clause body (already has `e` bound in `symbols`) into `emit`: its
258
/// leading forms for effect, its value form as an arm. An empty body is `nil`;
259
/// an earlier diverging form makes the rest dead.
260
4150
fn compile_clause_body<'a>(
261
4150
    ctx: &mut CompileContext,
262
4150
    emit: &mut FunctionEmitter,
263
4150
    symbols: &mut SymbolTable,
264
4150
    clause: &Clause<'a>,
265
4150
) -> Result<(&'a Expr, Option<Arm>)> {
266
4150
    let Some((value_form, leading)) = clause.body.split_last() else {
267
        return Ok((&EMPTY_CLAUSE_VALUE, Some(Arm::Nil)));
268
    };
269
4150
    for form in leading {
270
        let diverges = form_diverges(&mut symbols.clone(), form)?;
271
        crate::compiler::expr::compile_for_effect(ctx, emit, symbols, form)?;
272
        if diverges {
273
            return Ok((value_form, None));
274
        }
275
    }
276
4150
    Ok((value_form, compile_arm(ctx, emit, symbols, value_form)?))
277
4150
}
278

            
279
/// Emit the flat dispatch at depth `parent+1` (only `$outer` open). Each
280
/// non-`t` clause is an independent `if` (Empty type) whose body, on match,
281
/// runs the spliced clause and `br $outer`s — so every clause body sits at
282
/// the same depth. A `t` clause runs unconditionally. With no `t`, a
283
/// fall-through re-throws the same condition.
284
3818
fn emit_dispatch(
285
3818
    ctx: &mut CompileContext,
286
3818
    emit: &mut FunctionEmitter,
287
3818
    clauses: &[Clause<'_>],
288
3818
    scratches: &[(FunctionEmitter, bool)],
289
3818
    cond_local: u32,
290
3818
    result_ty: WasmType,
291
3818
) -> Result<()> {
292
3818
    let condition_idx = ctx.condition_type_idx();
293
3818
    let string_eq = ctx.ids.string_eq;
294
3818
    let tag = ctx.nomi_error_tag();
295

            
296
3901
    for (clause, (scratch, diverges)) in clauses.iter().zip(scratches) {
297
3901
        match clause.code {
298
2739
            Some(code) => {
299
                // condition.code == "CODE" ?  → guard an Empty `if`.
300
2739
                emit.local_get(cond_local);
301
2739
                emit.ref_cast(condition_idx);
302
2739
                emit.struct_get(condition_idx, 0);
303
2739
                push_string_literal(ctx, emit, code)?;
304
2739
                emit.call(string_eq);
305
2739
                emit.if_block(BlockType::Empty);
306
2739
                emit.splice(scratch.bytes_ref());
307
                // The clause value (if it didn't diverge) is on the stack;
308
                // carry it out to `$outer` (relative depth 1 from inside the
309
                // `if`). A diverging clause already left a polymorphic stack.
310
2739
                if !*diverges {
311
2656
                    emit.br(1);
312
2656
                }
313
2739
                emit.block_end();
314
            }
315
            None => {
316
                // `t` catch-all (last clause): runs unconditionally at
317
                // `$outer`'s depth. Its value falls through to `$outer`'s
318
                // end — no `br` needed. A diverging `t` body left a
319
                // polymorphic stack, also fine.
320
1162
                emit.splice(scratch.bytes_ref());
321
1162
                let _ = (diverges, result_ty);
322
1162
                return Ok(());
323
            }
324
        }
325
    }
326

            
327
    // No `t` and nothing matched: re-throw a fresh $nomi_error carrying the
328
    // same condition (no observable identity loss). The condition is stashed
329
    // as `anyref`, so cast back to the concrete `$nomi_condition` (the tag
330
    // param type) before throwing. `throw` is stack-polymorphic, satisfying
331
    // `$outer`'s declared result type.
332
2656
    emit.local_get(cond_local);
333
2656
    emit.ref_cast(condition_idx);
334
2656
    emit.throw(tag);
335
2656
    Ok(())
336
3818
}
337

            
338
2739
fn push_string_literal(
339
2739
    ctx: &mut CompileContext,
340
2739
    emit: &mut FunctionEmitter,
341
2739
    value: &str,
342
2739
) -> Result<()> {
343
2739
    let data_idx = ctx.add_data(value.as_bytes())?;
344
2739
    emit.i32_const(0);
345
2739
    emit.i32_const(value.len() as i32);
346
2739
    emit.array_new_data(ctx.ids.ty_i8_array, data_idx);
347
2739
    Ok(())
348
2739
}
349

            
350
/// Parse `(handler-case body clause...)`. Requires ≥1 body form; clauses
351
/// follow. Each clause is `(code-sym (var) body...)`; `t` is the catch-all
352
/// and, if present, must be the last clause.
353
4814
fn parse(args: &[Expr]) -> Result<(&Expr, Vec<Clause<'_>>)> {
354
4814
    let (body, clause_exprs) = args.split_first().ok_or_else(|| {
355
        Error::Compile(format!(
356
            "{HANDLER_CASE} requires a body and zero or more clauses"
357
        ))
358
    })?;
359
4814
    let mut clauses = Vec::with_capacity(clause_exprs.len());
360
4897
    for (i, clause) in clause_exprs.iter().enumerate() {
361
4897
        let elems = clause.as_list().ok_or_else(|| {
362
            Error::Compile(format!(
363
                "{HANDLER_CASE}: clause must be a list, got {clause:?}"
364
            ))
365
        })?;
366
4897
        if elems.len() < 2 {
367
            return Err(Error::Compile(format!(
368
                "{HANDLER_CASE}: clause needs a code symbol, a (var), and a body"
369
            )));
370
4897
        }
371
4897
        let code = match &elems[0] {
372
            // `t` reads as the `t` constant, not a symbol — it's the
373
            // catch-all marker. A symbol head is a condition code.
374
1660
            Expr::T => None,
375
3237
            Expr::Symbol(s) if s == CATCH_ALL => None,
376
3237
            Expr::Symbol(s) => Some(s.as_str()),
377
            other => {
378
                return Err(Error::Compile(format!(
379
                    "{HANDLER_CASE}: clause head must be a code symbol or t, got {other:?}"
380
                )));
381
            }
382
        };
383
4897
        if code.is_none() && i + 1 != clause_exprs.len() {
384
83
            return Err(Error::Compile(format!(
385
83
                "{HANDLER_CASE}: the t catch-all clause must be last"
386
83
            )));
387
4814
        }
388
4814
        let var = match elems[1].as_list() {
389
4731
            Some([Expr::Symbol(v)]) => v.as_str(),
390
            _ => {
391
83
                return Err(Error::Compile(format!(
392
83
                    "{HANDLER_CASE}: clause binding must be a single (var), got {:?}",
393
83
                    elems[1]
394
83
                )));
395
            }
396
        };
397
4731
        clauses.push(Clause {
398
4731
            code,
399
4731
            var,
400
4731
            body: &elems[2..],
401
4731
        });
402
    }
403
4648
    Ok((body, clauses))
404
4814
}