Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/int-range-patterns.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Int range patterns such as `200 .. 299` are supported in `switch`, and their bounds may mix ints and chars.
31 changes: 21 additions & 10 deletions compiler/ml/parmatch.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1140,18 +1140,29 @@ let build_other ext env : Typedtree.pattern =
_ )
:: _ ->
(* Int and char constants and intervals can share a column; the example is
the least value from 0 up that no range covers, of the kind of the
first pattern. Ranges sorted by lower bound are skipped in one pass. *)
let ranges =
List.filter_map (fun (q, _) -> int_range q) env
|> List.sort (fun (lo1, _) (lo2, _) -> compare lo1 lo2)
the least value from 0 up that no range covers, else the greatest one
below 0, of the kind of the first pattern. Ranges sorted by a bound are
skipped in one pass. *)
let ranges = List.filter_map (fun (q, _) -> int_range q) env in
let up =
List.sort (fun (lo1, _) (lo2, _) -> compare lo1 lo2) ranges
|> List.fold_left
(fun i (lo, hi) -> if lo <= i && i <= hi then hi + 1 else i)
0
in
let other =
List.fold_left
(fun i (lo, hi) -> if lo <= i && i <= hi then hi + 1 else i)
0 ranges
let down () =
List.sort (fun (_, hi1) (_, hi2) -> compare hi2 hi1) ranges
|> List.fold_left
(fun i (lo, hi) -> if lo <= i && i <= hi then lo - 1 else i)
(-1)
in
make_int_range p other other
let int32_max = Int32.to_int Int32.max_int
and int32_min = Int32.to_int Int32.min_int in
if up <= int32_max then make_int_range p up up
else
let other = down () in
(* Every int is covered; ints are never matched exhaustively *)
if other < int32_min then omega else make_int_range p other other
| (({pat_desc = Tpat_constant (Const_bigint _)} as p), _) :: _ ->
build_other_constant
(function
Expand Down
44 changes: 30 additions & 14 deletions compiler/ml/typecore.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1423,19 +1423,35 @@ and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env sp
pat_attributes = sp.ppat_attributes;
pat_env = !env;
}
| Ppat_interval (Pconst_char {semantic = c1}, Pconst_char {semantic = c2}) ->
let lo = Const_char (min c1 c2) and hi = Const_char (max c1 c2) in
unify_pat_types loc !env (type_constant lo) expected_ty;
rp k
{
pat_desc = (if c1 = c2 then Tpat_constant lo else Tpat_interval (lo, hi));
pat_loc = loc;
pat_extra = [];
pat_type = expected_ty;
pat_attributes = sp.ppat_attributes;
pat_env = !env;
}
| Ppat_interval _ -> raise (Error (loc, !env, Invalid_interval))
| Ppat_interval (c1, c2) -> (
(* [char] is an alias of [int], so bounds may mix both *)
let bound c =
match constant_or_raise !env loc c with
| (Const_int i | Const_char i) as c -> Some (i, c)
| Const_string _ | Const_float _ | Const_bigint _ -> None
in
match (bound c1, bound c2) with
| Some b1, Some b2 ->
let (i1, lo), (i2, hi) =
if fst b1 <= fst b2 then (b1, b2) else (b2, b1)
in
let ty =
match (lo, hi) with
| Const_char _, Const_char _ -> type_constant lo
| _ -> instance_def (Predef.type_int ())
in
unify_pat_types loc !env ty expected_ty;
rp k
{
pat_desc =
(if i1 = i2 then Tpat_constant lo else Tpat_interval (lo, hi));
Comment thread
cknitt marked this conversation as resolved.
pat_loc = loc;
pat_extra = [];
pat_type = expected_ty;
pat_attributes = sp.ppat_attributes;
pat_env = !env;
}
| _ -> raise (Error (loc, !env, Invalid_interval)))
| Ppat_tuple spl ->
assert (List.length spl >= 2);
let spl_ann = List.map (fun p -> (p, newvar ())) spl in
Expand Down Expand Up @@ -5369,7 +5385,7 @@ let report_error env loc ppf error =
prefix in this pattern.@]"
name Printtyp.path tpath
| Invalid_interval ->
fprintf ppf "@[Only character intervals are supported in patterns.@]"
fprintf ppf "@[Only int and char intervals are supported in patterns.@]"
| Invalid_for_loop_index ->
fprintf ppf "@[Invalid for-loop index: only variables and _ are allowed.@]"
| No_value_clauses ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

We've found a bug for you!
/.../fixtures/pattern_interval_float.res:3:5-14

1 │ let f = x =>
2 │ switch x {
3 │ | 1.0 .. 5.0 => "low"
4 │ | _ => "other"
5 │ }

Only int and char intervals are supported in patterns.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

Warning number 8
/.../fixtures/warning_8_int_range_example.res:3:3-6:3

1 │ // All non-negative ints are covered, so the example is negative.
2 │ let f = x =>
3 │ switch x {
4 │  | 0 .. 0x7FFFFFFF => true
5 │  | -10 .. -1 => false
6 │  }
7 │

You forgot to handle a possible case here, for example:
| -11
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

Warning number 8
/.../fixtures/warning_8_mixed_range_example.res:4:3-6:3

