Skip to content

Support Java 21 switch pattern matching (#68) - #168

Merged
paulirwin merged 1 commit into
masterfrom
issue/68-switch-pattern-matching
Aug 16, 2026
Merged

Support Java 21 switch pattern matching (#68)#168
paulirwin merged 1 commit into
masterfrom
issue/68-switch-pattern-matching

Conversation

@paulirwin

Copy link
Copy Markdown
Owner

Fixes #68.

Record pattern support (#67) already covered most of JEP 441 — pattern labels, when guards, and type patterns in both switch expressions and switch statements were all prerequisites for putting record patterns in switch positions. This PR fixes the one construct that slipped through and adds end-to-end coverage for the JEP as a whole.

The bug: case null, default silently dropped its default

javaparser models case null, default as a single null label carrying a separate isDefault() flag:

entry type=EXPRESSION labels=1 isDefault=true
   label class=NullLiteralExpr text=null

Both switch visitors keyed off the label list alone, so the default half was invisible:

case Integer i -> "int " + i;
case null, default -> "fallback";     // Java: null AND everything else
int i => "int " + i,
null => "fallback"                     // C#: only null

The arm ended up matching only null, so a non-null value matching no other arm threw SwitchExpressionException where Java returned a value. The generated code still compiled — C# only warns (CS8509) about the now-inexhaustive switch — so this failed at runtime rather than at conversion or build time.

Both visitors now consult isDefault(). C#'s discard pattern and default: section already match null (verified both), so the combined form collapses cleanly onto them.

What was already working

Confirmed by running each construct through the converter and comparing against a JDK:

JEP 441 feature Status
Type patterns in switch ✅ Already worked (#67)
when guards ✅ Already worked (#67)
case null on its own ✅ Already worked
case null, default Fixed here
Exhaustive switch over sealed types ✅ Already worked

Testing

Java21SwitchPatternMatching.java is wired into FullIntegrationTests, which compiles the generated C#, executes it, and asserts on runtime output. It covers the null label, both combined case null, default forms (expression and statement), guards, exhaustive switching over a sealed hierarchy, and type patterns over unrelated types.

The expected output is the verbatim output of running the Java source under a JDK, so the test pins the conversion to Java's real behaviour.

Plus 5 unit tests in ConvertSwitchPatternTests. I verified the two covering case null, default fail against the unfixed visitors and pass with the fix, so they genuinely pin the bug rather than just passing.

Full suite: 335 passed, 0 failed (329 before this change).

Two pre-existing bugs found while writing the resource

Both reproduce on master without any of these changes, so they're left alone and worked around in the test resource. Happy to file issues:

  1. Record accessor casing. record Circle(int r) keeps the lowercase property r, but a call to c.r() converts to c.R() — so the generated code doesn't compile. The resource uses deconstruction instead.
  2. long literal suffix dropped. Object o = 9L converts to object o = 9, which boxes as int. In a type-pattern switch that means case Long l never matches and the value takes the Integer arm instead. The resource avoids Long patterns.

🤖 Generated with Claude Code

Record pattern support (#67) already covered most of JEP 441, since
pattern labels, `when` guards, and type patterns in both switch
expressions and switch statements were needed to place record patterns
in switch positions. This fixes the one construct it missed.

javaparser models `case null, default` as a single null label carrying
a separate isDefault flag, and both switch visitors keyed off the label
list alone. The default half was silently dropped, so the arm matched
only null. A non-null value matching no other arm then threw
SwitchExpressionException instead of taking the arm, having returned a
value under Java. The generated code compiled, so nothing surfaced this
until runtime.

Both visitors now consult isDefault(). C#'s discard pattern and default
section both already match null, so the combined form collapses onto
them.

Adds Java21SwitchPatternMatching.java to the executing integration
tests, covering the null label, both combined forms, guards, exhaustive
switching over a sealed hierarchy, and type patterns over unrelated
types. Its expected output is the verbatim output of the Java source
run under a JDK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@paulirwin
paulirwin marked this pull request as ready for review August 16, 2026 22:26
@paulirwin
paulirwin merged commit bbb78a3 into master Aug 16, 2026
5 checks passed
@paulirwin
paulirwin deleted the issue/68-switch-pattern-matching branch August 16, 2026 22:26
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.

Java 21 Switch Pattern Matching

1 participant