mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-270438 - completeness for sealed types and plain classes
GitOrigin-RevId: 0aa0559c6186bac92cf36b20d896000a0a64964e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c74b01a7fe
commit
b6c313031d
+64
-13
@@ -10,10 +10,13 @@ import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.PsiClassReferenceType;
|
||||
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.containers.SmartHashSet;
|
||||
import com.siyeh.ig.psiutils.ControlFlowUtils;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -61,6 +64,7 @@ abstract class SwitchBlockHighlightingModel {
|
||||
@NotNull
|
||||
List<HighlightInfo> checkIfAccessibleType(@NotNull PsiExpression selector, @NotNull PsiType selectorType) {
|
||||
PsiClass member = PsiUtil.resolveClassInClassTypeOnly(selectorType);
|
||||
Object o = new Object();
|
||||
if (member != null && !PsiUtil.isAccessible(member.getProject(), member, selector, null)) {
|
||||
String className = PsiFormatUtil.formatClass(member, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME);
|
||||
String message = JavaErrorBundle.message("inaccessible.type", className);
|
||||
@@ -562,20 +566,18 @@ class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlightingMode
|
||||
}
|
||||
PsiElement elementCoversType = findElementCoversType(selectorType, elements);
|
||||
PsiElement defaultElement = findDefaultElement();
|
||||
if (defaultElement != null) {
|
||||
if (elementCoversType != null) {
|
||||
results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(defaultElement)
|
||||
.descriptionAndTooltip(JavaErrorBundle.message("switch.17.total.pattern.and.default.exist")).create());
|
||||
results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementCoversType)
|
||||
.descriptionAndTooltip(JavaErrorBundle.message("switch.17.total.pattern.and.default.exist")).create());
|
||||
return;
|
||||
}
|
||||
if (defaultElement != null && elementCoversType != null) {
|
||||
results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(defaultElement)
|
||||
.descriptionAndTooltip(JavaErrorBundle.message("switch.17.total.pattern.and.default.exist")).create());
|
||||
results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementCoversType)
|
||||
.descriptionAndTooltip(JavaErrorBundle.message("switch.17.total.pattern.and.default.exist")).create());
|
||||
return;
|
||||
}
|
||||
if (defaultElement != null || elementCoversType != null) return;
|
||||
PsiClass selectorClass = PsiUtil.resolveClassInClassTypeOnly(selectorType);
|
||||
if (selectorClass == null) return;
|
||||
if (getSwitchSelectorKind(selectorType) == SelectorKind.ENUM) {
|
||||
if (defaultElement != null || elementCoversType != null) return;
|
||||
PsiClass enumClass = PsiUtil.resolveClassInClassTypeOnly(selectorType);
|
||||
if (enumClass == null) return;
|
||||
Set<PsiEnumConstant> missingConstants = StreamEx.of(enumClass.getFields()).select(PsiEnumConstant.class).toSet();
|
||||
Set<PsiEnumConstant> missingConstants = StreamEx.of(selectorClass.getFields()).select(PsiEnumConstant.class).toSet();
|
||||
for (PsiCaseLabelElement element : elements) {
|
||||
if (element instanceof PsiReferenceExpression) {
|
||||
PsiElement resolved = ((PsiReferenceExpression)element).resolve();
|
||||
@@ -596,6 +598,55 @@ class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlightingMode
|
||||
QuickFixAction.registerQuickFixAction(info, getFixFactory().createAddSwitchDefaultFix(myBlock, null));
|
||||
results.add(info);
|
||||
}
|
||||
else if (selectorClass.hasModifierProperty(PsiModifier.SEALED) && selectorClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
|
||||
Set<PsiClass> patternClasses = new SmartHashSet<>();
|
||||
for (PsiCaseLabelElement element : elements) {
|
||||
if (element instanceof PsiPattern) {
|
||||
PsiClass patternClass = PsiUtil.resolveClassInClassTypeOnly(JavaPsiPatternUtil.getPatternType(((PsiPattern)element)));
|
||||
if (patternClass != null) {
|
||||
patternClasses.add(patternClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
// for now javac just looks check completeness using only the direct inherited classes of selector class.
|
||||
// but here is a new PR https://github.com/openjdk/jdk17/pull/78 that extends that functionality
|
||||
List<PsiClass> directInheritedClasses =
|
||||
new ArrayList<>(DirectClassInheritorsSearch.search(selectorClass, selectorClass.getUseScope(), false).findAll());
|
||||
while (!patternClasses.isEmpty() && !directInheritedClasses.isEmpty()) {
|
||||
Iterator<PsiClass> inheritedClassesIterator = directInheritedClasses.iterator();
|
||||
List<PsiClass> newDirectInheritedClasses = new SmartList<>();
|
||||
while (inheritedClassesIterator.hasNext()) {
|
||||
PsiClass nextInheritedClass = inheritedClassesIterator.next();
|
||||
if (patternClasses.remove(nextInheritedClass)) {
|
||||
inheritedClassesIterator.remove();
|
||||
}
|
||||
else {
|
||||
Collection<PsiClass> newInheritedClasses =
|
||||
DirectClassInheritorsSearch.search(nextInheritedClass, selectorClass.getUseScope(), false).findAll();
|
||||
if (!newInheritedClasses.isEmpty()) {
|
||||
inheritedClassesIterator.remove();
|
||||
newDirectInheritedClasses.addAll(newInheritedClasses);
|
||||
}
|
||||
}
|
||||
}
|
||||
directInheritedClasses.addAll(newDirectInheritedClasses);
|
||||
}
|
||||
if (!directInheritedClasses.isEmpty()) {
|
||||
// todo maybe it's possible to deduplicate
|
||||
String message = JavaErrorBundle.message(myBlock instanceof PsiExpression ? "switch.17.expression.cover.not.all.cases"
|
||||
: "switch.17.statement.cover.not.all.cases");
|
||||
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(myBlock.getFirstChild())
|
||||
.descriptionAndTooltip(message).create();
|
||||
results.add(info);
|
||||
}
|
||||
}
|
||||
else {
|
||||
String message = JavaErrorBundle.message(myBlock instanceof PsiExpression ? "switch.17.expression.cover.not.all.cases"
|
||||
: "switch.17.statement.cover.not.all.cases");
|
||||
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(myBlock.getFirstChild())
|
||||
.descriptionAndTooltip(message).create();
|
||||
results.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -622,7 +673,7 @@ class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlightingMode
|
||||
@Nullable
|
||||
private static PsiElement findElementCoversType(@NotNull PsiType type, @NotNull List<PsiCaseLabelElement> labelElements) {
|
||||
return ContainerUtil.find(labelElements, element -> element instanceof PsiPattern
|
||||
&& JavaPsiPatternUtil.isTotalForType(((PsiPattern)element), type));
|
||||
&& JavaPsiPatternUtil.isTotalForType(((PsiPattern)element), type));
|
||||
}
|
||||
|
||||
private static boolean isNullType(@NotNull PsiElement element) {
|
||||
|
||||
+85
-4
@@ -269,6 +269,8 @@ class Main {
|
||||
break;
|
||||
case Float d:
|
||||
System.out.println("float");
|
||||
default:
|
||||
System.out.println("ok");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,12 +464,13 @@ class Main {
|
||||
};
|
||||
}
|
||||
|
||||
void completeness(Day d) {
|
||||
void completeness(Day d, I i, I2 i2) {
|
||||
// old style switch, no completeness check
|
||||
switch (d) {
|
||||
case MONDAY, TUESDAY -> System.out.println("ok");
|
||||
}
|
||||
|
||||
// If the type of the selector expression is an enum type E
|
||||
String str;
|
||||
<error descr="The switch statement does not cover all possible input values">switch</error> (d) {
|
||||
case Day dd && dd != null:
|
||||
@@ -498,10 +501,75 @@ class Main {
|
||||
default:
|
||||
System.out.println("mon");
|
||||
};
|
||||
|
||||
// If the type of the selector expression, T, names a sealed interface or a sealed class that is abstract
|
||||
switch(i) {
|
||||
case Sub1 s1:
|
||||
System.out.println("ok");
|
||||
break;
|
||||
case Sub2 s2:
|
||||
System.out.println("ok");
|
||||
break;
|
||||
case Sub3 s3:
|
||||
System.out.println("ok");
|
||||
break;
|
||||
}
|
||||
str = switch(i) {
|
||||
case Sub1 s1 -> "ok";
|
||||
case Sub2 s2 -> "ok";
|
||||
case Sub3 s3 -> "ok";
|
||||
};
|
||||
|
||||
<error descr="The switch statement does not cover all possible input values">switch</error> (i) {
|
||||
case Sub1 s1:
|
||||
System.out.println("ok");
|
||||
break;
|
||||
case Sub2 s2:
|
||||
System.out.println("ok");
|
||||
break;
|
||||
}
|
||||
str = <error descr="The switch expression does not cover all possible input values">switch</error>(i) {
|
||||
case Sub1 s1 -> "ok";
|
||||
case Sub2 s2 -> "ok";
|
||||
};
|
||||
switch (i) {
|
||||
case Sub1 s1:
|
||||
System.out.println("ok");
|
||||
break;
|
||||
case Sub2 s2:
|
||||
System.out.println("ok");
|
||||
break;
|
||||
case Sub4 s4:
|
||||
System.out.println("ok");
|
||||
break;
|
||||
case Sub6 s6:
|
||||
System.out.println("ok");
|
||||
break;
|
||||
}
|
||||
str = switch(i) {
|
||||
case Sub1 s1 -> "ok";
|
||||
case Sub2 s2 -> "ok";
|
||||
case Sub4 s4 -> "ok";
|
||||
case Sub6 s6 -> "ok";
|
||||
};
|
||||
|
||||
// If the type of the selector expression, T, is not an enum type and also does not name a sealed interface or a sealed class that is abstract
|
||||
<error descr="The switch statement does not cover all possible input values">switch</error> (i2) {
|
||||
case Sub7 s1:
|
||||
System.out.println("ok");
|
||||
break;
|
||||
case Sub8 s2:
|
||||
System.out.println("ok");
|
||||
break;
|
||||
}
|
||||
str = <error descr="The switch expression does not cover all possible input values">switch</error> (i2) {
|
||||
case Sub7 s1 -> "ok";
|
||||
case Sub8 s2 -> "ok";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface I permits Sub1, Sub2, Sub3 {
|
||||
sealed interface I {
|
||||
}
|
||||
|
||||
enum Day {
|
||||
@@ -514,10 +582,23 @@ final class Sub1 implements I {
|
||||
final class Sub2 implements I {
|
||||
}
|
||||
|
||||
sealed class Sub3 implements I permits Sub4 {
|
||||
sealed class Sub3 implements I {
|
||||
}
|
||||
|
||||
final class Sub4 extends Sub3 {
|
||||
}
|
||||
|
||||
final class Sub5 {}
|
||||
final class Sub5 {
|
||||
}
|
||||
|
||||
final class Sub6 extends Sub3 {
|
||||
}
|
||||
|
||||
interface I2 {
|
||||
}
|
||||
|
||||
class Sub7 implements I2 {
|
||||
}
|
||||
|
||||
class Sub8 implements I2 {
|
||||
}
|
||||
Reference in New Issue
Block a user