8311815: Incorrect exhaustivity computation

Reviewed-by: vromero
This commit is contained in:
Jan Lahoda 2023-07-17 08:19:51 +00:00
parent 1c9691b1f7
commit a4412166ec
2 changed files with 78 additions and 47 deletions
src/jdk.compiler/share/classes/com/sun/tools/javac/comp
test/langtools/tools/javac/patterns

@ -773,15 +773,16 @@ public class Flow {
patternSet.add(new BindingPattern(e.getKey().type));
}
}
List<PatternDescription> patterns = List.from(patternSet);
Set<PatternDescription> patterns = patternSet;
try {
boolean repeat = true;
while (repeat) {
List<PatternDescription> updatedPatterns;
Set<PatternDescription> updatedPatterns;
updatedPatterns = reduceBindingPatterns(selector.type, patterns);
updatedPatterns = reduceNestedPatterns(updatedPatterns);
updatedPatterns = reduceRecordPatterns(updatedPatterns);
repeat = updatedPatterns != patterns;
updatedPatterns = removeCoveredRecordPatterns(updatedPatterns);
repeat = !updatedPatterns.equals(patterns);
patterns = updatedPatterns;
if (checkCovered(selector.type, patterns)) {
return true;
@ -794,7 +795,7 @@ public class Flow {
}
}
private boolean checkCovered(Type seltype, List<PatternDescription> patterns) {
private boolean checkCovered(Type seltype, Iterable<PatternDescription> patterns) {
for (Type seltypeComponent : components(seltype)) {
for (PatternDescription pd : patterns) {
if (pd instanceof BindingPattern bp &&
@ -830,7 +831,7 @@ public class Flow {
* is found, it is removed, and replaced with a binding pattern
* for the sealed supertype.
*/
private List<PatternDescription> reduceBindingPatterns(Type selectorType, List<PatternDescription> patterns) {
private Set<PatternDescription> reduceBindingPatterns(Type selectorType, Set<PatternDescription> patterns) {
Set<Symbol> existingBindings = patterns.stream()
.filter(pd -> pd instanceof BindingPattern)
.map(pd -> ((BindingPattern) pd).type.tsym)
@ -838,7 +839,6 @@ public class Flow {
for (PatternDescription pdOne : patterns) {
if (pdOne instanceof BindingPattern bpOne) {
Set<PatternDescription> toRemove = new HashSet<>();
Set<PatternDescription> toAdd = new HashSet<>();
for (Type sup : types.directSupertypes(bpOne.type)) {
@ -871,7 +871,6 @@ public class Flow {
for (PatternDescription pdOther : patterns) {
if (pdOther instanceof BindingPattern bpOther) {
boolean reduces = false;
Set<Symbol> currentPermittedSubTypes =
allPermittedSubTypes((ClassSymbol) bpOther.type.tsym, s -> true);
@ -888,33 +887,21 @@ public class Flow {
if (types.isSubtype(types.erasure(perm.type),
types.erasure(bpOther.type))) {
it.remove();
reduces = true;
}
}
if (reduces) {
if (!types.isSubtype(types.erasure(clazz.type), types.erasure(bpOther.type))) {
bindings.append(pdOther);
}
}
}
}
if (permitted.isEmpty()) {
toRemove.addAll(bindings);
toAdd.add(new BindingPattern(clazz.type));
}
}
}
if (!toAdd.isEmpty() || !toRemove.isEmpty()) {
for (PatternDescription pd : toRemove) {
patterns = List.filter(patterns, pd);
}
for (PatternDescription pd : toAdd) {
patterns = patterns.prepend(pd);
}
return patterns;
if (!toAdd.isEmpty()) {
Set<PatternDescription> newPatterns = new HashSet<>(patterns);
newPatterns.addAll(toAdd);
return newPatterns;
}
}
}
@ -958,7 +945,7 @@ public class Flow {
* of patterns is replaced with a new set of patterns of the form:
* $record($prefix$, $resultOfReduction, $suffix$)
*/
private List<PatternDescription> reduceNestedPatterns(List<PatternDescription> patterns) {
private Set<PatternDescription> reduceNestedPatterns(Set<PatternDescription> patterns) {
/* implementation note:
* finding a sub-set of patterns that only differ in a single
* column is time-consuming task, so this method speeds it up by:
@ -977,13 +964,14 @@ public class Flow {
for (var e : groupByRecordClass.entrySet()) {
int nestedPatternsCount = e.getKey().getRecordComponents().size();
Set<RecordPattern> current = new HashSet<>(e.getValue());
for (int mismatchingCandidate = 0;
mismatchingCandidate < nestedPatternsCount;
mismatchingCandidate++) {
int mismatchingCandidateFin = mismatchingCandidate;
var groupByHashes =
e.getValue()
current
.stream()
//error recovery, ignore patterns with incorrect number of nested patterns:
.filter(pd -> pd.nested.length == nestedPatternsCount)
@ -1018,37 +1006,35 @@ public class Flow {
}
}
var nestedPatterns = join.stream().map(rp -> rp.nested[mismatchingCandidateFin]).collect(List.collector());
var nestedPatterns = join.stream().map(rp -> rp.nested[mismatchingCandidateFin]).collect(Collectors.toSet());
var updatedPatterns = reduceNestedPatterns(nestedPatterns);
updatedPatterns = reduceRecordPatterns(updatedPatterns);
updatedPatterns = removeCoveredRecordPatterns(updatedPatterns);
updatedPatterns = reduceBindingPatterns(rpOne.fullComponentTypes()[mismatchingCandidateFin], updatedPatterns);
if (nestedPatterns != updatedPatterns) {
ListBuffer<PatternDescription> result = new ListBuffer<>();
Set<PatternDescription> toRemove = Collections.newSetFromMap(new IdentityHashMap<>());
toRemove.addAll(join);
for (PatternDescription p : patterns) {
if (!toRemove.contains(p)) {
result.append(p);
}
}
if (!nestedPatterns.equals(updatedPatterns)) {
current.removeAll(join);
for (PatternDescription nested : updatedPatterns) {
PatternDescription[] newNested =
Arrays.copyOf(rpOne.nested, rpOne.nested.length);
newNested[mismatchingCandidateFin] = nested;
result.append(new RecordPattern(rpOne.recordType(),
current.add(new RecordPattern(rpOne.recordType(),
rpOne.fullComponentTypes(),
newNested));
}
return result.toList();
}
}
}
}
if (!current.equals(new HashSet<>(e.getValue()))) {
Set<PatternDescription> result = new HashSet<>(patterns);
result.removeAll(e.getValue());
result.addAll(current);
return result;
}
}
return patterns;
}
@ -1058,22 +1044,22 @@ public class Flow {
* all the $nestedX pattern cover the given record component,
* and replace those with a simple binding pattern over $record.
*/
private List<PatternDescription> reduceRecordPatterns(List<PatternDescription> patterns) {
var newPatterns = new ListBuffer<PatternDescription>();
private Set<PatternDescription> reduceRecordPatterns(Set<PatternDescription> patterns) {
var newPatterns = new HashSet<PatternDescription>();
boolean modified = false;
for (PatternDescription pd : patterns) {
if (pd instanceof RecordPattern rpOne) {
PatternDescription reducedPattern = reduceRecordPattern(rpOne);
if (reducedPattern != rpOne) {
newPatterns.append(reducedPattern);
newPatterns.add(reducedPattern);
modified = true;
continue;
}
}
newPatterns.append(pd);
newPatterns.add(pd);
}
return modified ? newPatterns.toList() : patterns;
}
return modified ? newPatterns : patterns;
}
private PatternDescription reduceRecordPattern(PatternDescription pattern) {
if (pattern instanceof RecordPattern rpOne) {
@ -1105,6 +1091,23 @@ public class Flow {
return pattern;
}
private Set<PatternDescription> removeCoveredRecordPatterns(Set<PatternDescription> patterns) {
Set<Symbol> existingBindings = patterns.stream()
.filter(pd -> pd instanceof BindingPattern)
.map(pd -> ((BindingPattern) pd).type.tsym)
.collect(Collectors.toSet());
Set<PatternDescription> result = new HashSet<>(patterns);
for (Iterator<PatternDescription> it = result.iterator(); it.hasNext();) {
PatternDescription pd = it.next();
if (pd instanceof RecordPattern rp && existingBindings.contains(rp.recordType.tsym)) {
it.remove();
}
}
return result;
}
public void visitTry(JCTry tree) {
ListBuffer<PendingExit> prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();

@ -23,7 +23,7 @@
/**
* @test
* @bug 8262891 8268871 8274363 8281100 8294670 8311038
* @bug 8262891 8268871 8274363 8281100 8294670 8311038 8311815
* @summary Check exhaustiveness of switches over sealed types.
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
@ -1540,7 +1540,7 @@ public class Exhaustiveness extends TestRunner {
"1 error");
}
private static final int NESTING_CONSTANT = 5;
private static final int NESTING_CONSTANT = 4;
Set<String> createDeeplyNestedVariants() {
Set<String> variants = new HashSet<>();
@ -1968,6 +1968,34 @@ public class Exhaustiveness extends TestRunner {
""");
}
@Test //JDK-8311815:
public void testAmbiguousRecordUsage(Path base) throws Exception {
doTest(base,
new String[0],
"""
package test;
public class Test {
record Pair(I i1, I i2) {}
sealed interface I {}
record C() implements I {}
record D() implements I {}
void exhaustinvenessWithInterface(Pair pairI) {
switch (pairI) {
case Pair(D fst, C snd) -> {
}
case Pair(C fst, C snd) -> {
}
case Pair(C fst, I snd) -> {
}
case Pair(D fst, D snd) -> {
}
}
}
}
""");
}
private void doTest(Path base, String[] libraryCode, String testCode, String... expectedErrors) throws IOException {
doTest(base, libraryCode, testCode, false, expectedErrors);
}