From 3356a69e1cc574e34ec8375632aa2d85406e8efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Christiansen=20S=C3=B8rby?= Date: Wed, 19 Aug 2026 09:10:09 +0200 Subject: [PATCH] diag: fix gutter/caret indentation; slow_tests: tighten and re-enable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Diagnostic source-context rendering (Rust + Monad) had a duplicated bug: `core/src/diag.rs`'s `write_source_context` formatted the error line as `{:>3} | ...` but every other context line additionally prepended a stray literal 3-space prefix, and the caret line hardcoded a 4-space indent instead of the 6 (3-wide number + " | ") the error line's own content actually starts at. `lang/parser/diagnostic.mo` is a deliberate hand-port of `diag.rs` and inherited the identical bug. Fixed both by computing a single gutter width per diagnostic (max(3, digit count of the widest line number shown)) and applying it uniformly to every displayed line, with the caret indented by width + 3. Added precise (exact-string, not substring) regression tests in both implementations, including a case that spans a 3-to-4- digit line-number boundary. slow_tests/ cleanup, following up on this session's earlier json.mo/toml.mo/map.mo truncation-bug fixes: - `count_decls_in_file`'s floor-count tests (`I64.gt count N`) proved only "didn't truncate to near-zero", not "parsed the whole file". Replaced with `file_fully_parses`, asserting `decls_parser`'s own `remaining` field is empty. Added the missing std/map.mo case. - Moved 5 sub-millisecond use/open round-trip tests out of slow_tests/parser_file_tests.mo into a new lang/tests/ parser_roundtrip_tests.mo — they parse tiny snippets, not real files, so they belong in the fast pre-commit sweep instead. - Re-enabled 14 tests in slow_tests/typecheck_init_tests.mo and typecheck_std_tests.mo that were disabled with stale "module dependency resolution not implemented yet" comments — resolution has been implemented and in live use by every other test in both files for a while. All 14 pass. - Updated the stale decls_parser_strict doc comment in lang/parser.mo claiming json.mo/toml.mo/map.mo still don't fully parse. Verified: full corpus + slow_tests combined, 1376/1376 passing. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Tb6xkSNyx1mvQf4XRN7dky --- core/src/diag.rs | 63 +++++++++++- lang/parser.mo | 18 ++-- lang/parser/diagnostic.mo | 102 +++++++++++++++---- lang/tests/parser_roundtrip_tests.mo | 55 +++++++++++ slow_tests/parser_file_tests.mo | 142 +++++++++------------------ slow_tests/typecheck_init_tests.mo | 58 ++++++----- slow_tests/typecheck_std_tests.mo | 32 +++++- 7 files changed, 318 insertions(+), 152 deletions(-) create mode 100644 lang/tests/parser_roundtrip_tests.mo diff --git a/core/src/diag.rs b/core/src/diag.rs index 300c600..59504be 100644 --- a/core/src/diag.rs +++ b/core/src/diag.rs @@ -206,6 +206,15 @@ fn write_source_context( total_lines }; + // Gutter width is the width of the widest line number actually shown, + // with a minimum of 3 (matches rustc-style alignment and keeps small + // files looking the same as before). Every displayed line -- the error + // line and its context neighbors alike -- uses this same width, so the + // `|` separators all line up in one column. The caret line then indents + // by `width + 3` spaces (the number field plus the literal `" | "`) to + // land under the first character of the source line's content. + let width = end_line.to_string().len().max(3); + for i in start_line..=end_line { if i > total_lines { break; @@ -213,12 +222,20 @@ fn write_source_context( let line_content = source_lines.get(i - 1).unwrap_or(&""); if i == line_num { - writeln!(f, "{}{:>3} | {}{}", c.bold(), i, line_content, c.reset())?; + writeln!( + f, + "{}{:>width$} | {}{}", + c.bold(), + i, + line_content, + c.reset() + )?; if column > 0 { let indent = " ".repeat(column.saturating_sub(1)); writeln!( f, - " {}{}{}^---{}", + "{}{}{}{}^---{}", + " ".repeat(width + 3), indent, c.error(), c.bold(), @@ -226,9 +243,49 @@ fn write_source_context( )?; } } else { - writeln!(f, " {:>3} | {}", i, line_content)?; + writeln!(f, "{:>width$} | {}", i, line_content)?; } } Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + fn render(source: &str, line_num: usize, column: usize) -> String { + let mut out = String::new(); + write_source_context(source, line_num, column, false, &mut out).unwrap(); + out + } + + #[test] + fn gutter_aligns_error_line_with_context_lines() { + let out = render("def add (a : I64 (b : I64) : I64 :=\n a + b\n", 1, 28); + let lines: Vec<&str> = out.lines().collect(); + // Error line and its "N | " gutter. + assert_eq!(lines[0], " 1 | def add (a : I64 (b : I64) : I64 :="); + // Caret aligned under column 28 of the content above (6-char prefix: + // 3-wide number field + " | ", then 27 spaces before the caret). + assert_eq!(lines[1], format!("{}{}^---", " ".repeat(6), " ".repeat(27))); + // Context line below uses the exact same gutter width as the error + // line -- no stray extra indent. + assert_eq!(lines[2], " 2 | a + b"); + } + + #[test] + fn gutter_width_grows_for_four_digit_line_numbers() { + let mut source = String::new(); + for i in 1..=1001 { + source.push_str(&format!("line{}\n", i)); + } + let out = render(&source, 1000, 1); + let lines: Vec<&str> = out.lines().collect(); + // end_line is 1001 (4 digits), so every gutter -- including the + // 3-digit-wide "999" context line -- pads out to width 4. + assert_eq!(lines[0], " 999 | line999"); + assert_eq!(lines[1], "1000 | line1000"); + assert_eq!(lines[3], "1001 | line1001"); + } +} diff --git a/lang/parser.mo b/lang/parser.mo index 0397735..4882f3b 100644 --- a/lang/parser.mo +++ b/lang/parser.mo @@ -3105,12 +3105,18 @@ def decls_try (r : ParseResult Decl) (orig : String) (acc : List Decl) : ParseRe /// spurious top-level decl). This is a SEPARATE function, not a /// replacement, specifically so nothing that already depends on the /// lenient behavior (`lang.module`'s scope-building, and by extension -/// most of this corpus's own test suite — several real files still -/// don't fully parse, e.g. `lang/json.mo`/`lang/toml.mo`/`std/map.mo`, -/// see `slow_tests/parser_file_tests.mo`'s own conservative decl-count -/// floor tests) breaks. Used only where a real diagnostic is actually -/// wanted: `lang.module`'s `try_parse_decls_strict`, wired into -/// `lang/main.mo`'s CLI compile-failure path. +/// most of this corpus's own test suite) breaks. `lang/json.mo`/ +/// `lang/toml.mo`/`std/map.mo` were the real files motivating this split +/// in the first place — confirmed (2026-08-19) to now fully self-parse +/// with `decls_parser` too (see `slow_tests/parser_file_tests.mo`'s +/// `test_json_fully_parses`/`test_toml_fully_parses`/ +/// `test_map_fully_parses`), but the strict twin stays regardless since +/// the lenient parser can still silently truncate on OTHER not-yet- +/// encountered constructs, and a real diagnostic on failure is worth +/// having independent of any specific file's current status. Used only +/// where a real diagnostic is actually wanted: `lang.module`'s +/// `try_parse_decls_strict`, wired into `lang/main.mo`'s CLI +/// compile-failure path. #[partial] def decls_parser_strict (input : String) : ParseResult (List Decl) := decls_skip_strict (skip_docstrings (skip_spaces input)) List.empty diff --git a/lang/parser/diagnostic.mo b/lang/parser/diagnostic.mo index 0406fb1..4aec6cf 100644 --- a/lang/parser/diagnostic.mo +++ b/lang/parser/diagnostic.mo @@ -118,20 +118,24 @@ def line_end_after_go (s : String) (pos : I64) (len : I64) : I64 := Option.none => len } -/// Right-justify a line number to width 3 (mirrors `diag.rs`'s -/// `{:>3}`) — approximate for line numbers over 999 (no truncation, the -/// field just grows, same as Rust's own formatter). +/// Right-justify a line number to `width` (mirrors `diag.rs`'s +/// `{:>width$}`) — `width` is computed once per diagnostic from the +/// widest line number actually shown (see `render_source_context`), with +/// a minimum of 3, so every displayed line (the error line and its +/// context neighbors alike) lines up its `|` in the same column. If `n` +/// itself needs more digits than `width`, the field just grows (no +/// truncation), same as Rust's own formatter. #[partial] -def pad_line_num (n : I64) : String := +def pad_line_num (width : I64) (n : I64) : String := let s : String := I64.to_string n in - if I64.lt (String.length s) 3 - then pad_line_num_loop s + if I64.lt (String.length s) width + then pad_line_num_loop width s else s #[partial] -def pad_line_num_loop (s : String) : String := - if I64.lt (String.length s) 3 - then pad_line_num_loop (String.concat " " s) +def pad_line_num_loop (width : I64) (s : String) : String := + if I64.lt (String.length s) width + then pad_line_num_loop width (String.concat " " s) else s /// Bounded by `n` (a column number — line length, not file size). @@ -141,18 +145,33 @@ def spaces (n : I64) : String := then String.concat " " (spaces (I64.sub n 1)) else "" +/// Every displayed line — error line and context neighbors alike — uses +/// the SAME `width`-padded number field followed by `" | "`, with no +/// extra literal prefix on either branch, so the gutters all align. #[partial] -def render_context_line (line_num : I64) (content : String) (is_error_line : Bool) (col : I64) : String := +def render_context_line (width : I64) (line_num : I64) (content : String) (is_error_line : Bool) (col : I64) : String := + let prefix : String := String.concat (pad_line_num width line_num) (String.concat " | " content) in if is_error_line - then String.concat (pad_line_num line_num) (String.concat " | " (String.concat content (String.concat "\n" (render_caret col)))) - else String.concat " " (String.concat (pad_line_num line_num) (String.concat " | " (String.concat content "\n"))) + then String.concat prefix (String.concat "\n" (render_caret width col)) + else String.concat prefix "\n" +/// Caret indent is `width + 3` spaces (the number field plus the literal +/// `" | "`) so it lands under the first character of the error line's +/// own content, then `col - 1` more spaces to reach the actual column. #[partial] -def render_caret (col : I64) : String := +def render_caret (width : I64) (col : I64) : String := if I64.gt col 0 - then String.concat " " (String.concat (spaces (I64.sub col 1)) "^---\n") + then String.concat (spaces (I64.add width 3)) (String.concat (spaces (I64.sub col 1)) "^---\n") else "" +/// Number of decimal digits in a non-negative line number. +#[partial] +def digit_count (n : I64) : I64 := + String.length (I64.to_string n) + +def i64_max (a : I64) (b : I64) : I64 := + if I64.gt a b then a else b + /// Renders up to 3 lines centered on `line` (the failing line, plus one /// before and after, clamped to the file's real extent) plus a `^---` /// caret positioned under `col` right after the failing line — mirrors @@ -167,23 +186,29 @@ def render_source_context (source : String) (line : I64) (col : I64) (offset : I let cur_start : I64 := line_start_before source offset in let cur_end : I64 := line_end_after source offset in let cur_content : String := String.slice source cur_start (I64.sub cur_end cur_start) in + let has_next : Bool := I64.lt cur_end (String.length source) in + // The gutter width is shared across the whole 3-line window, based + // on the widest line number actually shown (mirrors `diag.rs`'s + // `end_line`) — a next line, if there is one, is always the widest. + let end_line : I64 := if has_next then I64.add line 1 else line in + let width : I64 := i64_max 3 (digit_count end_line) in let prev_str : String := if I64.gt line 1 then let prev_end : I64 := I64.sub cur_start 1 in let prev_start : I64 := line_start_before source prev_end in let prev_content : String := String.slice source prev_start (I64.sub prev_end prev_start) in - render_context_line (I64.sub line 1) prev_content false 0 + render_context_line width (I64.sub line 1) prev_content false 0 else "" in - let cur_str : String := render_context_line line cur_content true col in + let cur_str : String := render_context_line width line cur_content true col in let next_str : String := - if I64.lt cur_end (String.length source) + if has_next then let next_start : I64 := I64.add cur_end 1 in let next_end : I64 := line_end_after source next_start in let next_content : String := String.slice source next_start (I64.sub next_end next_start) in - render_context_line (I64.add line 1) next_content false 0 + render_context_line width (I64.add line 1) next_content false 0 else "" in String.concat prev_str (String.concat cur_str next_str) @@ -203,7 +228,7 @@ def test_render_parse_error_simple : Bool := let source : String := "def f := x" in let err : ParseError := ParseError.custom "unknown declaration" "x" in let rendered : String := render_parse_error source Option.none err in - // "error: unknown declaration at 1:10\n --> :1:10\n 1 | def f := x\n ...^---\n" + // "error: unknown declaration at 1:10\n --> :1:10\n 1 | def f := x\n ^---\n" String_contains rendered "error: unknown declaration at 1:10" && String_contains rendered "--> :1:10" && String_contains rendered "def f := x" @@ -269,6 +294,45 @@ def test_render_source_context_last_line_no_next : Bool := && String_contains rendered "2 | def b := 2" && String_contains rendered "3 | bad_here" +/// Regression test for the gutter/caret indentation bug (matching +/// `core/src/diag.rs`'s own `gutter_aligns_error_line_with_context_lines` +/// test): the error line and a context neighbor must share the exact +/// same "N | " gutter width — this line-1/line-2 fixture used to render +/// with the context line ("2 | ...") indented 3 extra stray spaces past +/// the error line ("1 | ..."), and the caret 2 columns short of the +/// content it was supposed to point at. `String_contains` checks a +/// LITERAL substring (see `String_contains_go` above), so matching these +/// exact strings (exact leading-space counts included) really does pin +/// down the alignment, not just presence of the text. +#[test] +def test_render_source_context_gutter_alignment_exact : Bool := + let source : String := "abcdefghij\nklmnop" in + // remaining is the genuine unconsumed suffix starting at column 5 ('e'). + let err : ParseError := ParseError.custom "unknown declaration" "efghij\nklmnop" in + let rendered : String := render_parse_error source Option.none err in + // width = 3 (only 1- and 2-digit line numbers shown): error line's + // " 1 | " gutter and the context line's " 2 | " gutter must match + // exactly, and the caret (6-char prefix + 4 more for column 5) must + // land under the content, not 2 columns short. + String_contains rendered " 1 | abcdefghij\n ^---\n 2 | klmnop\n" + +/// Regression test for the latent width bug alongside the gutter fix: +/// once the window's widest line number needs 4 digits (999/1000/1001), +/// EVERY displayed line — including the 3-digit "999" one — must pad out +/// to that same shared width, not just the 4-digit ones. Mirrors +/// `core/src/diag.rs`'s own +/// `gutter_width_grows_for_four_digit_line_numbers` test. +#[test] +def test_render_source_context_gutter_width_grows_for_wide_line_numbers : Bool := + let padding : String := repeat_line "// padding line to bulk up the source\n" 999 in + let source : String := String.concat padding "bad_here\nfinal_line" in + let err : ParseError := ParseError.custom "unknown declaration" "bad_here\nfinal_line" in + let rendered : String := render_parse_error source Option.none err in + String_contains rendered "at 1000:1" + && String_contains rendered " 999 | // padding line to bulk up the source\n" + && String_contains rendered "1000 | bad_here\n ^---\n" + && String_contains rendered "1001 | final_line\n" + /// Regression test for the specific bug this whole rewrite fixes: a /// linear (even tail-recursive) scan over the *entire* source to find /// line boundaries overflows the interpreter's stack on a real-sized diff --git a/lang/tests/parser_roundtrip_tests.mo b/lang/tests/parser_roundtrip_tests.mo new file mode 100644 index 0000000..3131a26 --- /dev/null +++ b/lang/tests/parser_roundtrip_tests.mo @@ -0,0 +1,55 @@ +/// Parse -> pretty-print -> re-parse round-trip tests for the `use`/`open` +/// brace syntax. Moved here from `slow_tests/parser_file_tests.mo`: these +/// parse tiny inline snippets (sub-millisecond each), not real files, so +/// they don't belong in the directory reserved for expensive whole-file +/// parses -- they were just sitting outside the fast pre-commit sweep for +/// no reason. + +use lang.types {Decl} +use lang.parser {open_parser, use_parser} +use lang.parser.core {ParseResult, fail, success} +use lang.pretty {show_decl} + +open ParseResult {fail, success} + +#[partial] +def parse_decl_succeeds (r : ParseResult Decl) : Bool := + match r { + success _ _ => true, + fail _ => false + } + +#[test] +def test_roundtrip_use_glob : Bool := + match use_parser "use io {*}" { + success _ out => parse_decl_succeeds (use_parser (show_decl out)), + fail _ => false + } + +#[test] +def test_roundtrip_use_nested : Bool := + match use_parser "use io {file {read}}" { + success _ out => parse_decl_succeeds (use_parser (show_decl out)), + fail _ => false + } + +#[test] +def test_roundtrip_use_nested_rename : Bool := + match use_parser "use io {file as f {read}}" { + success _ out => parse_decl_succeeds (use_parser (show_decl out)), + fail _ => false + } + +#[test] +def test_roundtrip_open_filtered : Bool := + match open_parser "open io {println}" { + success _ out => parse_decl_succeeds (open_parser (show_decl out)), + fail _ => false + } + +#[test] +def test_roundtrip_scoped_open : Bool := + match open_parser "open io {println} in def main : IO Unit := println \"hi\"" { + success _ out => parse_decl_succeeds (open_parser (show_decl out)), + fail _ => false + } diff --git a/slow_tests/parser_file_tests.mo b/slow_tests/parser_file_tests.mo index 88e6570..4db4767 100644 --- a/slow_tests/parser_file_tests.mo +++ b/slow_tests/parser_file_tests.mo @@ -3,10 +3,8 @@ use io {io, read_file} open IO {io, read_file} -use lang.types {Decl} -use lang.parser {decls_parser, open_parser, use_parser} -use lang.parser.core {ParseResult, fail, success} -use lang.pretty {show_decl} +use lang.parser {decls_parser} +use lang.parser.core {fail, success} open ParseResult {fail, success} @@ -146,112 +144,66 @@ def test_parse_lang_all_utf8 : Bool := parse_all lang_files_utf8 // failure), giving strictly worse debugging signal than the per-category // tests on any failure while adding no new coverage. -// ================ Helper for counting declarations ================ - -/// Count declarations in a file +// ================ Full-file-parse (no truncation) tests ================ +// +// `decls_parser` is lenient: on a construct it can't parse, `decls_try` +// silently stops and returns whatever it got so far as `success`, not a +// `fail` — so a floor count like `I64.gt count 5` only proves "didn't +// truncate to (near) zero", not "parsed the whole file". Checking +// `success`'s own `remaining` field for emptiness is the actual contract +// these tests care about: every byte of the file got consumed. (See +// `lang/parser.mo`'s `decls_parser_strict` for the twin that turns +// truncation into a hard `fail` instead of a silent partial `success` — +// not used here so this file keeps exercising the lenient path real +// callers actually use, just checking its result more precisely.) + +/// Whether a file's `decls_parser` run consumes the ENTIRE input, i.e. +/// nothing is left over in `success`'s own `remaining` field. A `fail` or +/// a non-empty remainder both count as "didn't fully parse." #[partial] -def count_decls_in_file (path : String) : I64 := +def file_fully_parses (path : String) : Bool := match IO.read_file path { io content => match decls_parser content { - success _ decls => list_length decls, - fail _ => 0 + success rem _ => String.is_empty rem, + fail _ => false }, - _ => 0 - } - -/// Helper: get list length as I64 -#[partial] -def list_length (xs : List A) : I64 := - list_length_help xs 0 - -#[partial] -def list_length_help (xs : List A) (acc : I64) : I64 := - match xs { - List.empty => acc, - List.cons _ rest => list_length_help rest (I64.add acc 1) + _ => false } -// ================ Declaration count tests ================ - #[test] -def test_hello_has_some_decls : Bool := - let count := count_decls_in_file "examples/hello.mo" in - I64.gt count 0 +def test_hello_fully_parses : Bool := + file_fully_parses "examples/hello.mo" #[test] -def test_string_has_some_decls : Bool := - let count := count_decls_in_file "init/string.mo" in - I64.gt count 0 +def test_string_fully_parses : Bool := + file_fully_parses "init/string.mo" #[test] -def test_scope_has_some_decls : Bool := - let count := count_decls_in_file "lang/scope.mo" in - I64.gt count 0 +def test_scope_fully_parses : Bool := + file_fully_parses "lang/scope.mo" -// `lang/json.mo`/`lang/toml.mo` both open with a `//`/`///` comment -// containing an em dash (multi-byte UTF-8) before their very first real -// declaration — before the `take_while`/`string_body` UTF-8 stepping -// fix (see `utf8_char_width`'s doc comment, lang/parser/combinators.mo) -// this silently truncated the parse to ZERO declarations (verified by -// bisection while landing that fix). Both files still stop short of -// their true decl count today (~194/~167 respectively, going by a raw -// grep of top-level declaration keywords) — some other, not yet -// identified construct further down still trips `decls_try`'s -// silent-truncate-on-fail fallback — so these floors are deliberately -// conservative (verified non-regression against the *specific* UTF-8 -// bug, not a claim of full-file completeness) rather than exact counts. +// `lang/json.mo`/`lang/toml.mo`/`std/map.mo` all open with a `//`/`///` +// comment containing an em dash (multi-byte UTF-8) before their very +// first real declaration — before the `take_while`/`string_body` UTF-8 +// stepping fix (see `utf8_char_width`'s doc comment, +// `lang/parser/combinators.mo`) this silently truncated the parse to +// ZERO declarations (verified by bisection while landing that fix). All +// three used to additionally stop short of their true decl count for +// other reasons (juxtaposed list-literal application, `let` inside `if` +// branches, paren type ascriptions, multi-param lambdas, multi-name +// constructor fields, nested-paren class param types — each fixed +// separately, motivated by exactly these files) — confirmed (2026-08-19) +// that all three now parse to completion with zero bytes remaining, so +// these tests assert that directly instead of a conservative floor. #[test] -def test_json_utf8_comment_does_not_truncate_to_zero : Bool := - let count := count_decls_in_file "lang/json.mo" in - I64.gt count 5 +def test_json_fully_parses : Bool := + file_fully_parses "lang/json.mo" #[test] -def test_toml_utf8_comment_does_not_truncate_to_zero : Bool := - let count := count_decls_in_file "lang/toml.mo" in - I64.gt count 5 - -// ================ use/open brace syntax round-trip tests ================ -// parse -> pretty-print -> re-parse should succeed for the new syntax. - -#[partial] -def parse_decl_succeeds (r : ParseResult Decl) : Bool := - match r { - success _ _ => true, - fail _ => false - } +def test_toml_fully_parses : Bool := + file_fully_parses "lang/toml.mo" #[test] -def test_roundtrip_use_glob : Bool := - match use_parser "use io {*}" { - success _ out => parse_decl_succeeds (use_parser (show_decl out)), - fail _ => false - } - -#[test] -def test_roundtrip_use_nested : Bool := - match use_parser "use io {file {read}}" { - success _ out => parse_decl_succeeds (use_parser (show_decl out)), - fail _ => false - } - -#[test] -def test_roundtrip_use_nested_rename : Bool := - match use_parser "use io {file as f {read}}" { - success _ out => parse_decl_succeeds (use_parser (show_decl out)), - fail _ => false - } - -#[test] -def test_roundtrip_open_filtered : Bool := - match open_parser "open io {println}" { - success _ out => parse_decl_succeeds (open_parser (show_decl out)), - fail _ => false - } - -#[test] -def test_roundtrip_scoped_open : Bool := - match open_parser "open io {println} in def main : IO Unit := println \"hi\"" { - success _ out => parse_decl_succeeds (open_parser (show_decl out)), - fail _ => false - } +def test_map_fully_parses : Bool := + file_fully_parses "std/map.mo" diff --git a/slow_tests/typecheck_init_tests.mo b/slow_tests/typecheck_init_tests.mo index d3d9a6c..285bc98 100644 --- a/slow_tests/typecheck_init_tests.mo +++ b/slow_tests/typecheck_init_tests.mo @@ -60,9 +60,8 @@ def test_typecheck_init_number : IO Bool := typecheck_file "init/number.mo" "num #[test] def test_typecheck_init_string : IO Bool := typecheck_file "init/string.mo" "string" -// Skip process.mo for now - it has native functions with dependencies -// #[test] -// def test_typecheck_init_process : IO Bool := typecheck_file "init/process.mo" "process" +#[test] +def test_typecheck_init_process : IO Bool := typecheck_file "init/process.mo" "process" #[test] def test_typecheck_init_init : IO Bool := typecheck_file "init/init.mo" "init" @@ -91,27 +90,32 @@ def test_typecheck_init_test_constraints : IO Bool := typecheck_file "init/test_ #[test] def test_typecheck_init_string_profile : IO Bool := typecheck_file "init/string_profile.mo" "string_profile" -// Test module dependency loading with init/process.mo which uses io -// Note: This test is commented out because IO.read_file has a working directory issue -// that affects init/process.mo and other files. This is a pre-existing issue. -// #[test] -// def test_typecheck_init_process_with_deps : IO Bool := -// typecheck_file_with_deps "init/process.mo" "process" - -// Note: test files (foldable_tests*, optics_tests, tests.mo) require module loading -// and are skipped for now. They can be added once module dependency resolution is implemented. - -// #[test] -// def test_typecheck_init_foldable_tests : IO Bool := typecheck_file "init/foldable_tests.mo" "foldable_tests" -// -// #[test] -// def test_typecheck_init_foldable_tests_fold : IO Bool := typecheck_file "init/foldable_tests_fold.mo" "foldable_tests_fold" -// -// #[test] -// def test_typecheck_init_foldable_tests_semi_monoid : IO Bool := typecheck_file "init/foldable_tests_semi_monoid.mo" "foldable_tests_semi_monoid" -// -// #[test] -// def test_typecheck_init_optics_tests : IO Bool := typecheck_file "init/optics_tests.mo" "optics_tests" -// -// #[test] -// def test_typecheck_init_tests : IO Bool := typecheck_file "init/tests.mo" "tests" +// test_typecheck_init_process_with_deps removed: it called a +// `typecheck_file_with_deps` that was never even defined in this file +// (and its own "IO.read_file has a working directory issue" reasoning +// predates this file's rewrite to route through +// `load_module_with_dependencies`/`extract_directory`, per the doc +// comment on `typecheck_file` above) — genuinely dead, not a real +// second test. `test_typecheck_init_process` above already exercises +// `init/process.mo` through the real, working `typecheck_file`. + +// The five tests below were disabled with "requires module loading... +// once module dependency resolution is implemented" — dependency +// resolution IS implemented (`typecheck_file` above already uses it, +// same as every other test in this file), so that reasoning is stale. +// Re-enabled and confirmed passing (2026-08-19). + +#[test] +def test_typecheck_init_foldable_tests : IO Bool := typecheck_file "init/foldable_tests.mo" "foldable_tests" + +#[test] +def test_typecheck_init_foldable_tests_fold : IO Bool := typecheck_file "init/foldable_tests_fold.mo" "foldable_tests_fold" + +#[test] +def test_typecheck_init_foldable_tests_semi_monoid : IO Bool := typecheck_file "init/foldable_tests_semi_monoid.mo" "foldable_tests_semi_monoid" + +#[test] +def test_typecheck_init_optics_tests : IO Bool := typecheck_file "init/optics_tests.mo" "optics_tests" + +#[test] +def test_typecheck_init_tests : IO Bool := typecheck_file "init/tests.mo" "tests" diff --git a/slow_tests/typecheck_std_tests.mo b/slow_tests/typecheck_std_tests.mo index ddb9cf5..d62c8fd 100644 --- a/slow_tests/typecheck_std_tests.mo +++ b/slow_tests/typecheck_std_tests.mo @@ -55,5 +55,33 @@ def test_typecheck_std_concurrent_fiber : IO Bool := typecheck_file "std/concurr #[test] def test_typecheck_std_concurrent_combine : IO Bool := typecheck_file "std/concurrent/combine.mo" "concurrent_combine" -// Note: Test files (list_tests*, map_tests*, etc.) require module loading -// and are skipped for now. +// --- std/ test files --- +// +// Previously skipped as "require module loading... for now" — dependency +// resolution IS implemented (`typecheck_file` above already uses it, same +// as every other test in this file), so that reasoning was stale. +// Re-enabled and confirmed passing (2026-08-19). + +#[test] +def test_typecheck_std_list_tests1 : IO Bool := typecheck_file "std/list_tests1.mo" "list_tests1" + +#[test] +def test_typecheck_std_list_tests2 : IO Bool := typecheck_file "std/list_tests2.mo" "list_tests2" + +#[test] +def test_typecheck_std_list_tests3a : IO Bool := typecheck_file "std/list_tests3a.mo" "list_tests3a" + +#[test] +def test_typecheck_std_list_tests3b : IO Bool := typecheck_file "std/list_tests3b.mo" "list_tests3b" + +#[test] +def test_typecheck_std_map_tests : IO Bool := typecheck_file "std/map_tests.mo" "map_tests" + +#[test] +def test_typecheck_std_derive_tests : IO Bool := typecheck_file "std/derive_tests.mo" "derive_tests" + +#[test] +def test_typecheck_std_sha256_tests : IO Bool := typecheck_file "std/sha256_tests.mo" "sha256_tests" + +#[test] +def test_typecheck_std_test_map_full : IO Bool := typecheck_file "std/test_map_full.mo" "test_map_full" -- 2.51.2