Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 19 Pattern Matching Compilation Error: "the switch statement does not cover all possible input values"

Using the Brian Goetz article: https://www.infoq.com/articles/data-oriented-programming-java/

sealed interface Opt<T> { 
    record Some<T>(T value) implements Opt<T> { }
    record None<T>() implements Opt<T> { }
}

This compiles and runs as expected. The exhaustive pattern matching works:

Opt<String> optValue = doCalc(value);
switch (optValue) {
  case Opt.Some<String> some -> System.out.printf("got string: %s%n", some.value());
  case Opt.None<String> none -> System.out.printf("got none%n");
};

This variation where I use the new Record patterns preview feature, breaks the exhaustive pattern matching, where this won't compile without adding a default case statement:

Opt<String> optValue = doCalc(value);
switch (optValue) {
    case Opt.Some<String>(String v) -> System.out.printf("got string: %s%n", v);
    case Opt.None<String> none -> System.out.printf("got none%n");
};

With OpenJDK Runtime Environment (build 19-ea+32-2220), I get the compilation error: the switch statement does not cover all possible input values.

When I add a default case statement, and the program works, but I don't get exhaustive pattern matching.

If I remove the record pattern matching, the program works.

If I create a variation of this without generics, that uses sealed classes, exhaustive pattern matching, and record patterns, it works.

However, it seems the combination of record patterns, generics and exhaustive pattern matching does not work.

like image 963
clay Avatar asked Apr 29 '26 17:04

clay


1 Answers

JDK 19 has only Early-Access Builds available now.

I think I have found the important part for this question in the JEPS 427 documentation

The progress of Pattern Matching (Third Preview) could also be tracked from here.

According to the following JEPS example, I would expect this to work, so I think that you should wait first for the first JDK 19 build for general availability and try again. If it still not works then I think you should proceed and report it as a bug.

Some extra care is needed when a permitted direct subclass only implements a specific parameterization of a (generic) sealed superclass. For example:

sealed interface I<T> permits A, B {}
final class A<X> implements I<String> {}
final class B<Y> implements I<Y> {}

static int testGenericSealedExhaustive(I<Integer> i) {
    return switch (i) {
        // Exhaustive as no A case possible!
        case B<Integer> bi -> 42;
    }
}

The only permitted subclasses of I are A and B, but the compiler can detect that the switch block need only cover the class B to be exhaustive since the selector expression is of type I.

I also think that the comment from @Kayaman is not the case here as it is explicitly stated in the documentation how this case is handled to be safe.

To defend against incompatible separate compilation, in this case of a switch over a sealed class where the switch block is exhaustive and there is no match-all case, the compiler automatically adds a default label whose code throws an IncompatibleClassChangeError. This label will only be reached if the sealed interface is changed and the switch code is not recompiled. In effect, the compiler hardens your code for you.

like image 162
Panagiotis Bougioukos Avatar answered May 01 '26 06:05

Panagiotis Bougioukos