IDEA-270438 - completeness for sealed types and plain classes

GitOrigin-RevId: 0aa0559c6186bac92cf36b20d896000a0a64964e
This commit is contained in:
Ilyas Selimov
2021-06-24 14:39:32 +00:00
committed by intellij-monorepo-bot
parent c74b01a7fe
commit b6c313031d
2 changed files with 149 additions and 17 deletions
@@ -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) {
@@ -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 {
}