diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraints.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraints.java
index a7d0507c83f0..161a5dc78cdd 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraints.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/TypeConstraints.java
@@ -13,6 +13,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
+import static com.intellij.psi.util.TypeConversionUtil.canConvertSealedTo;
+
public final class TypeConstraints {
/**
* Top constraint (no restriction; any non-primitive value satisfies this)
@@ -374,6 +376,8 @@ public final class TypeConstraints {
}
if (other instanceof ExactClass) {
PsiClass otherClass = ((ExactClass)other).myClass;
+ if (otherClass.hasModifierProperty(PsiModifier.SEALED)) return canConvertSealedTo(otherClass, myClass);
+ if (myClass.hasModifierProperty(PsiModifier.SEALED)) return canConvertSealedTo(myClass, otherClass);
if (myClass.isInterface() && otherClass.isInterface()) return true;
if (myClass.isInterface() && !otherClass.hasModifierProperty(PsiModifier.FINAL)) return true;
if (otherClass.isInterface() && !myClass.hasModifierProperty(PsiModifier.FINAL)) return true;
diff --git a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java
index fb2502412190..e64213fcd596 100644
--- a/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java
+++ b/java/java-psi-api/src/com/intellij/psi/util/TypeConversionUtil.java
@@ -11,6 +11,7 @@ import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.IElementType;
+import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
@@ -252,6 +253,15 @@ public final class TypeConversionUtil {
PsiManager manager = fromClass.getManager();
final LanguageLevel languageLevel = toClassType.getLanguageLevel();
+ // jep-397
+ if (languageLevel.isAtLeast(LanguageLevel.JDK_16_PREVIEW)) {
+ if (fromClass.hasModifierProperty(PsiModifier.SEALED)) {
+ if (!canConvertSealedTo(fromClass, toClass)) return false;
+ }
+ else if (toClass.hasModifierProperty(PsiModifier.SEALED)) {
+ if (!canConvertSealedTo(toClass, fromClass)) return false;
+ }
+ }
if (!fromClass.isInterface()) {
if (toClass.isInterface()) {
return (!fromClass.hasModifierProperty(PsiModifier.FINAL) || fromClass.isInheritor(toClass, true)) &&
@@ -320,6 +330,74 @@ public final class TypeConversionUtil {
}
}
+ /**
+ * Check if sealed class can be narrowed down to a given interface.
+ * Sealed class can be narrowed down to an interface in one of the following cases:
+ *
+ * - sealed class implements interface
+ *
- sealed class have at least one non-sealed subclass
+ *
- at least one of final/sealed subclasses of sealed parent implement interface
+ *
+ *
+ * Note that sealed subclasses are checked recursively, e.g. in hierarchy:
+ *
+ *
+ * sealed class Parent {}
+ *
sealed class A extends Parent {}
+ *
final class C extends A {}
+ *
+ * all classes would be checked.
+ *
+ * See JEP-397 for more details.
+ */
+ public static boolean canConvertSealedTo(@NotNull PsiClass sealedClass, @NotNull PsiClass psiClass) {
+ PsiReferenceList permitsList = sealedClass.getPermitsList();
+ List sealedSubClasses = new SmartList<>();
+ boolean hasClassInheritors;
+ if (permitsList == null) {
+ Set subClasses = findDirectSubClassesInFile(sealedClass);
+ hasClassInheritors = subClasses.stream().anyMatch(subClass -> subClassExtendsClass(subClass, psiClass, sealedSubClasses));
+ }
+ else {
+ hasClassInheritors = Arrays.stream(permitsList.getReferencedTypes())
+ .map(t -> t.resolve())
+ .anyMatch(subClass -> subClassExtendsClass(subClass, psiClass, sealedSubClasses));
+ }
+ return hasClassInheritors || sealedSubClasses.stream().anyMatch(subClass -> canConvertSealedTo(subClass, psiClass));
+ }
+
+ private static @NotNull Set findDirectSubClassesInFile(@NotNull PsiClass sealedClass) {
+ Set subClasses = new HashSet<>();
+ sealedClass.getContainingFile().accept(new JavaElementVisitor() {
+ @Override
+ public void visitJavaFile(PsiJavaFile file) {
+ for (PsiClass psiClass : file.getClasses()) {
+ visitClass(psiClass);
+ }
+ }
+
+ @Override
+ public void visitClass(PsiClass psiClass) {
+ for (PsiClass inner : psiClass.getInnerClasses()) {
+ visitClass(inner);
+ }
+ if (psiClass.isInheritor(sealedClass, false)) {
+ subClasses.add(psiClass);
+ }
+ }
+ });
+ return subClasses;
+ }
+
+ private static boolean subClassExtendsClass(@Nullable PsiClass subClass,
+ @NotNull PsiClass psiClass,
+ @NotNull List sealedClasses) {
+ if (subClass == null) return false;
+ if (subClass.hasModifierProperty(PsiModifier.NON_SEALED) || subClass.isInheritor(psiClass, true)) return true;
+ if (subClass.hasModifierProperty(PsiModifier.SEALED)) sealedClasses.add(subClass);
+ return false;
+ }
+
@NotNull
private static PsiClassType obtainSafeSuperType(@NotNull PsiTypeParameter typeParameter) {
final PsiClassType superType = typeParameter.getSuperTypes()[0];
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/SealedClassCast.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/SealedClassCast.java
new file mode 100644
index 000000000000..ff14ac9d4726
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingSealedTypes/SealedClassCast.java
@@ -0,0 +1,79 @@
+interface Foo {
+
+ sealed class A {}
+ final class B extends A {}
+
+ sealed class C {}
+ non-sealed class D extends C {}
+
+ sealed class E {}
+ sealed class F extends E {}
+ final class G extends F implements Foo {}
+
+ sealed class H permits I {}
+ final class I extends H {}
+
+ sealed class J permits K {}
+ non-sealed class K extends J {}
+
+ final class L implements M {}
+ sealed interface M permits L {}
+
+ class N {}
+ sealed interface O permits P {}
+ final class P extends N implements O {}
+
+ class R {}
+ sealed interface S permits T {}
+ final class T extends U implements S {}
+ class U extends R {}
+
+ class V {}
+ sealed interface W permits X {}
+ final class X implements W {}
+
+ static void testA(A a) {
+ if (a instanceof Foo)
+ System.out.println("It's a Foo");
+ }
+
+ static void testC(C c) {
+ if (c instanceof Foo)
+ System.out.println("It's a Foo");
+ }
+
+ static void testE(E e) {
+ if (e instanceof Foo)
+ System.out.println("It's a Foo");
+ }
+
+ static void testH(H h) {
+ if (h instanceof Foo)
+ System.out.println("It's a Foo");
+ }
+
+ static void testJ(J j) {
+ if (j instanceof Foo)
+ System.out.println("It's a Foo");
+ }
+
+ static void testL(L l) {
+ if (l instanceof M)
+ System.out.println("It's a M");
+ }
+
+ static void testN(N n) {
+ if (n instanceof O)
+ System.out.println("It's an O");
+ }
+
+ static void testR(R r) {
+ if (r instanceof S)
+ System.out.println("It's a S");
+ }
+
+ static void testV(V v) {
+ if (v instanceof W)
+ System.out.println("It's a W");
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/CastToSealedInterface.java b/java/java-tests/testData/inspection/dataFlow/fixture/CastToSealedInterface.java
new file mode 100644
index 000000000000..778b4745193a
--- /dev/null
+++ b/java/java-tests/testData/inspection/dataFlow/fixture/CastToSealedInterface.java
@@ -0,0 +1,9 @@
+class Foo {
+ static void test(Foo foo) {
+ if (foo instanceof I)
+ System.out.println("This is a Foo");
+ }
+}
+
+sealed interface I {}
+final class C implements I {}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/SealedClassCast.java b/java/java-tests/testData/inspection/dataFlow/fixture/SealedClassCast.java
new file mode 100644
index 000000000000..69865394be45
--- /dev/null
+++ b/java/java-tests/testData/inspection/dataFlow/fixture/SealedClassCast.java
@@ -0,0 +1,13 @@
+interface Foo {
+
+ sealed class A permits B, C {}
+ final class B extends A {}
+ sealed class C extends A permits D {}
+ final class D extends C {}
+
+ static void test(A a) {
+ if (a instanceof Foo)
+ System.out.println("This is a Foo");
+ }
+
+}
\ No newline at end of file
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java
index c6af04befb1b..275d625201bc 100644
--- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java
+++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightSealedTypesHighlightingTest.java
@@ -15,7 +15,7 @@ public class LightSealedTypesHighlightingTest extends LightJavaCodeInsightFixtur
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
- return JAVA_15;
+ return JAVA_16;
}
public void testSealedTypesBasics() { doTest(); }
@@ -26,6 +26,7 @@ public class LightSealedTypesHighlightingTest extends LightJavaCodeInsightFixtur
myFixture.addClass("package p; public class P extends A {}");
doTest();
}
+ public void testSealedClassCast() { doTest(); }
private void doTest() {
myFixture.configureByFile(getTestName(false) + ".java");
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection14Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection16Test.java
similarity index 84%
rename from java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection14Test.java
rename to java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection16Test.java
index 1d26f46337c4..9c285c4e4dd7 100644
--- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection14Test.java
+++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection16Test.java
@@ -5,11 +5,11 @@ import com.intellij.JavaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
-public class DataFlowInspection14Test extends DataFlowInspectionTestCase {
+public class DataFlowInspection16Test extends DataFlowInspectionTestCase {
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
- return JAVA_15;
+ return JAVA_16;
}
@Override
@@ -27,4 +27,6 @@ public class DataFlowInspection14Test extends DataFlowInspectionTestCase {
}
public void testSwitchExpressionAndLambdaInlining() { doTest(); }
public void testRecordAccessorStability() { doTest(); }
+ public void testSealedClassCast() { doTest(); }
+ public void testCastToSealedInterface() { doTest(); }
}
\ No newline at end of file
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTestSuite.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTestSuite.java
index 27e4299e756d..180161a9678a 100644
--- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTestSuite.java
+++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspectionTestSuite.java
@@ -30,7 +30,7 @@ import org.junit.runners.Suite;
DataFlowInspection8Test.class,
DataFlowInspection9Test.class,
DataFlowInspection10Test.class,
- DataFlowInspection14Test.class,
+ DataFlowInspection16Test.class,
DataFlowInspectionHeavyTest.class,
DataFlowInspectionAncientTest.class,
DataFlowInspectionCancellingTest.class,