[java-highlighting] Switch dominance: extract reusable code to JavaPsiSwitchUtil

Also: move JavaPsiSwitchUtil to codeserver.core; isEnhancedSwitch to ExpressionUtil (used in ControlFlowAnalyzer which is psi-impl module)
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: d9b1070db6b5b44f7523db284ca5fabcc1cdef22
This commit is contained in:
Tagir Valeev
2025-02-26 18:59:35 +00:00
committed by intellij-monorepo-bot
parent 30c5bb9996
commit fa60db5164
10 changed files with 250 additions and 243 deletions
@@ -1,18 +1,21 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.util;
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeserver.core;
import com.intellij.codeInsight.ExpressionUtil;
import com.intellij.openapi.util.Comparing;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.util.ConstantExpressionUtil;
import com.intellij.psi.util.JavaPsiPatternUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import static java.util.Objects.requireNonNull;
@@ -21,80 +24,11 @@ import static java.util.Objects.requireNonNull;
*/
public final class JavaPsiSwitchUtil {
/**
* Checks if the given switch is enhanced.
*
* @param statement the switch to check
* @return true if the switch is an enhanced switch, false otherwise
*/
public static boolean isEnhancedSwitch(@NotNull PsiSwitchBlock statement) {
if(statement instanceof PsiSwitchExpression) return true;
PsiExpression selector = statement.getExpression();
if (selector == null) {
return false;
}
PsiType selectorType = selector.getType();
if (selectorType == null) {
return false;
}
PsiCodeBlock body = statement.getBody();
if (body == null) {
return false;
}
List<PsiCaseLabelElement> cases = new ArrayList<>();
for (PsiStatement psiStatement : body.getStatements()) {
if (psiStatement instanceof PsiSwitchLabelStatementBase) {
PsiSwitchLabelStatementBase labelStatementBase = (PsiSwitchLabelStatementBase)psiStatement;
PsiCaseLabelElementList elementList = labelStatementBase.getCaseLabelElementList();
if (elementList == null) {
continue;
}
PsiCaseLabelElement[] elements = elementList.getElements();
for (PsiCaseLabelElement caseLabelElement : elements) {
if (caseLabelElement != null) {
cases.add(caseLabelElement);
}
}
}
}
return isEnhancedSwitch(cases, selectorType);
}
public static boolean isEnhancedSwitch(@NotNull List<? extends PsiCaseLabelElement> labelElements, @NotNull PsiType selectorType) {
if (isEnhancedSelectorType(selectorType)) return true;
return ContainerUtil.exists(labelElements, st -> st instanceof PsiPattern || isNullType(st));
}
private static boolean isNullType(@NotNull PsiElement element) {
return element instanceof PsiExpression && TypeConversionUtil.isNullType(((PsiExpression)element).getType());
}
private static boolean isEnhancedSelectorType(@NotNull PsiType type) {
PsiPrimitiveType unboxedType = PsiPrimitiveType.getOptionallyUnboxedType(type);
if (unboxedType != null &&
(unboxedType.equals(PsiTypes.booleanType()) ||
unboxedType.equals(PsiTypes.floatType()) ||
unboxedType.equals(PsiTypes.doubleType()) ||
unboxedType.equals(PsiTypes.longType()))) {
return true;
}
if (TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.INT_RANK) return false;
if (TypeConversionUtil.isPrimitiveAndNotNull(type)) return false;
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (psiClass != null) {
if (psiClass.isEnum()) return false;
String fqn = psiClass.getQualifiedName();
if (Comparing.strEqual(fqn, CommonClassNames.JAVA_LANG_STRING)) return false;
}
return true;
}
/**
* Returns the selector kind based on the type.
* <p>
* The result may depend on type language level for boxed primitive types.
* The result may depend on the type language level for boxed primitive types.
* E.g., if selector type is {@link Double} then {@link SelectorKind#DOUBLE} will be returned
* if primitives in patterns are supported, but {@link SelectorKind#CLASS_OR_ARRAY} will be returned otherwise.
* <p>
@@ -148,8 +82,7 @@ public final class JavaPsiSwitchUtil {
PsiCodeBlock body = block.getBody();
if (body == null) return false;
for (PsiElement st = body.getFirstChild(); st != null; st = st.getNextSibling()) {
if (!(st instanceof PsiSwitchLabelStatementBase)) continue;
PsiSwitchLabelStatementBase labelStatement = (PsiSwitchLabelStatementBase)st;
if (!(st instanceof PsiSwitchLabelStatementBase labelStatement)) continue;
if (labelStatement.isDefaultCase()) {
return true;
}
@@ -169,8 +102,7 @@ public final class JavaPsiSwitchUtil {
}
private static @Nullable Object getBranchConstant(@NotNull PsiCaseLabelElement labelElement, @NotNull PsiType selectorType) {
if (labelElement instanceof PsiExpression) {
PsiExpression expr = (PsiExpression)labelElement;
if (labelElement instanceof PsiExpression expr) {
if (expr instanceof PsiReferenceExpression) {
PsiEnumConstant enumConstant = getEnumConstant(expr);
if (enumConstant != null) {
@@ -216,8 +148,7 @@ public final class JavaPsiSwitchUtil {
PsiType selectorType = selector.getType();
if (selectorType == null) return elementsToCheckDuplicates;
for (PsiStatement st : body.getStatements()) {
if (!(st instanceof PsiSwitchLabelStatementBase)) continue;
PsiSwitchLabelStatementBase labelStatement = (PsiSwitchLabelStatementBase)st;
if (!(st instanceof PsiSwitchLabelStatementBase labelStatement)) continue;
if (labelStatement.isDefaultCase()) {
elementsToCheckDuplicates.putValue(SwitchSpecialValue.DEFAULT_VALUE, requireNonNull(labelStatement.getFirstChild()));
continue;
@@ -234,6 +165,125 @@ public final class JavaPsiSwitchUtil {
return elementsToCheckDuplicates;
}
/**
* Determines if the given case label element is dominated by another case label element according to JEP 440-441
*
* @param overWhom The case label element that may dominate.
* @param who The case label element that may be dominated.
* @param selectorType The type used to select the case label element.
* @return {@code true} if the 'overWhom' case label element dominates the 'who' case label element, {@code false} otherwise.
*/
public static boolean isDominated(@NotNull PsiCaseLabelElement overWhom,
@NotNull PsiElement who,
@NotNull PsiType selectorType) {
boolean isOverWhomUnconditionalForSelector = JavaPsiPatternUtil.isUnconditionalForType(overWhom, selectorType);
if (!isOverWhomUnconditionalForSelector &&
((!(overWhom instanceof PsiExpression expression) || JavaPsiExpressionUtil.isNullLiteral(expression)) &&
who instanceof PsiKeyword &&
PsiKeyword.DEFAULT.equals(who.getText()) || isInCaseNullDefaultLabel(who))) {
// JEP 440-441
// A 'default' label dominates a case label with a case pattern,
// and it also dominates a case label with a null case constant.
// A 'case null, default' label dominates all other switch labels.
return true;
}
if (who instanceof PsiCaseLabelElement currentElement) {
if (JavaPsiPatternUtil.isGuarded(currentElement)) return false;
if (isConstantLabelElement(overWhom)) {
PsiExpression constExpr = ObjectUtils.tryCast(overWhom, PsiExpression.class);
assert constExpr != null;
if (JavaPsiPatternUtil.dominatesOverConstant(currentElement, constExpr.getType())) {
return true;
}
}
else {
if (JavaPsiPatternUtil.dominates(currentElement, overWhom)) {
return true;
}
}
}
return false;
}
public static boolean isInCaseNullDefaultLabel(@NotNull PsiElement element) {
PsiCaseLabelElementList list = ObjectUtils.tryCast(element.getParent(), PsiCaseLabelElementList.class);
if (list == null || list.getElementCount() != 2) return false;
PsiCaseLabelElement[] elements = list.getElements();
return elements[0] instanceof PsiExpression expr &&
JavaPsiExpressionUtil.isNullLiteral(expr) &&
elements[1] instanceof PsiDefaultCaseLabelElement;
}
private static boolean isConstantLabelElement(@NotNull PsiCaseLabelElement labelElement) {
Object value = JavaPsiFacade.getInstance(labelElement.getProject()).getConstantEvaluationHelper()
.computeConstantExpression(labelElement, false);
return value != null || isEnumConstant(labelElement);
}
private static boolean isEnumConstant(@NotNull PsiCaseLabelElement element) {
return getEnumConstant(element) != null;
}
/**
* @param block switch block to analyze
* @return map of labels where keys are dominated labels, and values are dominating labels (or 'default' keyword from default label,
* which may dominate over any other label).
* Only labels for which domination rules are violated will be returned.
*/
public static @NotNull Map<PsiCaseLabelElement, PsiElement> findDominatedLabels(@NotNull PsiSwitchBlock block) {
PsiCodeBlock body = block.getBody();
if (body == null) return Collections.emptyMap();
PsiExpression selector = block.getExpression();
if (selector == null) return Collections.emptyMap();
PsiType selectorType = selector.getType();
if (selectorType == null) return Collections.emptyMap();
List<PsiElement> elementsToCheckDominance = new ArrayList<>();
for (PsiStatement st : body.getStatements()) {
if (!(st instanceof PsiSwitchLabelStatementBase labelStatement)) continue;
if (labelStatement.isDefaultCase()) {
elementsToCheckDominance.add(requireNonNull(labelStatement.getFirstChild()));
continue;
}
PsiCaseLabelElementList labelElementList = labelStatement.getCaseLabelElementList();
if (labelElementList == null) continue;
for (PsiCaseLabelElement labelElement : labelElementList.getElements()) {
if (shouldConsiderForDominance(labelElement)) {
elementsToCheckDominance.add(labelElement);
}
}
}
Map<PsiCaseLabelElement, PsiElement> result = new HashMap<>();
for (int i = 0; i < elementsToCheckDominance.size() - 1; i++) {
PsiElement current = elementsToCheckDominance.get(i);
if (result.containsKey(current)) continue;
for (int j = i + 1; j < elementsToCheckDominance.size(); j++) {
PsiElement next = elementsToCheckDominance.get(j);
if (!(next instanceof PsiCaseLabelElement nextElement)) continue;
boolean dominated = isDominated(nextElement, current, selectorType);
if (dominated) {
result.put(nextElement, current);
}
}
}
return result;
}
private static boolean shouldConsiderForDominance(@NotNull PsiCaseLabelElement labelElement) {
if (labelElement instanceof PsiPattern) return true;
if (labelElement instanceof PsiExpression) {
boolean isNullType = ExpressionUtil.isNullType(labelElement);
if (isNullType && isInCaseNullDefaultLabel(labelElement)) {
// JEP 432
// A 'case null, default' label dominates all other switch labels.
//
// In this case, only the 'default' case will be added to the elements checked for dominance
return false;
}
return isNullType || isConstantLabelElement(labelElement);
}
return labelElement instanceof PsiDefaultCaseLabelElement;
}
/**
* Kinds of switch selector
@@ -1,8 +1,10 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeserver.highlighting;
import com.intellij.codeInsight.ExpressionUtil;
import com.intellij.core.JavaPsiBundle;
import com.intellij.java.codeserver.core.JavaPsiExpressionUtil;
import com.intellij.java.codeserver.core.JavaPsiSwitchUtil;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
import com.intellij.java.codeserver.highlighting.errors.JavaIncompatibleTypeErrorContext;
import com.intellij.pom.java.JavaFeature;
@@ -280,10 +282,12 @@ final class SwitchChecker {
PsiCaseLabelElementList labelElementList = labelStatement.getCaseLabelElementList();
if (labelElementList == null) continue;
for (PsiCaseLabelElement label : labelElementList.getElements()) {
if (!(label instanceof PsiParenthesizedExpression) && isNullType(label)) {
if (selectorType instanceof PsiPrimitiveType && !isNullType(selector)) {
myVisitor.report(JavaErrorKinds.SWITCH_NULL_TYPE_INCOMPATIBLE.create(label, selectorType));
return;
if (!(label instanceof PsiParenthesizedExpression) && ExpressionUtil.isNullType(label)) {
if (selectorType instanceof PsiPrimitiveType) {
if (!ExpressionUtil.isNullType(selector)) {
myVisitor.report(JavaErrorKinds.SWITCH_NULL_TYPE_INCOMPATIBLE.create(label, selectorType));
return;
}
}
return;
}
@@ -314,10 +318,6 @@ final class SwitchChecker {
}
}
private static boolean isNullType(@NotNull PsiElement element) {
return element instanceof PsiExpression expression && TypeConversionUtil.isNullType(expression.getType());
}
static @Nullable PsiEnumConstant getEnumConstant(@Nullable PsiElement element) {
if (element instanceof PsiReferenceExpression referenceExpression &&
referenceExpression.resolve() instanceof PsiEnumConstant enumConstant) {
@@ -6,6 +6,7 @@ import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil;
import com.intellij.core.JavaPsiBundle;
import com.intellij.java.codeserver.core.JavaPreviewFeatureUtil;
import com.intellij.java.codeserver.core.JavaPsiModuleUtil;
import com.intellij.java.codeserver.core.JavaPsiSwitchUtil;
import com.intellij.java.codeserver.core.JpmsModuleAccessInfo;
import com.intellij.java.codeserver.highlighting.JavaCompilationErrorBundle;
import com.intellij.java.codeserver.highlighting.errors.JavaErrorKind.Parameterized;
@@ -14,6 +14,7 @@ import com.intellij.codeInspection.dataFlow.fix.RedundantInstanceofFix;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.java.codeserver.core.JavaPsiModifierUtil;
import com.intellij.java.codeserver.core.JavaPsiSwitchUtil;
import com.intellij.java.codeserver.core.JpmsModuleAccessInfo;
import com.intellij.java.codeserver.highlighting.JavaErrorCollector;
import com.intellij.java.codeserver.highlighting.errors.JavaCompilationError;
@@ -6,16 +6,18 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.java.codeserver.core.JavaPsiSealedUtil;
import com.intellij.java.codeserver.core.JavaPsiSwitchUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.*;
import com.intellij.util.ObjectUtils;
import com.intellij.psi.util.JavaPsiPatternUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
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.fixes.MakeDefaultLastCaseFix;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.SwitchUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import one.util.streamex.StreamEx;
@@ -29,7 +31,6 @@ import java.util.stream.Collectors;
import static com.intellij.codeInsight.daemon.impl.analysis.PatternHighlightingModel.*;
import static com.intellij.codeInsight.daemon.impl.analysis.PatternsInSwitchBlockHighlightingModel.CompletenessResult.*;
import static java.util.Objects.requireNonNull;
/**
* This class represents the model for highlighting patterns in a switch block.
@@ -50,117 +51,36 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
@Override
void checkSwitchLabelValues(@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
if (checkDominance(errorSink)) return;
PsiCodeBlock body = myBlock.getBody();
if (body == null) return;
List<PsiElement> elementsToCheckDominance = new ArrayList<>();
List<PsiCaseLabelElement> elementsToCheckCompleteness = new ArrayList<>();
for (PsiStatement st : body.getStatements()) {
if (!(st instanceof PsiSwitchLabelStatementBase labelStatement)) continue;
if (labelStatement.isDefaultCase()) {
elementsToCheckDominance.add(requireNonNull(labelStatement.getFirstChild()));
continue;
}
if (!(st instanceof PsiSwitchLabelStatementBase labelStatement) || labelStatement.isDefaultCase()) continue;
PsiCaseLabelElementList labelElementList = labelStatement.getCaseLabelElementList();
if (labelElementList == null) continue;
for (PsiCaseLabelElement labelElement : labelElementList.getElements()) {
fillElementsToCheckDominance(elementsToCheckDominance, labelElement);
elementsToCheckCompleteness.add(labelElement);
if (labelElementList != null) {
Collections.addAll(elementsToCheckCompleteness, labelElementList.getElements());
}
}
if (checkDominance(elementsToCheckDominance, errorSink)) {
return;
}
if (needToCheckCompleteness(elementsToCheckCompleteness)) {
checkCompleteness(elementsToCheckCompleteness, true, errorSink);
}
}
@NotNull Map<PsiCaseLabelElement, PsiElement> findDominatedLabels(@NotNull List<? extends PsiElement> switchLabels) {
Map<PsiCaseLabelElement, PsiElement> result = new HashMap<>();
for (int i = 0; i < switchLabels.size() - 1; i++) {
PsiElement current = switchLabels.get(i);
if (result.containsKey(current)) continue;
for (int j = i + 1; j < switchLabels.size(); j++) {
PsiElement next = switchLabels.get(j);
if (!(next instanceof PsiCaseLabelElement nextElement)) continue;
boolean dominated = isDominated(nextElement, current, mySelectorType);
if (dominated) {
result.put(nextElement, current);
}
}
}
return result;
private boolean checkDominance(@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
Map<PsiCaseLabelElement, PsiElement> dominatedLabels = JavaPsiSwitchUtil.findDominatedLabels(myBlock);
return doCheckDominance(errorSink, dominatedLabels);
}
/**
* Determines if the given case label element is dominated by another case label element according to JEP 440-441
*
* @param overWhom The case label element that may dominate.
* @param who The case label element that may be dominated.
* @param selectorType The type used to select the case label element.
* @return {@code true} if the 'overWhom' case label element dominates the 'who' case label element, {@code false} otherwise.
*/
public static boolean isDominated(@NotNull PsiCaseLabelElement overWhom,
@NotNull PsiElement who,
@NotNull PsiType selectorType) {
boolean isOverWhomUnconditionalForSelector = JavaPsiPatternUtil.isUnconditionalForType(overWhom, selectorType);
if (!isOverWhomUnconditionalForSelector &&
((!(overWhom instanceof PsiExpression expression) || ExpressionUtils.isNullLiteral(expression)) &&
who instanceof PsiKeyword &&
PsiKeyword.DEFAULT.equals(who.getText()) || isInCaseNullDefaultLabel(who))) {
// JEP 440-441
// A 'default' label dominates a case label with a case pattern,
// and it also dominates a case label with a null case constant.
// A 'case null, default' label dominates all other switch labels.
return true;
}
if (who instanceof PsiCaseLabelElement currentElement) {
if (JavaPsiPatternUtil.isGuarded(currentElement)) return false;
if (isConstantLabelElement(overWhom)) {
PsiExpression constExpr = ObjectUtils.tryCast(overWhom, PsiExpression.class);
assert constExpr != null;
if (JavaPsiPatternUtil.dominatesOverConstant(currentElement, constExpr.getType())) {
return true;
}
}
else {
if (JavaPsiPatternUtil.dominates(currentElement, overWhom)) {
return true;
}
}
}
return false;
}
private static boolean isInCaseNullDefaultLabel(@NotNull PsiElement element) {
PsiCaseLabelElementList list = ObjectUtils.tryCast(element.getParent(), PsiCaseLabelElementList.class);
if (list == null || list.getElementCount() != 2) return false;
PsiCaseLabelElement[] elements = list.getElements();
return elements[0] instanceof PsiExpression expr &&
ExpressionUtils.isNullLiteral(expr) &&
elements[1] instanceof PsiDefaultCaseLabelElement;
}
/**
* 14.11.1 Switch Blocks
* To ensure the absence of unreachable statements, domination rules provide a possible order
* of different case label elements.
* <p>
* The dominance is based on Properties of Patterns (14.30.3).
*
* @see JavaPsiPatternUtil#isUnconditionalForType(PsiCaseLabelElement, PsiType)
* @see JavaPsiPatternUtil#dominates(PsiCaseLabelElement, PsiCaseLabelElement)
*/
private boolean checkDominance(@NotNull List<? extends PsiElement> switchLabels,
@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
Map<PsiCaseLabelElement, PsiElement> dominatedLabels = findDominatedLabels(switchLabels);
private static boolean doCheckDominance(@NotNull Consumer<? super HighlightInfo.Builder> errorSink,
Map<PsiCaseLabelElement, PsiElement> dominatedLabels) {
AtomicBoolean reported = new AtomicBoolean();
dominatedLabels.forEach((overWhom, who) -> {
HighlightInfo.Builder info = createError(overWhom, JavaErrorBundle.message("switch.dominance.of.preceding.label", who.getText()));
if (who instanceof PsiKeyword && PsiKeyword.DEFAULT.equals(who.getText()) ||
isInCaseNullDefaultLabel(who)) {
JavaPsiSwitchUtil.isInCaseNullDefaultLabel(who)) {
PsiSwitchLabelStatementBase labelStatementBase = PsiTreeUtil.getParentOfType(who, PsiSwitchLabelStatementBase.class);
if (labelStatementBase != null) {
MakeDefaultLastCaseFix action = new MakeDefaultLastCaseFix(labelStatementBase);
@@ -332,29 +252,6 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
return ContainerUtil.exists(elements, element -> extractPattern(element) != null);
}
static void fillElementsToCheckDominance(@NotNull List<? super PsiCaseLabelElement> elements,
@NotNull PsiCaseLabelElement labelElement) {
if (labelElement instanceof PsiPattern) {
elements.add(labelElement);
}
else if (labelElement instanceof PsiExpression) {
boolean isNullType = isNullType(labelElement);
if (isNullType && isInCaseNullDefaultLabel(labelElement)) {
// JEP 432
// A 'case null, default' label dominates all other switch labels.
//
// In this case, only the 'default' case will be added to the elements checked for dominance
return;
}
if (isNullType || isConstantLabelElement(labelElement)) {
elements.add(labelElement);
}
}
else if (labelElement instanceof PsiDefaultCaseLabelElement) {
elements.add(labelElement);
}
}
private static void registerDeleteFixForDefaultElement(@NotNull HighlightInfo.Builder info,
PsiElement defaultElement,
@NotNull PsiElement duplicateElement) {
@@ -417,14 +314,6 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
return ContainerUtil.find(labelElements, element -> JavaPsiPatternUtil.isUnconditionalForType(element, type));
}
private static boolean isConstantLabelElement(@NotNull PsiCaseLabelElement labelElement) {
return evaluateConstant(labelElement) != null || isEnumConstant(labelElement);
}
private static boolean isEnumConstant(@NotNull PsiCaseLabelElement element) {
return getEnumConstant(element) != null;
}
/**
* Evaluates the completeness of a switch block.
*
@@ -484,10 +373,6 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
return reported.get() ? INCOMPLETE : COMPLETE_WITHOUT_UNCONDITIONAL;
}
private static boolean isNullType(@NotNull PsiElement element) {
return element instanceof PsiExpression expression && TypeConversionUtil.isNullType(expression.getType());
}
private static QuickFixFactory getFixFactory() {
return QuickFixFactory.getInstance();
}
@@ -1,21 +1,20 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.ExpressionUtil;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInsight.intention.impl.PriorityIntentionActionWrapper;
import com.intellij.java.codeserver.core.JavaPsiSwitchUtil;
import com.intellij.modcommand.ModCommandAction;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.JavaPsiSwitchUtil;
import com.intellij.psi.util.PsiUtil;
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;
@@ -125,11 +124,7 @@ public class SwitchBlockHighlightingModel {
}
boolean needToCheckCompleteness(@NotNull List<? extends PsiCaseLabelElement> elements) {
return myBlock instanceof PsiSwitchExpression || myBlock instanceof PsiSwitchStatement && isEnhancedSwitch(elements);
}
private boolean isEnhancedSwitch(@NotNull List<? extends PsiCaseLabelElement> labelElements) {
return JavaPsiSwitchUtil.isEnhancedSwitch(labelElements, mySelectorType);
return ExpressionUtil.isEnhancedSwitch(myBlock);
}
void checkEnumCompleteness(@NotNull PsiClass selectorClass,
@@ -214,13 +209,7 @@ public class SwitchBlockHighlightingModel {
result.add(defaultElement);
}
PatternsInSwitchBlockHighlightingModel patternInSwitchModel =
ObjectUtils.tryCast(switchModel, PatternsInSwitchBlockHighlightingModel.class);
if (patternInSwitchModel == null) return result;
List<PsiCaseLabelElement> dominanceCheckingCandidates = new SmartList<>();
labelElements.forEach(label -> PatternsInSwitchBlockHighlightingModel.fillElementsToCheckDominance(dominanceCheckingCandidates, label));
if (dominanceCheckingCandidates.isEmpty()) return result;
return StreamEx.ofKeys(patternInSwitchModel.findDominatedLabels(dominanceCheckingCandidates), value -> value instanceof PsiPattern)
return StreamEx.ofKeys(JavaPsiSwitchUtil.findDominatedLabels(switchBlock), value -> value instanceof PsiPattern)
.into(result);
}
}
@@ -15,6 +15,7 @@
*/
package com.siyeh.ig.migration;
import com.intellij.codeInsight.ExpressionUtil;
import com.intellij.codeInspection.CommonQuickFixBundle;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.options.OptPane;
@@ -23,7 +24,10 @@ import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.*;
import com.intellij.psi.util.PsiLiteralUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
@@ -288,7 +292,7 @@ public final class UnnecessaryBoxingInspection extends BaseInspection {
return true;
}
//if it is an enhanced switch, it must work only with objects (at least until JEP 455 or similar)
if (parent instanceof PsiSwitchBlock switchBlock && JavaPsiSwitchUtil.isEnhancedSwitch(switchBlock)) {
if (parent instanceof PsiSwitchBlock switchBlock && ExpressionUtil.isEnhancedSwitch(switchBlock)) {
return true;
}
else if (parent instanceof PsiVariable) {
@@ -15,13 +15,17 @@
*/
package com.siyeh.ig.psiutils;
import com.intellij.codeInsight.daemon.impl.analysis.PatternsInSwitchBlockHighlightingModel;
import com.intellij.codeInsight.ExpressionUtil;
import com.intellij.java.codeserver.core.JavaPsiSwitchUtil;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.psi.util.JavaPsiPatternUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
@@ -944,7 +948,7 @@ public final class SwitchUtils {
if (unreachableElements.isEmpty() || hasDefault) {
return unreachableElements;
}
boolean isEnhancedSwitch = JavaPsiSwitchUtil.isEnhancedSwitch(statement);
boolean isEnhancedSwitch = ExpressionUtil.isEnhancedSwitch(statement);
if (isEnhancedSwitch) {
PsiExpression expression = statement.getExpression();
if (expression == null) {
@@ -960,7 +964,7 @@ public final class SwitchUtils {
boolean isDominated = false;
for (int j = i + 1; j < unreachableElements.size(); j++) {
PsiCaseLabelElement nextElement = unreachableElements.get(j);
isDominated = PatternsInSwitchBlockHighlightingModel.isDominated(currentElement, nextElement, selectorType);
isDominated = JavaPsiSwitchUtil.isDominated(currentElement, nextElement, selectorType);
if (!isDominated) {
break;
}
@@ -1,9 +1,12 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
public final class ExpressionUtil {
/**
@@ -22,4 +25,70 @@ public final class ExpressionUtil {
}
return false;
}
/**
* Checks if the given switch is enhanced.
*
* @param statement the switch to check
* @return true if the switch is an enhanced switch, false otherwise
*/
public static boolean isEnhancedSwitch(@NotNull PsiSwitchBlock statement) {
if (statement instanceof PsiSwitchExpression) return true;
PsiExpression selector = statement.getExpression();
if (selector == null) return false;
PsiType selectorType = selector.getType();
if (selectorType == null) return false;
if (isEnhancedSelectorType(selectorType)) return true;
PsiCodeBlock body = statement.getBody();
if (body == null) return false;
for (PsiStatement psiStatement : body.getStatements()) {
if (psiStatement instanceof PsiSwitchLabelStatementBase) {
PsiSwitchLabelStatementBase labelStatementBase = (PsiSwitchLabelStatementBase)psiStatement;
PsiCaseLabelElementList elementList = labelStatementBase.getCaseLabelElementList();
if (elementList == null) continue;
PsiCaseLabelElement[] elements = elementList.getElements();
for (PsiCaseLabelElement caseLabelElement : elements) {
if (caseLabelElement != null) {
if (caseLabelElement instanceof PsiPattern || isNullType(caseLabelElement)) return true;
}
}
}
}
return false;
}
/**
* @param element element to check
* @return true if the element is an expression whose type is {@link PsiTypes#nullType()}.
*/
public static boolean isNullType(@NotNull PsiElement element) {
return element instanceof PsiExpression && TypeConversionUtil.isNullType(((PsiExpression)element).getType());
}
/**
* @param type type of switch selector to check
* @return true if this type of switch selector is a type of enhanced switch selector;
* false if it's a classic (Java 7) switch selector type.
*/
private static boolean isEnhancedSelectorType(@NotNull PsiType type) {
PsiPrimitiveType unboxedType = PsiPrimitiveType.getOptionallyUnboxedType(type);
if (unboxedType != null &&
(unboxedType.equals(PsiTypes.booleanType()) ||
unboxedType.equals(PsiTypes.floatType()) ||
unboxedType.equals(PsiTypes.doubleType()) ||
unboxedType.equals(PsiTypes.longType()))) {
return true;
}
if (TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.INT_RANK) return false;
if (TypeConversionUtil.isPrimitiveAndNotNull(type)) return false;
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (psiClass != null) {
if (psiClass.isEnum()) return false;
String fqn = psiClass.getQualifiedName();
if (Comparing.strEqual(fqn, CommonClassNames.JAVA_LANG_STRING)) return false;
}
return true;
}
}
@@ -2,6 +2,7 @@
package com.intellij.psi.controlFlow;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.ExpressionUtil;
import com.intellij.core.JavaPsiBundle;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
@@ -9,7 +10,10 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.psi.util.JavaPsiPatternUtil;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.Stack;
import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -956,7 +960,7 @@ final class ControlFlowAnalyzer extends JavaElementVisitor {
if (body != null) {
PsiStatement[] statements = body.getStatements();
//16.2.9 (for statements) and 16.1.6 (for expressions)
boolean needToCreateDefault = JavaPsiSwitchUtil.isEnhancedSwitch(statement);
boolean needToCreateDefault = ExpressionUtil.isEnhancedSwitch(statement);
PsiType exprType = expr == null ? null : expr.getType();
for (PsiStatement aStatement : statements) {
ProgressManager.checkCanceled();