8366968: Exhaustive switch expression rejected by for not covering all possible values

Reviewed-by: abimpoudis
This commit is contained in:
Jan Lahoda 2025-10-30 07:08:18 +00:00
parent f3dfdfa3fd
commit 87a4772198
2 changed files with 153 additions and 19 deletions

View File

@ -210,7 +210,6 @@ public class ExhaustivenessComputer {
if (clazz.isSealed() && clazz.isAbstract() &&
//if a binding pattern for clazz already exists, no need to analyze it again:
!existingBindings.contains(clazz)) {
ListBuffer<PatternDescription> bindings = new ListBuffer<>();
//do not reduce to types unrelated to the selector type:
Type clazzErasure = types.erasure(clazz.type);
if (components(selectorType).stream()
@ -230,30 +229,36 @@ public class ExhaustivenessComputer {
return instantiated != null && types.isCastable(selectorType, instantiated);
});
//the set of pending permitted subtypes needed to cover clazz:
Set<Symbol> pendingPermitted = new HashSet<>(permitted);
for (PatternDescription pdOther : patterns) {
if (pdOther instanceof BindingPattern bpOther) {
Set<Symbol> currentPermittedSubTypes =
allPermittedSubTypes(bpOther.type.tsym, s -> true);
//remove all types from pendingPermitted that we can
//cover using bpOther:
PERMITTED: for (Iterator<Symbol> it = permitted.iterator(); it.hasNext();) {
Symbol perm = it.next();
//all types that are permitted subtypes of bpOther's type:
pendingPermitted.removeIf(pending -> types.isSubtype(types.erasure(pending.type),
types.erasure(bpOther.type)));
for (Symbol currentPermitted : currentPermittedSubTypes) {
if (types.isSubtype(types.erasure(currentPermitted.type),
types.erasure(perm.type))) {
it.remove();
continue PERMITTED;
}
}
if (types.isSubtype(types.erasure(perm.type),
types.erasure(bpOther.type))) {
it.remove();
}
if (bpOther.type.tsym.isAbstract()) {
//all types that are in a diamond hierarchy with bpOther's type
//i.e. there's a common subtype of the given type and bpOther's type:
Predicate<Symbol> check =
pending -> permitted.stream()
.filter(perm -> types.isSubtype(types.erasure(perm.type),
types.erasure(bpOther.type)))
.filter(perm -> types.isSubtype(types.erasure(perm.type),
types.erasure(pending.type)))
.findAny()
.isPresent();
pendingPermitted.removeIf(check);
}
}
}
if (permitted.isEmpty()) {
if (pendingPermitted.isEmpty()) {
toAdd.add(new BindingPattern(clazz.type));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,7 +23,7 @@
/**
* @test
* @bug 8262891 8268871 8274363 8281100 8294670 8311038 8311815 8325215 8333169 8327368
* @bug 8262891 8268871 8274363 8281100 8294670 8311038 8311815 8325215 8333169 8327368 8366968
* @summary Check exhaustiveness of switches over sealed types.
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
@ -2182,6 +2182,135 @@ public class Exhaustiveness extends TestRunner {
""");
}
@Test //JDK-8366968
public void testNonSealedDiamond(Path base) throws Exception {
doTest(base,
new String[0],
"""
class Demo {
sealed interface Base permits Special, Value {}
non-sealed interface Value extends Base {}
sealed interface Special extends Base permits SpecialValue {}
non-sealed interface SpecialValue extends Value, Special {}
static int demo(final Base base) {
return switch (base) {
case Value value -> 0;
};
}
}
""");
}
@Test //JDK-8366968
public void testNonSealedDiamond2(Path base) throws Exception {
doTest(base,
new String[0],
"""
class Demo {
sealed interface Base permits Special, Value {}
non-sealed interface Value extends Base {}
non-sealed interface Special extends Base {}
interface SpecialValue extends Value, Special {}
static int demo(final Base base) {
return switch (base) {
case Value value -> 0;
};
}
}
""",
"Demo.java:12:16: compiler.err.not.exhaustive",
"1 error");
}
@Test //JDK-8366968
public void testNonAbstract(Path base) throws Exception {
doTest(base,
new String[0],
"""
class Demo {
sealed interface I permits Base, C3 { }
sealed class Base implements I permits C1, C2 { }
final class C1 extends Base { }
final class C2 extends Base { }
final class C3 implements I { }
void method1(I i) {
switch (i) {
case C1 _ -> {}
case C2 _ -> {}
case C3 _ -> {}
}
}
}
""",
"Demo.java:9:9: compiler.err.not.exhaustive.statement",
"1 error");
}
@Test //JDK-8366968
public void testNonSealedDiamondGeneric(Path base) throws Exception {
doTest(base,
new String[0],
"""
class Demo {
class SomeType {}
sealed interface Base<T extends SomeType> permits Special, Value {}
non-sealed interface Value<T extends SomeType> extends Base<T> {}
sealed interface Special<T extends SomeType> extends Base<T> permits SpecialValue {}
non-sealed interface SpecialValue<T extends SomeType> extends Value<T>, Special<T> {}
static <T extends SomeType> int demo(final Base<T> base) {
return switch (base) {
case Value<T> value -> 0;
};
}
}
""");
}
@Test //JDK-8366968
public void testNonSealedDiamondMultiple(Path base) throws Exception {
doTest(base,
new String[0],
"""
class Demo {
sealed interface Base permits Special, Value {}
non-sealed interface Value extends Base {}
sealed interface Special extends Base permits SpecialValue, Special2 {}
non-sealed interface SpecialValue extends Value, Special {}
non-sealed interface Special2 extends Special {}
static int demo(final Base base) {
return switch (base) {
case Value value -> 0;
};
}
}
""",
"Demo.java:13:16: compiler.err.not.exhaustive",
"1 error");
}
private void doTest(Path base, String[] libraryCode, String testCode, String... expectedErrors) throws IOException {
doTest(base, libraryCode, testCode, false, expectedErrors);
}