Summary
The unionNode candidate pass for an @unboxed variant calls memberOf, which can call classify and register records before runtime-bucket validation has succeeded. When a later arm makes buildable = false, those speculative records remain in the shared registry even though the final prop falls back to string.
Minimal reproduction
type JsxElement = { __brand: "element" }
type Leaked =
| { left: string }
| { right: number }
export declare const LeakProbe: (props: { value?: Leaked }) => JsxElement
Run:
node src/cli.mjs --dir <fixture> --out <out> --from demo --report --no-install
Current result:
// LeakProbe.res
type props = {
value?: string, // loose fallback
}
but generation also reports 2 unique types and writes an otherwise unreferenced SpeculativeUnboxedLeakTypes.res:
type speculativeUnboxedLeakValueConfigV1a2ot = {left: string}
type speculativeUnboxedLeakValueConfigV5p5js = {right: float}
Expected
A rejected speculative mapping must leave no registry or emission side effects. Either map Leaked faithfully or keep the fallback without emitting unused shared records/modules.
Root cause
memberOf is side-effectful for object arms. The if (buildable) candidate construction in unionNode is not wrapped in the registry snapshot/rollback used by other speculative builders, so failure on the duplicate object runtime bucket abandons already-registered records.
The same class of leak can also leave shared.usesJsFn set after a rejected candidate containing bare Function, causing an unused JsFn.res file.
Verified on main at 5afe64c and on PR #174 at 7b25189. A golden fixture should assert both the final prop and the complete emitted file set.
Summary
The
unionNodecandidate pass for an@unboxedvariant callsmemberOf, which can callclassifyand register records before runtime-bucket validation has succeeded. When a later arm makesbuildable = false, those speculative records remain in the shared registry even though the final prop falls back tostring.Minimal reproduction
Run:
Current result:
but generation also reports
2 unique typesand writes an otherwise unreferencedSpeculativeUnboxedLeakTypes.res:Expected
A rejected speculative mapping must leave no registry or emission side effects. Either map
Leakedfaithfully or keep the fallback without emitting unused shared records/modules.Root cause
memberOfis side-effectful for object arms. Theif (buildable)candidate construction inunionNodeis not wrapped in the registry snapshot/rollback used by other speculative builders, so failure on the duplicate object runtime bucket abandons already-registered records.The same class of leak can also leave
shared.usesJsFnset after a rejected candidate containing bareFunction, causing an unusedJsFn.resfile.Verified on
mainat5afe64cand on PR #174 at7b25189. A golden fixture should assert both the final prop and the complete emitted file set.