You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Untagged variants: no warning for patterns shadowed by a literal case, and a spurious non-exhaustive warning for bool payloads #8806
An untagged variant may have a literal case whose runtime value a payload case can also hold (#8632, #8777):
@unboxedtypet= | @as("a") A | S(string)
Matching tests declared literals first, so the pattern S("a") can never match. The value "a" always goes to A, or to a wildcard if there is no A arm. The type checker treats S("a") and A as different, which causes two problems.
1. No warning for patterns that can never match
Checked on master. None of these produce a warning, and each S(...) arm marked dead is dead in the generated JS:
letf1=x=>switchx { | A=>1 | S("a") =>2 | S(_) =>3 } // S("a") deadletf2=x=>switchx { | S("a") =>2 | A=>1 | S(_) =>3 } // dead even as first armletf3=x=>switchx { | S("a") =>2 | _=>3 } // compiles to `return 3`letf4=x=>switchx { | S("a" | "b") =>2 | _=>3 } // the "a" alternative is dead
The same applies when:
the literal comes from the constructor name (Primary | Color(string): Color("Primary") is dead);
an int literal overlaps a float payload (@as(1) One | F(float): F(1.0) is dead);
a nested untagged constant has the same value (@as("x") X | W(inner) where inner has @as("x") P: W(P) is dead).
In a refutable binding such as let l = (S("a") as v) => v, the pattern never matches at all, so every call with S("a") throws Match_failure.
Proposal: warn on a constructor pattern S(p) of an untagged variant when every value p can match is the runtime value of a literal case in the same variant. Use warning 11 (unused case) for a whole arm and warning 12 (redundant sub-pattern) for or-pattern alternatives. Suggested message: "this pattern can never match: "a" is the value of constructor A".
Notes:
The check is local to each pattern and doesn't depend on arm order, so it can run beside check_unused without touching the exhaustiveness algorithm.
Values must be compared the way they are at runtime, as in Fix constant folding of overlapping unboxed variant matches #8631: 1 and 1.0 are equal, bigint spellings differ (1_0n = 10n), ints use their 32-bit value, and a constructor without @as is represented by its own name.
Guards (S(s) if s == "a") are out of scope.
2. Spurious non-exhaustive warning for bool payloads
With #8777 a bool payload can partly overlap a literal:
This match covers every value, yet the compiler reports warning 8 with the counterexample Ans(true) and emits an unreachable Match_failure throw. The only way to silence it is to add the dead arm Ans(true) => ..., which point 1 would then flag. The two parts therefore need to land together, or the bool case ends up with no warning-free way to write the match.
Proposal: during the exhaustiveness check only, treat a literal-case pattern as also covering the payload values equal to it, e.g. check Yes as Yes | Ans(true). This is harder than point 1 because it changes parmatch.ml. For string and number payloads it rarely matters, since their domain is infinite, but for bool it does.
Activity