diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java index d7bb8c63a8b9..e419e7c4e8f7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java @@ -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 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 missingConstants = StreamEx.of(enumClass.getFields()).select(PsiEnumConstant.class).toSet(); + Set 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 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 directInheritedClasses = + new ArrayList<>(DirectClassInheritorsSearch.search(selectorClass, selectorClass.getUseScope(), false).findAll()); + while (!patternClasses.isEmpty() && !directInheritedClasses.isEmpty()) { + Iterator inheritedClassesIterator = directInheritedClasses.iterator(); + List newDirectInheritedClasses = new SmartList<>(); + while (inheritedClassesIterator.hasNext()) { + PsiClass nextInheritedClass = inheritedClassesIterator.next(); + if (patternClasses.remove(nextInheritedClass)) { + inheritedClassesIterator.remove(); + } + else { + Collection 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 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) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingInSwitchExpr.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingInSwitchExpr.java index 693c572cba8b..34af99031d94 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingInSwitchExpr.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingInSwitchExpr.java @@ -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; switch (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"; + }; + + switch (i) { + case Sub1 s1: + System.out.println("ok"); + break; + case Sub2 s2: + System.out.println("ok"); + break; + } + str = switch(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 + switch (i2) { + case Sub7 s1: + System.out.println("ok"); + break; + case Sub8 s2: + System.out.println("ok"); + break; + } + str = switch (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 {} \ No newline at end of file +final class Sub5 { +} + +final class Sub6 extends Sub3 { +} + +interface I2 { +} + +class Sub7 implements I2 { +} + +class Sub8 implements I2 { +} \ No newline at end of file