Fix four Java <= 21 syntax conversion gaps - #172
Merged
Conversation
Local records (Java 16) previously threw InvalidOperationException and aborted conversion of the whole file, since LocalRecordDeclarationStmt was missing from the statement visitor registry. C# has no local record declaration -- the compiler parses `record R(int a);` in a method body as a local function -- so the record is hoisted to the enclosing type, reusing the queue that already lifts anonymous class bodies. Method references were emitted as invocations, so `String::length` became `string.Length()`, calling the method instead of passing it as a delegate. They now convert to method groups. Constructor references have no C# equivalent and become a lambda, preserving any generic arguments. Static imports were converted to plain namespace usings with the declaring type stripped off, so the imported members did not resolve. They now emit `using static`, keeping the type. Instance initializer blocks were emitted as a static constructor, which runs once per type rather than once per instance and collided with any real static initializer. They are now prepended to each constructor body, matching Java's semantics, and skipped for constructors chaining to `this(...)` since the initializer already ran there. A class with an initializer but no declared constructor gets one synthesized. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The four new resources were registered in GeneralSuccessfulConversionTest, which only asserts that conversion returned non-null without warnings. That passes even when the emitted C# is wrong, so it would not catch a regression in any of the fixes it was meant to cover. Three of them are rewritten as example.Program with an `/// - output:` expectation and moved to FullIntegrationTests, which compiles the generated C# with Roslyn, invokes Main, and asserts on captured stdout. Verified by mutation: breaking the `this(...)` chaining guard now fails on the program's actual output. The resources avoid types the harness cannot resolve, since it references only System.Private.CoreLib, System.Console, System.Linq and System.Runtime. Notably java.lang.Math maps to Java.Lang.Math, which does not exist in the BCL, so StaticImports imports from types declared in the file instead. Java8MethodReferences stays conversion-only: java.util.function has no BCL delegate mapping, and C# cannot assign a method group to an interface, so the output cannot be compiled and run. It instead gets explicit assertions on the converted syntax for all four method reference kinds, including that none of them became an invocation. 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 four Java syntax constructs at or below Java 21 that either failed to convert or produced code with different runtime behavior than the Java source. Labeled
break/continueis tracked separately.Local records (Java 16)
Previously threw
InvalidOperationExceptionand aborted conversion of the entire file, sinceLocalRecordDeclarationStmtwas missing from the statement visitor registry.C# has no local record declaration — the compiler parses
record R(int a);in a method body as a local function, which I confirmed against the compiler directly (error CS8112/CS0246). The record is therefore hoisted to the enclosing type, reusing the queue that already lifts anonymous class bodies. This widens the record's scope, which is harmless unless the enclosing type already declares a member of the same name.Method references
Emitted as invocations rather than method groups, so
String::lengthbecamestring.Length()— calling the method at the point of reference instead of passing it as a delegate.Constructor references have no C# equivalent, so
ArrayList<String>::newbecomes() => new List<string>(), preserving generic arguments.Static imports
import.isStatic()was never checked, soimport static java.lang.Math.maxbecame a plain namespace using with the declaring type stripped off, leaving the imported members unresolvable.An on-demand static import (
import static Foo.*) already ends in the type, so it is used as-is. Non-static imports are unchanged.Instance initializer blocks
Emitted as a static constructor, which runs once per type rather than once per instance, and collided with any real static initializer (two
static Foo()declarations).They are now prepended to each constructor body, matching Java's semantics:
this(...), since the initializer already ran in the constructor being chained to.Testing
ConvertJava21GapTests.cscovering each fix, including the negative cases (non-static imports stay namespace usings; static initializers stay static constructors; chained constructors do not re-run the initializer).IntegrationTests, all converting warning-free.Notes
Two pre-existing issues were observed while testing and deliberately left out of scope: record accessor methods (
rect.width()) convert to a call on a property, and methods inside a record are emitted with an invalidvirtualmodifier. Both affect member-level records equally and are unrelated to these fixes.🤖 Generated with Claude Code