8277105: Inconsistent handling of missing permitted subclasses
Reviewed-by: vromero
This commit is contained in:
parent
adf39522c1
commit
ab781874b2
src/jdk.compiler/share/classes/com/sun/tools/javac/comp
test/langtools/tools/javac
@ -207,7 +207,6 @@ public class Flow {
|
||||
private final JCDiagnostic.Factory diags;
|
||||
private Env<AttrContext> attrEnv;
|
||||
private Lint lint;
|
||||
private final DeferredCompletionFailureHandler dcfh;
|
||||
private final boolean allowEffectivelyFinalInInnerClasses;
|
||||
|
||||
public static Flow instance(Context context) {
|
||||
@ -332,7 +331,6 @@ public class Flow {
|
||||
lint = Lint.instance(context);
|
||||
rs = Resolve.instance(context);
|
||||
diags = JCDiagnostic.Factory.instance(context);
|
||||
dcfh = DeferredCompletionFailureHandler.instance(context);
|
||||
Source source = Source.instance(context);
|
||||
allowEffectivelyFinalInInnerClasses = Feature.EFFECTIVELY_FINAL_IN_INNER_CLASSES.allowedInSource(source);
|
||||
}
|
||||
@ -693,7 +691,7 @@ public class Flow {
|
||||
}
|
||||
if (!tree.hasTotalPattern && exhaustiveSwitch &&
|
||||
!TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) &&
|
||||
(constants == null || !isExhaustive(tree.selector.type, constants))) {
|
||||
(constants == null || !isExhaustive(tree.selector.pos(), tree.selector.type, constants))) {
|
||||
log.error(tree, Errors.NotExhaustiveStatement);
|
||||
}
|
||||
if (!tree.hasTotalPattern) {
|
||||
@ -728,7 +726,7 @@ public class Flow {
|
||||
}
|
||||
}
|
||||
if (!tree.hasTotalPattern && !TreeInfo.isErrorEnumSwitch(tree.selector, tree.cases) &&
|
||||
!isExhaustive(tree.selector.type, constants)) {
|
||||
!isExhaustive(tree.selector.pos(), tree.selector.type, constants)) {
|
||||
log.error(tree, Errors.NotExhaustive);
|
||||
}
|
||||
alive = prevAlive;
|
||||
@ -751,7 +749,7 @@ public class Flow {
|
||||
}
|
||||
}
|
||||
|
||||
private void transitiveCovers(Type seltype, Set<Symbol> covered) {
|
||||
private void transitiveCovers(DiagnosticPosition pos, Type seltype, Set<Symbol> covered) {
|
||||
List<Symbol> todo = List.from(covered);
|
||||
while (todo.nonEmpty()) {
|
||||
Symbol sym = todo.head;
|
||||
@ -773,7 +771,7 @@ public class Flow {
|
||||
case TYP -> {
|
||||
for (Type sup : types.directSupertypes(sym.type)) {
|
||||
if (sup.tsym.kind == TYP) {
|
||||
if (isTransitivelyCovered(seltype, sup.tsym, covered) &&
|
||||
if (isTransitivelyCovered(pos, seltype, sup.tsym, covered) &&
|
||||
covered.add(sup.tsym)) {
|
||||
todo = todo.prepend(sup.tsym);
|
||||
}
|
||||
@ -784,9 +782,8 @@ public class Flow {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTransitivelyCovered(Type seltype, Symbol sealed, Set<Symbol> covered) {
|
||||
DeferredCompletionFailureHandler.Handler prevHandler =
|
||||
dcfh.setHandler(dcfh.speculativeCodeHandler);
|
||||
private boolean isTransitivelyCovered(DiagnosticPosition pos, Type seltype,
|
||||
Symbol sealed, Set<Symbol> covered) {
|
||||
try {
|
||||
if (covered.stream().anyMatch(c -> sealed.isSubClass(c, types)))
|
||||
return true;
|
||||
@ -796,30 +793,30 @@ public class Flow {
|
||||
.filter(s -> {
|
||||
return types.isCastable(seltype, s.type/*, types.noWarnings*/);
|
||||
})
|
||||
.allMatch(s -> isTransitivelyCovered(seltype, s, covered));
|
||||
.allMatch(s -> isTransitivelyCovered(pos, seltype, s, covered));
|
||||
}
|
||||
return false;
|
||||
} catch (CompletionFailure cf) {
|
||||
//safe to ignore, the symbol will be un-completed when the speculative handler is removed.
|
||||
return false;
|
||||
} finally {
|
||||
dcfh.setHandler(prevHandler);
|
||||
chk.completionError(pos, cf);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isExhaustive(Type seltype, Set<Symbol> covered) {
|
||||
transitiveCovers(seltype, covered);
|
||||
private boolean isExhaustive(DiagnosticPosition pos, Type seltype, Set<Symbol> covered) {
|
||||
transitiveCovers(pos, seltype, 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 ((Type.IntersectionClassType) seltype).getComponents()
|
||||
.stream()
|
||||
.anyMatch(t -> isExhaustive(pos, t, covered));
|
||||
}
|
||||
yield false;
|
||||
}
|
||||
yield covered.contains(seltype.tsym);
|
||||
}
|
||||
case TYPEVAR -> isExhaustive(((TypeVar) seltype).getUpperBound(), covered);
|
||||
case TYPEVAR -> isExhaustive(pos, ((TypeVar) seltype).getUpperBound(), covered);
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
@ -369,102 +369,6 @@ public class Exhaustiveness extends TestRunner {
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInaccessiblePermitted(Path base) throws IOException {
|
||||
Path current = base.resolve(".");
|
||||
Path libSrc = current.resolve("lib-src");
|
||||
|
||||
tb.writeJavaFiles(libSrc,
|
||||
"""
|
||||
package lib;
|
||||
public sealed interface S permits A, B {}
|
||||
""",
|
||||
"""
|
||||
package lib;
|
||||
public final class A implements S {}
|
||||
""",
|
||||
"""
|
||||
package lib;
|
||||
final class B implements S {}
|
||||
""");
|
||||
|
||||
Path libClasses = current.resolve("libClasses");
|
||||
|
||||
Files.createDirectories(libClasses);
|
||||
|
||||
new JavacTask(tb)
|
||||
.options("--enable-preview",
|
||||
"-source", JAVA_VERSION)
|
||||
.outdir(libClasses)
|
||||
.files(tb.findJavaFiles(libSrc))
|
||||
.run();
|
||||
|
||||
Path src = current.resolve("src");
|
||||
tb.writeJavaFiles(src,
|
||||
"""
|
||||
package test;
|
||||
import lib.*;
|
||||
public class Test {
|
||||
private int test(S obj) {
|
||||
return switch (obj) {
|
||||
case A a -> 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Path classes = current.resolve("libClasses");
|
||||
|
||||
Files.createDirectories(libClasses);
|
||||
|
||||
var log =
|
||||
new JavacTask(tb)
|
||||
.options("--enable-preview",
|
||||
"-source", JAVA_VERSION,
|
||||
"-XDrawDiagnostics",
|
||||
"-Xlint:-preview",
|
||||
"--class-path", libClasses.toString())
|
||||
.outdir(classes)
|
||||
.files(tb.findJavaFiles(src))
|
||||
.run(Task.Expect.FAIL)
|
||||
.writeAll()
|
||||
.getOutputLines(Task.OutputKind.DIRECT);
|
||||
|
||||
List<String> expectedErrors = List.of(
|
||||
"Test.java:5:16: compiler.err.not.exhaustive",
|
||||
"- compiler.note.preview.filename: Test.java, DEFAULT",
|
||||
"- compiler.note.preview.recompile",
|
||||
"1 error");
|
||||
|
||||
if (!expectedErrors.equals(log)) {
|
||||
throw new AssertionError("Incorrect errors, expected: " + expectedErrors +
|
||||
", actual: " + log);
|
||||
}
|
||||
|
||||
Path bClass = libClasses.resolve("lib").resolve("B.class");
|
||||
|
||||
Files.delete(bClass);
|
||||
|
||||
var log2 =
|
||||
new JavacTask(tb)
|
||||
.options("--enable-preview",
|
||||
"-source", JAVA_VERSION,
|
||||
"-XDrawDiagnostics",
|
||||
"-Xlint:-preview",
|
||||
"--class-path", libClasses.toString())
|
||||
.outdir(classes)
|
||||
.files(tb.findJavaFiles(src))
|
||||
.run(Task.Expect.FAIL)
|
||||
.writeAll()
|
||||
.getOutputLines(Task.OutputKind.DIRECT);
|
||||
|
||||
if (!expectedErrors.equals(log2)) {
|
||||
throw new AssertionError("Incorrect errors, expected: " + expectedErrors +
|
||||
", actual: " + log2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExhaustiveStatement1(Path base) throws Exception {
|
||||
doTest(base,
|
||||
|
199
test/langtools/tools/javac/sealed/MissingPermittedSubtypes.java
Normal file
199
test/langtools/tools/javac/sealed/MissingPermittedSubtypes.java
Normal file
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8277105
|
||||
* @summary Verify missing permitted subtype is handled properly for both casts and pattern switches.
|
||||
* @library /tools/lib
|
||||
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* jdk.compiler/com.sun.tools.javac.util
|
||||
* @build toolbox.ToolBox toolbox.JavacTask
|
||||
* @run main MissingPermittedSubtypes
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import toolbox.TestRunner;
|
||||
import toolbox.JavacTask;
|
||||
import toolbox.Task;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
public class MissingPermittedSubtypes extends TestRunner {
|
||||
|
||||
private static final String JAVA_VERSION = System.getProperty("java.specification.version");
|
||||
|
||||
ToolBox tb;
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
new MissingPermittedSubtypes().runTests();
|
||||
}
|
||||
|
||||
MissingPermittedSubtypes() {
|
||||
super(System.err);
|
||||
tb = new ToolBox();
|
||||
}
|
||||
|
||||
public void runTests() throws Exception {
|
||||
runTests(m -> new Object[] { Paths.get(m.getName()) });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInaccessiblePermitted(Path base) throws IOException {
|
||||
Path current = base.resolve(".");
|
||||
Path libSrc = current.resolve("lib-src");
|
||||
|
||||
tb.writeJavaFiles(libSrc,
|
||||
"""
|
||||
package lib;
|
||||
public sealed interface S permits A, B1, B2 {}
|
||||
""",
|
||||
"""
|
||||
package lib;
|
||||
public final class A implements S {}
|
||||
""",
|
||||
"""
|
||||
package lib;
|
||||
final class B1 implements S {}
|
||||
""",
|
||||
"""
|
||||
package lib;
|
||||
final class B2 implements S, Runnable {
|
||||
public void run() {}
|
||||
}
|
||||
""");
|
||||
|
||||
Path libClasses = current.resolve("libClasses");
|
||||
|
||||
Files.createDirectories(libClasses);
|
||||
|
||||
new JavacTask(tb)
|
||||
.options("--enable-preview",
|
||||
"-source", JAVA_VERSION)
|
||||
.outdir(libClasses)
|
||||
.files(tb.findJavaFiles(libSrc))
|
||||
.run();
|
||||
|
||||
Path b1Class = libClasses.resolve("lib").resolve("B1.class");
|
||||
|
||||
Files.delete(b1Class);
|
||||
|
||||
Path b2Class = libClasses.resolve("lib").resolve("B2.class");
|
||||
|
||||
Files.delete(b2Class);
|
||||
|
||||
{
|
||||
Path src1 = current.resolve("src1");
|
||||
tb.writeJavaFiles(src1,
|
||||
"""
|
||||
package test;
|
||||
import lib.*;
|
||||
public class Test1 {
|
||||
private void test(S obj) {
|
||||
int i = switch (obj) {
|
||||
case A a -> 0;
|
||||
};
|
||||
Runnable r = () -> {obj = null;};
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Path classes1 = current.resolve("classes1");
|
||||
|
||||
Files.createDirectories(classes1);
|
||||
|
||||
var log =
|
||||
new JavacTask(tb)
|
||||
.options("--enable-preview",
|
||||
"-source", JAVA_VERSION,
|
||||
"-XDrawDiagnostics",
|
||||
"-Xlint:-preview",
|
||||
"--class-path", libClasses.toString())
|
||||
.outdir(classes1)
|
||||
.files(tb.findJavaFiles(src1))
|
||||
.run(Task.Expect.FAIL)
|
||||
.writeAll()
|
||||
.getOutputLines(Task.OutputKind.DIRECT);
|
||||
|
||||
List<String> expectedErrors = List.of(
|
||||
"Test1.java:5:24: compiler.err.cant.access: lib.B1, (compiler.misc.class.file.not.found: lib.B1)",
|
||||
"Test1.java:8:29: compiler.err.cant.ref.non.effectively.final.var: obj, (compiler.misc.lambda)",
|
||||
"- compiler.note.preview.filename: Test1.java, DEFAULT",
|
||||
"- compiler.note.preview.recompile",
|
||||
"2 errors");
|
||||
|
||||
if (!expectedErrors.equals(log)) {
|
||||
throw new AssertionError("Incorrect errors, expected: " + expectedErrors +
|
||||
", actual: " + log);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Path src2 = current.resolve("src2");
|
||||
tb.writeJavaFiles(src2,
|
||||
"""
|
||||
package test;
|
||||
import lib.*;
|
||||
public class Test1 {
|
||||
private void test(S obj) {
|
||||
Runnable r = (Runnable) obj;
|
||||
String s = (String) obj;
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Path classes2 = current.resolve("classes2");
|
||||
|
||||
Files.createDirectories(classes2);
|
||||
|
||||
var log =
|
||||
new JavacTask(tb)
|
||||
.options("--enable-preview",
|
||||
"-source", JAVA_VERSION,
|
||||
"-XDrawDiagnostics",
|
||||
"-Xlint:-preview",
|
||||
"--class-path", libClasses.toString())
|
||||
.outdir(classes2)
|
||||
.files(tb.findJavaFiles(src2))
|
||||
.run(Task.Expect.FAIL)
|
||||
.writeAll()
|
||||
.getOutputLines(Task.OutputKind.DIRECT);
|
||||
|
||||
List<String> expectedErrors = List.of(
|
||||
"Test1.java:5:19: compiler.err.cant.access: lib.B1, (compiler.misc.class.file.not.found: lib.B1)",
|
||||
"Test1.java:6:26: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: lib.S, java.lang.String)",
|
||||
"2 errors");
|
||||
|
||||
if (!expectedErrors.equals(log)) {
|
||||
throw new AssertionError("Incorrect errors, expected: " + expectedErrors +
|
||||
", actual: " + log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user