Repository navigation
Build the untagged dispatch as a plan before emitting it - #9045
Conversation
Why:
The dispatch of a switch on an untagged variant was decided in two
places, the literal side in `compile_switch` and the payload side in
`compile_untagged_cases`, each with part of the facts, while printing
JavaScript.
`Untagged_dispatch.plan` builds the dispatch as a tree of tests and
statements from the variant's facts and the match's arms, making today's
decisions in one place; `Lam_compile.compile_untagged_plan` prints it. ounit
tests pin plans as data. The output is unchanged except that a body of an
`if` is no longer compiled in a `switch` context.
Generated code changed (a loop left from an arm of an if):
Example:
@unboxed type t = TextItem(string) | CountItem(float)
for i in 0 to Array.length(values) - 1 {
switch values->Array.getUnsafe(i) {
| TextItem(text) => seen->Array.push(text)
| CountItem(_) => break
}
}
Before (JS):
loop_0: for (let i = 0, i_finish = values.length; i < i_finish; ++i) {
...
} else {
break loop_0;
}
}
After (JS):
for (let i = 0, i_finish = values.length; i < i_finish; ++i) {
...
} else {
break;
}
}
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t
Signed-off-by: Christoph Knittel <ck@cca.io>
Changelog🏠 Internal
Semantic Diff2 commits · Full page Build the untagged dispatch as a plan before emitting itfbfe930 · cknitt + Claude · Oct 25, 2026, 20:21 UTC
Generated code changed (a loop left from an arm of an if): @unboxed type t = TextItem(string) | CountItem(float)
for i in 0 to Array.length(values) - 1 {
switch values->Array.getUnsafe(i) {
| TextItem(text) => seen->Array.push(text)
| CountItem(_) => break
}
}Before (JS): loop_0: for (let i = 0, i_finish = values.length; i < i_finish; ++i) {
...
} else {
break loop_0;
}
}After (JS): for (let i = 0, i_finish = values.length; i < i_finish; ++i) {
...
} else {
break;
}
}✨ Compile the bodies of an if in the order they are printedd764e3d · cknitt + Claude · Oct 26, 2026, 06:09 UTC · new
An Generated code changed (loop labels in literal a loop needing a label in each of two literal arms printed as `if`sBefore (JS): the label of the second arm is |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #9045 +/- ##
==========================================
+ Coverage 81.33% 81.36% +0.02%
==========================================
Files 456 458 +2
Lines 64273 64366 +93
==========================================
+ Hits 52275 52369 +94
+ Misses 11998 11997 -1
🚀 New features to boost your workflow:
|
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
|
Developer playground preview: https://rescript-lang.github.io/rescript/dev-playground/?version=pr-9045 |
|
@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. |
|
Codex Review: Didn't find any major issues. 👍 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". |
Why:
The `if` chain of the payload dispatch compiled its last arm first, so
loop labels in its arms were numbered bottom to top. A dispatch on one
`typeof` kind fell back to a `switch` for an empty case, with bodies
compiled outside a `switch` context.
An `if` compiles its `then` branch before its `else` branch, so labels are
numbered top to bottom, as master numbered them in the class, array and
object chain; arms of the literal `if`s are now numbered in that order too.
A dispatch on one `typeof` kind is always an `if`.
Generated code changed (loop labels in literal `if` arms):
Example:
a loop needing a label in each of two literal arms printed as `if`s
Before (JS): the label of the second arm is `loop_0`, of the first `loop_1`
After (JS): the label of the first arm is `loop_0`, of the second `loop_1`
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t
Signed-off-by: Christoph Knittel <ck@cca.io>
|
@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". |
The dispatch of a
switchon an untagged variant was decided in two places while printing JavaScript: the literal side incompile_switch, and the payload side incompile_untagged_cases, each with only part of the facts. Constant folding inLambda.switchhad a third copy of the rules.This PR builds the dispatch as data first. The new module
compiler/ml/untagged_dispatch.mlturns the variant's facts and the match's arms into a tree of tests (x === lit,typeof x,instanceof,x !== null,&&,||,!) and statements (if,switch (x),switch (typeof x)). It makes exactly today's decisions, step by step.Lam_compile.compile_untagged_planprints the tree, andcompile_untagged_casesand the literal closure incompile_switchare gone.The output is byte-identical across the tests, the stdlib and gentype, with one deliberate exception. A body of an
ifis no longer compiled in aswitchcontext, so a loop left from such an arm loses a needless label (loop_0: … break loop_0;becomesbreak;). Bodies are compiled in the order they are printed, so loop labels in arms are numbered top to bottom; in the arms of the literalifs that reverses master's numbering, which no output in the tests shows.New tests:
ounit_untagged_dispatch_tests.mlpins plans as data, one per dispatch rule.untagged_loop_control_test.res(scanIf) covers thebreakfrom an arm of anif.The next PRs in the stack fold constants by evaluating the plan (#9046), and decide the literal cases and same-body arms with it (#9047).
🤖 Generated with Claude Code
https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t