Fix static using leakage and qualify nested enum static usings (#148) - #154
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #148.
The reported bug
StaticUsingEnumNameslived onJavaConversionOptions, 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:
UsingsHelperprefixes each name with the current file's namespace, so an enum fromcom.foosurfaced in acom.barfile asusing static Com.Bar.Color;— a reference to a type that does not exist there.Rather than a
try/finallyaroundConvertText, this moves the accumulator toConversionContext, which is already constructed once perConvertTextcall 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:
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
ConversionStatemoved off options. It was per-run state written on a caller-owned config object — the same defect shape asStaticUsingEnumNames. State changes now go through the context, which records the value and raises the existingStateChangedevent, so event ordering is unchanged. An audit of the rest ofcontext.Options.*found only legitimate config reads.GetUsings' redundantJavaConversionOptionsparameter removed. Its only caller passed the same instance already reachable viacontext.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:
JavaConversionOptions.StaticUsingEnumNamesJavaConversionOptions.ConversionStateStateChangedevent.UsingsHelper.GetUsings(…, JavaConversionOptions?, …)context.Options.Verification
NestedEnumStaticUsing.javais added to the existing integration harness, which Roslyn-compiles and executes the generated C# — it fails to emit before this change.NestedEnumStaticUsingintegrationGUI and CLI both build clean against the removed properties.
🤖 Generated with Claude Code