Skip to content

Fix #178: recompute usesJsFn after the reachability sweep (no orphan JsFn.res) - #197

Open
jagguji wants to merge 2 commits into
mainfrom
agent/178-jsfn-orphan
Open

Fix #178: recompute usesJsFn after the reachability sweep (no orphan JsFn.res)#197
jagguji wants to merge 2 commits into
mainfrom
agent/178-jsfn-orphan

Conversation

@jagguji

@jagguji jagguji commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

#178's primary defect — speculative @unboxed classification stranding orphan shared records — was already fixed by the reachability sweep shipped in #191 (which is exactly the mechanism #178's own comment proposed). Its repro no longer leaks: {left} | {right} now becomes an opaque Leaked.t module with both records referenced via from* constructors.

This PR closes the one residual gap #178 explicitly called out: the usesJsFn / JsFn.res orphan.

The gap

A bare Function maps to JsFn.t and sets shared.usesJsFn = true. JsFn.t is a hand-authored raw node, not a keyed registry entry, so the key-based sweep can't see it. When the type owning that Function field is swept as an orphan, usesJsFn stayed set and cli.mjs still emitted an unreferenced JsFn.res.

Reproduced on main:

interface Box<T> { cb: Function; v: T }
export declare function makeBox<T>(): Box<T>;   // return-only T → return flagged
export declare function ping(): void;
[bindgen] wrote 0 shared type module(s) (0 unique types)   ← sweep dropped the orphan Box record
[bindgen] wrote JsFn.res (bare-Function opaque module)      ← but JsFn.res emitted, referenced by NOTHING

The fix

After the sweep drops unreachable entries, recompute usesJsFn from the survivors (roots + remaining entries): scan for a JsFn.t raw node and clear the flag when none remain. A legitimately-referenced JsFn.t still keeps its JsFn.res.

Validation

  • New fixture speculative-jsfn-orphan asserts the complete emitted file set (only DemoBindings.res — no JsFn.res, no orphan *Types.res), which is exactly what the issue asked for.
  • npm test green; all 118 goldens compile; npm run bench — all 10 packages identical (surgical fix).
  • Verified the legit case (register(cb: Function)) still emits and references JsFn.res.

Also confirmed the other item from #178's comment — @set_index value types — is already covered (their refs go into entry.deps, which the sweep walks).

Fixes #178

🤖 Generated with Claude Code

…JsFn.res)

The #191 reachability sweep already drops orphan records stranded by a discarded
speculative build. But a bare `Function` sets `shared.usesJsFn` while such a build
is in progress, and `JsFn.t` is a hand-authored raw node (not a keyed registry
entry), so the key-based sweep can't see it: dropping the owner record left
`usesJsFn` set and emitted an unreferenced `JsFn.res`.

Reproduced: `makeBox<T>(): Box<T>` flags its return-only generic, the `Box` record
(with `cb: Function`) is swept, but `JsFn.res` was still written referenced by
nothing. Now `usesJsFn` is recomputed from the survivors (roots + remaining
entries) after the sweep, so the emitted file set stays orphan-free. A legitimately
referenced `JsFn.t` still keeps its `JsFn.res`.

Fixture `speculative-jsfn-orphan` asserts the complete emitted file set (only
DemoBindings.res — no JsFn.res, no orphan *Types.res). All 118 goldens compile;
benchmark unchanged.

Fixes #178
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jagguji
jagguji deployed to benchmark August 18, 2026 09:59 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown

Benchmark: ✅ PASS

Package Compile Diff vs baseline usable review broken Verdict
@juspay/blend-design-system@0.0.36 identical 102 5 0 ✅ PASS
@juspay/blend-design-system@0.0.37-beta.8 identical 215 7 0 ✅ PASS
@juspay/blend-design-system@0.0.37 identical 219 7 0 ✅ PASS
react-day-picker@10.0.1 identical 19 7 0 ✅ PASS
react-tooltip@6.0.7 identical 1 0 0 ✅ PASS
react-markdown@10.1.0 identical 0 2 0 ✅ PASS
@smastrom/react-rating@1.5.0 identical 1 0 0 ✅ PASS
clsx@2.1.1 identical 0 0 0 ✅ PASS
hono@4.12.25 identical 0 0 0 ✅ PASS
@base-ui-components/react@1.0.0-rc.0 identical 174 21 0 ✅ PASS

@pkg-pr-new

pkg-pr-new Bot commented Aug 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@juspay/rescript-bindgen@197

commit: 85c4d01

…ivor scan

The post-sweep usesJsFn recompute scanned roots + entries.flatMap(entryChildTypes),
but entryChildTypes's record branch omits e.indexValue — the `@set_index` value
type. A SURVIVING record whose only JsFn.t is a Function-typed index signature
(`[k: string]: Function` -> `@set_index …Set: (rec, string, JsFn.t)`) was therefore
not scanned, so usesJsFn was wrongly cleared and JsFn.res dropped, leaving the
@set_index external dangling — uncompilable. (Same class as the #195 setter/static
gap: the survivor surface was narrower than what the emitter produces.)

Surgical fix: add each entry's indexValue to the survivor set (kept out of
entryChildTypes, which feeds deps/home/SCC and would move the bench). Fixture
jsfn-function-index-reachable guards it — JsFn.res stays in the emitted file set
and the output compiles.

All 119 goldens compile; benchmark unchanged.
@jagguji
jagguji deployed to benchmark August 18, 2026 13:16 — with GitHub Actions Active
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Speculative @unboxed classification leaks orphan shared records

1 participant