Skip to content

Commit 33fbfc7

Browse files
cknittclaude
andauthored
Fix compiler crash on unreachable guarded or-patterns (#8896)
* Skip handlers of unreachable or-patterns A guarded or-pattern after a case that matches everything crashed the compiler with "Fatal error: Matching.comp_exit". The or-pattern is compiled to a handler that its rows jump to. Its rows were dropped as unreachable, but the handler was still compiled; when its guard fails it falls through to the following cases, and in a match without a default there is nothing to fall through to. A handler that the compiled match never jumps to is now skipped. Example (rescript): let g = () => true let f = x => switch x { | _ => 2 | 1 | 2 if g() => 3 } Before (text): >> Fatal error: Matching.comp_exit Fatal error: exception Misc.Fatal_error After (js): function f(x) { return 2; } Source: compiled with the parent commit and with this one. Review: Can skipping a handler drop reachable code? The handler is skipped only when no `Lstaticraise` to its exit occurs in the compiled match, so no path can reach it. Area: lambda tests Output: unchanged, all committed generated JS is byte-identical; programs that crashed now compile Tests: +tests/tests/src/unreachable_or_guard_test.res Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JALXUyHhkDqzcB3WuW9Svo Signed-off-by: Christoph Knittel <ck@cca.io> * Test unreachable guarded or-patterns as a warning fixture The regression test disabled warning 11 for its module. It is now a super_errors fixture, which compiles with all warnings and records the four unused-case warnings; without the fix its snapshot shows the fatal error. Area: tests Output: unchanged Tests: +tests/build_tests/super_errors/fixtures/warning_11_unreachable_guarded_or_pattern.res -tests/tests/src/unreachable_or_guard_test.res Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JALXUyHhkDqzcB3WuW9Svo Signed-off-by: Christoph Knittel <ck@cca.io> * Collect raised exits once when compiling or-pattern handlers Whether a handler is reachable was checked by scanning the whole compiled match, including function bodies, once per handler. The exits are now collected into a table once, extended with each compiled handler, and function bodies are skipped, since static exits cannot leave a function. Area: lambda Output: unchanged Tests: none, because the output is unchanged; the reachability check is covered by warning_11_unreachable_guarded_or_pattern.res Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JALXUyHhkDqzcB3WuW9Svo Signed-off-by: Christoph Knittel <ck@cca.io> --------- Signed-off-by: Christoph Knittel <ck@cca.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 1eb91bb commit 33fbfc7

4 files changed

Lines changed: 95 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fix a compiler crash ("Fatal error: Matching.comp_exit") on a guarded or-pattern that an earlier case makes unreachable.

‎compiler/ml/matching.ml‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2592,13 +2592,31 @@ let as_exit_call lam =
25922592
in
25932593
go Ident.empty lam
25942594

2595+
(* Adds the static exits [lam] jumps to to [raised]. Exits cannot leave a
2596+
function, so function bodies are skipped. *)
2597+
let rec add_raised_exits raised lam =
2598+
match lam with
2599+
| Lfunction _ -> ()
2600+
| Lstaticraise (i, _) ->
2601+
Hashtbl.replace raised i ();
2602+
Lambda_traverse.iter (add_raised_exits raised) lam
2603+
| _ -> Lambda_traverse.iter (add_raised_exits raised) lam
2604+
25952605
let compile_orhandlers compile_fun lambda1 total1 ctx to_catch =
2606+
(* The exits of the match and of the handlers compiled so far *)
2607+
let raised = Hashtbl.create 16 in
2608+
add_raised_exits raised lambda1;
25962609
let rec do_rec r total_r = function
25972610
| [] -> (r, total_r)
2611+
| (_, i, _, _) :: rem when not (Hashtbl.mem raised i) ->
2612+
(* The or-pattern is unreachable, e.g. after a wildcard. Its handler is
2613+
not compiled: with a guard and no default, its failure has no exit. *)
2614+
do_rec r total_r rem
25982615
| (mat, i, vars, pm) :: rem -> (
25992616
try
26002617
let ctx = select_columns mat ctx in
26012618
let handler_i, total_i = compile_fun ctx pm in
2619+
add_raised_exits raised handler_i;
26022620
match as_exit_call r with
26032621
| Some (j, args) ->
26042622
if i = j then
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
Warning number 11
3+
/.../fixtures/warning_11_unreachable_guarded_or_pattern.res:9:5-9
4+
5+
7 ┆ switch x {
6+
8 ┆ | _ => 2
7+
9 ┆ | 1 | 2 if g() => 3
8+
10 ┆ }
9+
11 ┆
10+
11+
this match case is unused.
12+
13+
14+
Warning number 11
15+
/.../fixtures/warning_11_unreachable_guarded_or_pattern.res:15:5-13
16+
17+
13 ┆ switch c {
18+
14 ┆ | _ => "any"
19+
15 ┆ | 'a' | 'b' if g() => "ab"
20+
16 ┆ }
21+
17 ┆
22+
23+
this match case is unused.
24+
25+
26+
Warning number 11
27+
/.../fixtures/warning_11_unreachable_guarded_or_pattern.res:21:5-13
28+
29+
19 ┆ switch s {
30+
20 ┆ | _ => "any"
31+
21 ┆ | "a" | "b" if g() => "ab"
32+
22 ┆ }
33+
23 ┆
34+
35+
this match case is unused.
36+
37+
38+
Warning number 11
39+
/.../fixtures/warning_11_unreachable_guarded_or_pattern.res:28:5-19
40+
41+
26 ┆ | (0, _) => "zero"
42+
27 ┆ | (_, _) => "other"
43+
28 ┆ | (1, 1) | (2, 2) if g() => "same"
44+
29 ┆ }
45+
30 ┆
46+
47+
this match case is unused.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Each guarded or-pattern is unreachable after a case that matches
2+
// everything. Compiling them used to fail with
3+
// "Fatal error: Matching.comp_exit" instead of reporting them as unused.
4+
let g = () => true
5+
6+
let int = x =>
7+
switch x {
8+
| _ => 2
9+
| 1 | 2 if g() => 3
10+
}
11+
12+
let char = c =>
13+
switch c {
14+
| _ => "any"
15+
| 'a' | 'b' if g() => "ab"
16+
}
17+
18+
let string = s =>
19+
switch s {
20+
| _ => "any"
21+
| "a" | "b" if g() => "ab"
22+
}
23+
24+
let pair = p =>
25+
switch p {
26+
| (0, _) => "zero"
27+
| (_, _) => "other"
28+
| (1, 1) | (2, 2) if g() => "same"
29+
}

0 commit comments

Comments
 (0)