Something went wrong. Try again.
The Monad language. Dependent types, functional programming compiled with LLVM. Hobby project. monad-lang.org
dependent-types language compiler programming-language functional-programming
Something went wrong. Try again.
6.6 kB · 150 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151/// Regression tests for the `compose_seq` pure-argument splice/// corruption (fixed via `compose_seq_acc`, `lang/codegen/emit.mo`).////// A struct update `{ x with f := <expr> }` desugars at typecheck to a/// constructor application inside a single-case match on `x` -- a/// BRANCHING term, so the arm's value sits in a merge block closed/// `ret <phi>`. When these rows were written the desugaring built one/// projection match PER un-overridden field instead of one match over the/// base (`type_check_struct_update`, `lang/src/typecheck/infer.mo`,/// changed 2026-10-05 so the base is evaluated once); the composition/// hazard is the same either way, and so are these rows' numbers. When a field's/// expression contained a LITERAL operand (`x.f + 1`), that literal/// argument (no instructions, no blocks) used to be composed via plain/// `compose_seq`, whose `splice_into_terminal_block` rewrote the/// projection's merge block from `ret <phi>` to `ret <literal>` --/// destroying the projected value -- and, since `llvm_value_eq`/// deliberately never matches literal pairs, left an unmatchable/// splice-target token so the FOLLOWING compose steps (the arithmetic/// call, the constructor alloc) all degraded into dead code appended/// after the branch. The function's real return stayed `ret i64 1`/// (the raw literal), so every caller did `monad_get_tag` on a small/// integer and SIGSEGV'd -- the exact crash v29's/// `module_info_cache_insert`/`module_info_cache_hit` hit on/// `check examples/hello.mo`.////// `compose_seq_acc` now makes a pure argument a complete no-op at/// every accumulation site (argument lists, native operands,/// callee-with-spine): it contributes no code and moves execution/// nowhere, so the running splice-target token keeps identifying the/// block execution is actually in. These tests compile each source/// through the self-hosted backend and run the native binary, so a/// regression reproduces the miscompile (wrong exit code) rather than/// just an IR-shape mismatch.open IO {println}use lang::codegen::test::e2e_harness {compile_source_run_expect}/// The minimal repro shape: `{ c with b := c.b + 1 }` -- the `+ 1`'s/// literal operand directly follows the `b`-projection's branching/// fragment. Before the fix, `bump`'s compiled body returned the raw/// literal `1` (an untagged i64) instead of the new struct, and main/// segfaulted on `monad_get_tag(1)`.#[test]def test_struct_update_field_plus_one : IO Bool := let source := r#"struct C { a : I64, b : I64,}def cempty : C := { a := 0, b := 0 }def bump (c : C) : C := { c with b := c.b + 1 }def main (args : List String) : IO I64 := do { let c : C := bump cempty; return c.b}"# in compile_source_run_expect source "test_struct_update_field_plus_one" 1/// The `module_info_cache_hit` shape: a MIDDLE field updated with an/// arithmetic expression while BOTH neighbors are preserved untouched/// (they compile to their own projection matches before/after the/// `hits + 1` fragment).#[test]def test_struct_update_middle_field_neighbors_preserved : IO Bool := let source := r#"struct D { x : I64, hits : I64, y : I64,}def dempty : D := { x := 7, hits := 0, y := 3 }def hit (d : D) : D := { d with hits := d.hits + 1 }def main (args : List String) : IO I64 := do { let d : D := hit dempty; return (I64.add d.hits (I64.add d.x d.y))}"# in compile_source_run_expect source "test_struct_update_middle_field_neighbors_preserved" 11/// A PURE field value (a bare literal, no instructions at all) placed/// BETWEEN two branching projection arguments -- the literal must reach/// the constructor's set_field without rewriting the preceding `x`-/// projection's merge block `ret`. (A literal in the FIRST field/// composes against an empty accumulator and can't reproduce the/// corruption -- the desugar emits arguments in struct-field order.)#[test]def test_struct_update_literal_field_value : IO Bool := let source := r#"struct D { x : I64, hits : I64, y : I64,}def dempty : D := { x := 7, hits := 41, y := 3 }def zero_hits (d : D) : D := { d with hits := 0 }def main (args : List String) : IO I64 := do { let d : D := zero_hits dempty; return (I64.add d.x (I64.add d.hits d.y))}"# in compile_source_run_expect source "test_struct_update_literal_field_value" 10/// MULTIPLE updated fields in one update -- each field's expression/// fragment must splice into the right place after the previous one/// (`module_info_cache_insert`'s exact two-field shape: one call-valued/// field, one arithmetic-valued field).#[test]def test_struct_update_two_fields_at_once : IO Bool := let source := r#"struct D { x : I64, hits : I64, y : I64,}def dempty : D := { x := 7, hits := 0, y := 3 }def both (d : D) : D := { d with x := d.x + 1, y := d.y + 2 }def main (args : List String) : IO I64 := do { let d : D := both dempty; return (I64.add d.x (I64.add d.hits d.y))}"# in compile_source_run_expect source "test_struct_update_two_fields_at_once" 13/// A struct update whose base is a parenthesized CALL -- which did not/// parse at all until the base stopped being a bare identifier/// (`plans/implementations/2026-10-04-struct-update-paren-base-fails-to-/// parse.md`). It runs the whole way through, so the row covers the/// desugaring as well as the grammar: the override lands and both/// untouched fields come back from the base, which is what a wrong binder/// or a wrong index would change (40 + 5 + 1 = 46; an override that failed/// to land gives 206, and the harness compares a process exit code, so/// every total here stays under 256).////// That the base is evaluated ONCE is a property of the term shape rather/// than of any value this program can print; it is pinned where the shape/// is built, by `test_struct_update_is_one_match_over_the_base`/// (`lang/src/typecheck/infer.mo`).#[test]def test_struct_update_paren_call_base : IO Bool := let source := r#"struct Resp { status : I64, body : I64, tag : I64,}def make (b : I64) : Resp := { status := 200, body := b, tag := 1 : Resp }def bad_request (b : I64) : Resp := { (make b) with status := 40 }def rstatus (r : Resp) : I64 := r.statusdef rbody (r : Resp) : I64 := r.bodydef rtag (r : Resp) : I64 := r.tagdef main (args : List String) : IO I64 := do { let r : Resp := bad_request 5; return (I64.add (rstatus r) (I64.add (rbody r) (rtag r)))}"# in compile_source_run_expect source "test_struct_update_paren_call_base" 46