Skip to content

[Bug]: Guarded multi-pattern cases lose their pattern hierarchy #6808

Description

@martinfrancois

Describe the bug

In this label, First _ and Second _ are two alternative patterns:

case First _, Second _ when enabled -> 1;

At run time, First _ matches a First value and Second _ matches a Second value. The expression after when is a separate boolean guard. Spoon must therefore build two case-pattern nodes and one guard.

ECJ stores both patterns inside one EitherOrMultiPattern node, then wraps that node and the enabled condition in a GuardedPattern.

For this label, the correct alternative count is two. Spoon uses that count while it receives ECJ's child nodes: the first two pattern children belong to the comma-separated case label, and the following expression is the guard.

Spoon inspected EitherOrMultiPattern only when that node was the direct case expression. The surrounding GuardedPattern hid the count, so Spoon expected one case pattern instead of two. It attached First _ correctly, then mistook the CtTypePattern for Second _ for the guard. Spoon rejected that parent because a CtTypePattern belongs under a CtCasePattern, not directly under a CtCase guard.

A compatible fix must unwrap the ECJ guard only while counting alternatives. Each alternative must receive its own CtCasePattern, and the real guard must stay attached to the CtCase.

Source code you are trying to analyze/transform

Reduced form:

sealed interface Value permits First, Second {}
record First(int value) implements Value {}
record Second(int value) implements Value {}

class GuardedPatterns {
    int read(Value value, boolean enabled) {
        return switch (value) {
            case First _, Second _ when enabled -> 1;
            default -> 0;
        };
    }
}

The issue was found in Unnamed.java from openjdk/jdk@be40b6bcdab37368ea3c769e575b36e290d0c6a1.

Source code for your Spoon processing

Launcher launcher = new Launcher();
launcher.getEnvironment().setComplianceLevel(25);
launcher.addInputResource("Unnamed.java");
launcher.buildModel();

Actual output

spoon.SpoonException: type pattern can only be used in an instanceof binary
operator or a case pattern (was class spoon.support.reflect.code.CtCaseImpl)
    at CtTypePatternImpl.setParent(...)
    at CtCaseImpl.setGuard(...)
    at ParentExiter.visitCtCase(...)

Expected output

Spoon builds this hierarchy:

  • one CtCasePattern containing the CtTypePattern for First _;
  • one CtCasePattern containing the CtTypePattern for Second _;
  • the expression enabled as the CtCase guard.

Spoon Version

Reproduced on Spoon master at 06b187819380700384762416d15db2cc4f112175.

JVM Version

OpenJDK 25.0.3

What operating system are you using?

Linux x86-64

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions