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

Maurizio Cimadamore mcimadamore at openjdk.org
Wed Nov 19 12:48:26 UTC 2025


On Tue, 18 Nov 2025 16:13:16 GMT, Jan Lahoda <jlahoda at openjdk.org> wrote:

>> 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.

Thanks, I think my question originated from a wrong assumption on how your code behaved -- after some debugging sessions I've seen cases where the set of patterns does not cover the target

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

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


More information about the compiler-dev mailing list