char is now an alias of int, so char literals and ints mix freely, including in switch patterns.Semantic Diff
5 commits, rendered from their records.
| Range | Commits | Changelog | Areas | JS output changes | Formats | Failed checks |
|---|---|---|---|---|---|---|
06cc086c2324008d907df13562671457a89640f1..pr-8894 | 5 | ๐ New feature ร2 ๐ Polish | typecheckerlambdaanalysistestsruntime | 1 | cmt (Caml1999T045 -> Caml1999T046) | 1 |
char is now an alias of int, so char literals and ints mix freely, including in switch patterns.200 .. 299 are supported in switch, and their bounds may mix ints and chars.'\u{0}' .. '\u{10FFFF}' compile as fast as a single character, and missing-case warnings show ranges instead of listing every character.06cc086c2324008d907df13562671457a89640f1..pr-8894char an alias of intchar was a private abbreviation of int: a char could be coerced to an int with :>, but an int could not be used as a char. It is now a plain alias, so char literals and ints mix freely, including in one switch. Pattern matching treats a char constant and an int constant with the same value as the same constant, for exhaustiveness, unused cases and switch compilation. Char literals keep their spelling in generated JavaScript comments and in warning examples. First step of #7028.
let f = x => switch x { | '\000' .. '\255' => 0 | 'ล' => 2 | _ => 3 }
function f(x) { if (x !== 333) { if (x >= 256) { return 3; } else { return 0; } } else { return 2; }}
function f(x) { if (x >= 256) { if (x !== 333) { return 3; } else { return 2; } } else if (x >= 0) { return 0; } else { return 3; }}
Source tests/tests/src/string_unicode_test.mjs in the diff (the range starts at code point 0, written as a literal NUL character in the test).
char is now an alias of int, so char literals and ints mix freely, including in switch patterns.Const_char instead of typing char literals as ints? It keeps the /* 'a' */97 comments in the JavaScript and the char spelling in warnings such as "for example: | '\x00'".x >= 0. Two committed snapshots change for that reason.1 .. 5 work now? No, they still report Invalid_interval. Char ranges expand to one case per value, which does not scale to arbitrary ints; that needs interval patterns of their own.char value offers Int functions; it offered none before (tests/analysis_tests/tests/src/CompletionChar.res)tests/tests/src/+ char_int_alias_test.res~ adt_optimize_test.mjs~ string_unicode_test.mjstests/build_tests/+ super_errors/fixtures/warning_8_char_int_patterns.res+ super_errors/fixtures/warning_11_equivalent_char_int_patterns.res| M | analysis/src/type_utils.ml | +4 | โ2 |
| A | changes/char-int-alias.feature.md | +1 | โ0 |
| M | compiler/ml/matching.ml | +17 | โ22 |
| M | compiler/ml/parmatch.ml | +20 | โ16 |
| M | compiler/ml/parmatch.mli | +3 | โ0 |
| M | compiler/ml/predef.ml | +2 | โ3 |
| M | tests/ERROR_VARIANTS.md | +4 | โ1 |
| A | tests/analysis_tests/tests/src/CompletionChar.res | +4 | โ0 |
| A | tests/analysis_tests/tests/src/expected/CompletionChar.res.txt | +34 | โ0 |
| A | tests/build_tests/super_errors/expected/warning_11_equivalent_char_int_patterns.res.expected | +11 | โ0 |
| A | tests/build_tests/super_errors/expected/warning_8_char_int_patterns.res.expected | +14 | โ0 |
| A | tests/build_tests/super_errors/fixtures/warning_11_equivalent_char_int_patterns.res | +7 | โ0 |
| A | tests/build_tests/super_errors/fixtures/warning_8_char_int_patterns.res | +6 | โ0 |
| M | tests/tests/src/adt_optimize_test.mjs | +2 | โ3 |
| A | tests/tests/src/char_int_alias_test.mjs | +121 | โ0 |
| A | tests/tests/src/char_int_alias_test.res | +90 | โ0 |
| M | tests/tests/src/string_unicode_test.mjs | +6 | โ4 |
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.
let f = (a, b) => switch (a, b) { | ('a' .. 'z', 'a' .. 'z') => 1 | (_, '\u{0}' .. '`') => 2 }
You forgot to handle a possible case here, for example: | ('t', '{')| ('u', '{')| ('v', '{')| ('w', '{')| ('x', '{')| ('y', '{')| ('z', '{')
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.
'\u{0}' .. '\u{10FFFF}' compile as fast as a single character, and missing-case warnings show ranges instead of listing every character.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.tests/tests/src/~ char_range_test.restests/build_tests/+ super_errors/fixtures/warning_8_char_range_example.res+ super_errors/fixtures/warning_11_char_range_covered.res| M | analysis/src/process_cmt.ml | +3 | โ1 |
| A | changes/char-range-patterns.polish.md | +1 | โ0 |
| M | compiler/common/pattern_printer.ml | +5 | โ3 |
| M | compiler/ext/config.ml | +1 | โ1 |
| M | compiler/ml/dict_pattern.ml | +3 | โ1 |
| M | compiler/ml/matching.ml | +203 | โ67 |
| M | compiler/ml/parmatch.ml | +161 | โ29 |
| M | compiler/ml/parmatch.mli | +7 | โ0 |
| M | compiler/ml/printtyped.ml | +2 | โ0 |
| M | compiler/ml/rec_check.ml | +2 | โ2 |
| M | compiler/ml/tast_iterator.ml | +1 | โ1 |
| M | compiler/ml/tast_mapper.ml | +1 | โ1 |
| M | compiler/ml/typecore.ml | +13 | โ15 |
| M | compiler/ml/typedtree.ml | +5 | โ2 |
| M | compiler/ml/typedtree.mli | +4 | โ0 |
| M | compiler/ml/typedtree_iter.ml | +1 | โ1 |
| M | tests/ERROR_VARIANTS.md | +5 | โ4 |
| A | tests/build_tests/super_errors/expected/warning_11_char_range_covered.res.expected | +11 | โ0 |
| A | tests/build_tests/super_errors/expected/warning_8_char_range_example.res.expected | +14 | โ0 |
| A | tests/build_tests/super_errors/fixtures/warning_11_char_range_covered.res | +8 | โ0 |
| A | tests/build_tests/super_errors/fixtures/warning_8_char_range_example.res | +6 | โ0 |
| M | tests/tests/src/char_range_test.mjs | +142 | โ13 |
| M | tests/tests/src/char_range_test.res | +81 | โ2 |
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.
let status = code => switch code { | 200 .. 299 => "success" | 301 | 302 | 307 | 308 => "redirect" | 400 .. 499 => "client error" | 500 .. 599 => "server error" | _ => "unknown" }
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."
200 .. 299 are supported in switch, and their bounds may mix ints and chars._ for a switch on -0x80000000 .. -1 | 0 .. 0x7FFFFFFF, as it does for constants.Fatal error: Matching.comp_exit; each reduces to | _ => ... | p1 | p2 if g() => ..., which crashes the parent commits the same way with char patterns.tests/tests/src/+ int_range_test.restests/build_tests/+ super_errors/fixtures/warning_8_int_range_example.res+ super_errors/fixtures/pattern_interval_float.resโ super_errors/fixtures/pattern_interval_non_char.res| A | changes/int-range-patterns.feature.md | +1 | โ0 |
| M | compiler/ml/parmatch.ml | +21 | โ10 |
| M | compiler/ml/typecore.ml | +30 | โ14 |
| M | tests/ERROR_VARIANTS.md | +7 | โ6 |
| A | tests/build_tests/super_errors/expected/pattern_interval_float.res.expected | +11 | โ0 |
| D | tests/build_tests/super_errors/expected/pattern_interval_non_char.res.expected | +0 | โ11 |
| A | tests/build_tests/super_errors/expected/warning_8_int_range_example.res.expected | +14 | โ0 |
| R | tests/build_tests/super_errors/fixtures/pattern_interval_float.res | +1 | โ1 |
| A | tests/build_tests/super_errors/fixtures/warning_8_int_range_example.res | +6 | โ0 |
| A | tests/tests/src/int_range_test.mjs | +155 | โ0 |
| A | tests/tests/src/int_range_test.res | +87 | โ0 |
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.
let f = c => switch c { | '\u{0}' .. '\u{10FFFF}' => 1 }
You forgot to handle a possible case here, for example: | '\u{110000}'
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.
tests/build_tests/+ super_errors/fixtures/warning_8_char_range_beyond.res| M | compiler/ml/parmatch.ml | +8 | โ5 |
| M | tests/ERROR_VARIANTS.md | +2 | โ1 |
| A | tests/build_tests/super_errors/expected/warning_8_char_range_beyond.res.expected | +13 | โ0 |
| A | tests/build_tests/super_errors/fixtures/warning_8_char_range_beyond.res | +5 | โ0 |
Primitive_string_extern.getChar returned option<char>; it returns option<int> now that char is an alias of int, which leaves no use of char in the runtime. The generated JavaScript is unchanged.
| M | packages/@rescript/runtime/Primitive_string_extern.res | +1 | โ1 |