mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[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:
committed by
intellij-monorepo-bot
parent
30c5bb9996
commit
fa60db5164
+131
-81
@@ -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
|
||||
+8
-8
@@ -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) {
|
||||
|
||||
+1
@@ -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;
|
||||
|
||||
+1
@@ -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;
|
||||
|
||||
+16
-131
@@ -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();
|
||||
}
|
||||
|
||||
+4
-15
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user