RFR: 8325215: Incorrect not exhaustive switch error

Jan Lahoda jlahoda at openjdk.org
Wed Feb 21 14:53:14 UTC 2024


Consider code like:

public class Test {

    sealed interface A permits T, U {}
    sealed interface B permits V, W {}
    static final class T implements A {}
    static final class U implements A {}
    static final class V implements B {}
    static final class W implements B {}
    final static record R(A a, B b) {}

    static int r(R r) {
        return switch (r) {
            case R(A a, V b) -> 1;
            case R(T a, W b) -> 2;
            case R(U a, W b) -> 3;
        };
    }
}


javac will correctly consider this to be exhaustive, as `R(T a, W b)` and `R(U a, W b)` can be merged into `R(A a, W b)`, and then `R(A a, V b)` and `R(A a, W b)` are merge into `R(A a, B b)` and then into `R`.

But, if we replace `R(T a, W b)` with a more general `R(T a, B b)`, which matches everything that `R(T a, W b)` does:

public class Test {

    sealed interface A permits T, U {}
    sealed interface B permits V, W {}
    static final class T implements A {}
    static final class U implements A {}
    static final class V implements B {}
    static final class W implements B {}
    final static record R(A a, B b) {}

    static int r(R r) {
        return switch (r) {
            case R(A a, V b) -> 1;
            case R(T a, B b) -> 2;
            case R(U a, W b) -> 3;
        };
    }
}


Leads to:

$ javac /tmp/Test.java
/tmp/Test.java:12: error: the switch expression does not cover all possible input values
        return switch (r) {
               ^
1 error


The issue is that the exhaustivity algorithm is no longer able to merge `R(T a, B b)` and `R(U a, W b)` into `R(A a, W b)`, as the second nested pattern is not the same.

The rules permit replacing patterns with more strict patterns, i.e. it is permitted to replace `R(T a, B b)` with `R(T a, W b)`, and hence proceed with the merging as in the original case. The trouble is that doing such conversions may lead to high computational complexity.

The proposal in this patch is to replace all binding patterns in all patterns with their transitive permitted subtypes, but only once, and only if the algorithm fails to determine the exhaustiveness using the standing approach. This may lead to a potentially significant performance penalty, but only in case where the algorithm would mark the switch exhaustive.

The main alternative to this proposal would be to allow the algorithm to see `B` and `W` as equivalent when attempting to merge  `R(T a, B b)` and `R(U a, W b)`. But, currently, this equivalence check is based on hash codes, which prevents doing the check using subtyping checks, and this hashing improved performance in "common" cases tremendously. So, I would like to attempt to keep that. But it is possible we might need to fall back on the subtyping check for equivalence instead of hashing if other solutions will prove to be unusable.

-------------

Commit messages:
 - Enhancing test.
 - 8325215: Incorrect not exhaustive switch error

Changes: https://git.openjdk.org/jdk/pull/17949/files
 Webrev: https://webrevs.openjdk.org/?repo=jdk&pr=17949&range=00
  Issue: https://bugs.openjdk.org/browse/JDK-8325215
  Stats: 130 lines in 2 files changed: 128 ins; 1 del; 1 mod
  Patch: https://git.openjdk.org/jdk/pull/17949.diff
  Fetch: git fetch https://git.openjdk.org/jdk.git pull/17949/head:pull/17949

PR: https://git.openjdk.org/jdk/pull/17949


More information about the compiler-dev mailing list