8273328: Compiler implementation for Pattern Matching for switch (Second Preview)

Reviewed-by: vromero, mcimadamore
This commit is contained in:
Jan Lahoda 2021-11-24 10:07:49 +00:00
parent 6d734604a3
commit d085c2b8a7
9 changed files with 207 additions and 49 deletions
src/jdk.compiler/share/classes/com/sun/tools/javac/comp
test/langtools/tools/javac/patterns

@ -1680,7 +1680,8 @@ public class Attr extends JCTree.Visitor {
// Attribute all cases and
// check that there are no duplicate case labels or default clauses.
Set<Object> labels = new HashSet<>(); // The set of case labels.
List<Type> coveredTypes = List.nil();
List<Type> coveredTypesForPatterns = List.nil();
List<Type> coveredTypesForConstants = List.nil();
boolean hasDefault = false; // Is there a default label?
boolean hasTotalPattern = false; // Is there a total pattern?
boolean hasNullPattern = false; // Is there a null pattern?
@ -1718,7 +1719,7 @@ public class Attr extends JCTree.Visitor {
} else if (!labels.add(sym)) {
log.error(pat.pos(), Errors.DuplicateCaseLabel);
} else {
checkCaseLabelDominated(pat.pos(), coveredTypes, sym.type);
checkCaseLabelDominated(pat.pos(), coveredTypesForConstants, sym.type);
}
} else if (errorEnumSwitch) {
//error recovery: the selector is erroneous, and all the case labels
@ -1751,7 +1752,7 @@ public class Attr extends JCTree.Visitor {
} else if (!labels.add(pattype.constValue())) {
log.error(c.pos(), Errors.DuplicateCaseLabel);
} else {
checkCaseLabelDominated(pat.pos(), coveredTypes, types.boxedTypeOrType(pattype));
checkCaseLabelDominated(pat.pos(), coveredTypesForConstants, types.boxedTypeOrType(pattype));
}
}
}
@ -1784,9 +1785,12 @@ public class Attr extends JCTree.Visitor {
}
hasTotalPattern = true;
}
checkCaseLabelDominated(pat.pos(), coveredTypes, patternType);
if (primary.unconditional() && !patternType.isErroneous()) {
coveredTypes = coveredTypes.prepend(patternType);
checkCaseLabelDominated(pat.pos(), coveredTypesForPatterns, patternType);
if (!patternType.isErroneous()) {
coveredTypesForConstants = coveredTypesForConstants.prepend(patternType);
if (primary.unconditional()) {
coveredTypesForPatterns = coveredTypesForPatterns.prepend(patternType);
}
}
}
currentBindings = matchBindingsComputer.switchCase(pat, currentBindings, matchBindings);

