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