Repository navigation
Support int range patterns - #8894
Conversation
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/darwin-x64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9a4e37f913
ℹ️ 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".
A missing-case example in a char column took the kind of the first
pattern, so a value that no char literal can denote was printed as an
invalid one: a switch covering every char literal suggested
`'\u{110000}'`. Such values are now printed as ints.
Example (rescript):
let f = c =>
switch c {
| '\u{0}' .. '\u{10FFFF}' => 1
}
Before (text):
You forgot to handle a possible case here, for example:
| '\u{110000}'
After (text):
You forgot to handle a possible case here, for example:
| 1114112
Source: tests/build_tests/super_errors/fixtures/warning_8_char_range_beyond.res,
compiled with the parent commit and with this one.
Review: Codex found this for mixed ranges on #8894, where an example below 0
was printed as a char; the cause is here.
Area: typechecker tests
Output: unchanged
Errors: ERROR_VARIANTS.md warnings section lists warning_8_char_range_beyond.res
Tests: +tests/build_tests/super_errors/fixtures/warning_8_char_range_beyond.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>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## char-intervals #8894 +/- ##
===============================================
Coverage 81.04% 81.04%
===============================================
Files 455 455
Lines 63660 63675 +15
===============================================
+ Hits 51591 51605 +14
- Misses 12069 12070 +1
🚀 New features to boost your workflow:
|
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
A missing-case example in a char column took the kind of the first
pattern, so a value that no char literal can denote was printed as an
invalid one: a switch covering every char literal suggested
`'\u{110000}'`. Such values are now printed as ints.
Example (rescript):
let f = c =>
switch c {
| '\u{0}' .. '\u{10FFFF}' => 1
}
Before (text):
You forgot to handle a possible case here, for example:
| '\u{110000}'
After (text):
You forgot to handle a possible case here, for example:
| 1114112
Source: tests/build_tests/super_errors/fixtures/warning_8_char_range_beyond.res,
compiled with the parent commit and with this one.
Review: Codex found this for mixed ranges on #8894, where an example below 0
was printed as a char; the cause is here.
Area: typechecker tests
Output: unchanged
Errors: ERROR_VARIANTS.md warnings section lists warning_8_char_range_beyond.res
Tests: +tests/build_tests/super_errors/fixtures/warning_8_char_range_beyond.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>
A missing-case example in a char column took the kind of the first
pattern, so a value that no char literal can denote was printed as an
invalid one: a switch covering every char literal suggested
`'\u{110000}'`. Such values are now printed as ints.
Example (rescript):
let f = c =>
switch c {
| '\u{0}' .. '\u{10FFFF}' => 1
}
Before (text):
You forgot to handle a possible case here, for example:
| '\u{110000}'
After (text):
You forgot to handle a possible case here, for example:
| 1114112
Source: tests/build_tests/super_errors/fixtures/warning_8_char_range_beyond.res,
compiled with the parent commit and with this one.
Review: Codex found this for mixed ranges on #8894, where an example below 0
was printed as a char; the cause is here.
Area: typechecker tests
Output: unchanged
Errors: ERROR_VARIANTS.md warnings section lists warning_8_char_range_beyond.res
Tests: +tests/build_tests/super_errors/fixtures/warning_8_char_range_beyond.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>
`switch` now accepts int ranges such as `200 .. 299`, and bounds that mix
ints and chars, since `char` is an alias of `int`. They use the
`Tpat_interval` node char ranges compile to, so a range costs the same
however wide it is. Ranges of other constants, such as `1.0 .. 5.0`, still
report `Invalid_interval`, whose message now names int and char intervals.
Int switches are never exhaustive, as before: a missing-case example is the
least uncovered value from 0 up, else the greatest one below 0.
Example (rescript):
let status = code =>
switch code {
| 200 .. 299 => "success"
| 301 | 302 | 307 | 308 => "redirect"
| 400 .. 499 => "client error"
| 500 .. 599 => "server error"
| _ => "unknown"
}
After (js):
function status(code) {
if (code >= 309) {
if (code >= 500) {
if (code >= 600) {
return "unknown";
} else {
return "server error";
}
} else if (code >= 400) {
return "client error";
} else {
return "unknown";
}
}
if (code < 200) {
return "unknown";
}
if (code < 300) {
return "success";
}
switch (code) {
case 300 :
case 303 :
case 304 :
case 305 :
case 306 :
return "unknown";
case 301 :
case 302 :
case 307 :
case 308 :
return "redirect";
}
}
Source: tests/tests/src/int_range_test.mjs in the diff. Before this commit
the example reported "Only character intervals are supported in patterns."
Review: Does a switch covering all ints count as exhaustive? No. Ints are
never matched exhaustively, so warning 8 still reports `_` for a switch on
`-0x80000000 .. -1 | 0 .. 0x7FFFFFFF`, as it does for constants.
Review: Can a mixed range give an invalid example? Codex found that a range
from a char to an int covering all non-negative ints suggested a char for
-1. Examples are written as chars only when they are valid code points, a
fix in the parent commit; warning_8_mixed_range_example.res covers this case.
Review: How was it checked? A fuzzer generated 600 switches over int, char and
mixed ranges with negative bounds, or-patterns, guards, tuples and options;
the results equal a reference evaluation for every input. Three programs hit
`Fatal error: Matching.comp_exit`; each reduces to `| _ => ... | p1 | p2 if
g() => ...`, which crashes the parent commits the same way with char
patterns.
Area: typechecker tests
Output: unchanged, existing programs compile the same; int ranges are new
Formatting: unchanged
Formats: none
PPX: unchanged
Editor: unchanged
Tests: +tests/tests/src/int_range_test.res
+tests/build_tests/super_errors/fixtures/warning_8_int_range_example.res
+tests/build_tests/super_errors/fixtures/warning_8_mixed_range_example.res
+tests/build_tests/super_errors/fixtures/pattern_interval_float.res
-tests/build_tests/super_errors/fixtures/pattern_interval_non_char.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>
003b92e to
24a773a
Compare
* Represent char range patterns with a typed-tree node
A char range pattern such as `'a' .. 'z'` used to be expanded by the type
checker into an or-pattern with one constant per character, so compile time
and memory grew with the width of the range. It is now one `Tpat_interval`
node. Exhaustiveness and match compilation split each column at the bounds
of its ranges into pieces that every range covers fully or not at all, so
the work depends on the number of patterns, not on the number of characters.
Missing-case warnings show ranges instead of listing every character.
Example (rescript):
let f = (a, b) =>
switch (a, b) {
| ('a' .. 'z', 'a' .. 'z') => 1
| (_, '\u{0}' .. '`') => 2
}
Before (text):
You forgot to handle a possible case here, for example:
| ('t', '{')
| ('u', '{')
| ('v', '{')
| ('w', '{')
| ('x', '{')
| ('y', '{')
| ('z', '{')
After (text):
You forgot to handle a possible case here, for example:
| ('a' .. 'z', '{') | ('\x00', 'a')
Source: tests/build_tests/super_errors/fixtures/warning_8_char_range_example.res,
compiled with the parent commit and with this one.
Review: Is the generated JavaScript the same? Every committed snapshot is
byte-identical. A differential fuzzer (900 random switches over overlapping
char ranges, or-patterns, guards, tuples and options) compiled each program
with the parent commit and with this one: results equal a reference
evaluation for every input, warning numbers and locations are equal, and the
JS differs in shape for some programs and is 0.4% smaller in total
(296,645 -> 295,495 characters).
Review: Why are intervals precompiled like or-patterns? An expanded range
used to be an or-pattern, whose row is compiled once in a handler. Doing the
same for an interval whose row tests further columns keeps that sharing;
dividing such rows by piece instead made the fuzzer's output 16% larger.
Review: Why is the action of a range stored as shared? The switch emits it
for each value of the range. An expanded range shared it the same way,
because its values had equal actions; a copy per value would declare the
action's variables twice in one JavaScript scope.
Review: Are int ranges such as `1 .. 5` accepted? Not yet; they still report
`Invalid_interval`. The node and the algorithms already work on int and
char constants, for a follow-up.
Area: typechecker lambda analysis tests
Output: unchanged, all committed generated JS is byte-identical
(char_range_test.mjs changes with its source)
Formatting: unchanged
Formats: cmt (Caml1999T045 -> Caml1999T046)
PPX: unchanged, Ppat_interval already exists in the v0 parsetree
Editor: unchanged; analysis visits the new node like a constant
Tests: ~tests/tests/src/char_range_test.res
+tests/build_tests/super_errors/fixtures/warning_8_char_range_example.res
+tests/build_tests/super_errors/fixtures/warning_11_char_range_covered.res
Measured: switch on '\u{0}' .. '\u{10FFFF}': 15.75 s and 1.78 GB -> 0.02 s and
19 MB (bsc wall time and max RSS, /usr/bin/time, parent commit vs this one);
switch on '\u{0}' .. '\u{FFFF}': 0.55 s and 135 MB -> 0.02 s and 20 MB
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>
* Write missing-case examples beyond char literals as ints
A missing-case example in a char column took the kind of the first
pattern, so a value that no char literal can denote was printed as an
invalid one: a switch covering every char literal suggested
`'\u{110000}'`. Such values are now printed as ints.
Example (rescript):
let f = c =>
switch c {
| '\u{0}' .. '\u{10FFFF}' => 1
}
Before (text):
You forgot to handle a possible case here, for example:
| '\u{110000}'
After (text):
You forgot to handle a possible case here, for example:
| 1114112
Source: tests/build_tests/super_errors/fixtures/warning_8_char_range_beyond.res,
compiled with the parent commit and with this one.
Review: Codex found this for mixed ranges on #8894, where an example below 0
was printed as a char; the cause is here.
Area: typechecker tests
Output: unchanged
Tests: +tests/build_tests/super_errors/fixtures/warning_8_char_range_beyond.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>
Third step of #7028, stacked on #8893.
switchnow accepts int range patterns, and their bounds may mix ints and chars, sincecharis an alias ofint(#8892). They use theTpat_intervalnode from #8893, so a range costs the same however wide it is.1.0 .. 5.0) still reportInvalid_interval, whose message now reads "Only int and char intervals are supported in patterns."_if all of int32 is covered.New tests:
int_range_test.res: negative, hex, reversed and mixed bounds, large ranges, HTTP status codes, ranges in several columns.super_errors: a missing-case example below 0 (warning 8), andInvalid_intervalwith float bounds. That fixture replacespattern_interval_non_char.res, since1 .. 5is now valid.A differential fuzzer ran 600 switches over int, char and mixed ranges (negative bounds, or-patterns, guards, tuples, options). For every input, the results equal a reference evaluation. Three programs hit the pre-existing
Matching.comp_exitcrash described in #8893.🤖 Generated with Claude Code
https://claude.ai/code/session_01JALXUyHhkDqzcB3WuW9Svo