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.
3.2 kB · 82 lines
at main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283/// Regression tests for a constructor stealing a call whose head is a LOCAL/// of the same bare name (`try_compile_constructor_app_db`,/// `lang/src/codegen/emit.mo`).////// That function already bails out when the bare name is ALSO a real/// top-level def -- a function/constructor collision -- but it never looked/// at `c`'s locals, so a parameter or match-arm binder lost to any/// constructor of the same name anywhere in the whole-program namespace./// `compile_call_head`, the fallback it bails out to, checks locals first;/// this is that check, one level up.////// The blast radius is why these are e2e and not unit tests. `init/src/io.mo`/// spells `Monad IO`.bind as `match a { io a => f a }`, so a single arity-1/// constructor named `f` in ANY loaded module compiled `f a` to/// `alloc_constructor` + `set_field`: bind returned its payload re-wrapped/// and never invoked the continuation. Every `do` block in the program then/// silently did nothing -- no output, no crash, and an exit code that varied/// run to run because `main` unwrapped a field of a constructor that was/// never the right one. Found on `motes/tui`'s `Key.f (n : I64)`, where it/// took down all four of that mote's test files while leaving the Rust host/// completely silent.open IO {println}use lang::codegen::test::e2e_harness {compile_source_run_expect}/// A parameter named `f` applied as a function, alongside an arity-1/// constructor `f`. The direct unit: with the bug `f x` allocates a/// `Probe.f` object and `apply1` returns it, so the exit code is a pointer/// rather than 42.#[test]def test_local_param_shadows_same_named_ctor : IO Bool := let source := r#"type Probe { f (n : I64), zero_key}def apply1 (f : I64 -> I64) (x : I64) : I64 := f xdef bump (y : I64) : I64 := y + 1def main (args : List String) : IO I64 := do { let r := apply1 bump 41; return r}"# in compile_source_run_expect source "test_local_param_shadows_same_named_ctor" 42/// The real-world shape: the program never names `f` itself, it only/// declares the constructor. A `do` block's own desugaring reaches/// `init/src/io.mo`'s bind, whose continuation parameter IS named `f`, so/// with the bug the `return 7` after the first statement never runs.#[test]def test_ctor_named_f_does_not_break_do_blocks : IO Bool := let source := r#"type Probe { f (n : I64), zero_key}def main (args : List String) : IO I64 := do { println "probe"; return 7}"# in compile_source_run_expect source "test_ctor_named_f_does_not_break_do_blocks" 7/// A match-arm binder, not a parameter -- the other kind of local, and the/// one `Monad IO`.bind's own `io a => f a` arm actually binds through.#[test]def test_match_arm_binder_shadows_same_named_ctor : IO Bool := let source := r#"type Probe { f (n : I64), zero_key}type Boxed { wrap (g : I64 -> I64)}def bump (y : I64) : I64 := y + 1def run_boxed (b : Boxed) (x : I64) : I64 := match b { wrap f => f x }def main (args : List String) : IO I64 := do { let r := run_boxed (Boxed.wrap bump) 41; return r}"# in compile_source_run_expect source "test_match_arm_binder_shadows_same_named_ctor" 42