Skip to content

Commit 96eed7f

Browse files
author
Vicente Romero
committed
8343306: javac is failing to determine if a class and a sealed interface are disjoint
Reviewed-by: jlahoda, mcimadamore
1 parent 0c281ac commit 96eed7f

File tree

2 files changed

+69
-24
lines changed

2 files changed

+69
-24
lines changed

src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,39 +1672,51 @@ public boolean isCastable(Type t, Type s, Warner warn) {
16721672
// where
16731673
class DisjointChecker {
16741674
Set<Pair<ClassSymbol, ClassSymbol>> pairsSeen = new HashSet<>();
1675+
/* there are three cases for ts and ss:
1676+
* - one is a class and the other one is an interface (case I)
1677+
* - both are classes (case II)
1678+
* - both are interfaces (case III)
1679+
* all those cases are covered in JLS 23, section: "5.1.6.1 Allowed Narrowing Reference Conversion"
1680+
*/
16751681
private boolean areDisjoint(ClassSymbol ts, ClassSymbol ss) {
16761682
Pair<ClassSymbol, ClassSymbol> newPair = new Pair<>(ts, ss);
16771683
/* if we are seeing the same pair again then there is an issue with the sealed hierarchy
16781684
* bail out, a detailed error will be reported downstream
16791685
*/
16801686
if (!pairsSeen.add(newPair))
16811687
return false;
1682-
if (isSubtype(erasure(ts.type), erasure(ss.type))) {
1683-
return false;
1684-
}
1685-
// if both are classes or both are interfaces, shortcut
1686-
if (ts.isInterface() == ss.isInterface() && isSubtype(erasure(ss.type), erasure(ts.type))) {
1687-
return false;
1688-
}
1689-
if (ts.isInterface() && !ss.isInterface()) {
1690-
/* so ts is interface but ss is a class
1691-
* an interface is disjoint from a class if the class is disjoint form the interface
1692-
*/
1693-
return areDisjoint(ss, ts);
1694-
}
1695-
// a final class that is not subtype of ss is disjoint
1696-
if (!ts.isInterface() && ts.isFinal()) {
1697-
return true;
1698-
}
1699-
// if at least one is sealed
1700-
if (ts.isSealed() || ss.isSealed()) {
1701-
// permitted subtypes have to be disjoint with the other symbol
1702-
ClassSymbol sealedOne = ts.isSealed() ? ts : ss;
1703-
ClassSymbol other = sealedOne == ts ? ss : ts;
1704-
return sealedOne.getPermittedSubclasses().stream().allMatch(type -> areDisjoint((ClassSymbol)type.tsym, other));
1688+
1689+
if (ts.isInterface() != ss.isInterface()) { // case I: one is a class and the other one is an interface
1690+
ClassSymbol isym = ts.isInterface() ? ts : ss; // isym is the interface and csym the class
1691+
ClassSymbol csym = isym == ts ? ss : ts;
1692+
if (!isSubtype(erasure(csym.type), erasure(isym.type))) {
1693+
if (csym.isFinal()) {
1694+
return true;
1695+
} else if (csym.isSealed()) {
1696+
return areDisjoint(isym, csym.getPermittedSubclasses());
1697+
} else if (isym.isSealed()) {
1698+
// if the class is not final and not sealed then it has to be freely extensible
1699+
return areDisjoint(csym, isym.getPermittedSubclasses());
1700+
}
1701+
} // now both are classes or both are interfaces
1702+
} else if (!ts.isInterface()) { // case II: both are classes
1703+
return !isSubtype(erasure(ss.type), erasure(ts.type)) && !isSubtype(erasure(ts.type), erasure(ss.type));
1704+
} else { // case III: both are interfaces
1705+
if (!isSubtype(erasure(ts.type), erasure(ss.type)) && !isSubtype(erasure(ss.type), erasure(ts.type))) {
1706+
if (ts.isSealed()) {
1707+
return areDisjoint(ss, ts.getPermittedSubclasses());
1708+
} else if (ss.isSealed()) {
1709+
return areDisjoint(ts, ss.getPermittedSubclasses());
1710+
}
1711+
}
17051712
}
1713+
// at this point we haven't been able to statically prove that the classes or interfaces are disjoint
17061714
return false;
17071715
}
1716+
1717+
boolean areDisjoint(ClassSymbol csym, List<Type> permittedSubtypes) {
1718+
return permittedSubtypes.stream().allMatch(psubtype -> areDisjoint(csym, (ClassSymbol) psubtype.tsym));
1719+
}
17081720
}
17091721

17101722
private TypeRelation isCastable = new TypeRelation() {

test/langtools/tools/javac/sealed/SealedCompilationTests.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -1178,6 +1178,39 @@ void test () {
11781178
I[] i = (I[]) c;
11791179
}
11801180
}
1181+
""",
1182+
"""
1183+
class Test {
1184+
sealed interface I permits C1 {}
1185+
non-sealed class C1 implements I {}
1186+
class C2 extends C1 {}
1187+
class C3 {}
1188+
I m(int s, C3 c3) {
1189+
I i = (I)c3;
1190+
}
1191+
}
1192+
""",
1193+
"""
1194+
class Test {
1195+
sealed interface I permits C1 {}
1196+
non-sealed class C1 implements I {}
1197+
class C2 extends C1 {}
1198+
class C3 {}
1199+
I m(int s, C3 c3) {
1200+
I i = (C1)c3;
1201+
}
1202+
}
1203+
""",
1204+
"""
1205+
class Test {
1206+
sealed interface I permits C1 {}
1207+
non-sealed class C1 implements I {}
1208+
class C2 extends C1 {}
1209+
class C3 {}
1210+
I m(int s, C3 c3) {
1211+
I i = (C2)c3;
1212+
}
1213+
}
11811214
"""
11821215
)) {
11831216
assertFail("compiler.err.prob.found.req", s);

0 commit comments

Comments
 (0)