Skip to content

Add a post-emit dangling-reference guard (assert every emitted Module.type / JsFn.t resolves to a written declaration) #202

Description

@jagguji

Why

The "reachability sweep's root/child allowlist fell behind the emitter" bug has recurred four times, each shipping uncompilable output caught only by manual review or downstream validation:

  1. return-only-generic orphan (Return-only generic dead end strands an orphan record in the FUNCTION path (method path is fixed) #191)
  2. setter / static roots (Fix #191: reachability sweep drops return-only-generic orphan records #195)
  3. record indexValue (@set_index) in the usesJsFn survivor scan (Fix #178: recompute usesJsFn after the reachability sweep (no orphan JsFn.res) #197)
  4. --variant-props shadowed branch fields (Fix #178: recompute usesJsFn after the reachability sweep (no orphan JsFn.res) #197 review)

Each fix added one more entry to an allowlist that the next emitter change can outrun. This does not converge on its own — every one produced a dangling reference (Module.type or JsFn.t with no emitted declaration → ReScript compile error) or an orphan file.

Proposal

A single, allowlist-independent post-emit invariant check: after generation writes all .res files, scan each for Module.member references where Module is one of our generated modules (in the written set — stdlib/React skipped) plus bare JsFn.t, and assert each resolves to a declaration actually emitted in that module. Fail loudly (and/or fail the golden/bench suite) on any dangling ref.

That single guard would have caught all four bugs mechanically, regardless of which allowlist was incomplete — converting "the next reviewer finds it" into "CI finds it."

Rollout notes

  • Hard-fail in the test/golden suite (controlled output — must be clean).
  • Warn (non-fatal) at generation time for real packages, so a text-parse edge case can't break a user's build.
  • Acceptance bar: passes clean on all goldens + all benchmark packages (zero false positives on known-good output), and trips on a synthetic dangling ref.
  • The hard part is distinguishing type refs from value/external refs without false positives — validate against all existing output before making it hard-fail anywhere.

Suggested during the reviews of #195 and #197.

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