Skip to content

Speculative @unboxed classification leaks orphan shared records #178

Description

@jagguji

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions