Semantic Diff

#8967 Test primitive untagged cases before class checks

1 commit

df024f4a0208fdfaca3ee8f0f4a5d629c41a9e92..pr-8967

4154702f0 · Christoph Knittel with Claude Opus 5.5 · Oct 22, 2026

Test primitive untagged cases before class checks

Why

A switch on an untagged variant ran its class checks before the typeof switch, so strings and numbers reached v instanceof C too. A class missing where the code runs, such as a user class or a newer Intl class, then threw for every value, even for the primitive cases.

When a variant has class cases, the arms of primitive kinds (string, number, bigint, boolean, symbol) are now tested first, in an outer switch (typeof v) whose default holds the class checks and the remaining object/function arms. A class instance is never a primitive, so no value changes arm. Primitive and other arms no longer share a body through fall-through, since they sit in different switches. Variants with only an array case keep the old form, since Array.isArray cannot throw.

modifiedJS a class case beside primitive cases

@instanceOf("Nope") type nope@unboxed type t = N(nope) | S(string) | F(float) | A(array<int>)
js
Before
After
if (v instanceof Nope) { return "N";}if (Array.isArray(v)) { return "A";} switch (typeof v) {switch (typeof v) { case "string" : case "string" : return v; return v; case "number" : case "number" : return "F"; return "F"; default: if (v instanceof Nope) { return "N"; } if (Array.isArray(v)) { return "A"; }}}

Review

  • What does it cost? A second switch (typeof v) when there are object or function arms too, and a body shared by a primitive and an object arm is emitted twice (awaitUser in UntaggedVariants.mjs).