RFR: 8367530: The exhaustiveness errors could be improved [v8]

Jan Lahoda jlahoda at openjdk.org
Tue Nov 18 16:16:11 UTC 2025


On Fri, 14 Nov 2025 13:18:46 GMT, Maurizio Cimadamore <mcimadamore at openjdk.org> wrote:

>> Jan Lahoda has updated the pull request incrementally with one additional commit since the last revision:
>> 
>>   Fixing trailing whitespaces.
>
> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java line 939:
> 
>> 937:                 removeUnnecessaryPatterns(selectorType, bp, basePatterns, inMissingPatterns, combinatorialPatterns);
>> 938: 
>> 939:                 CoverageResult coverageResult = computeCoverage(targetType, combinatorialPatterns, PatternEquivalence.LOOSE);
> 
> How can the expansion of a record pattern not cover? E.g. if we start with an exhaustive pattern `R r` and we expand `R` into all its components `R(A1, B1, C1)`, `R(A2, B2, C2)` ... -- how can we get into a situation where we're no longer exhaustive?

The purpose of the code here (and a few lines below) is to merge "unnecesarily" specific patterns inside the missing patterns into their more generic supertypes. Like if at a specific place in the record patterns, there are both `A` and `B`, permitted subtypes of `Base`, we could merge and replace with `Base` here. But I understand it is somewhat matter of opinion on what is better.

Note the "unnecessary" `combinatorialPatterns` (i.e. those that are covered by the user provided patterns) are already removed here, and  `computeCoverage` is ran on `combinatorialPatterns`, so those may not cover the original type, because they no longer contain anything covered by the user-provided patterns.

One example where this changes the outcomes is:
https://github.com/openjdk/jdk/blob/da1307178912ab7bbca3ab520b44c6599cdcc1c2/test/langtools/tools/javac/patterns/ExhaustivenessConvenientErrors.java#L214

where the current outcome is:

test.Test.Root(test.Test.R2 _, test.Test.Base _, test.Test.Base _)
test.Test.Root(test.Test.R3 _, test.Test.Base _, test.Test.Base _)


but if I disable this merging, we would get:

test.Test.Root(test.Test.R3 _, test.Test.R1 _, test.Test.R2 _)
test.Test.Root(test.Test.R2 _, test.Test.R1 _, test.Test.R2 _)
test.Test.Root(test.Test.R2 _, test.Test.R1 _, test.Test.R3 _)
test.Test.Root(test.Test.R3 _, test.Test.R2 _, test.Test.R1 _)
test.Test.Root(test.Test.R3 _, test.Test.R1 _, test.Test.R1 _)
test.Test.Root(test.Test.R3 _, test.Test.R2 _, test.Test.R2 _)
test.Test.Root(test.Test.R3 _, test.Test.R1 _, test.Test.R3 _)
test.Test.Root(test.Test.R3 _, test.Test.R3 _, test.Test.R3 _)
test.Test.Root(test.Test.R2 _, test.Test.R2 _, test.Test.R3 _)
test.Test.Root(test.Test.R2 _, test.Test.R3 _, test.Test.R1 _)
test.Test.Root(test.Test.R3 _, test.Test.R2 _, test.Test.R3 _)
test.Test.Root(test.Test.R3 _, test.Test.R3 _, test.Test.R1 _)
test.Test.Root(test.Test.R3 _, test.Test.R3 _, test.Test.R2 _)
test.Test.Root(test.Test.R2 _, test.Test.R1 _, test.Test.R1 _)
test.Test.Root(test.Test.R2 _, test.Test.R2 _, test.Test.R2 _)
test.Test.Root(test.Test.R2 _, test.Test.R2 _, test.Test.R1 _)
test.Test.Root(test.Test.R2 _, test.Test.R3 _, test.Test.R2 _)
test.Test.Root(test.Test.R2 _, test.Test.R3 _, test.Test.R3 _)


There may be a way to better control this merging (given the original user-provided pattern has `_` on both the second and third component), but given how coverage works for record patterns, it is a bit tricky.

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

PR Review Comment: https://git.openjdk.org/jdk/pull/27256#discussion_r2538826592


More information about the compiler-dev mailing list