Skip to content

Scope generated opens to the tagged type's namespace block - #565

Merged
Smaug123 merged 2 commits into
mainfrom
fix-opens-leak
Jul 14, 2026
Merged

Scope generated opens to the tagged type's namespace block#565
Smaug123 merged 2 commits into
mainfrom
fix-opens-leak

Conversation

@Smaug123

Copy link
Copy Markdown
Owner

Stacked on #564.

Problem

All six generators that emit opens collected every open from the whole input file (via Whippet's AstHelper.extractOpens — which, note, shadows the identically-named and therefore dead local helper in this repo's AstHelper) and emitted them into every generated namespace. A relative open that resolves in one namespace block need not resolve in another, so generated code for a type in a second namespace block could fail to compile.

Fix

AstHelper.extractOpensForNamespace collects opens only from namespace blocks whose name matches the tagged type's namespace. Two blocks with the same name share a resolution context, so unioning them is sound. All six generators (ArgParser, Cata, HttpClient, InterfaceMock, CapturingInterfaceMock, JsonSerialize) now call it per namespace. The dead local extractOpens is deleted.

Verification

  • ConsumePlugin/OpensLeakRegression.fs is a compile-time regression test: a bait namespace block contains a relative open Sub which cannot resolve in the victim namespace. I ran the pre-fix plugin against it manually: it emits open Sub into namespace ConsumePlugin.OpensLeakVictim, which does not compile. The fixed plugin emits only the victim block's own opens.
  • Regenerating all checked-in generated files produces no diff: no existing input file relied on the leak.
  • Full test suite passes (896 tests).

🤖 Generated with Claude Code

Base automatically changed from argparser-bug-characterization to main July 14, 2026 06:55
Every generator previously emitted every `open` from the whole input file
into every generated namespace (via Whippet's AstHelper.extractOpens,
which shadows the identically-named dead local helper). A relative open
that is valid in one namespace block is not necessarily valid in another,
so generated code for a type in a different block could fail to compile.

extractOpensForNamespace collects opens only from the namespace blocks
whose name is a segment-wise prefix of the tagged type's namespace:
prefix rather than exact matching because CataGenerator's type discovery
descends into nested modules (a type in module M of namespace N is
reported as N.M, and block N's opens are lexically in scope around it).
Same-named blocks share a resolution context, so unioning them is sound.
The dead local extractOpens is replaced.

ConsumePlugin/OpensLeakRegression.fs is a compile-time regression test:
its bait block contains a relative `open RelativeOpenBait` which does
not resolve in the victim namespace, so the pre-fix generator produces
uncompilable output for it (verified against the pre-fix plugin).

No checked-in generated code changes: no existing input relied on the leak.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Smaug123
Smaug123 enabled auto-merge (squash) July 14, 2026 21:17
@Smaug123
Smaug123 merged commit aed0824 into main Jul 14, 2026
20 checks passed
@Smaug123
Smaug123 deleted the fix-opens-leak branch July 14, 2026 21:20
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.

1 participant