Skip to content
Permalink
Browse files
8268871: Adjust javac to updated exhaustiveness specification
Reviewed-by: vromero
  • Loading branch information
Jan Lahoda committed Jun 25, 2021
1 parent 44691cc commit 4eb321298a1abf6b24bd9515c5c0c3580b2f31f7
Showing 2 changed files with 288 additions and 74 deletions.
@@ -30,7 +30,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.StreamSupport;

import com.sun.source.tree.LambdaExpressionTree.BodyKind;
import com.sun.tools.javac.code.*;
@@ -45,17 +45,16 @@
import com.sun.tools.javac.util.JCDiagnostic.Error;
import com.sun.tools.javac.util.JCDiagnostic.Warning;

import com.sun.tools.javac.code.Kinds.Kind;
import com.sun.tools.javac.code.Symbol.*;
import com.sun.tools.javac.tree.JCTree.*;

import static com.sun.tools.javac.code.Flags.*;
import static com.sun.tools.javac.code.Flags.BLOCK;
import static com.sun.tools.javac.code.Kinds.Kind.*;
import com.sun.tools.javac.code.Type.TypeVar;
import static com.sun.tools.javac.code.TypeTag.BOOLEAN;
import static com.sun.tools.javac.code.TypeTag.VOID;
import com.sun.tools.javac.resources.CompilerProperties.Fragments;
import com.sun.tools.javac.tree.JCTree.JCParenthesizedPattern;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
import com.sun.tools.javac.util.JCDiagnostic.Fragment;

@@ -665,7 +664,7 @@ public void visitSwitch(JCSwitch tree) {
ListBuffer<PendingExit> prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();
scan(tree.selector);
Set<Object> constants = tree.patternSwitch ? allSwitchConstants(tree.selector) : null;
Set<Symbol> constants = tree.patternSwitch ? new HashSet<>() : null;
for (List<JCCase> l = tree.cases; l.nonEmpty(); l = l.tail) {
alive = Liveness.ALIVE;
JCCase c = l.head;
@@ -687,8 +686,9 @@ public void visitSwitch(JCSwitch tree) {
l.tail.head.pos(),
Warnings.PossibleFallThroughIntoCase);
}
if ((constants == null || !constants.isEmpty()) && !tree.hasTotalPattern &&
tree.patternSwitch && !TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases)) {
if (!tree.hasTotalPattern && tree.patternSwitch &&
!TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) &&
(constants == null || !isExhaustive(tree.selector.type, constants))) {
log.error(tree, Errors.NotExhaustiveStatement);
}
if (!tree.hasTotalPattern) {
@@ -702,7 +702,7 @@ public void visitSwitchExpression(JCSwitchExpression tree) {
ListBuffer<PendingExit> prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();
scan(tree.selector);
Set<Object> constants = allSwitchConstants(tree.selector);
Set<Symbol> constants = new HashSet<>();
Liveness prevAlive = alive;
for (List<JCCase> l = tree.cases; l.nonEmpty(); l = l.tail) {
alive = Liveness.ALIVE;
@@ -723,47 +723,83 @@ public void visitSwitchExpression(JCSwitchExpression tree) {
}
c.completesNormally = alive != Liveness.DEAD;
}
if ((constants == null || !constants.isEmpty()) && !tree.hasTotalPattern &&
!TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases)) {
if (!tree.hasTotalPattern && !TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) &&
!isExhaustive(tree.selector.type, constants)) {
log.error(tree, Errors.NotExhaustive);
}
alive = prevAlive;
alive = alive.or(resolveYields(tree, prevPendingExits));
}

private Set<Object> allSwitchConstants(JCExpression selector) {
Set<Object> constants = null;
TypeSymbol selectorSym = selector.type.tsym;
if ((selectorSym.flags() & ENUM) != 0) {
constants = new HashSet<>();
Predicate<Symbol> enumConstantFilter =
s -> (s.flags() & ENUM) != 0 && s.kind == Kind.VAR;
for (Symbol s : selectorSym.members().getSymbols(enumConstantFilter)) {
constants.add(s.name);
}
} else if (selectorSym.isAbstract() && selectorSym.isSealed() && selectorSym.kind == Kind.TYP) {
constants = new HashSet<>();
constants.addAll(((ClassSymbol) selectorSym).permitted);
}
return constants;
}

private void handleConstantCaseLabel(Set<Object> constants, JCCaseLabel pat) {
private void handleConstantCaseLabel(Set<Symbol> constants, JCCaseLabel pat) {
if (constants != null) {
if (pat.isExpression()) {
JCExpression expr = (JCExpression) pat;
if (expr.hasTag(IDENT))
constants.remove(((JCIdent) expr).name);
if (expr.hasTag(IDENT) && ((JCIdent) expr).sym.isEnum())
constants.add(((JCIdent) expr).sym);
} else if (pat.isPattern()) {
PatternPrimaryType patternType = TreeInfo.primaryPatternType((JCPattern) pat);

if (patternType.unconditional()) {
constants.remove(patternType.type().tsym);
constants.add(patternType.type().tsym);
}
}
}
}

private void transitiveCovers(Set<Symbol> covered) {
List<Symbol> todo = List.from(covered);
while (todo.nonEmpty()) {
Symbol sym = todo.head;
todo = todo.tail;
switch (sym.kind) {
case VAR -> {
Iterable<Symbol> constants = sym.owner
.members()
.getSymbols(s -> s.isEnum() &&
s.kind == VAR);
boolean hasAll = StreamSupport.stream(constants.spliterator(), false)
.allMatch(covered::contains);

if (hasAll && covered.add(sym.owner)) {
todo = todo.prepend(sym.owner);
}
}

case TYP -> {
for (Type sup : types.directSupertypes(sym.type)) {
if (sup.tsym.kind == TYP && sup.tsym.isAbstract() && sup.tsym.isSealed()) {
boolean hasAll = ((ClassSymbol) sup.tsym).permitted
.stream()
.allMatch(covered::contains);

if (hasAll && covered.add(sup.tsym)) {
todo = todo.prepend(sup.tsym);
}
}
}
}
}
}
}

private boolean isExhaustive(Type seltype, Set<Symbol> covered) {
transitiveCovers(covered);
return switch (seltype.getTag()) {
case CLASS -> {
if (seltype.isCompound()) {
if (seltype.isIntersection()) {
yield ((Type.IntersectionClassType) seltype).getComponents().stream().anyMatch(t -> isExhaustive(t, covered));
}
yield false;
}
yield covered.contains(seltype.tsym);
}
case TYPEVAR -> isExhaustive(((TypeVar) seltype).getUpperBound(), covered);
default -> false;
};
}

public void visitTry(JCTry tree) {
ListBuffer<PendingExit> prevPendingExits = pendingExits;
pendingExits = new ListBuffer<>();

0 comments on commit 4eb3212

Please sign in to comment.