Skip to content

Fix compiler crash on unreachable guarded or-patterns - #8896

Merged
cknitt merged 3 commits into
masterfrom
fix-comp-exit
Oct 19, 2026
Merged

cknitt merged 3 commits into
masterfrom
fix-comp-exit

Conversation

@cknitt

@cknitt cknitt commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

A guarded or-pattern after a case that matches everything crashed the compiler:

let f = x =>
  switch x {
  | _ => 2
  | 1 | 2 if g() => 3
  }
// >> Fatal error: Matching.comp_exit

This happens with int, char and string constants and in tuples; a fuzzer working on #8893 found it.

Cause: the error is an invariant check in comp_exit. When a sub-match has no rows left, it jumps to the first entry of its default list, which holds the handlers of the rows that come after it. That list must not be empty if the sub-match can fail. Here, it fails like this:

  1. precompile_or compiles 1 | 2 if g() => 3 into a handler with an exit number. In the main match, its alternatives become rows that jump to that exit. The handler is one guarded row, and its default list holds the rows after the or-pattern: none.
  2. _ covers every value, so the match is total. A partial match gets a final default that raises Match_failure; a total one gets none, so the handler's default list is empty.
  3. _ matches every value, so the main match compiles to return 2, and the rows that jump to the handler are dropped as unreachable.
  4. compile_orhandlers compiles the handler anyway. Its guarded row compiles a path for a failing guard: no rows remain and the default list is empty, so comp_exit raises the fatal error.

In reachable code the invariant holds: in a total match, a value whose guard fails must be matched by a later row, and later rows are what the default list holds. Here the value is matched by an earlier row (_), which only happens when the handler is unreachable.

Fix: a handler that the compiled match never jumps to is not compiled. "Never jumps to" means no Lstaticraise to its exit occurs in the compiled match. The check in comp_exit stays in place, so it still catches real bugs. Making it tolerate an empty default would hide them. Every committed JS snapshot is byte-identical; only programs that used to crash compile differently.

New test: the super_errors fixture warning_11_unreachable_guarded_or_pattern.res, with int, char, string and tuple cases. It expects four unused-case warnings; without the fix, its snapshot shows the fatal error, and each case crashes the compiler on its own.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JALXUyHhkDqzcB3WuW9Svo

@codecov

codecov Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.04%. Comparing base (1869441) to head (3f32388).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #8896   +/-   ##
=======================================
  Coverage   81.04%   81.04%           
=======================================
  Files         455      455           
  Lines       63508    63518   +10     
=======================================
+ Hits        51469    51481   +12     
+ Misses      12039    12037    -2     
Files with missing lines Coverage Δ
compiler/ml/matching.ml 77.67% <100.00%> (+0.29%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pkg-pr-new

pkg-pr-new Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript@8896

@rescript/belt

npm i https://pkg.pr.new/@rescript/belt@8896

@rescript/darwin-arm64

npm i https://pkg.pr.new/@rescript/darwin-arm64@8896

@rescript/darwin-x64

npm i https://pkg.pr.new/@rescript/darwin-x64@8896

@rescript/linux-arm64

npm i https://pkg.pr.new/@rescript/linux-arm64@8896

@rescript/linux-x64

npm i https://pkg.pr.new/@rescript/linux-x64@8896

@rescript/runtime

npm i https://pkg.pr.new/@rescript/runtime@8896

@rescript/win32-x64

npm i https://pkg.pr.new/@rescript/win32-x64@8896

commit: 3f32388

@github-actions

Copy link
Copy Markdown

@cknitt

cknitt commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

@codex review

1 similar comment
@cknitt

cknitt commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-18T12:59:25.227372Z e861ce0 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a00b41e16b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/tests/src/unreachable_or_guard_test.res Outdated
@cknitt

cknitt commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

@codex review

@cknitt
cknitt marked this pull request as ready for review October 18, 2026 12:02
@cknitt
cknitt requested a review from cristianoc October 18, 2026 12:02
@cknitt cknitt changed the title Skip handlers of unreachable or-patterns Fix compiler crash on unreachable guarded or-patterns Oct 18, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: adf8ee9208

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread compiler/ml/matching.ml Outdated
@cknitt

cknitt commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: e861ce0598

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

cknitt and others added 3 commits October 19, 2026 06:33
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>
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>
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>
@github-actions

Copy link
Copy Markdown

@cknitt
cknitt merged commit 33fbfc7 into master Oct 19, 2026
31 checks passed
@cknitt
cknitt deleted the fix-comp-exit branch October 19, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants