diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java index 9de7a6292db67..737672afbc125 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java @@ -815,6 +815,7 @@ private boolean exhausts(JCExpression selector, List<JCCase> cases) { } } Set<PatternDescription> patterns = patternSet; + boolean genericPatternsExpanded = false; try { boolean repeat = true; while (repeat) { @@ -824,10 +825,22 @@ private boolean exhausts(JCExpression selector, List<JCCase> cases) { updatedPatterns = reduceRecordPatterns(updatedPatterns); updatedPatterns = removeCoveredRecordPatterns(updatedPatterns); repeat = !updatedPatterns.equals(patterns); - patterns = updatedPatterns; if (checkCovered(selector.type, patterns)) { return true; } + if (!repeat && !genericPatternsExpanded) { + //there may be situation like: + //class B extends S1, S2 + //patterns: R(S1, B), R(S2, S2) + //this should be joined to R(B, S2), + //but hashing in reduceNestedPatterns will not allow that + //attempt to once expand all types to their transitive permitted types, + //on all depth of nesting: + updatedPatterns = expandGenericPatterns(updatedPatterns); + genericPatternsExpanded = true; + repeat = !updatedPatterns.equals(patterns); + } + patterns = updatedPatterns; } return checkCovered(selector.type, patterns); } catch (CompletionFailure cf) { @@ -1130,6 +1143,40 @@ private PatternDescription reduceRecordPattern(PatternDescription pattern) { return pattern; } + private Set<PatternDescription> expandGenericPatterns(Set<PatternDescription> patterns) { + var newPatterns = new HashSet<PatternDescription>(patterns); + boolean modified; + do { + modified = false; + for (PatternDescription pd : patterns) { + if (pd instanceof RecordPattern rpOne) { + for (int i = 0; i < rpOne.nested.length; i++) { + Set<PatternDescription> toExpand = Set.of(rpOne.nested[i]); + Set<PatternDescription> expanded = expandGenericPatterns(toExpand); + if (expanded != toExpand) { + expanded.removeAll(toExpand); + for (PatternDescription exp : expanded) { + PatternDescription[] newNested = Arrays.copyOf(rpOne.nested, rpOne.nested.length); + newNested[i] = exp; + modified |= newPatterns.add(new RecordPattern(rpOne.recordType(), rpOne.fullComponentTypes(), newNested)); + } + } + } + } else if (pd instanceof BindingPattern bp) { + Set<Symbol> permittedSymbols = allPermittedSubTypes((ClassSymbol) bp.type.tsym, cs -> true); + + if (!permittedSymbols.isEmpty()) { + for (Symbol permitted : permittedSymbols) { + //TODO infer.instantiatePatternType(selectorType, csym); (?) + modified |= newPatterns.add(new BindingPattern(permitted.type)); + } + } + } + } + } while (modified); + return newPatterns; + } + private Set<PatternDescription> removeCoveredRecordPatterns(Set<PatternDescription> patterns) { Set<Symbol> existingBindings = patterns.stream() .filter(pd -> pd instanceof BindingPattern) diff --git a/test/langtools/tools/javac/patterns/Exhaustiveness.java b/test/langtools/tools/javac/patterns/Exhaustiveness.java index dad1a0c86f8d7..726bd1d9a2e9a 100644 --- a/test/langtools/tools/javac/patterns/Exhaustiveness.java +++ b/test/langtools/tools/javac/patterns/Exhaustiveness.java @@ -23,7 +23,7 @@ /** * @test - * @bug 8262891 8268871 8274363 8281100 8294670 8311038 8311815 + * @bug 8262891 8268871 8274363 8281100 8294670 8311038 8311815 8325215 * @summary Check exhaustiveness of switches over sealed types. * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -1996,6 +1996,86 @@ case Pair(D fst, D snd) -> { """); } + @Test //JDK-8325215: + public void testTooGenericPatternInRecord(Path base) throws Exception { + doTest(base, + new String[0], + """ + package test; + public class Test { + sealed interface A permits T, U {} + sealed interface B permits V, W {} + + static final class T implements A { public T() {} } + static final class U implements A { public U() {} } + + static final class V implements B { public V() {} } + static final class W implements B { public W() {} } + + final static record R(A a, B b) { } + + static int r(R r) { + return switch (r) { + case R(A a, V b) -> 1; // Any A with specific B + case R(T a, B b) -> 2; // Specific A with any B + case R(U a, W b) -> 3; // Specific A with specific B + }; + } + } + """); + doTest(base, + new String[0], + """ + package test; + public class Test { + sealed interface A permits T, U {} + sealed interface B permits V, W {} + + static final class T implements A { public T() {} } + static final class U implements A { public U() {} } + + static final class V implements B { public V() {} } + static final class W implements B { public W() {} } + + final static record R(B b, A a) { } + + static int r(R r) { + return switch (r) { + case R(V b, A a) -> 1; // Any A with specific B + case R(B b, T a) -> 2; // Specific A with any B + case R(W b, U a) -> 3; // Specific A with specific B + }; + } + } + """); + doTest(base, + new String[0], + """ + package test; + public class Test { + sealed interface A permits T, U {} + sealed interface B permits V, W {} + + static final class T implements A { public T() {} } + static final class U implements A { public U() {} } + + static final class V implements B { public V() {} } + static final class W implements B { public W() {} } + + final static record X(B b) { } + final static record R(A a, X x) { } + + static int r(R r) { + return switch (r) { + case R(A a, X(V b)) -> 1; // Any A with specific B + case R(T a, X(B b)) -> 2; // Specific A with any B + case R(U a, X(W b)) -> 3; // Specific A with specific B + }; + } + } + """); + } + private void doTest(Path base, String[] libraryCode, String testCode, String... expectedErrors) throws IOException { doTest(base, libraryCode, testCode, false, expectedErrors); }