Skip to content

Fix static using leakage and qualify nested enum static usings (#148) - #154

Merged
paulirwin merged 2 commits into
masterfrom
fix/148-static-using-leak
Aug 14, 2026
Merged

paulirwin merged 2 commits into
masterfrom
fix/148-static-using-leak

Conversation

@paulirwin

Copy link
Copy Markdown
Owner

Fixes #148.

The reported bug

StaticUsingEnumNames lived on JavaConversionOptions, which callers reuse across every file in a conversion run, and was never cleared. Each successive file emitted the static usings of every enum seen before it.

The leak was also cross-namespace: UsingsHelper prefixes each name with the current file's namespace, so an enum from com.foo surfaced in a com.bar file as using static Com.Bar.Color; — a reference to a type that does not exist there.

Rather than a try/finally around ConvertText, this moves the accumulator to ConversionContext, which is already constructed once per ConvertText call and already holds the other per-file state (PendingAnonymousTypes, UsedAnonymousTypeNames). That scopes the state correctly by construction instead of relying on cleanup, and is safe when one options instance is shared across concurrent conversions.

A second, more serious bug found while fixing it

Nested enums emitted a static using that named only the enum, omitting its declaring type. This needed no multi-file setup — a single file was enough:

package com.example;
public class Holder { public enum Inner { A, B } }
using static Com.Example.Inner;   // the C# type is Com.Example.Holder.Inner

That using does not resolve, so any file containing a nested enum generated C# that failed to compile. The name is now built by walking the AST's enclosing type declarations, which also handles arbitrary nesting depth (Outer.Middle.Deep) and enums declared in interfaces.

Related cleanup

  • ConversionState moved off options. It was per-run state written on a caller-owned config object — the same defect shape as StaticUsingEnumNames. State changes now go through the context, which records the value and raises the existing StateChanged event, so event ordering is unchanged. An audit of the rest of context.Options.* found only legitimate config reads.
  • GetUsings' redundant JavaConversionOptions parameter removed. Its only caller passed the same instance already reachable via context.Options, so the two could not disagree; reading it from the context also lets the internal null-guards go away.

Breaking changes

Public API removals, intended for the upcoming major version bump:

Removed Notes
JavaConversionOptions.StaticUsingEnumNames Only ever written internally; not usable as an input.
JavaConversionOptions.ConversionState Written but never read anywhere in the repo; the GUI tracks state via the StateChanged event.
UsingsHelper.GetUsings(…, JavaConversionOptions?, …) Parameter dropped; read from context.Options.

Verification

NestedEnumStaticUsing.java is added to the existing integration harness, which Roslyn-compiles and executes the generated C# — it fails to emit before this change.

Before After
Cross-file leak tests (2) fail pass
Nested-enum tests (4) fail pass
NestedEnumStaticUsing integration fails to compile pass
Full suite 256 pass, 0 warnings

GUI and CLI both build clean against the removed properties.

🤖 Generated with Claude Code

paulirwin and others added 2 commits August 14, 2026 13:39
Static usings discovered while visiting an enum were accumulated on
JavaConversionOptions, which callers reuse across every file in a
conversion run. The list was never cleared, so each successive file
emitted the static usings of every enum seen before it.

The leak was also cross-namespace: UsingsHelper prefixes each name with
the *current* file's namespace, so an enum from com.foo would surface in
a com.bar file as "using static Com.Bar.Color;" - a reference to a type
that does not exist there.

Move the accumulator to ConversionContext, which is already constructed
once per ConvertText call and already holds the other per-file state
(PendingAnonymousTypes, UsedAnonymousTypeNames). This scopes the state
correctly by construction rather than relying on cleanup, and is safe
when one options instance is shared across concurrent conversions.

Use a HashSet since the names are a set; the existing Distinct in
GetUsings was previously masking duplicate accumulation.

Removes the public JavaConversionOptions.StaticUsingEnumNames property.
It was only ever written to internally and was not usable as an input,
so this is a breaking change in name only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nested enums produced a static using that named only the enum, omitting
its declaring type: an enum "Inner" inside class "Holder" emitted
"using static Com.Example.Inner;" when the C# type is
Com.Example.Holder.Inner. That using does not resolve, so any file with
a nested enum - a common Java shape - generated C# that failed to
compile. Build the name by walking the AST's enclosing type
declarations, so arbitrary nesting depth and enums in interfaces are
handled too.

Also move ConversionState off JavaConversionOptions and onto
ConversionContext. It was per-run state written on a caller-owned
config object, the same defect as StaticUsingEnumNames. State changes
now go through the context, which records the value and raises the
existing StateChanged event, so event ordering is unchanged. The public
property was written but never read anywhere in the repo; the GUI tracks
state via the event.

Drop GetUsings' redundant JavaConversionOptions parameter. Its only
caller passed the same instance already reachable via context.Options,
so the two could not disagree; reading it from the context also lets the
internal null-guards go away.

Adds an executable integration case (NestedEnumStaticUsing.java) that
compiles and runs the generated C#, which fails before this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@paulirwin
paulirwin merged commit 93ca773 into master Aug 14, 2026
5 checks passed
@paulirwin
paulirwin deleted the fix/148-static-using-leak branch August 14, 2026 19:52
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.

JavaConversionOptions.StaticUsingEnumNames Reused Across Multiple Files

1 participant