Repository navigation
Conversation
Semantic Diff1 commit · Full page Test primitive untagged cases before class checks24e4412 · Christoph Knittel with Claude Opus 5.5 · Oct 22, 2026
When a variant has class cases, the arms of primitive kinds (string, number, bigint, boolean, symbol) are now tested first, in an outer JS modified (a class case beside primitive cases) @instanceOf("Nope") type nope
@unboxed type t = N(nope) | S(string) | F(float) | A(array<int>)
-if (v instanceof Nope) {
- return "N";
-}
-if (Array.isArray(v)) {
- return "A";
-}
switch (typeof v) {
case "string" :
return v;
case "number" :
return "F";
+ default:
+ if (v instanceof Nope) {
+ return "N";
+ }
+ if (Array.isArray(v)) {
+ return "A";
+ }
}Review
|
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 24e441242e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
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.
JS modified (a class case beside primitive cases):
Example:
@instanceof("Nope") type nope
@unboxed type t = N(nope) | S(string) | F(float) | A(array<int>)
Before (js):
if (v instanceof Nope) {
return "N";
}
if (Array.isArray(v)) {
return "A";
}
switch (typeof v) {
case "string" :
return v;
case "number" :
return "F";
}
After (js):
switch (typeof v) {
case "string" :
return v;
case "number" :
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`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fc7LEuyEfynjaKCVEBPmTF
Signed-off-by: Christoph Knittel <ck@cca.io>
24e4412 to
4154702
Compare
Semantic Diff1 commit · Full page Test primitive untagged cases before class checks4154702 · Christoph Knittel with Claude Opus 5.5 · Oct 22, 2026
When a variant has class cases, the arms of primitive kinds (string, number, bigint, boolean, symbol) are now tested first, in an outer JS modified (a class case beside primitive cases) @instanceOf("Nope") type nope
@unboxed type t = N(nope) | S(string) | F(float) | A(array<int>)
-if (v instanceof Nope) {
- return "N";
-}
-if (Array.isArray(v)) {
- return "A";
-}
switch (typeof v) {
case "string" :
return v;
case "number" :
return "F";
+ default:
+ if (v instanceof Nope) {
+ return "N";
+ }
+ if (Array.isArray(v)) {
+ return "A";
+ }
}Review
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## not-undefined-inclusion #8967 +/- ##
========================================================
Coverage 81.22% 81.22%
========================================================
Files 457 457
Lines 63997 64016 +19
========================================================
+ Hits 51982 51998 +16
- Misses 12015 12018 +3
🚀 New features to boost your workflow:
|
Why:
With the primitive cases tested first, a class whose
`Symbol.hasInstance` accepts a primitive no longer wins over a case of
that primitive kind, while the spec still said `Symbol.hasInstance` is
trusted without qualification.
Rule 8 now says that `Symbol.hasInstance` is trusted for objects and
functions, and that a primitive whose kind is a case lands in that case
first. The values of an `@instanceOf` type are instances, so a primitive of
that type already contradicts its annotation.
Representation modified (a primitive accepted by Symbol.hasInstance):
Example:
%%raw(`globalThis.Even = class { static [Symbol.hasInstance](x) { return x % 2 === 0 } }`)
@instanceof("Even") type even
@unboxed type t = E(even) | N(float) | S(string)
Before (representation): 4 ↦ E
After (representation): 4 ↦ N
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fc7LEuyEfynjaKCVEBPmTF
Signed-off-by: Christoph Knittel <ck@cca.io>
Semantic Diff1 new commit (2 total) · Full page Trust Symbol.hasInstance only for objects and functions2e15e8c · Christoph Knittel with Claude Opus 5.5 · Oct 23, 2026
Rule 8 now says that Representation modified (a primitive accepted by Symbol.hasInstance) %%raw(`globalThis.Even = class { static [Symbol.hasInstance](x) { return x % 2 === 0 } }`)
@instanceOf("Even") type even
@unboxed type t = E(even) | N(float) | S(string)
-4 ↦ E
+4 ↦ N |
|
@codex review |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Stacked on #8966. Follow-up from the risk assessment of #8950 (section 4).
A
switchon an untagged variant ran its class checks before itstypeofswitch, so strings and numbers reachedv instanceof Ctoo. A class missing where the code runs, such as a user class, orIntl.Segmenteron an older engine, then threwReferenceError/TypeErrorfor every value, the primitive cases included.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). Its default holds the class checks and the remainingobject/functionarms:A class instance is never a primitive, so no value changes arm. Two details:
Array.isArraycannot throw.Symbol.hasInstance: a class whose hook accepts a primitive no longer wins over a case of that primitive kind. The spec now trustsSymbol.hasInstanceonly for objects and functions. The values of an@instanceOftype are instances, so such a primitive already contradicts its annotation.Cost: variants with class cases and object or function arms get a second
switch (typeof v), and a body shared by a primitive and an object arm is emitted twice, as inawaitUserinUntaggedVariants.mjs. The two-arm form is unchanged: it still checks the arm declared first.UntaggedVariants.mdrules 8 and 10 describe the new order, and the item is off the "still open" list.New tests in
untagged_runtime_annotations_test.res: a missing class beside string and number cases, and a default shared by a primitive and an object case.🤖 Generated with Claude Code
https://claude.ai/code/session_01Fc7LEuyEfynjaKCVEBPmTF