diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java index 7a8067ce983..3b66ac010cf 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java @@ -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 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 pendingPermitted = new HashSet<>(permitted); + for (PatternDescription pdOther : patterns) { if (pdOther instanceof BindingPattern bpOther) { - Set currentPermittedSubTypes = - allPermittedSubTypes(bpOther.type.tsym, s -> true); + //remove all types from pendingPermitted that we can + //cover using bpOther: - PERMITTED: for (Iterator 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 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)); } } diff --git a/test/langtools/tools/javac/patterns/Exhaustiveness.java b/test/langtools/tools/javac/patterns/Exhaustiveness.java index 3f86ada6e7e..f328765b579 100644 --- a/test/langtools/tools/javac/patterns/Exhaustiveness.java +++ b/test/langtools/tools/javac/patterns/Exhaustiveness.java @@ -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 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 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); }