diff --git a/tests/ERROR_VARIANTS.md b/tests/ERROR_VARIANTS.md index b37e62fdcbe..830d74c98b0 100644 --- a/tests/ERROR_VARIANTS.md +++ b/tests/ERROR_VARIANTS.md @@ -590,6 +590,14 @@ warnings still fire. Fixtures follow the naming convention `warning__.res` so coverage gaps stay greppable. +Pattern-matching warnings (8 non-exhaustive, 11/12 unused-case / +sub-pattern, all-clauses-guarded) additionally have shape-oriented +fixtures named `non_exhaustive_*`, `redundant_or_branch`, +`overlapping_or_lub`, and `all_clauses_guarded`. These exist to drive the +`parmatch.ml` witness pretty-printers and redundancy analysis per pattern +category; see [`PATTERN_MATCHING_COVERAGE.md`](./PATTERN_MATCHING_COVERAGE.md) +for the reachable-vs-dead breakdown of `matching.ml` / `parmatch.ml`. + ### Removed warnings The warning constructors listed in **Removed in `jono/remove-dead-errors`** diff --git a/tests/PATTERN_MATCHING_COVERAGE.md b/tests/PATTERN_MATCHING_COVERAGE.md new file mode 100644 index 00000000000..e614c200ed7 --- /dev/null +++ b/tests/PATTERN_MATCHING_COVERAGE.md @@ -0,0 +1,120 @@ +# Pattern-matching coverage notes + +Companion to [`ERROR_VARIANTS.md`](./ERROR_VARIANTS.md), scoped to the two +central pattern-matching modules: + +- `compiler/ml/matching.ml` — pattern compilation to Lambda IR +- `compiler/ml/parmatch.ml` — exhaustiveness / redundancy analysis + +It records which uncovered (cold) lines are **reachable** from `.res` +fixtures and which are **structurally unreachable** (dead code, debug-only, +or defensive). The goal is to stop coverage reviews from chasing lines that +no test can ever hit, and to point future fixtures at the lines that remain. + +Baseline (fresh `make coverage` off `master` @ `7d96fe384`): + +| Module | Coverage | Cold lines | +|---|---|---| +| `compiler/ml/matching.ml` | 72.6% | 366 | +| `compiler/ml/parmatch.ml` | 75.6% | 256 | + +> **Why the common paths are already covered.** Every `switch` in the stdlib +> and the existing test suite flows through `matching.ml`, so the ordinary +> per-category dispatchers (constants, constructors, variants, tuples, +> records, arrays, or-patterns) are already exercised. The cold lines are the +> *residue*: dead OCaml-heritage branches, debug printers, and genuinely +> exotic edge cases. Adding fixtures for "common" shapes does **not** move the +> metric — they overlap existing coverage. Realistic ceiling for both files is +> roughly **82–88%**, not ~100%. + +Line numbers are as of `master` @ `7d96fe384` and will drift; treat them as +approximate anchors and re-confirm against the current source. + +--- + +## Structurally unreachable — do not chase + +### `compiler/ml/matching.ml` + +| Lines | What | Why unreachable | +|---|---|---| +| ~1530–1600 | `expand_stringswitch`, `do_make_string_test_tree`, `make_string_test_sequence`, `split`, `tree_way_test`, `bind_sw` | **Dead for the JS target.** `combine_constant` routes `Const_string` to `Lstringswitch` (≈ line 2060); the dichotomic string-search tree is only used by the OCaml bytecode compiler (`bytegen.ml`), which ReScript does not use. | +| 2064–2065 | `Const_int32 -> assert false`, `Const_int64 -> assert false` | ReScript has no `int32`/`int64` literal patterns, so these constant cases never reach `combine_constant`. | +| ~2175, ~2190 | `... when false ->` branches in `combine_constructor` | Guarded by a literal `false` ("relies on tag being an int"); intentionally disabled. | +| 1976–1979, 2007–2009, 2013, 2015–2019 | `if dbg then (...)` blocks in `mk_failaction_pos` | `dbg` is a compile-time `false`; debug tracing only. | +| 69–75, 377–428, 1084–1089, 2540–2552 | `pretty_*` / `pretty_pm` / `pretty_precompiled` / `prerr_endline "** SPLIT **"` / `"COMPILE: "` | Debug pretty-printers, reachable only under `dbg`. | +| 153–155, 227, 1158–1161, 1665, 1679 | `fatal_error "Matching.filter_matrix"` / `"Matching.ctx_matcher"` / `"BAD: " ^ caller; assert false` / `Invalid_argument "cut"` / `fatal_error "Matching.do_tests_nofail"` | Defensive assertions guarding internal invariants; not reachable from well-typed input. | + +### `compiler/ml/parmatch.ml` + +| Lines | What | Why unreachable | +|---|---|---| +| ~364–434 | `pretty_const`, `pretty_val`, `pretty_car`, `pretty_cdr`, `pretty_arg`, `pretty_lvals` | **Debug-only.** These are *not* the user-facing witness printer. The non-exhaustive warning builds its counter-example via `!print_res_pat` (parmatch.ml:2088), which is bound to `Pattern_printer.print_pattern` in `compiler/common/pattern_printer.ml` (already ~88% covered). The `pretty_*` family here is reachable only from the `dbg` tracing in this module. | +| 369–370 | `Const_int32 -> "%ldl"`, `Const_int64 -> "%LdL"` in `pretty_const` | (Subsumed by the row above; also: no `int32`/`int64` literal patterns exist.) | +| 461–476 | `pretty_line` / `pretty_matrix` (`prerr_endline "begin matrix"` …) | Debug-only matrix dumpers. | +| 981–986 | `fatal_error "Parmatch.get_variant_constructors"` | Defensive; the guarded shapes are guaranteed by typing. | + +--- + +## Reachable but cold — worth fixtures + +> **Correction / lesson.** An earlier draft of this file claimed the +> `pretty_val` / `pretty_const` family (~364–434) was the reachable witness +> printer and that one non-exhaustive fixture *per pattern shape* would light +> it up. That is **wrong** — those are debug printers (see the dead-code table +> above); the user-facing witness is rendered by `Pattern_printer.print_pattern`. +> Direct measurement (compile each fixture with a dedicated `BISECT_FILE`) +> confirmed the `pretty_*` lines stay cold even when the witness prints. Always +> verify a "reachable" hypothesis against an actual `.coverage` file, not +> against the visible warning text. + +### `compiler/ml/parmatch.ml` — analysis edges + +These *are* reachable, but the payoff is small (a handful of lines), because the +surrounding exhaustiveness/redundancy machinery is already exercised by the +existing suite. + +| Lines | What | Trigger | Status | +|---|---|---|---| +| 2058–2061 | `Warnings.All_clauses_guarded` | every clause carries an `if` guard | partially covered by `all_clauses_guarded` | +| 1649–1652, 1757–1761 | `Upartial` merge in or-pattern redundancy | an or-pattern clause whose *some* sub-branch is redundant | partially covered by `redundant_or_branch` | +| 1814–1859 | `lub` (least upper bound) for tuple/variant/record/array | a clause covered by the *combination* of two overlapping or-branches | still cold — `overlapping_or_lub` did **not** reach the tuple-`lub` branch; needs a sharper trigger | + +### `compiler/ml/matching.ml` — compilation edges + +| Lines | What | Trigger | Status | +|---|---|---|---| +| 1662–1705 | `make_test_sequence` dichotomic split (`split_sequence`/`cut`) | float/bigint/char match with **≥ 4** value cases | ✓ covered by `pattern_match_constants_test` | +| 2182–2186, 2396–2399, 2479–2482, 2911–2915, 2952–2957 | partial-match failaction / unused-handler / cannot-flatten paths | a *simple* non-exhaustive match is **not** enough; these need an unused or-pattern handler, a `Cannot_flatten` tuple-`let`, or a partial match whose non-constant constructors share an action | still cold — needs targeted fixtures | +| 1295–1300, 1311–1314, 1390–1393, 1491–1494 | `matcher_*` `Tpat_or` (`raise OrPat`) | or-patterns at a column being specialized during precompilation | still cold | +| 1877–1923 | `as_interval_canfail` / `as_interval_nofail` hole logic | integer switches with non-contiguous values and a shared fail action | still cold | + +> **Measured contribution of this PR's fixtures.** Confirmed via per-fixture +> `BISECT_FILE` compilation (full JS, not `-bs-cmi-only`): ~10 lines in +> `matching.ml` (the dichotomic split) and ~5 in `parmatch.ml` +> (`All_clauses_guarded`, `Upartial`). The rest of the apparent full-suite +> delta is measurement noise from differing compile sets between runs. The +> behaviour fixtures' main value is **regression protection + cross-`bsc` +> portability**, not the coverage metric — most pattern shapes were already +> covered by the existing suite. + +--- + +## How to re-measure + +```bash +# Fresh baseline (records compiler line hits via bisect_ppx): +make coverage # writes _coverage/coverage.json (Coveralls format) + +# Per-file cold lines: +jq -r --arg f compiler/ml/matching.ml \ + '.source_files[] | select(.name|endswith($f)) | .coverage + | to_entries | map(select(.value==0) | .key+1) | @csv' \ + _coverage/coverage.json +``` + +To check whether one fixture hits a target line without a full run, compile it +with the bisect-instrumented `bsc` and a dedicated `BISECT_FILE`, then inspect +the resulting `*.coverage`. For witness branches it is usually enough to read +the warning text: if the printed counter-example has the shape you targeted, +the matching `pretty_val` branch executed. diff --git a/tests/build_tests/super_errors/expected/all_clauses_guarded.res.expected b/tests/build_tests/super_errors/expected/all_clauses_guarded.res.expected new file mode 100644 index 00000000000..fad3bf34727 --- /dev/null +++ b/tests/build_tests/super_errors/expected/all_clauses_guarded.res.expected @@ -0,0 +1,14 @@ + + Warning number 8 + /.../fixtures/all_clauses_guarded.res:4:3-7:3 + + 2 │ // exercises Warnings.All_clauses_guarded in parmatch.ml. + 3 │ let f = (x: int) => + 4 │ switch x { + 5 │  | n if n > 0 => "pos" + 6 │  | n if n <= 0 => "nonpos" + 7 │  } + 8 │ + + this pattern-matching is not exhaustive. +All clauses in this pattern-matching are guarded. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/non_exhaustive_array.res.expected b/tests/build_tests/super_errors/expected/non_exhaustive_array.res.expected new file mode 100644 index 00000000000..1a378569efe --- /dev/null +++ b/tests/build_tests/super_errors/expected/non_exhaustive_array.res.expected @@ -0,0 +1,14 @@ + + Warning number 8 + /.../fixtures/non_exhaustive_array.res:4:3-7:3 + + 2 │ // witness. + 3 │ let f = (x: array) => + 4 │ switch x { + 5 │  | [] => 0 + 6 │  | [_] => 1 + 7 │  } + 8 │ + + You forgot to handle a possible case here, for example: + | [_, _] \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/non_exhaustive_bigint.res.expected b/tests/build_tests/super_errors/expected/non_exhaustive_bigint.res.expected new file mode 100644 index 00000000000..59f6fc98033 --- /dev/null +++ b/tests/build_tests/super_errors/expected/non_exhaustive_bigint.res.expected @@ -0,0 +1,13 @@ + + Warning number 8 + /.../fixtures/non_exhaustive_bigint.res:3:3-5:3 + + 1 │ // Witness uses pretty_const for Const_bigint. + 2 │ let f = (x: bigint) => + 3 │ switch x { + 4 │  | 0n => 1 + 5 │  } + 6 │ + + You forgot to handle a possible case here, for example: + | 0n \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/non_exhaustive_char.res.expected b/tests/build_tests/super_errors/expected/non_exhaustive_char.res.expected new file mode 100644 index 00000000000..6bb0825d23f Binary files /dev/null and b/tests/build_tests/super_errors/expected/non_exhaustive_char.res.expected differ diff --git a/tests/build_tests/super_errors/expected/non_exhaustive_float.res.expected b/tests/build_tests/super_errors/expected/non_exhaustive_float.res.expected new file mode 100644 index 00000000000..5968331f192 --- /dev/null +++ b/tests/build_tests/super_errors/expected/non_exhaustive_float.res.expected @@ -0,0 +1,13 @@ + + Warning number 8 + /.../fixtures/non_exhaustive_float.res:3:3-5:3 + + 1 │ // Witness uses pretty_const for Const_float. + 2 │ let f = (x: float) => + 3 │ switch x { + 4 │  | 0.0 => 1 + 5 │  } + 6 │ + + You forgot to handle a possible case here, for example: + | 1. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/non_exhaustive_int.res.expected b/tests/build_tests/super_errors/expected/non_exhaustive_int.res.expected new file mode 100644 index 00000000000..902b9730be6 --- /dev/null +++ b/tests/build_tests/super_errors/expected/non_exhaustive_int.res.expected @@ -0,0 +1,13 @@ + + Warning number 8 + /.../fixtures/non_exhaustive_int.res:3:3-5:3 + + 1 │ // Witness uses pretty_const for Const_int. + 2 │ let f = (x: int) => + 3 │ switch x { + 4 │  | 0 => "zero" + 5 │  } + 6 │ + + You forgot to handle a possible case here, for example: + | 1 \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/non_exhaustive_list.res.expected b/tests/build_tests/super_errors/expected/non_exhaustive_list.res.expected new file mode 100644 index 00000000000..c8f2b1dcbf6 --- /dev/null +++ b/tests/build_tests/super_errors/expected/non_exhaustive_list.res.expected @@ -0,0 +1,13 @@ + + Warning number 8 + /.../fixtures/non_exhaustive_list.res:4:3-6:3 + + 2 │ // printer (pretty_car / pretty_cdr) in parmatch.ml. + 3 │ let f = (x: list) => + 4 │ switch x { + 5 │  | list{} => 0 + 6 │  } + 7 │ + + You forgot to handle a possible case here, for example: + | list{_, ..._} \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/non_exhaustive_polyvariant.res.expected b/tests/build_tests/super_errors/expected/non_exhaustive_polyvariant.res.expected new file mode 100644 index 00000000000..f4ffd97e1e7 --- /dev/null +++ b/tests/build_tests/super_errors/expected/non_exhaustive_polyvariant.res.expected @@ -0,0 +1,13 @@ + + Warning number 8 + /.../fixtures/non_exhaustive_polyvariant.res:4:3-6:3 + + 2 │ // witness printer. + 3 │ let f = (x: [#A | #B(int)]) => + 4 │ switch x { + 5 │  | #A => 0 + 6 │  } + 7 │ + + You forgot to handle a possible case here, for example: + | #B(_) \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/non_exhaustive_record.res.expected b/tests/build_tests/super_errors/expected/non_exhaustive_record.res.expected new file mode 100644 index 00000000000..5b7ae5e23ff --- /dev/null +++ b/tests/build_tests/super_errors/expected/non_exhaustive_record.res.expected @@ -0,0 +1,14 @@ + + Warning number 8 + /.../fixtures/non_exhaustive_record.res:5:3-7:3 + + 3 │ // Non-exhaustive record match: exercises the Tpat_record witness printe + │ r. + 4 │ let f = x => + 5 │ switch x { + 6 │  | {a: true, b: true} => 1 + 7 │  } + 8 │ + + You forgot to handle a possible case here, for example: + | {a: true, b: false, _} | {a: false, b: _, _} \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/non_exhaustive_string.res.expected b/tests/build_tests/super_errors/expected/non_exhaustive_string.res.expected new file mode 100644 index 00000000000..4ba59f8eafb --- /dev/null +++ b/tests/build_tests/super_errors/expected/non_exhaustive_string.res.expected @@ -0,0 +1,13 @@ + + Warning number 8 + /.../fixtures/non_exhaustive_string.res:3:3-5:3 + + 1 │ // Witness uses pretty_const for Const_string. + 2 │ let f = (x: string) => + 3 │ switch x { + 4 │  | "a" => 1 + 5 │  } + 6 │ + + You forgot to handle a possible case here, for example: + | "" \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/non_exhaustive_tuple.res.expected b/tests/build_tests/super_errors/expected/non_exhaustive_tuple.res.expected new file mode 100644 index 00000000000..32784978dfa --- /dev/null +++ b/tests/build_tests/super_errors/expected/non_exhaustive_tuple.res.expected @@ -0,0 +1,14 @@ + + Warning number 8 + /.../fixtures/non_exhaustive_tuple.res:4:3-7:3 + + 2 │ // printer in parmatch.ml (pretty_val). + 3 │ let f = (x: (bool, bool)) => + 4 │ switch x { + 5 │  | (true, true) => 1 + 6 │  | (false, false) => 2 + 7 │  } + 8 │ + + You forgot to handle a possible case here, for example: + | (false, true) | (true, false) \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/non_exhaustive_variant.res.expected b/tests/build_tests/super_errors/expected/non_exhaustive_variant.res.expected new file mode 100644 index 00000000000..b3af70e4052 --- /dev/null +++ b/tests/build_tests/super_errors/expected/non_exhaustive_variant.res.expected @@ -0,0 +1,13 @@ + + Warning number 8 + /.../fixtures/non_exhaustive_variant.res:6:3-8:3 + + 4 │ // witness printer. + 5 │ let f = x => + 6 │ switch x { + 7 │  | A => 0 + 8 │  } + 9 │ + + You forgot to handle a possible case here, for example: + | B(_) | D(_, _) \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/overlapping_or_lub.res.expected b/tests/build_tests/super_errors/expected/overlapping_or_lub.res.expected new file mode 100644 index 00000000000..68b1336cc0b --- /dev/null +++ b/tests/build_tests/super_errors/expected/overlapping_or_lub.res.expected @@ -0,0 +1,11 @@ + + Warning number 11 + /.../fixtures/overlapping_or_lub.res:7:5-10 + + 5 ┆ switch x { + 6 ┆ | (1, _) | (_, 2) => "a" + 7 ┆ | (1, 2) => "b" + 8 ┆ | _ => "c" + 9 ┆ } + + this match case is unused. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/redundant_or_branch.res.expected b/tests/build_tests/super_errors/expected/redundant_or_branch.res.expected new file mode 100644 index 00000000000..cd7b4449c52 --- /dev/null +++ b/tests/build_tests/super_errors/expected/redundant_or_branch.res.expected @@ -0,0 +1,39 @@ + + Warning number 4 + /.../fixtures/redundant_or_branch.res:6:3-10:3 + + 4 │ // Upartial (partially-redundant or-pattern) path in parmatch.ml. + 5 │ let f = x => + 6 │ switch x { + 7 │  | A | B => 1 + 8 │  | B | C => 2 + 9 │  | _ => 3 + 10 │  } + 11 │ + + this pattern-matching is fragile. +It will remain exhaustive when constructors are added to type t. + + + Warning number 12 + /.../fixtures/redundant_or_branch.res:8:5 + + 6 ┆ switch x { + 7 ┆ | A | B => 1 + 8 ┆ | B | C => 2 + 9 ┆ | _ => 3 + 10 ┆ } + + this sub-pattern is unused. + + + Warning number 11 + /.../fixtures/redundant_or_branch.res:9:5 + + 7 ┆ | A | B => 1 + 8 ┆ | B | C => 2 + 9 ┆ | _ => 3 + 10 ┆ } + 11 ┆ + + this match case is unused. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/all_clauses_guarded.res b/tests/build_tests/super_errors/fixtures/all_clauses_guarded.res new file mode 100644 index 00000000000..2adee7927b7 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/all_clauses_guarded.res @@ -0,0 +1,7 @@ +// Every clause is guarded, so exhaustiveness cannot be proven: +// exercises Warnings.All_clauses_guarded in parmatch.ml. +let f = (x: int) => + switch x { + | n if n > 0 => "pos" + | n if n <= 0 => "nonpos" + } diff --git a/tests/build_tests/super_errors/fixtures/non_exhaustive_array.res b/tests/build_tests/super_errors/fixtures/non_exhaustive_array.res new file mode 100644 index 00000000000..6da190e1f52 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/non_exhaustive_array.res @@ -0,0 +1,7 @@ +// Non-exhaustive array match: exercises array-length exhaustiveness and its +// witness. +let f = (x: array) => + switch x { + | [] => 0 + | [_] => 1 + } diff --git a/tests/build_tests/super_errors/fixtures/non_exhaustive_bigint.res b/tests/build_tests/super_errors/fixtures/non_exhaustive_bigint.res new file mode 100644 index 00000000000..a1aaa5e4887 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/non_exhaustive_bigint.res @@ -0,0 +1,5 @@ +// Witness uses pretty_const for Const_bigint. +let f = (x: bigint) => + switch x { + | 0n => 1 + } diff --git a/tests/build_tests/super_errors/fixtures/non_exhaustive_char.res b/tests/build_tests/super_errors/fixtures/non_exhaustive_char.res new file mode 100644 index 00000000000..ebc1f9d1d57 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/non_exhaustive_char.res @@ -0,0 +1,5 @@ +// Witness uses pretty_const for Const_char. +let f = (x: char) => + switch x { + | 'a' => 1 + } diff --git a/tests/build_tests/super_errors/fixtures/non_exhaustive_float.res b/tests/build_tests/super_errors/fixtures/non_exhaustive_float.res new file mode 100644 index 00000000000..3b55a812cbb --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/non_exhaustive_float.res @@ -0,0 +1,5 @@ +// Witness uses pretty_const for Const_float. +let f = (x: float) => + switch x { + | 0.0 => 1 + } diff --git a/tests/build_tests/super_errors/fixtures/non_exhaustive_int.res b/tests/build_tests/super_errors/fixtures/non_exhaustive_int.res new file mode 100644 index 00000000000..c9a3adad1b7 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/non_exhaustive_int.res @@ -0,0 +1,5 @@ +// Witness uses pretty_const for Const_int. +let f = (x: int) => + switch x { + | 0 => "zero" + } diff --git a/tests/build_tests/super_errors/fixtures/non_exhaustive_list.res b/tests/build_tests/super_errors/fixtures/non_exhaustive_list.res new file mode 100644 index 00000000000..a86f37b037b --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/non_exhaustive_list.res @@ -0,0 +1,6 @@ +// Non-exhaustive list match: exercises the cons (`list{_, ..._}`) witness +// printer (pretty_car / pretty_cdr) in parmatch.ml. +let f = (x: list) => + switch x { + | list{} => 0 + } diff --git a/tests/build_tests/super_errors/fixtures/non_exhaustive_polyvariant.res b/tests/build_tests/super_errors/fixtures/non_exhaustive_polyvariant.res new file mode 100644 index 00000000000..ab04b42de5d --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/non_exhaustive_polyvariant.res @@ -0,0 +1,6 @@ +// Non-exhaustive polymorphic-variant match: exercises the Tpat_variant +// witness printer. +let f = (x: [#A | #B(int)]) => + switch x { + | #A => 0 + } diff --git a/tests/build_tests/super_errors/fixtures/non_exhaustive_record.res b/tests/build_tests/super_errors/fixtures/non_exhaustive_record.res new file mode 100644 index 00000000000..2a818356597 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/non_exhaustive_record.res @@ -0,0 +1,7 @@ +type point = {a: bool, b: bool} + +// Non-exhaustive record match: exercises the Tpat_record witness printer. +let f = x => + switch x { + | {a: true, b: true} => 1 + } diff --git a/tests/build_tests/super_errors/fixtures/non_exhaustive_string.res b/tests/build_tests/super_errors/fixtures/non_exhaustive_string.res new file mode 100644 index 00000000000..7036c920cb5 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/non_exhaustive_string.res @@ -0,0 +1,5 @@ +// Witness uses pretty_const for Const_string. +let f = (x: string) => + switch x { + | "a" => 1 + } diff --git a/tests/build_tests/super_errors/fixtures/non_exhaustive_tuple.res b/tests/build_tests/super_errors/fixtures/non_exhaustive_tuple.res new file mode 100644 index 00000000000..ee0eaeeec2b --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/non_exhaustive_tuple.res @@ -0,0 +1,7 @@ +// Non-exhaustive tuple match: exercises the Tpat_tuple / Tpat_or witness +// printer in parmatch.ml (pretty_val). +let f = (x: (bool, bool)) => + switch x { + | (true, true) => 1 + | (false, false) => 2 + } diff --git a/tests/build_tests/super_errors/fixtures/non_exhaustive_variant.res b/tests/build_tests/super_errors/fixtures/non_exhaustive_variant.res new file mode 100644 index 00000000000..f9f37227f04 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/non_exhaustive_variant.res @@ -0,0 +1,8 @@ +type t = A | B(int) | D(int, int) + +// Non-exhaustive variant match: exercises the Tpat_construct (with-args) +// witness printer. +let f = x => + switch x { + | A => 0 + } diff --git a/tests/build_tests/super_errors/fixtures/overlapping_or_lub.res b/tests/build_tests/super_errors/fixtures/overlapping_or_lub.res new file mode 100644 index 00000000000..96c30d2a7d5 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/overlapping_or_lub.res @@ -0,0 +1,9 @@ +// `(1, 2)` is covered by the COMBINATION of the two or-branches above, so the +// redundancy check computes the least upper bound (lub) of `(1, _)` and +// `(_, 2)` — exercising lub for Tpat_tuple in parmatch.ml. +let f = (x: (int, int)) => + switch x { + | (1, _) | (_, 2) => "a" + | (1, 2) => "b" + | _ => "c" + } diff --git a/tests/build_tests/super_errors/fixtures/redundant_or_branch.res b/tests/build_tests/super_errors/fixtures/redundant_or_branch.res new file mode 100644 index 00000000000..a3d63c2a500 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/redundant_or_branch.res @@ -0,0 +1,10 @@ +type t = A | B | C + +// `B` in the second clause is already covered by the first, exercising the +// Upartial (partially-redundant or-pattern) path in parmatch.ml. +let f = x => + switch x { + | A | B => 1 + | B | C => 2 + | _ => 3 + } diff --git a/tests/tests/src/pattern_match_array_test.mjs b/tests/tests/src/pattern_match_array_test.mjs new file mode 100644 index 00000000000..279d53bf642 --- /dev/null +++ b/tests/tests/src/pattern_match_array_test.mjs @@ -0,0 +1,83 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; + +function sum(a) { + let len = a.length; + if (len >= 4) { + return -1; + } + switch (len) { + case 0 : + return 0; + case 1 : + return a[0]; + case 2 : + let x = a[0]; + let y = a[1]; + return x + y | 0; + case 3 : + let x$1 = a[0]; + let y$1 = a[1]; + let z = a[2]; + return (x$1 + y$1 | 0) + z | 0; + } +} + +function describeLen(a) { + let len = a.length; + if (len >= 3) { + return "many"; + } + switch (len) { + case 0 : + return "empty"; + case 1 : + return "one"; + case 2 : + return "two"; + } +} + +Mocha.describe("Pattern_match_array_test", () => { + Mocha.test("array match by length", () => { + Test_utils.eq("File \"pattern_match_array_test.res\", line 26, characters 7-14", sum([]), 0); + Test_utils.eq("File \"pattern_match_array_test.res\", line 27, characters 7-14", sum([7]), 7); + Test_utils.eq("File \"pattern_match_array_test.res\", line 28, characters 7-14", sum([ + 1, + 2 + ]), 3); + Test_utils.eq("File \"pattern_match_array_test.res\", line 29, characters 7-14", sum([ + 1, + 2, + 3 + ]), 6); + Test_utils.eq("File \"pattern_match_array_test.res\", line 30, characters 7-14", sum([ + 1, + 2, + 3, + 4 + ]), -1); + }); + Mocha.test("array match by length (wildcards)", () => { + Test_utils.eq("File \"pattern_match_array_test.res\", line 34, characters 7-14", describeLen([]), "empty"); + Test_utils.eq("File \"pattern_match_array_test.res\", line 35, characters 7-14", describeLen([9]), "one"); + Test_utils.eq("File \"pattern_match_array_test.res\", line 36, characters 7-14", describeLen([ + 1, + 2 + ]), "two"); + Test_utils.eq("File \"pattern_match_array_test.res\", line 37, characters 7-14", describeLen([ + 1, + 2, + 3, + 4 + ]), "many"); + }); +}); + +export { + sum, + describeLen, +} +/* Not a pure module */ diff --git a/tests/tests/src/pattern_match_array_test.res b/tests/tests/src/pattern_match_array_test.res new file mode 100644 index 00000000000..5b7d25bdd6a --- /dev/null +++ b/tests/tests/src/pattern_match_array_test.res @@ -0,0 +1,39 @@ +open Mocha +open Test_utils + +/* Exercises `divide_array` in compiler/ml/matching.ml: dispatch on array + length and element projection. */ + +let sum = a => + switch a { + | [] => 0 + | [x] => x + | [x, y] => x + y + | [x, y, z] => x + y + z + | _ => -1 + } + +let describeLen = a => + switch a { + | [] => "empty" + | [_] => "one" + | [_, _] => "two" + | _ => "many" + } + +describe(__MODULE__, () => { + test("array match by length", () => { + eq(__LOC__, sum([]), 0) + eq(__LOC__, sum([7]), 7) + eq(__LOC__, sum([1, 2]), 3) + eq(__LOC__, sum([1, 2, 3]), 6) + eq(__LOC__, sum([1, 2, 3, 4]), -1) + }) + + test("array match by length (wildcards)", () => { + eq(__LOC__, describeLen([]), "empty") + eq(__LOC__, describeLen([9]), "one") + eq(__LOC__, describeLen([1, 2]), "two") + eq(__LOC__, describeLen([1, 2, 3, 4]), "many") + }) +}) diff --git a/tests/tests/src/pattern_match_constants_test.mjs b/tests/tests/src/pattern_match_constants_test.mjs new file mode 100644 index 00000000000..eaa2b8453d3 --- /dev/null +++ b/tests/tests/src/pattern_match_constants_test.mjs @@ -0,0 +1,146 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; + +function classifyInt(x) { + if (x >= 4) { + if (x !== 100) { + return "other"; + } else { + return "hundred"; + } + } + if (x < -5) { + return "other"; + } + switch (x) { + case -5 : + return "neg5"; + case -4 : + case -3 : + case -2 : + case -1 : + return "other"; + case 0 : + return "zero"; + case 1 : + case 2 : + case 3 : + return "small"; + } +} + +function classifyChar(c) { + switch (c) { + case 97 : + return 1; + case 101 : + return 2; + case 109 : + return 3; + case 113 : + return 4; + case 122 : + return 5; + default: + return 0; + } +} + +function classifyFloat(x) { + if (x < 2.5) { + if (x !== 0.0) { + if (x !== 1.5) { + return "other"; + } else { + return "onehalf"; + } + } else { + return "zero"; + } + } else if (x !== 2.5) { + if (x !== 3.5) { + if (x !== 4.5) { + return "other"; + } else { + return "fourhalf"; + } + } else { + return "threehalf"; + } + } else { + return "twohalf"; + } +} + +function classifyBig(b) { + if (b < 10n) { + if (b !== 0n) { + if (b !== 1n) { + return "other"; + } else { + return "one"; + } + } else { + return "zero"; + } + } else if (b !== 10n) { + if (b !== 50n) { + if (b !== 100n) { + return "other"; + } else { + return "hundred"; + } + } else { + return "fifty"; + } + } else { + return "ten"; + } +} + +Mocha.describe("Pattern_match_constants_test", () => { + Mocha.test("int switch with negatives, or-pattern and holes", () => { + Test_utils.eq("File \"pattern_match_constants_test.res\", line 56, characters 7-14", classifyInt(-5), "neg5"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 57, characters 7-14", classifyInt(0), "zero"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 58, characters 7-14", classifyInt(1), "small"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 59, characters 7-14", classifyInt(2), "small"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 60, characters 7-14", classifyInt(3), "small"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 61, characters 7-14", classifyInt(100), "hundred"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 62, characters 7-14", classifyInt(4), "other"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 63, characters 7-14", classifyInt(-1), "other"); + }); + Mocha.test("char switch", () => { + Test_utils.eq("File \"pattern_match_constants_test.res\", line 67, characters 7-14", classifyChar(/* 'a' */97), 1); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 68, characters 7-14", classifyChar(/* 'e' */101), 2); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 69, characters 7-14", classifyChar(/* 'm' */109), 3); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 70, characters 7-14", classifyChar(/* 'q' */113), 4); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 71, characters 7-14", classifyChar(/* 'z' */122), 5); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 72, characters 7-14", classifyChar(/* 'x' */120), 0); + }); + Mocha.test("float switch (dichotomic comparison split)", () => { + Test_utils.eq("File \"pattern_match_constants_test.res\", line 76, characters 7-14", classifyFloat(0.0), "zero"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 77, characters 7-14", classifyFloat(1.5), "onehalf"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 78, characters 7-14", classifyFloat(2.5), "twohalf"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 79, characters 7-14", classifyFloat(3.5), "threehalf"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 80, characters 7-14", classifyFloat(4.5), "fourhalf"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 81, characters 7-14", classifyFloat(9.9), "other"); + }); + Mocha.test("bigint switch (dichotomic comparison split)", () => { + Test_utils.eq("File \"pattern_match_constants_test.res\", line 85, characters 7-14", classifyBig(0n), "zero"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 86, characters 7-14", classifyBig(1n), "one"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 87, characters 7-14", classifyBig(10n), "ten"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 88, characters 7-14", classifyBig(50n), "fifty"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 89, characters 7-14", classifyBig(100n), "hundred"); + Test_utils.eq("File \"pattern_match_constants_test.res\", line 90, characters 7-14", classifyBig(7n), "other"); + }); +}); + +export { + classifyInt, + classifyChar, + classifyFloat, + classifyBig, +} +/* Not a pure module */ diff --git a/tests/tests/src/pattern_match_constants_test.res b/tests/tests/src/pattern_match_constants_test.res new file mode 100644 index 00000000000..594dc98a7f0 --- /dev/null +++ b/tests/tests/src/pattern_match_constants_test.res @@ -0,0 +1,92 @@ +open Mocha +open Test_utils + +/* Exercises `combine_constant` in compiler/ml/matching.ml across every constant + kind, each routed to a different compilation strategy: + - Const_int -> call_switcher / interval logic (edges, holes, negatives) + - Const_char -> call_switcher (0..max) + - Const_float -> make_test_sequence with Pfloatcomp + - Const_bigint -> make_test_sequence with Pbigintcomp + All matches are exhaustive (trailing `_`) to stay warning-clean under + -warn-error A. */ + +let classifyInt = x => + switch x { + | -5 => "neg5" + | 0 => "zero" + | 1 | 2 | 3 => "small" + | 100 => "hundred" + | _ => "other" + } + +let classifyChar = c => + switch c { + | 'a' => 1 + | 'e' => 2 + | 'm' => 3 + | 'q' => 4 + | 'z' => 5 + | _ => 0 + } + +/* >= 4 cases so make_test_sequence performs the dichotomic split + (split_sequence / cut) rather than a flat comparison chain. */ +let classifyFloat = x => + switch x { + | 0.0 => "zero" + | 1.5 => "onehalf" + | 2.5 => "twohalf" + | 3.5 => "threehalf" + | 4.5 => "fourhalf" + | _ => "other" + } + +let classifyBig = b => + switch b { + | 0n => "zero" + | 1n => "one" + | 10n => "ten" + | 50n => "fifty" + | 100n => "hundred" + | _ => "other" + } + +describe(__MODULE__, () => { + test("int switch with negatives, or-pattern and holes", () => { + eq(__LOC__, classifyInt(-5), "neg5") + eq(__LOC__, classifyInt(0), "zero") + eq(__LOC__, classifyInt(1), "small") + eq(__LOC__, classifyInt(2), "small") + eq(__LOC__, classifyInt(3), "small") + eq(__LOC__, classifyInt(100), "hundred") + eq(__LOC__, classifyInt(4), "other") + eq(__LOC__, classifyInt(-1), "other") + }) + + test("char switch", () => { + eq(__LOC__, classifyChar('a'), 1) + eq(__LOC__, classifyChar('e'), 2) + eq(__LOC__, classifyChar('m'), 3) + eq(__LOC__, classifyChar('q'), 4) + eq(__LOC__, classifyChar('z'), 5) + eq(__LOC__, classifyChar('x'), 0) + }) + + test("float switch (dichotomic comparison split)", () => { + eq(__LOC__, classifyFloat(0.0), "zero") + eq(__LOC__, classifyFloat(1.5), "onehalf") + eq(__LOC__, classifyFloat(2.5), "twohalf") + eq(__LOC__, classifyFloat(3.5), "threehalf") + eq(__LOC__, classifyFloat(4.5), "fourhalf") + eq(__LOC__, classifyFloat(9.9), "other") + }) + + test("bigint switch (dichotomic comparison split)", () => { + eq(__LOC__, classifyBig(0n), "zero") + eq(__LOC__, classifyBig(1n), "one") + eq(__LOC__, classifyBig(10n), "ten") + eq(__LOC__, classifyBig(50n), "fifty") + eq(__LOC__, classifyBig(100n), "hundred") + eq(__LOC__, classifyBig(7n), "other") + }) +}) diff --git a/tests/tests/src/pattern_match_constructors_test.mjs b/tests/tests/src/pattern_match_constructors_test.mjs new file mode 100644 index 00000000000..30ecdb0c9b1 --- /dev/null +++ b/tests/tests/src/pattern_match_constructors_test.mjs @@ -0,0 +1,81 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; +import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.mjs"; + +function area(s) { + if (typeof s !== "object") { + return 0.0; + } + switch (s.TAG) { + case "Circle" : + let r = s._0; + return 3.0 * r * r; + case "Square" : + let side = s._0; + return side * side; + case "Rect" : + return s._0 * s._1; + } +} + +let NotFound = /* @__PURE__ */Primitive_exceptions.create("Pattern_match_constructors_test.NotFound"); + +let Code = /* @__PURE__ */Primitive_exceptions.create("Pattern_match_constructors_test.Code"); + +function describeExn(e) { + if (e.RE_EXN_ID === NotFound) { + return "not-found"; + } else if (e.RE_EXN_ID === Code) { + return "code:" + String(e._1); + } else { + return "other"; + } +} + +function unwrap(m) { + return m; +} + +Mocha.describe("Pattern_match_constructors_test", () => { + Mocha.test("exhaustive variant switch, mixed arities", () => { + Test_utils.eq("File \"pattern_match_constructors_test.res\", line 42, characters 7-14", 0.0, 0.0); + Test_utils.eq("File \"pattern_match_constructors_test.res\", line 43, characters 7-14", area({ + TAG: "Circle", + _0: 2.0 + }), 12.0); + Test_utils.eq("File \"pattern_match_constructors_test.res\", line 44, characters 7-14", area({ + TAG: "Square", + _0: 3.0 + }), 9.0); + Test_utils.eq("File \"pattern_match_constructors_test.res\", line 45, characters 7-14", area({ + TAG: "Rect", + _0: 2.0, + _1: 5.0 + }), 10.0); + }); + Mocha.test("extension/exception constructor match", () => { + Test_utils.eq("File \"pattern_match_constructors_test.res\", line 49, characters 7-14", describeExn({ + RE_EXN_ID: NotFound + }), "not-found"); + Test_utils.eq("File \"pattern_match_constructors_test.res\", line 50, characters 7-14", describeExn({ + RE_EXN_ID: Code, + _1: 5 + }), "code:5"); + Test_utils.eq("File \"pattern_match_constructors_test.res\", line 51, characters 7-14", describeExn({ + RE_EXN_ID: "Failure", + _1: "x" + }), "other"); + }); + Mocha.test("unboxed single constructor", () => Test_utils.eq("File \"pattern_match_constructors_test.res\", line 55, characters 7-14", 4.5, 4.5)); +}); + +export { + area, + NotFound, + Code, + describeExn, + unwrap, +} +/* Not a pure module */ diff --git a/tests/tests/src/pattern_match_constructors_test.res b/tests/tests/src/pattern_match_constructors_test.res new file mode 100644 index 00000000000..51cbb4d04b4 --- /dev/null +++ b/tests/tests/src/pattern_match_constructors_test.res @@ -0,0 +1,57 @@ +open Mocha +open Test_utils + +/* Exercises `combine_constructor` in compiler/ml/matching.ml: + - regular variant, mixed arities, exhaustive -> Lswitch, sig_complete (no fail) + - extension constructors (exceptions) -> Pextension_slot_eq chain + - unboxed single constructor -> direct projection */ + +type shape = + | Point + | Circle(float) + | Square(float) + | Rect(float, float) + +let area = s => + switch s { + | Point => 0.0 + | Circle(r) => 3.0 *. r *. r + | Square(side) => side *. side + | Rect(w, h) => w *. h + } + +exception NotFound +exception Code(int) + +let describeExn = e => + switch e { + | NotFound => "not-found" + | Code(n) => "code:" ++ string_of_int(n) + | _ => "other" + } + +@unboxed type meters = Meters(float) + +let unwrap = m => + switch m { + | Meters(f) => f + } + +describe(__MODULE__, () => { + test("exhaustive variant switch, mixed arities", () => { + eq(__LOC__, area(Point), 0.0) + eq(__LOC__, area(Circle(2.0)), 12.0) + eq(__LOC__, area(Square(3.0)), 9.0) + eq(__LOC__, area(Rect(2.0, 5.0)), 10.0) + }) + + test("extension/exception constructor match", () => { + eq(__LOC__, describeExn(NotFound), "not-found") + eq(__LOC__, describeExn(Code(5)), "code:5") + eq(__LOC__, describeExn(Failure("x")), "other") + }) + + test("unboxed single constructor", () => { + eq(__LOC__, unwrap(Meters(4.5)), 4.5) + }) +}) diff --git a/tests/tests/src/pattern_match_orpat_test.mjs b/tests/tests/src/pattern_match_orpat_test.mjs new file mode 100644 index 00000000000..8ea4053d7af --- /dev/null +++ b/tests/tests/src/pattern_match_orpat_test.mjs @@ -0,0 +1,62 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; + +function isOp(t) { + return typeof t !== "object"; +} + +function rootVal(t) { + return t._0; +} + +function classify(x) { + if ((x[0] - 1 | 0) > 2) { + return "no"; + } + switch (x[1]) { + case "a" : + case "b" : + return "match"; + default: + return "no"; + } +} + +Mocha.describe("Pattern_match_orpat_test", () => { + Mocha.test("or-pattern of constant constructors", () => { + Test_utils.eq("File \"pattern_match_orpat_test.res\", line 34, characters 7-14", true, true); + Test_utils.eq("File \"pattern_match_orpat_test.res\", line 35, characters 7-14", true, true); + Test_utils.eq("File \"pattern_match_orpat_test.res\", line 36, characters 7-14", false, false); + }); + Mocha.test("or-pattern binding shared variable", () => { + Test_utils.eq("File \"pattern_match_orpat_test.res\", line 40, characters 7-14", 5, 5); + Test_utils.eq("File \"pattern_match_orpat_test.res\", line 41, characters 7-14", 9, 9); + }); + Mocha.test("or-patterns inside tuple columns", () => { + Test_utils.eq("File \"pattern_match_orpat_test.res\", line 45, characters 7-14", classify([ + 1, + "a" + ]), "match"); + Test_utils.eq("File \"pattern_match_orpat_test.res\", line 46, characters 7-14", classify([ + 3, + "b" + ]), "match"); + Test_utils.eq("File \"pattern_match_orpat_test.res\", line 47, characters 7-14", classify([ + 4, + "a" + ]), "no"); + Test_utils.eq("File \"pattern_match_orpat_test.res\", line 48, characters 7-14", classify([ + 1, + "c" + ]), "no"); + }); +}); + +export { + isOp, + rootVal, + classify, +} +/* Not a pure module */ diff --git a/tests/tests/src/pattern_match_orpat_test.res b/tests/tests/src/pattern_match_orpat_test.res new file mode 100644 index 00000000000..38db0929a1f --- /dev/null +++ b/tests/tests/src/pattern_match_orpat_test.res @@ -0,0 +1,50 @@ +open Mocha +open Test_utils + +/* Exercises the or-pattern compilation paths in compiler/ml/matching.ml + (`split_or`, `explode_or_pat`, `precompile_or`): + - or-pattern of constant constructors + - or-pattern binding a shared variable across both branches + - or-patterns inside tuple columns (forces explosion) */ + +type token = Plus | Minus | Times | Div | Num(int) + +let isOp = t => + switch t { + | Plus | Minus | Times | Div => true + | Num(_) => false + } + +type rec tree = Leaf(int) | Node(int, tree, tree) + +/* `v` is bound in both arms of the or-pattern */ +let rootVal = t => + switch t { + | Leaf(v) | Node(v, _, _) => v + } + +let classify = x => + switch x { + | (1 | 2 | 3, "a" | "b") => "match" + | (_, _) => "no" + } + +describe(__MODULE__, () => { + test("or-pattern of constant constructors", () => { + eq(__LOC__, isOp(Plus), true) + eq(__LOC__, isOp(Div), true) + eq(__LOC__, isOp(Num(1)), false) + }) + + test("or-pattern binding shared variable", () => { + eq(__LOC__, rootVal(Leaf(5)), 5) + eq(__LOC__, rootVal(Node(9, Leaf(1), Leaf(2))), 9) + }) + + test("or-patterns inside tuple columns", () => { + eq(__LOC__, classify((1, "a")), "match") + eq(__LOC__, classify((3, "b")), "match") + eq(__LOC__, classify((4, "a")), "no") + eq(__LOC__, classify((1, "c")), "no") + }) +}) diff --git a/tests/tests/src/pattern_match_polyvariant_test.mjs b/tests/tests/src/pattern_match_polyvariant_test.mjs new file mode 100644 index 00000000000..96bf5422932 --- /dev/null +++ b/tests/tests/src/pattern_match_polyvariant_test.mjs @@ -0,0 +1,48 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; + +function name(c) { + if (typeof c !== "object") { + if (c === "Blue") { + return "blue"; + } else if (c === "Green") { + return "green"; + } else { + return "red"; + } + } + if (c.NAME === "Named") { + return c.VAL; + } + let match = c.VAL; + return String((match[0] + match[1] | 0) + match[2] | 0); +} + +Mocha.describe("Pattern_match_polyvariant_test", () => { + Mocha.test("polymorphic variant: constant tags", () => { + Test_utils.eq("File \"pattern_match_polyvariant_test.res\", line 23, characters 7-14", name("Red"), "red"); + Test_utils.eq("File \"pattern_match_polyvariant_test.res\", line 24, characters 7-14", name("Green"), "green"); + Test_utils.eq("File \"pattern_match_polyvariant_test.res\", line 25, characters 7-14", name("Blue"), "blue"); + }); + Mocha.test("polymorphic variant: block tags", () => { + Test_utils.eq("File \"pattern_match_polyvariant_test.res\", line 29, characters 7-14", name({ + NAME: "Rgb", + VAL: [ + 1, + 2, + 3 + ] + }), "6"); + Test_utils.eq("File \"pattern_match_polyvariant_test.res\", line 30, characters 7-14", name({ + NAME: "Named", + VAL: "teal" + }), "teal"); + }); +}); + +export { + name, +} +/* Not a pure module */ diff --git a/tests/tests/src/pattern_match_polyvariant_test.res b/tests/tests/src/pattern_match_polyvariant_test.res new file mode 100644 index 00000000000..46639ec0559 --- /dev/null +++ b/tests/tests/src/pattern_match_polyvariant_test.res @@ -0,0 +1,32 @@ +open Mocha +open Test_utils + +/* Exercises `divide_variant` / `split_variant_cases` and the variant + switchers in compiler/ml/matching.ml. A closed polymorphic variant with + both constant tags (#Red ...) and block tags (#Rgb(..), #Named(..)) forces + the `Pis_poly_var_block` split plus both the constant and block switch + paths. Exhaustive, so it stays warning-clean. */ + +type color = [#Red | #Green | #Blue | #Rgb(int, int, int) | #Named(string)] + +let name = (c: color) => + switch c { + | #Red => "red" + | #Green => "green" + | #Blue => "blue" + | #Rgb(r, g, b) => string_of_int(r + g + b) + | #Named(s) => s + } + +describe(__MODULE__, () => { + test("polymorphic variant: constant tags", () => { + eq(__LOC__, name(#Red), "red") + eq(__LOC__, name(#Green), "green") + eq(__LOC__, name(#Blue), "blue") + }) + + test("polymorphic variant: block tags", () => { + eq(__LOC__, name(#Rgb(1, 2, 3)), "6") + eq(__LOC__, name(#Named("teal")), "teal") + }) +}) diff --git a/tests/tests/src/pattern_match_record_tuple_test.mjs b/tests/tests/src/pattern_match_record_tuple_test.mjs new file mode 100644 index 00000000000..6d69dfc1eaf --- /dev/null +++ b/tests/tests/src/pattern_match_record_tuple_test.mjs @@ -0,0 +1,119 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; + +function quadrant(p) { + let x = p.x; + if (x === 0) { + if (p.y !== 0) { + return "y-axis"; + } else { + return "origin"; + } + } + let y = p.y; + if (y !== 0) { + if (x > 0 && y > 0) { + return "q1"; + } else { + return "other"; + } + } else { + return "x-axis"; + } +} + +function bump(c) { + let count = c.count; + if (count !== 0) { + return c.label + ":" + String(count); + } else { + return "start-" + c.label; + } +} + +function cmpPair(t) { + let a = t[0]; + if (a === 0) { + if (t[1] !== 0) { + return "first-zero"; + } else { + return "both-zero"; + } + } + let b = t[1]; + if (b !== 0) { + if (a === b) { + return "equal"; + } else { + return "distinct"; + } + } else { + return "second-zero"; + } +} + +Mocha.describe("Pattern_match_record_tuple_test", () => { + Mocha.test("record field tests + guard", () => { + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 38, characters 7-14", quadrant({ + x: 0, + y: 0 + }), "origin"); + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 39, characters 7-14", quadrant({ + x: 0, + y: 4 + }), "y-axis"); + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 40, characters 7-14", quadrant({ + x: 4, + y: 0 + }), "x-axis"); + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 41, characters 7-14", quadrant({ + x: 1, + y: 1 + }), "q1"); + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 42, characters 7-14", quadrant({ + x: -1, + y: 2 + }), "other"); + }); + Mocha.test("mutable record field match", () => { + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 46, characters 7-14", bump({ + count: 0, + label: "a" + }), "start-a"); + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 47, characters 7-14", bump({ + count: 3, + label: "b" + }), "b:3"); + }); + Mocha.test("tuple match with guard", () => { + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 51, characters 7-14", cmpPair([ + 0, + 0 + ]), "both-zero"); + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 52, characters 7-14", cmpPair([ + 0, + 7 + ]), "first-zero"); + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 53, characters 7-14", cmpPair([ + 7, + 0 + ]), "second-zero"); + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 54, characters 7-14", cmpPair([ + 5, + 5 + ]), "equal"); + Test_utils.eq("File \"pattern_match_record_tuple_test.res\", line 55, characters 7-14", cmpPair([ + 5, + 6 + ]), "distinct"); + }); +}); + +export { + quadrant, + bump, + cmpPair, +} +/* Not a pure module */ diff --git a/tests/tests/src/pattern_match_record_tuple_test.res b/tests/tests/src/pattern_match_record_tuple_test.res new file mode 100644 index 00000000000..729aabad225 --- /dev/null +++ b/tests/tests/src/pattern_match_record_tuple_test.res @@ -0,0 +1,57 @@ +open Mocha +open Test_utils + +/* Exercises `divide_record` / `divide_tuple` (and guards) in + compiler/ml/matching.ml: field projection, mutable fields, nested + constant tests inside record/tuple columns, and `when` guards. */ + +type point = {x: int, y: int} + +let quadrant = p => + switch p { + | {x: 0, y: 0} => "origin" + | {x: 0, y: _} => "y-axis" + | {x: _, y: 0} => "x-axis" + | {x, y} if x > 0 && y > 0 => "q1" + | _ => "other" + } + +type counter = {mutable count: int, label: string} + +let bump = c => + switch c { + | {count: 0, label} => "start-" ++ label + | {count, label} => label ++ ":" ++ string_of_int(count) + } + +let cmpPair = t => + switch t { + | (0, 0) => "both-zero" + | (0, _) => "first-zero" + | (_, 0) => "second-zero" + | (a, b) if a == b => "equal" + | _ => "distinct" + } + +describe(__MODULE__, () => { + test("record field tests + guard", () => { + eq(__LOC__, quadrant({x: 0, y: 0}), "origin") + eq(__LOC__, quadrant({x: 0, y: 4}), "y-axis") + eq(__LOC__, quadrant({x: 4, y: 0}), "x-axis") + eq(__LOC__, quadrant({x: 1, y: 1}), "q1") + eq(__LOC__, quadrant({x: -1, y: 2}), "other") + }) + + test("mutable record field match", () => { + eq(__LOC__, bump({count: 0, label: "a"}), "start-a") + eq(__LOC__, bump({count: 3, label: "b"}), "b:3") + }) + + test("tuple match with guard", () => { + eq(__LOC__, cmpPair((0, 0)), "both-zero") + eq(__LOC__, cmpPair((0, 7)), "first-zero") + eq(__LOC__, cmpPair((7, 0)), "second-zero") + eq(__LOC__, cmpPair((5, 5)), "equal") + eq(__LOC__, cmpPair((5, 6)), "distinct") + }) +})