From fa60db51643dc38abf177c1cfdeb15e7c008e09d Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Wed, 26 Feb 2025 15:17:54 +0100 Subject: [PATCH] [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 --- .../codeserver/core}/JavaPsiSwitchUtil.java | 212 +++++++++++------- .../highlighting/SwitchChecker.java | 16 +- .../highlighting/errors/JavaErrorKinds.java | 1 + .../impl/analysis/JavaErrorFixProvider.java | 1 + ...atternsInSwitchBlockHighlightingModel.java | 147 ++---------- .../SwitchBlockHighlightingModel.java | 19 +- .../UnnecessaryBoxingInspection.java | 8 +- .../com/siyeh/ig/psiutils/SwitchUtils.java | 12 +- .../intellij/codeInsight/ExpressionUtil.java | 69 ++++++ .../psi/controlFlow/ControlFlowAnalyzer.java | 8 +- 10 files changed, 250 insertions(+), 243 deletions(-) rename java/{java-psi-impl/src/com/intellij/psi/util => codeserver/core/src/com/intellij/java/codeserver/core}/JavaPsiSwitchUtil.java (57%) diff --git a/java/java-psi-impl/src/com/intellij/psi/util/JavaPsiSwitchUtil.java b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiSwitchUtil.java similarity index 57% rename from java/java-psi-impl/src/com/intellij/psi/util/JavaPsiSwitchUtil.java rename to java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiSwitchUtil.java index 5514d7d2d5cb..22da27e43d16 100644 --- a/java/java-psi-impl/src/com/intellij/psi/util/JavaPsiSwitchUtil.java +++ b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiSwitchUtil.java @@ -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 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 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. *

- * 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. *

@@ -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 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 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 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 diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/SwitchChecker.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/SwitchChecker.java index c33e707ad57d..7fd0b6890e58 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/SwitchChecker.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/SwitchChecker.java @@ -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) { diff --git a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java index d1e4f2b9a40e..6cb815e34016 100644 --- a/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java +++ b/java/codeserver/highlighting/src/com/intellij/java/codeserver/highlighting/errors/JavaErrorKinds.java @@ -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; diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java index 9199956e9f31..b2c55cf63a20 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/JavaErrorFixProvider.java @@ -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; diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternsInSwitchBlockHighlightingModel.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternsInSwitchBlockHighlightingModel.java index 0c41460a759c..32d373704048 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternsInSwitchBlockHighlightingModel.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternsInSwitchBlockHighlightingModel.java @@ -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 errorSink) { + if (checkDominance(errorSink)) return; + PsiCodeBlock body = myBlock.getBody(); if (body == null) return; - List elementsToCheckDominance = new ArrayList<>(); List 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 findDominatedLabels(@NotNull List switchLabels) { - Map 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 errorSink) { + Map 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. - *

- * 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 switchLabels, - @NotNull Consumer errorSink) { - Map dominatedLabels = findDominatedLabels(switchLabels); + private static boolean doCheckDominance(@NotNull Consumer errorSink, + Map 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 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(); } 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 f124a1bc991d..bc4076fcf729 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 @@ -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 elements) { - return myBlock instanceof PsiSwitchExpression || myBlock instanceof PsiSwitchStatement && isEnhancedSwitch(elements); - } - - private boolean isEnhancedSwitch(@NotNull List 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 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); } } diff --git a/java/java-analysis-impl/src/com/siyeh/ig/migration/UnnecessaryBoxingInspection.java b/java/java-analysis-impl/src/com/siyeh/ig/migration/UnnecessaryBoxingInspection.java index 34c4e48af579..b82359e48f70 100644 --- a/java/java-analysis-impl/src/com/siyeh/ig/migration/UnnecessaryBoxingInspection.java +++ b/java/java-analysis-impl/src/com/siyeh/ig/migration/UnnecessaryBoxingInspection.java @@ -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) { diff --git a/java/java-analysis-impl/src/com/siyeh/ig/psiutils/SwitchUtils.java b/java/java-analysis-impl/src/com/siyeh/ig/psiutils/SwitchUtils.java index 504cf4ed7802..ed6405dd066b 100644 --- a/java/java-analysis-impl/src/com/siyeh/ig/psiutils/SwitchUtils.java +++ b/java/java-analysis-impl/src/com/siyeh/ig/psiutils/SwitchUtils.java @@ -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; } diff --git a/java/java-psi-impl/src/com/intellij/codeInsight/ExpressionUtil.java b/java/java-psi-impl/src/com/intellij/codeInsight/ExpressionUtil.java index ed895988428a..a747c2793288 100644 --- a/java/java-psi-impl/src/com/intellij/codeInsight/ExpressionUtil.java +++ b/java/java-psi-impl/src/com/intellij/codeInsight/ExpressionUtil.java @@ -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; + } } diff --git a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowAnalyzer.java b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowAnalyzer.java index eca4fc7953f8..a3ac5af711b1 100644 --- a/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowAnalyzer.java +++ b/java/java-psi-impl/src/com/intellij/psi/controlFlow/ControlFlowAnalyzer.java @@ -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();