Skip to content

Commit bc061ad

Browse files
hyperpolymathclaude
andcommitted
fix(#555): C backend fails loud on handle/resume instead of dropping arms
The C backend's ExprHandle arm compiled the body and discarded every handler arm (`handle 41 { return(v) => v+1 }` emitted 41, not 42), and ExprResume was a silent argument passthrough — the same silent wrong-value miscompile #555 fixed in the WASM/WasmGC/Deno-ESM/JS-text backends. Now failwith (caught by codegen_c → Error), matching the loud-fail policy; use the interpreter for algebraic effects. Adds an E2E loud-fail test for the C backend. Documents (in the handler- fence test block) that the Lean/Why3 text backends are experimental stubs which drop `return` statements wholesale — a broader incompleteness than #555, flagged separately rather than given a misleading handle-only fence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent aa1b73f commit bc061ad

2 files changed

Lines changed: 46 additions & 3 deletions

File tree

lib/c_codegen.ml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,25 @@ let rec gen_expr ctx (expr : expr) : string =
239239
| ExprIndex (arr, idx) ->
240240
Printf.sprintf "(%s)[%s]" (gen_expr ctx arr) (gen_expr ctx idx)
241241
| ExprSpan (inner, _) -> gen_expr ctx inner
242-
| ExprHandle { eh_body; eh_handlers = _ } -> gen_expr ctx eh_body
243-
| ExprResume (Some e) -> gen_expr ctx e
244-
| ExprResume None -> "((void)0)"
242+
| ExprHandle _ ->
243+
(* #555: compiling the body and dropping every handler arm (the previous
244+
behaviour) was a silent wrong-value miscompile — `handle 41 { return(v)
245+
=> v + 1 }` emitted 41 instead of 42, and a `perform` would never
246+
dispatch to its arm. The C backend has no handler-dispatch / CPS
247+
transform, so fail loudly (matching the WASM, WasmGC, Deno-ESM and
248+
JS-text backends) rather than emit wrong code; use the interpreter
249+
(`--interp` / `-i`) for algebraic effects. *)
250+
failwith
251+
"effect handler (handle { ... }) in the C backend — handler arms \
252+
cannot be dispatched (requires a CPS transform; Refs #555); \
253+
use `--interp` / `-i`"
254+
| ExprResume _ ->
255+
(* `resume` is only meaningful inside a handler arm; the enclosing
256+
`handle` already fails above. Fail consistently rather than emit a
257+
silent argument passthrough (issue #555). *)
258+
failwith
259+
"`resume` expression in the C backend — only valid inside a `handle` \
260+
block (Refs #555); use `--interp` / `-i`"
245261
| ExprRecord { er_fields; _ } ->
246262
let fs = List.map (fun (id, e_opt) ->
247263
let v = match e_opt with Some e -> gen_expr ctx e | None -> mangle id.name in

test/test_e2e.ml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2281,11 +2281,38 @@ let test_handle_deno_loud_fail () =
22812281
Alcotest.(check bool) "error names the handler fence" true
22822282
(contains_str "effect handler" msg)
22832283

2284+
let test_handle_c_loud_fail () =
2285+
let result =
2286+
let open Result in
2287+
let ( let* ) = bind in
2288+
let* (prog, resolve_ctx) = run_frontend (fixture "handle_return_arm.affine") in
2289+
C_codegen.codegen_c prog resolve_ctx.symbols
2290+
in
2291+
match result with
2292+
| Ok _ ->
2293+
Alcotest.fail
2294+
"expected loud failure for handle on the C backend (Refs #555); \
2295+
got Ok — silent arm-drop has regressed"
2296+
| Error msg ->
2297+
Alcotest.(check bool) "error names the handler fence" true
2298+
(contains_str "effect handler" msg)
2299+
2300+
(* NOTE: the Lean and Why3 text backends are experimental stubs whose
2301+
block-lowering only emits `let` bindings — a `return` statement (and thus
2302+
any expression it wraps, including `handle`) is silently dropped, so
2303+
`fn main() -> Int { return handle ... }` emits `def main : Int := ()`.
2304+
That is a *broader* incompleteness than #555 (they miscompile far more than
2305+
handlers), so a handle-specific fence there would be misleading. They are
2306+
flagged separately rather than fenced here; the reachable #555 codegen
2307+
holes were the production backends (WASM / WasmGC / Deno-ESM / JS-text)
2308+
and C, all fenced + tested above. *)
2309+
22842310
let handler_fence_tests = [
22852311
Alcotest.test_case "interp: return-arm handle still evaluates" `Quick test_handle_interp_still_works;
22862312
Alcotest.test_case "wasm: handle → loud UnsupportedFeature" `Quick test_handle_wasm_loud_fail;
22872313
Alcotest.test_case "js-text: handle → loud failure" `Quick test_handle_js_loud_fail;
22882314
Alcotest.test_case "deno-esm: handle → loud failure" `Quick test_handle_deno_loud_fail;
2315+
Alcotest.test_case "c: handle → loud failure" `Quick test_handle_c_loud_fail;
22892316
]
22902317

22912318
(* ============================================================================

0 commit comments

Comments
 (0)