mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Sealed classes: report narrow down conversion for sealed classes that have only sealed / final subclasses and don't have interface inheritors in hierarchy (IDEA-257414)
see JEP-397 - Sealed classes and conversions for details GitOrigin-RevId: 297386ccd61f7762508b630a60894360215d9e11
This commit is contained in:
committed by
intellij-monorepo-bot
parent
5beaaf1f5c
commit
e35b38f161
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
* <ul>
|
||||
* <li>sealed class implements interface
|
||||
* <li>sealed class have at least one non-sealed subclass
|
||||
* <li>at least one of final/sealed subclasses of sealed parent implement interface
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note that sealed subclasses are checked recursively, e.g. in hierarchy:</p>
|
||||
*
|
||||
* <code>
|
||||
* <p>sealed class Parent {}
|
||||
* <p>sealed class A extends Parent {}
|
||||
* <p>final class C extends A {}
|
||||
* </code>
|
||||
* <p>all classes would be checked.</p>
|
||||
* <br>
|
||||
* <p>See JEP-397 for more details.</p>
|
||||
*/
|
||||
public static boolean canConvertSealedTo(@NotNull PsiClass sealedClass, @NotNull PsiClass psiClass) {
|
||||
PsiReferenceList permitsList = sealedClass.getPermitsList();
|
||||
List<PsiClass> sealedSubClasses = new SmartList<>();
|
||||
boolean hasClassInheritors;
|
||||
if (permitsList == null) {
|
||||
Set<PsiClass> 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<PsiClass> findDirectSubClassesInFile(@NotNull PsiClass sealedClass) {
|
||||
Set<PsiClass> 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<PsiClass> 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];
|
||||
|
||||
+79
@@ -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 (<error descr="Inconvertible types; cannot cast 'Foo.A' to 'Foo'">a instanceof Foo</error>)
|
||||
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 (<error descr="Inconvertible types; cannot cast 'Foo.H' to 'Foo'">h instanceof Foo</error>)
|
||||
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 (<error descr="Inconvertible types; cannot cast 'Foo.V' to 'Foo.W'">v instanceof W</error>)
|
||||
System.out.println("It's a W");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
class Foo {
|
||||
static void test(Foo foo) {
|
||||
if (<error descr="Inconvertible types; cannot cast 'Foo' to 'I'"><warning descr="Condition 'foo instanceof I' is always 'false'">foo instanceof I</warning></error>)
|
||||
System.out.println("This is a Foo");
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface I {}
|
||||
final class C implements I {}
|
||||
@@ -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 (<error descr="Inconvertible types; cannot cast 'Foo.A' to 'Foo'"><warning descr="Condition 'a instanceof Foo' is always 'false'">a instanceof Foo</warning></error>)
|
||||
System.out.println("This is a Foo");
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -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");
|
||||
|
||||
+4
-2
@@ -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(); }
|
||||
}
|
||||
+1
-1
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user