@ -751,7 +751,7 @@ public class Flow {
}
}
private void transitiveCovers(Set<Symbol> covered) {
private void transitiveCovers(Type seltype, Set<Symbol> covered) {
List<Symbol> todo = List.from(covered);
while (todo.nonEmpty()) {
Symbol sym = todo.head;
@ -773,7 +773,7 @@ public class Flow {
case TYP -> {
for (Type sup : types.directSupertypes(sym.type)) {
if (sup.tsym.kind == TYP) {
if (isTransitivelyCovered(sup.tsym, covered) &&
if (isTransitivelyCovered(seltype, sup.tsym, covered) &&
covered.add(sup.tsym)) {
todo = todo.prepend(sup.tsym);
}
@ -784,7 +784,7 @@ public class Flow {
}
}
private boolean isTransitivelyCovered(Symbol sealed, Set<Symbol> covered) {
private boolean isTransitivelyCovered(Type seltype, Symbol sealed, Set<Symbol> covered) {
DeferredCompletionFailureHandler.Handler prevHandler =
dcfh.setHandler(dcfh.speculativeCodeHandler);
try {
@ -793,7 +793,10 @@ public class Flow {
if (sealed.kind == TYP && sealed.isAbstract() && sealed.isSealed()) {
return ((ClassSymbol) sealed).permitted
.stream()
.allMatch(s -> isTransitivelyCovered(s, covered));
.filter(s -> {
return types.isCastable(seltype, s.type/*, types.noWarnings*/);
})
.allMatch(s -> isTransitivelyCovered(seltype, s, covered));
}
return false;
} catch (CompletionFailure cf) {
@ -805,7 +808,7 @@ public class Flow {
}
private boolean isExhaustive(Type seltype, Set<Symbol> covered) {
transitiveCovers(covered);
transitiveCovers(seltype, covered);
return switch (seltype.getTag()) {
case CLASS -> {
if (seltype.isCompound()) {

@ -68,6 +68,20 @@ public class Domination {
}
}
int testDominatesStringConstant2(String str) {
switch (str) {
case (String s && s.isEmpty()): return 1;
case "": return -1;
}
}
int testDominatesStringConstant3(String str) {
switch (str) {
case (String s && !s.isEmpty()): return 1;
case "": return -1;
}
}
int testDominatesIntegerConstant(Integer i) {
switch (i) {
case Integer j: return 1;
@ -75,6 +89,20 @@ public class Domination {
}
}
int testDominatesIntegerConstant2(Integer i) {
switch (i) {
case (Integer j && j == 0): return 1;
case 0: return -1;
}
}
int testDominatesIntegerConstant3(Integer i) {
switch (i) {
case (Integer j && j == 1): return 1;
case 0: return -1;
}
}
int testDominatesEnumConstant() {
enum E {
A, B;
@ -86,4 +114,26 @@ public class Domination {
}
}
int testDominatesEnumConstant2() {
enum E {
A, B;
}
E e = E.A;
switch (e) {
case (E d && d == E.A): return 1;
case A: return -1;
}
}
int testDominatesEnumConstant3() {
enum E {
A, B;
}
E e = E.A;
switch (e) {
case (E d && d == E.B): return 1;
case A: return -1;
}
}
}

@ -3,7 +3,13 @@ Domination.java:43:18: compiler.err.pattern.dominated
Domination.java:51:18: compiler.err.pattern.dominated
Domination.java:67:18: compiler.err.pattern.dominated
Domination.java:74:18: compiler.err.pattern.dominated
Domination.java:85:18: compiler.err.pattern.dominated
Domination.java:81:18: compiler.err.pattern.dominated
Domination.java:88:18: compiler.err.pattern.dominated
Domination.java:95:18: compiler.err.pattern.dominated
Domination.java:102:18: compiler.err.pattern.dominated
Domination.java:113:18: compiler.err.pattern.dominated
Domination.java:124:18: compiler.err.pattern.dominated
Domination.java:135:18: compiler.err.pattern.dominated
- compiler.note.preview.filename: Domination.java, DEFAULT
- compiler.note.preview.recompile
6 errors
12 errors

@ -52,8 +52,8 @@ public class EnumTypeChanges {
String statementEnum(EnumTypeChangesEnum e) {
switch (e) {
case A -> { return "A"; }
case EnumTypeChangesEnum e1 && false -> throw new AssertionError();
case B -> { return "B"; }
case EnumTypeChangesEnum e1 && false -> throw new AssertionError();
default -> { return "D"; }
}
}
@ -61,8 +61,8 @@ public class EnumTypeChanges {
String expressionEnum(EnumTypeChangesEnum e) {
return switch (e) {
case A -> "A";
case EnumTypeChangesEnum e1 && false -> throw new AssertionError();
case B -> "B";
case EnumTypeChangesEnum e1 && false -> throw new AssertionError();
default -> "D";
};
}

@ -799,6 +799,94 @@ public class Exhaustiveness extends TestRunner {
""");
}
@Test
public void testOnlyApplicable(Path base) throws Exception {
record TestCase(String cases, String... errors) {}
TestCase[] subCases = new TestCase[] {
new TestCase("""
case C3<Integer> c -> {}
case C5<Integer, ?> c -> {}
case C6<?, Integer> c -> {}
"""), //OK
new TestCase("""
case C5<Integer, ?> c -> {}
case C6<?, Integer> c -> {}
""",
"Test.java:11:9: compiler.err.not.exhaustive.statement",
"- compiler.note.preview.filename: Test.java, DEFAULT",
"- compiler.note.preview.recompile",
"1 error"),
new TestCase("""
case C3<Integer> c -> {}
case C6<?, Integer> c -> {}
""",
"Test.java:11:9: compiler.err.not.exhaustive.statement",
"- compiler.note.preview.filename: Test.java, DEFAULT",
"- compiler.note.preview.recompile",
"1 error"),
new TestCase("""
case C3<Integer> c -> {}
case C5<Integer, ?> c -> {}
""",
"Test.java:11:9: compiler.err.not.exhaustive.statement",
"- compiler.note.preview.filename: Test.java, DEFAULT",
"- compiler.note.preview.recompile",
"1 error"),
new TestCase("""
case C1 c -> {}
case C3<Integer> c -> {}
case C5<Integer, ?> c -> {}
case C6<?, Integer> c -> {}
""",
"Test.java:12:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: test.Test.I<java.lang.Integer>, test.Test.C1)",
"- compiler.note.preview.filename: Test.java, DEFAULT",
"- compiler.note.preview.recompile",
"1 error"),
new TestCase("""
case C2<?> c -> {}
case C3<Integer> c -> {}
case C5<Integer, ?> c -> {}
case C6<?, Integer> c -> {}
""",
"Test.java:12:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: test.Test.I<java.lang.Integer>, test.Test.C2<?>)",
"- compiler.note.preview.filename: Test.java, DEFAULT",
"- compiler.note.preview.recompile",
"1 error"),
new TestCase("""
case C4<?, ?> c -> {}
case C3<Integer> c -> {}
case C5<Integer, ?> c -> {}
case C6<?, Integer> c -> {}
""",
"Test.java:12:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: test.Test.I<java.lang.Integer>, test.Test.C4<?,?>)",
"- compiler.note.preview.filename: Test.java, DEFAULT",
"- compiler.note.preview.recompile",
"1 error"),
};
for (TestCase tc : subCases) {
doTest(base,
new String[0],
"""
package test;
public class Test {
sealed interface I<T> {}
final class C1 implements I<String> {}
final class C2<T> implements I<String> {}
final class C3<T> implements I<T> {}
final class C4<T, E> implements I<String> {}
final class C5<T, E> implements I<T> {}
final class C6<T, E> implements I<E> {}
void t(I<Integer> i) {
switch (i) {
${cases}
}
}
}
""".replace("${cases}", tc.cases),
tc.errors);
}
}
private void doTest(Path base, String[] libraryCode, String testCode, String... expectedErrors) throws IOException {
Path current = base.resolve(".");
Path libClasses = current.resolve("libClasses");

@ -185,7 +185,16 @@ public class SwitchErrors {
default -> null;
};
}
void test8269146a(Integer i) {
void test8269146a1(Integer i) {
switch (i) {
//error - illegal combination of pattern and constant:
case 1, Integer o && o != null:
break;
default:
break;
}
}
void test8269146a2(Integer i) {
switch (i) {
//error - illegal combination of pattern and constant:
case Integer o && o != null, 1:
@ -210,7 +219,14 @@ public class SwitchErrors {
break;
}
}
void test8269301(Integer i) {
void test8269301a(Integer i) {
switch (i) {
//error - illegal combination of pattern, constant and default
case 1, Integer o && o != null, default:
break;
}
}
void test8269301b(Integer i) {
switch (i) {
//error - illegal combination of pattern, constant and default
case Integer o && o != null, 1, default:

@ -31,12 +31,15 @@ SwitchErrors.java:160:18: compiler.err.pattern.dominated
SwitchErrors.java:172:18: compiler.err.pattern.expected
SwitchErrors.java:178:76: compiler.err.cant.resolve.location: kindname.variable, n, , , (compiler.misc.location: kindname.class, SwitchErrors, null)
SwitchErrors.java:184:71: compiler.err.cant.resolve.location: kindname.variable, n, , , (compiler.misc.location: kindname.class, SwitchErrors, null)
SwitchErrors.java:191:42: compiler.err.flows.through.from.pattern
SwitchErrors.java:200:24: compiler.err.flows.through.to.pattern
SwitchErrors.java:209:29: compiler.err.total.pattern.and.default
SwitchErrors.java:216:42: compiler.err.flows.through.from.pattern
SwitchErrors.java:216:45: compiler.err.flows.through.from.pattern
SwitchErrors.java:228:18: compiler.err.duplicate.total.pattern
SwitchErrors.java:191:21: compiler.err.flows.through.to.pattern
SwitchErrors.java:200:42: compiler.err.pattern.dominated
SwitchErrors.java:209:24: compiler.err.flows.through.to.pattern
SwitchErrors.java:218:29: compiler.err.total.pattern.and.default
SwitchErrors.java:225:21: compiler.err.flows.through.to.pattern
SwitchErrors.java:225:45: compiler.err.flows.through.from.pattern
SwitchErrors.java:232:42: compiler.err.pattern.dominated
SwitchErrors.java:232:45: compiler.err.flows.through.from.pattern
SwitchErrors.java:244:18: compiler.err.duplicate.total.pattern
SwitchErrors.java:9:9: compiler.err.not.exhaustive.statement
SwitchErrors.java:15:9: compiler.err.not.exhaustive.statement
SwitchErrors.java:21:9: compiler.err.not.exhaustive.statement
@ -48,7 +51,7 @@ SwitchErrors.java:91:9: compiler.err.not.exhaustive.statement
SwitchErrors.java:97:9: compiler.err.not.exhaustive.statement
SwitchErrors.java:104:9: compiler.err.not.exhaustive.statement
SwitchErrors.java:164:9: compiler.err.not.exhaustive.statement
SwitchErrors.java:221:9: compiler.err.not.exhaustive.statement
SwitchErrors.java:237:9: compiler.err.not.exhaustive.statement
- compiler.note.preview.filename: SwitchErrors.java, DEFAULT
- compiler.note.preview.recompile
51 errors
54 errors

@ -265,8 +265,8 @@ public class Switches {
switch (e) {
case A: return "a";
case B: return "b";
case E x && "A".equals(x.name()): return "broken";
case C: return String.valueOf(e);
case E x && "A".equals(x.name()): return "broken";
case null, E x: return String.valueOf(x);
}
}
@ -275,8 +275,8 @@ public class Switches {
return switch (e) {
case A -> "a";
case B -> "b";
case E x && "A".equals(x.name()) -> "broken";
case C -> String.valueOf(e);
case E x && "A".equals(x.name()) -> "broken";
case null, E x -> String.valueOf(x);
};
}
@ -286,8 +286,7 @@ public class Switches {
case A: return "a";
case B: return "b";
case E x && "C".equals(x.name()): return "C";
case C: return "broken";
case null, E x: return String.valueOf(x);
case null, E x: return e == E.C ? "broken" : String.valueOf(x);
}
}
@ -296,8 +295,7 @@ public class Switches {
case A -> "a";
case B -> "b";
case E x && "C".equals(x.name()) -> "C";
case C -> "broken";
case null, E x -> String.valueOf(x);
case null, E x -> e == E.C ? "broken" : String.valueOf(x);
};
}
@ -306,8 +304,7 @@ public class Switches {
case A: return "a";
case B: return "b";
case Object x && "C".equals(x.toString()): return "C";
case C: return "broken";
case null, E x: return String.valueOf(x);
case null, E x: return e == E.C ? "broken" : String.valueOf(x);
}
}
@ -316,8 +313,7 @@ public class Switches {
case A -> "a";
case B -> "b";
case Object x && "C".equals(x.toString()) -> "C";
case C -> "broken";
case null, E x -> String.valueOf(x);
case null, E x -> e == E.C ? "broken" : String.valueOf(x);
};
}
@ -326,8 +322,7 @@ public class Switches {
case A: return "a";
case B: return "b";
case Runnable x && "C".equals(x.toString()): return "C";
case C: return "broken";
case null, E x: return String.valueOf(x);
case null, E x: return e == E.C ? "broken" : String.valueOf(x);
}
}
@ -336,8 +331,7 @@ public class Switches {
case A -> "a";
case B -> "b";
case Runnable x && "C".equals(x.toString()) -> "C";
case C -> "broken";
case null, E x -> String.valueOf(x);
case null, E x -> e == E.C ? "broken" : String.valueOf(x);
};
}
@ -346,8 +340,7 @@ public class Switches {
case "A": return "a";
case Switches.ConstantClassClash: return "b";
case String x && "C".equals(x): return "C";
case "C": return "broken";
case null, String x: return String.valueOf(x);
case null, String x: return "C".equals(x) ? "broken" : String.valueOf(x);
}
}
@ -356,8 +349,7 @@ public class Switches {
case "A" -> "a";
case ConstantClassClash -> "b";
case String x && "C".equals(x) -> "C";
case "C" -> "broken";
case null, String x -> String.valueOf(x);
case null, String x -> e == E.C ? "broken" : String.valueOf(x);
};
}
@ -366,8 +358,7 @@ public class Switches {
case 0: return "a";
case 1: return "b";
case Integer x && x.equals(2): return "C";
case 2: return "broken";
case null, Integer x: return String.valueOf(x);
case null, Integer x: return Objects.equals(x, 2) ? "broken" : String.valueOf(x);
}
}
@ -376,8 +367,7 @@ public class Switches {
case 0 -> "a";
case 1 -> "b";
case Integer x && x.equals(2) -> "C";
case 2 -> "broken";
case null, Integer x -> String.valueOf(x);
case null, Integer x -> Objects.equals(x, 2) ? "broken" : String.valueOf(x);
};
}
@ -412,7 +402,6 @@ public class Switches {
switch (i) {
case Integer o && o != null:
r = 1;
case -1: r = 1;
case null, default:
r = 2;
}
@ -424,7 +413,6 @@ public class Switches {
int r = switch (i) {
case Integer o && o != null:
r = 1;
case -1: r = 1;
case null, default:
r = 2;
yield r;