Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8277105: Inconsistent handling of missing permitted subclasses #6418

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 15 additions & 18 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java
Expand Up @@ -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) {
Expand Down Expand Up @@ -332,7 +331,6 @@ protected Flow(Context context) {
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);
}
Expand Down Expand Up @@ -693,7 +691,7 @@ public void visitSwitch(JCSwitch tree) {
}
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) {
Expand Down Expand Up @@ -728,7 +726,7 @@ public void visitSwitchExpression(JCSwitchExpression tree) {
}
}
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;
Expand All @@ -751,7 +749,7 @@ private void handleConstantCaseLabel(Set<Symbol> constants, JCCaseLabel pat) {
}
}

private void transitiveCovers(Set<Symbol> covered) {
private void transitiveCovers(DiagnosticPosition pos, Set<Symbol> covered) {
List<Symbol> todo = List.from(covered);
while (todo.nonEmpty()) {
Symbol sym = todo.head;
Expand All @@ -773,7 +771,7 @@ private void transitiveCovers(Set<Symbol> covered) {
case TYP -> {
for (Type sup : types.directSupertypes(sym.type)) {
if (sup.tsym.kind == TYP) {
if (isTransitivelyCovered(sup.tsym, covered) &&
if (isTransitivelyCovered(pos, sup.tsym, covered) &&
covered.add(sup.tsym)) {
todo = todo.prepend(sup.tsym);
}
Expand All @@ -784,39 +782,38 @@ private void transitiveCovers(Set<Symbol> covered) {
}
}

private boolean isTransitivelyCovered(Symbol sealed, Set<Symbol> covered) {
DeferredCompletionFailureHandler.Handler prevHandler =
dcfh.setHandler(dcfh.speculativeCodeHandler);
private boolean isTransitivelyCovered(DiagnosticPosition pos, Symbol sealed,
Set<Symbol> covered) {
try {
if (covered.stream().anyMatch(c -> sealed.isSubClass(c, types)))
return true;
if (sealed.kind == TYP && sealed.isAbstract() && sealed.isSealed()) {
return ((ClassSymbol) sealed).permitted
.stream()
.allMatch(s -> isTransitivelyCovered(s, covered));
.allMatch(s -> isTransitivelyCovered(pos, 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(covered);
private boolean isExhaustive(DiagnosticPosition pos, Type seltype, Set<Symbol> covered) {
transitiveCovers(pos, 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;
};
}
Expand Down
96 changes: 0 additions & 96 deletions test/langtools/tools/javac/patterns/Exhaustiveness.java
Expand Up @@ -369,102 +369,6 @@ private int test(S obj) {
""");
}

@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,
Expand Down