RFR: 8366968: Exhaustive switch expression rejected by for not covering all possible values [v4]

Aggelos Biboudis abimpoudis at openjdk.org
Wed Oct 22 10:45:04 UTC 2025


On Tue, 21 Oct 2025 11:28:57 GMT, Jan Lahoda <jlahoda at openjdk.org> wrote:

>> Consider case like (from the bug):
>> 
>> class Demo {
>> 
>>     sealed interface Base permits Special, Value {}
>> 
>>     non-sealed interface Value extends Base {}
>> 
>>     sealed interface Special extends Base permits SpecialValue {}
>> 
>>     non-sealed interface SpecialValue extends Value, Special {}
>> 
>>     static int demo(final Base base) {
>>         return switch (base) {
>>             case final Value value -> 0;
>>             // Uncommenting the following line will make javac accept this program
>>             //case final Special value -> throw new AssertionError();
>>         };
>> 
>>     }
>> 
>> }
>> 
>> 
>> This fails to compile:
>> 
>> /tmp/Demo.java:12: error: the switch expression does not cover all possible input values
>>         return switch (base) {
>>                ^
>> 1 error
>> 
>> 
>> Note there is no instance of `Special` that would not be an instance of `Value` as well. I.e. covering `Value` will catch all input.
>> 
>> Also, note that if `case Value` is replaced with `case SpecialValue`, javac also compile the code:
>> 
>>             case final Value value -> 0;
>> =>
>>             case final SpecialValue value -> 0;
>> 
>> $ ~/tools/jdk/jdk-25/bin/javac /tmp/Demo.java
>> $
>> 
>> 
>> Which shows the problem: replacing a type with a super type should not cause the switch to stop to be exhaustive (i.e. the switch is exhaustive for `SpecialValue`, but replacing it with its super type `Value`, the switch is no longer exhaustive for javac).
>> 
>> javac contains a piece of code that searches through subtypes to handle diamond class hierarchies like the one above. But, when it searches for subtypes, it does a search through permitted subtypes of the type. And since `Value` is not sealed, this search will not find `SpecialValue`, and javac won't see the switch to be exhaustive.
>> 
>> The proposal herein is to broaden the search, and consider all transitive permitted subtypes of the selector type, and filter subtypes of the current type from this set (if the current type is abstract, if it is not abstract, we can't do the subtype search at all). This should, I think, include all relevant subtypes.
>
> Jan Lahoda has updated the pull request with a new target base due to a merge or a rebase. The pull request now contains 17 commits:
> 
>  - Merge branch 'master' into JDK-8366968
>  - Merge branch 'JDK-8367499-2' into JDK-8366968-2
>  - Cleanup.
>  - Cleanup.
>  - Cleanup.
>  - Merge branch 'JDK-8367499-2' into JDK-8366968-2
>  - 8367499: Refactor exhaustiveness computation from Flow into a separate class
>  - Removing trailing whitespace.
>  - There are no relevant permitted subtypes of a non-abstract class, not even the class itself.
>  - 8366968: Exhaustive switch expression rejected by for not covering all possible values
>  - ... and 7 more: https://git.openjdk.org/jdk/compare/ea7186a8...f75a6830

Looks good 👍

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java line 231:

> 229:                             return instantiated != null && types.isCastable(selectorType, instantiated);
> 230:                         });
> 231:                         Set<Symbol> filteredPermitted = new HashSet<>(permitted);

`permitted` is the set of transitively permitted right? e.g., SpecialValue, Value and Special.

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java line 240:

> 238:                                     currentSubTypes =
> 239:                                             permitted.stream()
> 240:                                                      .filter(perm -> types.isSubtype(types.erasure(perm.type),

Can we also add a test with a generic hierarchy to witness the alternative path in the `types.erasure` part? I am sure the following would pass thought so feel free to ignore:


class SomeType {}
sealed interface Base<T extends SomeType> permits Special, Value {}
non-sealed interface Value<T extends SomeType>  extends Base<T> {}
sealed interface Special<T extends SomeType> extends Base<T> permits SpecialValue {}
non-sealed interface SpecialValue<T extends SomeType> extends Value<T>, Special<T> {}

static <T extends SomeType> int demo(final Base<T> base) {
    return switch (base) {  
         case Value<T> value -> 0;
    };
}

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

Marked as reviewed by abimpoudis (Reviewer).

PR Review: https://git.openjdk.org/jdk/pull/27547#pullrequestreview-3362237250
PR Review Comment: https://git.openjdk.org/jdk/pull/27547#discussion_r2449375638
PR Review Comment: https://git.openjdk.org/jdk/pull/27547#discussion_r2451555471


More information about the compiler-dev mailing list