2 │ // example is a negative int, not a char.
3 │ let f = x =>
4 │ switch x {
5 │  | '\x00' .. 0x7FFFFFFF => 1
6 │  }
7 │

You forgot to handle a possible case here, for example:
| -1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
let f = x =>
switch x {
| 1 .. 5 => "low"
| 1.0 .. 5.0 => "low"
| _ => "other"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// All non-negative ints are covered, so the example is negative.
let f = x =>
switch x {
| 0 .. 0x7FFFFFFF => true
| -10 .. -1 => false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// A range from a char to an int covers all non-negative ints, so the
// example is a negative int, not a char.
let f = x =>
switch x {
| '\x00' .. 0x7FFFFFFF => 1
}
155 changes: 155 additions & 0 deletions tests/tests/src/int_range_test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Generated by ReScript, PLEASE EDIT WITH CARE

import * as Mocha from "mocha";
import * as Test_utils from "./test_utils.mjs";

function classify(x) {
if (x >= 10) {
if (x >= 32) {
if (x > 200 || x < 100) {
return "other";
} else {
return "reversed";
}
} else if (x >= 16) {
return "hex";
} else {
return "other";
}
} else if (x >= 0) {
if (x !== 0) {
return "digit";
} else {
return "zero";
}
} else if (x >= -5) {
return "negative";
} else {
return "other";
}
}

function ascii(x) {
if (x >= 91) {
if (x > 122 || x < 97) {
return "other";
} else {
return "lower";
}
} else if (x >= 65) {
return "upper";
} else {
return "other";
}
}

function large(x) {
if (x >= 0) {
if (x >= 1073741824) {
return "high";
} else {
return "low";
}
} else {
return "negative";
}
}

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";
}
}

function pair(a, b) {
if (a > 9 || a < 0) {
if (!(b > 9 || b < 0)) {
return "one";
}
} else if (!(b > 9 || b < 0)) {
return "both";
}
if (a > 9 || a < 0) {
return "none";
} else {
return "one";
}
}

Mocha.describe("Int_range_test", () => {
Mocha.test("ranges", () => {
Test_utils.eq("File \"int_range_test.res\", line 47, characters 7-14", classify(-5), "negative");
Test_utils.eq("File \"int_range_test.res\", line 48, characters 7-14", classify(-1), "negative");
Test_utils.eq("File \"int_range_test.res\", line 49, characters 7-14", classify(-6), "other");
Test_utils.eq("File \"int_range_test.res\", line 50, characters 7-14", classify(0), "zero");
Test_utils.eq("File \"int_range_test.res\", line 51, characters 7-14", classify(9), "digit");
Test_utils.eq("File \"int_range_test.res\", line 52, characters 7-14", classify(16), "hex");
Test_utils.eq("File \"int_range_test.res\", line 53, characters 7-14", classify(31), "hex");
Test_utils.eq("File \"int_range_test.res\", line 54, characters 7-14", classify(32), "other");
Test_utils.eq("File \"int_range_test.res\", line 55, characters 7-14", classify(150), "reversed");
});
Mocha.test("mixed bounds", () => {
Test_utils.eq("File \"int_range_test.res\", line 59, characters 7-14", ascii(/* 'q' */113), "lower");
Test_utils.eq("File \"int_range_test.res\", line 60, characters 7-14", ascii(81), "upper");
Test_utils.eq("File \"int_range_test.res\", line 61, characters 7-14", ascii(/* '!' */33), "other");
});
Mocha.test("large ranges", () => {
Test_utils.eq("File \"int_range_test.res\", line 65, characters 7-14", large(-2147483648), "negative");
Test_utils.eq("File \"int_range_test.res\", line 66, characters 7-14", large(0), "low");
Test_utils.eq("File \"int_range_test.res\", line 67, characters 7-14", large(1073741823), "low");
Test_utils.eq("File \"int_range_test.res\", line 68, characters 7-14", large(1073741824), "high");
Test_utils.eq("File \"int_range_test.res\", line 69, characters 7-14", large(2147483647), "high");
});
Mocha.test("status codes", () => {
Test_utils.eq("File \"int_range_test.res\", line 73, characters 7-14", status(204), "success");
Test_utils.eq("File \"int_range_test.res\", line 74, characters 7-14", status(302), "redirect");
Test_utils.eq("File \"int_range_test.res\", line 75, characters 7-14", status(304), "unknown");
Test_utils.eq("File \"int_range_test.res\", line 76, characters 7-14", status(404), "client error");
Test_utils.eq("File \"int_range_test.res\", line 77, characters 7-14", status(503), "server error");
Test_utils.eq("File \"int_range_test.res\", line 78, characters 7-14", status(600), "unknown");
});
Mocha.test("several columns", () => {
Test_utils.eq("File \"int_range_test.res\", line 82, characters 7-14", pair(1, 2), "both");
Test_utils.eq("File \"int_range_test.res\", line 83, characters 7-14", pair(1, 20), "one");
Test_utils.eq("File \"int_range_test.res\", line 84, characters 7-14", pair(20, 2), "one");
Test_utils.eq("File \"int_range_test.res\", line 85, characters 7-14", pair(20, 20), "none");
});
});

export {
classify,
ascii,
large,
status,
pair,
}
/* Not a pure module */
Loading
Loading