[java-highlighting] evaluateSwitchCompleteness moved to SwitchUtils

Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: 6a71dfcf7c7166dea51e424d57c75998b68cb7c9
This commit is contained in:
Tagir Valeev
2025-02-28 17:16:32 +00:00
committed by intellij-monorepo-bot
parent a1d51b1179
commit 0f412df4d0
5 changed files with 91 additions and 93 deletions
@@ -230,91 +230,7 @@ public final class SwitchBlockHighlightingModel {
return StreamEx.of(result).distinct().toList();
}
private static @Nullable PsiCaseLabelElement findUnconditionalPatternForType(@NotNull List<? extends PsiCaseLabelElement> labelElements,
@NotNull PsiType type) {
return ContainerUtil.find(labelElements, element -> JavaPsiPatternUtil.isUnconditionalForType(element, type));
}
/**
* Evaluates the exhaustiveness state of a switch block.
*
* @param switchBlock the PsiSwitchBlock to evaluate
* @param considerNestedDeconstructionPatterns flag indicating whether to consider nested deconstruction patterns. It is necessary to take into account,
* because nested deconstruction patterns don't cover null values
* @return exhaustiveness state.
*/
public static @NotNull SwitchExhaustivenessState evaluateSwitchCompleteness(@NotNull PsiSwitchBlock switchBlock,
boolean considerNestedDeconstructionPatterns) {
PsiExpression selector = switchBlock.getExpression();
if (selector == null) return SwitchExhaustivenessState.MALFORMED;
PsiType selectorType = selector.getType();
if (selectorType == null) return SwitchExhaustivenessState.MALFORMED;
PsiCodeBlock switchBody = switchBlock.getBody();
if (switchBody == null) return SwitchExhaustivenessState.MALFORMED;
List<PsiCaseLabelElement> labelElements = StreamEx.of(JavaPsiSwitchUtil.getSwitchBranches(switchBlock)).select(PsiCaseLabelElement.class)
.filter(element -> !(element instanceof PsiDefaultCaseLabelElement)).toList();
if (labelElements.isEmpty()) return SwitchExhaustivenessState.EMPTY;
boolean needToCheckCompleteness = ExpressionUtil.isEnhancedSwitch(switchBlock);
boolean isEnumSelector = JavaPsiSwitchUtil.getSwitchSelectorKind(selectorType) == JavaPsiSwitchUtil.SelectorKind.ENUM;
if (findUnconditionalPatternForType(labelElements, selectorType) != null) {
return SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT;
}
if (JavaPsiSwitchUtil.isBooleanSwitchWithTrueAndFalse(switchBlock)) {
return SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT;
}
if (!needToCheckCompleteness && !isEnumSelector) return SwitchExhaustivenessState.INCOMPLETE;
// It is necessary because deconstruction patterns don't cover cases
// when some of their components are null and deconstructionPattern too
if (!considerNestedDeconstructionPatterns) {
labelElements = ContainerUtil.filter(
labelElements, label -> !(label instanceof PsiDeconstructionPattern deconstructionPattern &&
ContainerUtil.or(
deconstructionPattern.getDeconstructionList().getDeconstructionComponents(),
component -> component instanceof PsiDeconstructionPattern)));
}
boolean hasError = hasExhaustivenessError(switchBlock, labelElements);
// if a switch block is needed to check completeness and switch is incomplete we let highlighting to inform about it as it's a compilation error
if (needToCheckCompleteness) {
return hasError ? SwitchExhaustivenessState.UNNECESSARY : SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT;
}
return hasError ? SwitchExhaustivenessState.INCOMPLETE : SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT;
}
private static QuickFixFactory getFixFactory() {
return QuickFixFactory.getInstance();
}
/**
* State of switch exhaustiveness.
*/
public enum SwitchExhaustivenessState {
/**
* Switch is malformed and produces a compilation error (no body, no selector, etc.),
* no exhaustiveness analysis is performed
*/
MALFORMED,
/**
* Switch contains no labels (except probably default label)
*/
EMPTY,
/**
* Switch should not be exhaustive (classic switch statement)
*/
UNNECESSARY,
/**
* Switch is not exhaustive
*/
INCOMPLETE,
/**
* Switch is exhaustive (complete), and adding a default branch would be a compilation error.
* This includes a switch over boolean having both true and false branches,
* or a switch that has an unconditional pattern branch.
*/
EXHAUSTIVE_NO_DEFAULT,
/**
* Switch is exhaustive (complete), but it's possible to add a default branch.
*/
EXHAUSTIVE_CAN_ADD_DEFAULT
}
}
@@ -38,6 +38,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.java.codeserver.core.JavaPatternExhaustivenessUtil.hasExhaustivenessError;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING;
import static com.siyeh.ig.callMatcher.CallMatcher.instanceCall;
@@ -926,6 +927,88 @@ public final class SwitchUtils {
}
return unreachableElements;
}
/**
* Evaluates the exhaustiveness state of a switch block.
*
* @param switchBlock the PsiSwitchBlock to evaluate
* @param considerNestedDeconstructionPatterns flag indicating whether to consider nested deconstruction patterns. It is necessary to take into account,
* because nested deconstruction patterns don't cover null values
* @return exhaustiveness state.
*/
public static @NotNull SwitchExhaustivenessState evaluateSwitchCompleteness(@NotNull PsiSwitchBlock switchBlock,
boolean considerNestedDeconstructionPatterns) {
PsiExpression selector = switchBlock.getExpression();
if (selector == null) return SwitchExhaustivenessState.MALFORMED;
PsiType selectorType = selector.getType();
if (selectorType == null) return SwitchExhaustivenessState.MALFORMED;
PsiCodeBlock switchBody = switchBlock.getBody();
if (switchBody == null) return SwitchExhaustivenessState.MALFORMED;
List<PsiCaseLabelElement> labelElements = StreamEx.of(JavaPsiSwitchUtil.getSwitchBranches(switchBlock)).select(PsiCaseLabelElement.class)
.filter(element -> !(element instanceof PsiDefaultCaseLabelElement)).toList();
if (labelElements.isEmpty()) return SwitchExhaustivenessState.EMPTY;
boolean needToCheckCompleteness = ExpressionUtil.isEnhancedSwitch(switchBlock);
boolean isEnumSelector = JavaPsiSwitchUtil.getSwitchSelectorKind(selectorType) == JavaPsiSwitchUtil.SelectorKind.ENUM;
if (ContainerUtil.find(labelElements, element -> JavaPsiPatternUtil.isUnconditionalForType(element, selectorType)) != null) {
return SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT;
}
if (JavaPsiSwitchUtil.isBooleanSwitchWithTrueAndFalse(switchBlock)) {
return SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT;
}
if (!needToCheckCompleteness && !isEnumSelector) return SwitchExhaustivenessState.INCOMPLETE;
// It is necessary because deconstruction patterns don't cover cases
// when some of their components are null and deconstructionPattern too
if (!considerNestedDeconstructionPatterns) {
labelElements = ContainerUtil.filter(
labelElements, label -> !(label instanceof PsiDeconstructionPattern deconstructionPattern &&
ContainerUtil.or(
deconstructionPattern.getDeconstructionList().getDeconstructionComponents(),
component -> component instanceof PsiDeconstructionPattern)));
}
boolean hasError = hasExhaustivenessError(switchBlock, labelElements);
// if a switch block is needed to check completeness and switch is incomplete we let highlighting to inform about it as it's a compilation error
if (!hasError) {
return SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT;
}
if (needToCheckCompleteness) {
return SwitchExhaustivenessState.UNNECESSARY;
}
return SwitchExhaustivenessState.INCOMPLETE;
}
/**
* State of switch exhaustiveness.
*/
public enum SwitchExhaustivenessState {
/**
* Switch is malformed and produces a compilation error (no body, no selector, etc.),
* no exhaustiveness analysis is performed
*/
MALFORMED,
/**
* Switch contains no labels (except probably default label)
*/
EMPTY,
/**
* Switch should not be exhaustive (classic switch statement)
*/
UNNECESSARY,
/**
* Switch is not exhaustive
*/
INCOMPLETE,
/**
* Switch is exhaustive (complete), and adding a default branch would be a compilation error.
* This includes a switch over boolean having both true and false branches,
* or a switch that has an unconditional pattern branch.
*/
EXHAUSTIVE_NO_DEFAULT,
/**
* Switch is exhaustive (complete), but it's possible to add a default branch.
*/
EXHAUSTIVE_CAN_ADD_DEFAULT
}
private static class LabelSearchVisitor extends JavaRecursiveElementWalkingVisitor {
private final String m_labelName;
@@ -2,7 +2,6 @@
package com.intellij.codeInspection;
import com.intellij.codeInsight.BlockUtils;
import com.intellij.codeInsight.daemon.impl.analysis.SwitchBlockHighlightingModel;
import com.intellij.codeInspection.options.OptPane;
import com.intellij.java.JavaBundle;
import com.intellij.modcommand.ModPsiUpdater;
@@ -26,9 +25,9 @@ import org.jetbrains.annotations.PropertyKey;
import java.util.*;
import static com.intellij.codeInsight.daemon.impl.analysis.SwitchBlockHighlightingModel.evaluateSwitchCompleteness;
import static com.intellij.codeInspection.options.OptPane.*;
import static com.intellij.util.ObjectUtils.tryCast;
import static com.siyeh.ig.psiutils.SwitchUtils.evaluateSwitchCompleteness;
public final class EnhancedSwitchMigrationInspection extends AbstractBaseJavaLocalInspectionTool {
@SuppressWarnings("WeakerAccess") public boolean myWarnOnlyOnExpressionConversion = true;
@@ -285,9 +284,9 @@ public final class EnhancedSwitchMigrationInspection extends AbstractBaseJavaLoc
if (branch.isDefault()) return true;
if (existsDefaultLabelElement(branch.myLabelStatement)) return true;
}
SwitchBlockHighlightingModel.SwitchExhaustivenessState completenessResult = evaluateSwitchCompleteness(switchStatement, true);
return completenessResult == SwitchBlockHighlightingModel.SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT ||
completenessResult == SwitchBlockHighlightingModel.SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT;
SwitchUtils.SwitchExhaustivenessState completenessResult = evaluateSwitchCompleteness(switchStatement, true);
return completenessResult == SwitchUtils.SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT ||
completenessResult == SwitchUtils.SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT;
}
private static boolean isConvertibleBranch(@NotNull OldSwitchStatementBranch branch, boolean hasNext) {
@@ -30,10 +30,10 @@ import com.siyeh.ig.psiutils.SwitchUtils;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NotNull;
import static com.intellij.codeInsight.daemon.impl.analysis.SwitchBlockHighlightingModel.SwitchExhaustivenessState;
import static com.intellij.codeInsight.daemon.impl.analysis.SwitchBlockHighlightingModel.evaluateSwitchCompleteness;
import static com.intellij.codeInspection.options.OptPane.checkbox;
import static com.intellij.codeInspection.options.OptPane.pane;
import static com.siyeh.ig.psiutils.SwitchUtils.SwitchExhaustivenessState;
import static com.siyeh.ig.psiutils.SwitchUtils.evaluateSwitchCompleteness;
public final class SwitchStatementsWithoutDefaultInspection extends AbstractBaseJavaLocalInspectionTool {
@@ -40,12 +40,12 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import static com.intellij.codeInsight.daemon.impl.analysis.SwitchBlockHighlightingModel.SwitchExhaustivenessState;
import static com.intellij.codeInsight.daemon.impl.analysis.SwitchBlockHighlightingModel.evaluateSwitchCompleteness;
import static com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
import static com.intellij.codeInspection.ProblemHighlightType.INFORMATION;
import static com.intellij.codeInspection.options.OptPane.checkbox;
import static com.intellij.codeInspection.options.OptPane.pane;
import static com.siyeh.ig.psiutils.SwitchUtils.SwitchExhaustivenessState;
import static com.siyeh.ig.psiutils.SwitchUtils.evaluateSwitchCompleteness;
public final class UnnecessaryDefaultInspection extends BaseInspection {