RFR: 8364991: Incorrect not-exhaustive error [v4]
Vicente Romero
vromero at openjdk.org
Wed Oct 15 17:16:27 UTC 2025
On Wed, 15 Oct 2025 11:45:57 GMT, Jan Lahoda <jlahoda at openjdk.org> wrote:
>> Consider this code:
>>
>> $ cat Test.java
>> package test;
>> public class Test {
>> private int test1(Root r) {
>> return switch (r) {
>> case Root(R2(R1 _), R2(R1 _)) -> 0;
>> case Root(R2(R1 _), R2(R2 _)) -> 0;
>> case Root(R2(R2 _), R2(R1 _)) -> 0;
>> case Root(R2(R2 _), R2 _) -> 0;
>> };
>> }
>> sealed interface Base {}
>> record R1() implements Base {}
>> record R2(Base b1) implements Base {}
>> record Root(R2 b2, R2 b3) {}
>> }
>>
>>
>> javac (JDK 25) will produce a compile-time error for this code:
>>
>> $ javac test/Test.java
>> .../test/Test.java:4: error: the switch expression does not cover all possible input values
>> return switch (r) {
>> ^
>> 1 error
>>
>>
>> This error is not correct according to the JLS. JLS defines a set of possible reductions of pattern sets, and if there exists a series of reductions from the pattern set into a pattern set that covers the selector type, the switch is exhaustive.
>>
>> One such reduction is that if there's a sub-set of (record) patterns that only differ in one component ("the mismatching component"), we can replace them with a (set of) patterns where this component is reduced, and the other components are unmodified.
>>
>> Such path exists here (every line shows a set of patterns that is being transformed):
>>
>> Root(R2(R1 _), R2(R1 _)), Root(R2(R1 _), R2(R2 _)), Root(R2(R2 _), R2(R1 _)), Root(R2(R2 _), R2 _)
>> => choosing the second component as the mismatching component, then we can reduce Root(R2(R1 _), R2(R1 _)), Root(R2(R1 _), R2(R2 _)) => Root(R2(R1 _), R2 _); as we can reduce R2(R1 _), R2(R2 _) to R2 _
>> Root(R2(R1 _), R2 _), Root(R2(R2 _), R2(R1 _)), Root(R2(R2 _), R2 _)
>> => choosing the first component as the mismatching component, we can reduce Root(R2(R1 _), R2 _), Root(R2(R2 _), R2 _) => Root(R2 _, R2 _)
>> Root(R2 _, R2 _)
>> =>
>> Root _
>> =>
>> exhaustive
>>
>>
>> The problem here is that in the first step, javac chooses this path:
>>
>> Root(R2(R1 _), R2(R1 _)), Root(R2(R1 _), R2(R2 _)), Root(R2(R2 _), R2(R1 _)), Root(R2(R2 _), R2 _)
>> => reduce Root(R2(R1 _), R2(R1 _)), Root(R2(R2 _), R2(R1 _)) => Root(R2 _, R2(R1 _))
>> Root(R2 _, R2(R1 _)), Root(R2(R1 _), R2(R2 _)), Root(R2(R2 _), R2 _)
>> => dead end, as there are no two patterns that would have the same nested pattern in the same component
>>
>>
>> If javac would do full backtracking, it could go back, and choose the other path, and find out the switch ...
>
> Jan Lahoda has updated the pull request incrementally with three additional commits since the last revision:
>
> - Caching isSubtype, as suggested.
> - Adding explanation to the replaces map.
> - Factoring out the 'substitutable' check, as suggested.
src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java line 763:
> 761: }
> 762:
> 763: private final Map<Pair<Type, Type>, Boolean> isSubtypeCache = new HashMap<>();
I think that you want to compare a type using Types::isSameType instead of Type::equals method. There is one cache that does this: Infer::incorporationCache. Although it could be that type identity comparison is enough for this application, dunno
src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java line 1123:
> 1121: * the existing pattern
> 1122: */
> 1123: private boolean nestedComponentsEquivalent(RecordPattern existing,
yep looks better now, thanks!
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/27247#discussion_r2433301144
PR Review Comment: https://git.openjdk.org/jdk/pull/27247#discussion_r2433333979
More information about the compiler-dev
mailing list