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.4 kB · 59 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960/// Regression tests for `needs_io_wrap` (`lang/codegen/emit.mo`):/// `IO.read_file`/`IO.file_exists`'s raw call result must be wrapped in a/// real `IO.io`-tagged constructor before it can be used as an `IO` value,/// same as `IO.println`/`IO.write_file` already were.////// `is_void_native` was previously the ONLY signal deciding whether a/// native op's result needed this wrap, conflating "the C function is/// declared `void`" (true for `print_str`/`write_file`) with "the Monad/// type is `IO _`" (also true for `read_file`/`file_exists`, which return/// a real `char*`, not void). `read_file`/`file_exists`'s raw call result/// was used AS-IS wherever an `IO` value was expected -- `Monad_IO_bind`'s/// own generated body calls `monad_get_field(io_val, 0)` on it, misreading/// the raw pointer as if it were a tagged constructor object.////// Found while verifying `implementations/2026-08-29-native-io-op-non-/// exhaustive-match-crash.md`'s own regression test more thoroughly than a/// bare crash-freedom check: binding a native call's result via `<-` and/// merely IGNORING it compiled/ran fine (no consumer ever touches the/// mis-shaped value), but using it in ANY way afterward (a call argument,/// a comparison) segfaulted or returned a silently wrong value --/// confirmed pre-existing (unrelated to `write_file`) via `git worktree`/// bisection, filed as/// `implementations/2026-08-29-native-bind-result-use-crash.md`, then/// root-caused and fixed here.use std::process {process_id}use lang::codegen::test::e2e_harness {compile_source_run_expect}/// Binds `IO.read_file`'s result via `<-` and then ACTUALLY USES it/// (passed as a real function argument, compared against a literal) --/// the exact shape that used to segfault (or, for pure-value consumers,/// silently misbehave) before `read_file`'s result was `IO.io`-wrapped.#[test]def test_read_file_bind_result_used_downstream : IO Bool := let fixture_path := "/tmp/monad_e2e_" ++ I64.to_string process_id ++ "/io_result_wrap_fixture.txt" in let source := r#"def main (args : List String) : IO I64 := do { let path := ""# ++ fixture_path ++ r#""; let content := "needs io wrap"; IO.write_file_native path content; let read_back <- IO.read_file_native path; let matched := String.beq read_back content; let result := if matched then 1 else 0; return result}"# in compile_source_run_expect source "test_read_file_bind_result_used_downstream" 1// NOTE: an equivalent `IO.file_exists` test was attempted here (same// shape, `Bool`-producing instead of `String`-producing, to exercise// `needs_io_wrap` for a different inner type) but found a SEPARATE,// pre-existing, deeper bug: `monad_file_exists` (`runtime/src/runtime.c`)// returns a raw C string literal ("1" or NULL), not a real heap-allocated// Bool constructor via `alloc_constructor` -- so `if exists then ... else// ...` (which reads the value's TAG via `monad_get_tag`, expecting a// genuine boxed Bool object) reads garbage from whatever address that// static string happens to be at, not a real tag. Confirmed via direct// repro: `IO.file_exists` on a file that genuinely exists still took the// `else` branch. Unrelated to `needs_io_wrap`/this fix -- the IO-wrapping// itself is correct either way, the INNER value it wraps is wrong. Not// yet filed as its own dated plan; out of scope here.