/// Regression tests for a family of related codegen gaps found while /// chasing `bootstrap compile cli/src/main.mo monad`'s self-compile past the /// string phi-merge fix: values that reach a `phi` (an `if`/`match` /// merge point) or a bare `ret` need the SAME materialization call /// arguments already get (`materialize_void`/`materialize_native_bool_ /// arg`, `lang/codegen/emit.mo`) -- neither `build_db_if_blocks`/ /// `build_merge_result` (if/else merges), `build_match_case_block` /// (match-arm merges), nor `compile_db_def_ir_body` (a def's own bare /// return value) called them until this session's fixes. /// /// Also exercises the adjacent `I64.beq` native-dispatch fix: /// `lookup_native`'s table hardcoded `"I64_eq"`, an identifier that /// doesn't exist in real source (`I64.eq` is "unknown variable" -- /// confirmed) -- the real, only I64 equality function throughout the /// whole corpus is `I64.beq` (`init/number.mo`'s `BEq I64` instance), /// which never matched the table at all and silently miscompiled to a /// "return Unit" stub, both as a bare call and as an `if`'s own /// condition. open IO {println} use lang::codegen::test::e2e_harness {compile_source_run_expect} /// `I64.beq` used directly as an `if`'s own condition and as an /// ordinary comparison -- exercises `lookup_native`'s fixed dispatch /// table entry (was `"I64_eq"`, now `"I64_beq"`). Uses a `do`-block /// `let`/`return` rather than a bare `IO.io (if ...)` body -- the latter /// hits a SEPARATE, already-filed, pre-existing bug (`implementations/ /// 2026-08-29-io-wrap-dropped-after-branching-argument.md`), unrelated /// to what this test guards. #[test] def test_i64_beq_native_dispatch : IO Bool := let source := r#"def main (args : List String) : IO I64 := do { let r := if I64.beq 5 5 then 1 else 0; return r } "# in compile_source_run_expect source "test_i64_beq_native_dispatch" 1 /// An outer `if` whose `else` branch is ITSELF a nested `if` -- the /// nested if's own compiled body ends in ITS OWN branch instruction /// (not a `jump` to the outer merge block), so the outer merge's `phi` /// must have only ONE incoming edge (from `then`), not two. Before the /// fix, `build_merge_result` always built a 2-entry phi regardless, /// producing a phi edge from a non-predecessor block -- `llc`: "PHINode /// should have one entry for each predecessor of its parent basic /// block!" / "Instruction does not dominate all uses!". Mirrors the /// exact shape that blocked `lang/parser/position.mo`'s /// `line_col_scan_direct` during the self-compile. #[test] def test_nested_if_in_else_branch_phi_arity : IO Bool := let source := r#"def classify (n : I64) (m : I64) : I64 := if I64.beq n 0 then 100 else if I64.gt m 0 then 1 else 2 def main (args : List String) : IO I64 := do { let a := classify 0 5; let b := classify 3 5; let c := classify 3 (0 - 5); return (I64.add a (I64.add b c)) } "# in compile_source_run_expect source "test_nested_if_in_else_branch_phi_arity" 103 /// A def whose whole body is a BARE native comparison (no wrapping /// `if`/call) -- `compile_db_def_ir_body`'s own `ret val_r` used to pass /// a raw, unboxed `i1` straight through when `val_r` came from a native /// comparison with no materialization step, matching the exact shape of /// `lang/parser/position.mo`'s own `is_all_ascii_direct`/`String.is_ /// empty`-style helpers (a comparison as a function's entire /// implementation). `llc`: "'%tN' defined with type 'i1' but expected /// 'i64'" at the `ret` itself. #[test] def test_def_body_bare_native_comparison_return : IO Bool := let source := r#"def is_five (n : I64) : Bool := I64.beq n 5 def main (args : List String) : IO I64 := do { let r := if is_five 5 then 1 else 0; return r } "# in compile_source_run_expect source "test_def_body_bare_native_comparison_return" 1 /// A `let`'s own BODY/continuation (not its bound argument -- that site /// was already covered by `implementations/2026-08-29-let-bound-native- /// bool-boxing.md`) reaching `void_val` with no materialization step: /// `try_compile_let_beta_db` passes `body`'s own compiled value straight /// into `compose_seq`, which splices it into the ALREADY-COMPILED `arg` /// expression's own dangling `ret` -- if `body` is void-shaped (a struct /// literal that doesn't desugar to `Term.con`, `implementations/ /// 2026-08-29-struct-literal-not-desugared-in-branch-position.md`, is /// the real-world trigger; `lang/parser/position.mo`'s `combine_line_ /// col_scan` is the exact shape: `let trailing := (if ...) in { struct /// literal } `), the spliced `ret` ends up `ret void` where `i64` is /// expected. This test's own `main` never reads back `p`'s fields, so it /// only exercises whether `make_pair` compiles+links+runs without /// crashing (this fix's actual scope) -- the struct's own field /// correctness is the SEPARATE, still-open bug tracked in that doc. #[test] def test_let_body_void_struct_literal : IO Bool := let source := r#"struct Pair { a : I64, b : I64, } def make_pair (n : I64) : Pair := let x := if I64.gt n 0 then 1 else 2 in { a := x, b := n } def main (args : List String) : IO I64 := do { let p := make_pair 5; return 42 } "# in compile_source_run_expect source "test_let_body_void_struct_literal" 42 /// The hole `materialize_terminal_ret`'s own doc comment recorded and /// declined to fix blind: a body shaped `let x := in I64.beq /// ...`. The field access is the branching subterm, so the comparison's /// `icmp` is spliced into a block that already ends in `ret` -- the one /// `i1` position the TERM-shape gate could not see, because a `let`-shaped /// body is an application and not a comparison. Forge hit it /// (`plans/implementations/2026-10-05-raw-i1-return-after-a-branching- /// subterm.md`) and `llc` rejected the function outright: /// `'%tN' defined with type 'i1' but expected 'i64'`. /// /// Pre-fix this row fails at COMPILE time, not on its value -- which is /// why the expected number is just the comparison's own answer (1 for /// true, through `I64.add`), with nothing clever riding on it. #[test] def test_native_bool_ret_after_a_branching_subterm : IO Bool := let source := r#"struct Inner { items : List I64, } def icount (xs : List I64) : I64 := match xs { empty => 0, cons _ rest => I64.add 1 (icount rest), _ => 0 } def seed : Inner := { items := [1, 2] : Inner } def two_items : Bool := let i : Inner := seed in I64.beq (icount i.items) 2 def main (args : List String) : IO I64 := do { return (if two_items then 1 else 0) } "# in compile_source_run_expect source "test_native_bool_ret_after_a_branching_subterm" 1