Test a mutable field again after a guard that may have changed it
Why
A guard can change the value being matched. The cases after it read the mutable field again, but the match compiler kept what its tests had found on the old value, so a switch could take a case that no longer matches and bind a value of the wrong type (#9129, upstream ocaml/ocaml#7241). Upstream fixed it in OCaml 5.3; this takes over its erasure of contexts and replaces its per-position totality with a simpler criterion.
modifiedMatching contexts at a mutable read
type t = {a: bool, mutable b: (option<int>, option<int>)}let f = r => switch r { | {a: true, b: (None, _)} => 1 | {a: true, b: (Some(_), _)} if swap(r) => 0 | {a: false} => 3 | {b: (_, None)} => 4 | {b: (Some(x), Some(_))} => x }
matrix
Before context of the exit taken when the guard fails, Matching.compile_match
After the column of the mutable read is forgotten
{a: true, b: (Some(_), _)}{a: true, b: _}
js
Before
After
let match$1 = r.b;let match$1 = r.b;if (match$1[1] !== undefined) { return match$1[0];} else {let x = match$1[0];if (match$1[1] === undefined) { return 4; return 4;}} if (x !== undefined) { return x;}throw { RE_EXN_ID: "Match_failure", …};
modifiedMatching exhaustiveness after a guard
type flagged = {d: bool, mutable c: option<int>}let flagged = r => switch r { | {c: None} => 0 | {d: true} if clear(r) => 1 | {c: Some(x)} => x }
text
Before Matching.check_partial
After Matching.check_partial
Partial when one case both has a guard and reads a mutable fieldPartial when a case has a guard and a case reads a mutable field
js
Before
After
if (r.d && (r.c = undefined, false)) {if (r.d && (r.c = undefined, false)) { return 1; return 1;} else { return r.c; }} let x = r.c;if (x !== undefined) { return x;}throw { RE_EXN_ID: "Match_failure", …};
Review
- Why not upstream's per-position totality (a mut flag on each argument and a First/Following tempo)? Without lazy patterns, and on one thread, only a guard can change the value during a match, so marking the whole match Partial when it has a guard and reads a mutable field is enough. It needs no new state in the compiler; the cost is a failure branch at immutable positions of such matches too. No committed output in the stdlib, Belt or tests changes.
- Why are array elements not counted in check_partial? A Total match on arrays has a case matching any array, and the cases after a guard fall through to it; the contexts are still erased after an element is read.
- Why are dict patterns left alone? A dict entry is read once into a variable and the cases after the guard keep using it, so they see one consistent value.