Semantic Diff

#9010 Keep cases handled by a default arm out of the arms a switch names

1 commit

d8db3c6ebb93d50ae095b58c670b79cd388988b4..pr-9010

b35f4859e · cknitt + Claude · Oct 24, 2026, 06:17 UTC

Keep cases handled by a default arm out of the arms a switch names

Why

A switch on an untagged variant with a _ arm let the cases it does not name reach the arms it names: a Date matched the object arm, since only arrays were guarded, and an @as("lit") literal matched the string arm, since the literal test was skipped when the switch names no literal (#8995).

The switch (typeof x) is preceded by a guard sending every instance case without an arm to the default, as it already did for arrays. The literal test is skipped only when no literal of the variant would pass the check of a payload arm. Rules 7 and 9 of the spec state both.

modifiedJS an instance handled by the default

@unboxed type t = D(Date.t) | R(RegExp.t) | O({x: int}) | S(string) | I(int) | F(int => int)let f = v => switch v { | O(_) => "O" | S(_) => "S" | _ => "other" }
js
Before
After
if (v instanceof RegExp || v instanceof Date) { return "other";}switch (typeof v) {switch (typeof v) { case "object" : case "object" : return "O"; return "O";

modifiedJS a literal handled by the default

@unboxed type t = | @as("lit") Lit | S(string) | I(int) | B(bool) | O({x: int}) | F(int => int)let f = v => switch v { | S(_) => "S" | O(_) => "O" | _ => "other" }
js
Before
After
if (v === "lit") { return "other";}switch (typeof v) {switch (typeof v) { case "string" : case "string" : return "S"; return "S";