diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java index 2a0bab1e330..a099d634073 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java @@ -223,15 +223,31 @@ public class ExhaustivenessComputer { private boolean checkCovered(Type seltype, Iterable patterns) { for (Type seltypeComponent : components(seltype)) { - for (PatternDescription pd : patterns) { - if(isBpCovered(seltypeComponent, pd)) { - return true; - } + if (isCoveredBy(seltypeComponent, patterns)) { + return true; } } return false; } + private boolean isCoveredBy(Type seltype, Iterable patterns) { + for (PatternDescription pd : patterns) { + if(isBpCovered(seltype, pd)) { + return true; + } + if (seltype.tsym.isSealed() && seltype.tsym.isAbstract()) { + boolean allDirectPermittedSubtypesPermitted = + directPermittedSubTypes(seltype) + .map(csym -> instantiatePatternType(seltype, csym)) + .allMatch(currentPermitted -> isCoveredBy(currentPermitted, patterns)); + + return allDirectPermittedSubtypesPermitted; + } + } + + return false; + } + private List components(Type seltype) { return switch (seltype.getTag()) { case CLASS -> { @@ -244,10 +260,10 @@ public class ExhaustivenessComputer { } yield List.nil(); } - yield List.of(types.erasure(seltype)); + yield List.of(seltype); } case TYPEVAR -> components(((TypeVar) seltype).getUpperBound()); - default -> List.of(types.erasure(seltype)); + default -> List.of(seltype); }; } @@ -371,6 +387,14 @@ public class ExhaustivenessComputer { } } + private Stream directPermittedSubTypes(Type type) { + List permitted = ((ClassSymbol) type.tsym).getPermittedSubclasses(); + + return permitted.stream() + .map(permittedType -> permittedType.tsym) + .filter(isApplicableSubtypePredicate(type)); + } + private Set leafPermittedSubTypes(TypeSymbol root, Predicate accept) { Set permitted = new HashSet<>(); List permittedSubtypesClosure = baseClasses(root); @@ -877,11 +901,8 @@ public class ExhaustivenessComputer { if (toExpand instanceof BindingPattern bp) { if (bp.type.tsym.isSealed()) { //try to replace binding patterns for sealed types with all their immediate permitted applicable types: - List permitted = ((ClassSymbol) bp.type.tsym).getPermittedSubclasses(); Set applicableDirectPermittedPatterns = - permitted.stream() - .map(type -> type.tsym) - .filter(isApplicableSubtypePredicate(targetType)) + directPermittedSubTypes(bp.type) .map(csym -> new BindingPattern(types.erasure(csym.type))) .collect(Collectors.toCollection(LinkedHashSet::new)); diff --git a/test/langtools/tools/javac/patterns/Exhaustiveness.java b/test/langtools/tools/javac/patterns/Exhaustiveness.java index f1809e99e45..e1d6c61970e 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 8325215 8333169 8327368 8366968 8364991 + * @bug 8262891 8268871 8274363 8281100 8294670 8311038 8311815 8325215 8333169 8327368 8366968 8364991 8383414 * @summary Check exhaustiveness of switches over sealed types. * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -1486,9 +1486,7 @@ public class Exhaustiveness extends TestRunner { } } } - """, - "Test.java:11:9: compiler.err.not.exhaustive.statement", - "1 error"); + """); } @Test @@ -2487,6 +2485,96 @@ public class Exhaustiveness extends TestRunner { "1 error"); } + @Test //JDK-8383414 + public void testVerifyCoverageCheckWithApplicablePermittedSubTypes(Path base) throws Exception { + doTest(base, + new String[0], + """ + class Test { + interface J {} + static sealed interface II permits A2, B2 {} + static final class A2 implements II, J {} + static final class B2 implements II, J {} + static void f(II i) { + switch (i) { + case J j -> {} + } + } + } + """); + //deeper hierarchy: + doTest(base, + new String[0], + """ + class Test { + interface J {} + static sealed interface II permits Med, B2 {} + static sealed interface Med extends II {} + static final class A2 implements Med, J {} + static final class B2 implements II, J {} + static void f(II i) { + switch (i) { + case J j -> {} + } + } + } + """); + //even deeper hierarchy: + doTest(base, + new String[0], + """ + class Test { + interface J {} + static sealed interface II permits Med1, B2 {} + static sealed interface Med1 extends II {} + static sealed interface Med2 extends Med1 {} + static sealed interface Med3 extends Med2 {} + static sealed interface Med4 extends Med3 {} + static sealed interface Med5 extends Med4 {} + static final class A2 implements Med5, J {} + static final class B2 implements II, J {} + static void f(II i) { + switch (i) { + case J j -> {} + } + } + } + """); + //a class that does not implement J, but is not applicable: + doTest(base, + new String[0], + """ + class Test { + interface J {} + static sealed interface II permits A2, B2 {} + static final class A2 implements II {} + static final class B2 implements II, J {} + static void f(II i) { + switch (i) { + case J j -> {} + } + } + } + """); + doTest(base, + new String[0], + """ + class Test { + interface J1 {} + interface J2 {} + static sealed interface II permits A2, B2 {} + static final class A2 implements II, J1 {} + static final class B2 implements II, J2 {} + static void f(II i) { + switch (i) { + case J1 j -> {} + case J2 j -> {} + } + } + } + """); + } + private void doTest(Path base, String[] libraryCode, String testCode, String... expectedErrors) throws IOException { doTest(base, libraryCode, testCode, false, expectedErrors); }