8383414: Exhaustiveness rejects a type pattern that covers all permitted subtypes

Reviewed-by: abimpoudis
This commit is contained in:
Jan Lahoda 2026-05-04 05:09:26 +00:00
parent f74a4476ee
commit 5ccf99b8e9
2 changed files with 123 additions and 14 deletions

View File

@ -223,15 +223,31 @@ public class ExhaustivenessComputer {
private boolean checkCovered(Type seltype, Iterable<PatternDescription> 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<PatternDescription> 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<Type> 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<TypeSymbol> directPermittedSubTypes(Type type) {
List<Type> permitted = ((ClassSymbol) type.tsym).getPermittedSubclasses();
return permitted.stream()
.map(permittedType -> permittedType.tsym)
.filter(isApplicableSubtypePredicate(type));
}
private Set<ClassSymbol> leafPermittedSubTypes(TypeSymbol root, Predicate<ClassSymbol> accept) {
Set<ClassSymbol> permitted = new HashSet<>();
List<ClassSymbol> 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<Type> permitted = ((ClassSymbol) bp.type.tsym).getPermittedSubclasses();
Set<PatternDescription> 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));

View File

@ -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<T> permits A2, B2 {}
static final class A2 implements II<Long>, J {}
static final class B2 implements II<Long>, J {}
static void f(II<Long> i) {
switch (i) {
case J j -> {}
}
}
}
""");
//deeper hierarchy:
doTest(base,
new String[0],
"""
class Test {
interface J {}
static sealed interface II<T> permits Med, B2 {}
static sealed interface Med extends II<Long> {}
static final class A2 implements Med, J {}
static final class B2 implements II<Long>, J {}
static void f(II<Long> i) {
switch (i) {
case J j -> {}
}
}
}
""");
//even deeper hierarchy:
doTest(base,
new String[0],
"""
class Test {
interface J {}
static sealed interface II<T> permits Med1, B2 {}
static sealed interface Med1 extends II<Long> {}
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<Long>, J {}
static void f(II<Long> 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<T> permits A2, B2 {}
static final class A2 implements II<String> {}
static final class B2 implements II<Long>, J {}
static void f(II<Long> i) {
switch (i) {
case J j -> {}
}
}
}
""");
doTest(base,
new String[0],
"""
class Test {
interface J1 {}
interface J2 {}
static sealed interface II<T> permits A2, B2 {}
static final class A2 implements II<Long>, J1 {}
static final class B2 implements II<Long>, J2 {}
static void f(II<Long> 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);
}