diff --git a/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiSwitchUtil.java b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiSwitchUtil.java index 3e0a2a45c004..7b0eeaf0eec4 100644 --- a/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiSwitchUtil.java +++ b/java/codeserver/core/src/com/intellij/java/codeserver/core/JavaPsiSwitchUtil.java @@ -4,6 +4,8 @@ package com.intellij.java.codeserver.core; import com.intellij.codeInsight.ExpressionUtil; import com.intellij.java.syntax.parser.JavaKeywords; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.java.JavaFeature; import com.intellij.psi.CommonClassNames; import com.intellij.psi.JavaPsiFacade; @@ -13,14 +15,19 @@ import com.intellij.psi.PsiClass; import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiCodeBlock; import com.intellij.psi.PsiConstantEvaluationHelper; +import com.intellij.psi.PsiDeconstructionList; +import com.intellij.psi.PsiDeconstructionPattern; import com.intellij.psi.PsiDefaultCaseLabelElement; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementFactory; import com.intellij.psi.PsiEnumConstant; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiKeyword; import com.intellij.psi.PsiLiteralExpression; +import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiPattern; import com.intellij.psi.PsiPrimitiveType; +import com.intellij.psi.PsiRecordComponent; import com.intellij.psi.PsiReferenceExpression; import com.intellij.psi.PsiStatement; import com.intellij.psi.PsiSwitchBlock; @@ -32,6 +39,7 @@ 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.IncorrectOperationException; import com.intellij.util.ObjectUtils; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; @@ -45,6 +53,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import static java.util.Objects.requireNonNull; @@ -206,7 +215,8 @@ public final class JavaPsiSwitchUtil { if (!isOverWhomUnconditionalForSelector && ((!(overWhom instanceof PsiExpression expression) || ExpressionUtil.isNullLiteral(expression)) && who instanceof PsiKeyword && - JavaKeywords.DEFAULT.equals(who.getText()) || isInCaseNullDefaultLabel(who))) { + JavaKeywords.DEFAULT.equals(who.getText()) || isInCaseNullDefaultLabel(who) || + who instanceof PsiSwitchLabelStatementBase switchLabelStatementBase && switchLabelStatementBase.isDefaultCase())) { // 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. @@ -450,6 +460,106 @@ public final class JavaPsiSwitchUtil { return elementsToCheckCompleteness; } + /** + * Determines whether a MatchException may occur during the deconstruction process. + * + * @param deconstructionPattern the deconstruction pattern being analyzed + * @param recordComponent the record component being checked + * @param deconstructionComponent the deconstruction component corresponding to the record component being checked, + * @param skipDominatingElements a set of elements to skip during dominance check + * @return {@code true} if a MatchException may occur during deconstruction, + * {@code false} otherwise + */ + public static boolean mayCauseMatchExceptionDuringDeconstruction(@Nullable PsiDeconstructionPattern deconstructionPattern, + @Nullable PsiRecordComponent recordComponent, + @Nullable PsiPattern deconstructionComponent, + @NotNull Set<@NotNull PsiElement> skipDominatingElements) { + if (deconstructionPattern == null || deconstructionComponent == null || recordComponent == null) return false; + PsiDeconstructionPattern topLevelDeconstruction = deconstructionPattern; + while (topLevelDeconstruction.getParent() instanceof PsiDeconstructionList deconstructionList && + deconstructionList.getParent() instanceof PsiDeconstructionPattern parent) { + topLevelDeconstruction = parent; + } + PsiElement deconstructionParent = topLevelDeconstruction.getParent(); + if (!(deconstructionParent instanceof PsiCaseLabelElementList caseLabelElementList)) return false; + if (!(caseLabelElementList.getParent() instanceof PsiSwitchLabelStatementBase switchLabelStatement)) return false; + if (!(switchLabelStatement.getParent() instanceof PsiCodeBlock codeBlock && + codeBlock.getParent() instanceof PsiSwitchBlock switchBlock)) { + return false; + } + + PsiType recordComponentType = recordComponent.getType(); + PsiClass recordComponentClass = PsiUtil.resolveClassInClassTypeOnly(recordComponentType); + if (recordComponentClass == null) return false; + if (deconstructionComponent instanceof PsiDeconstructionPattern || + recordComponentClass.hasModifierProperty(PsiModifier.SEALED)) { + if (!hasDominated(switchBlock, + topLevelDeconstruction, + deconstructionComponent, + recordComponentClass, + skipDominatingElements)) { + return true; + } + } + return false; + } + + private static boolean hasDominated(@NotNull PsiSwitchBlock block, + @NotNull PsiDeconstructionPattern pattern, + @NotNull PsiPattern deconstructionComponent, + @NotNull PsiClass componentClass, + @NotNull Set<@NotNull PsiElement> skipDominatingElements) { + String text = pattern.getText(); + TextRange textRange = pattern.getTextRange(); + TextRange componentTextRange = deconstructionComponent.getTextRange(); + if (!textRange.contains(componentTextRange)) return true; + TextRange toChange = componentTextRange.shiftLeft(textRange.getStartOffset()); + String newPatternTe = StringUtil.replaceSubstring(text, toChange, componentClass.getQualifiedName() + " someVariable"); + PsiPattern newPattern = createPatternFromText(newPatternTe, block); + if (newPattern == null) return true; + List branches = getSwitchBranches(block); + PsiExpression expression = block.getExpression(); + if (expression == null) return true; + PsiType selectorType = expression.getType(); + if (selectorType == null) return true; + for (PsiElement branch : branches) { + if (skipDominatingElements.contains(branch) || + //case null, default + (isNullOrDefault(branch) && + ContainerUtil.exists(skipDominatingElements, e -> isInCaseNullDefaultLabel(e)))) { + continue; + } + boolean dominated = isDominated(newPattern, branch, selectorType); + if (dominated) return true; + } + return false; + } + + private static boolean isNullOrDefault(@NotNull PsiElement branch) { + return ExpressionUtil.isNullLiteral(branch) || + (branch instanceof PsiKeyword && JavaKeywords.DEFAULT.equals(branch.getText())); + } + + + private static @Nullable PsiPattern createPatternFromText(@NotNull String patternText, @NotNull PsiElement context) { + PsiElementFactory factory = PsiElementFactory.getInstance(context.getProject()); + String labelText = "case " + patternText + "->{}"; + PsiStatement statement; + try { + statement = factory.createStatementFromText(labelText, context); + } + catch (IncorrectOperationException e) { + return null; + } + PsiSwitchLabelStatementBase label = ObjectUtils.tryCast(statement, PsiSwitchLabelStatementBase.class); + if (label == null) return null; + PsiCaseLabelElementList list = label.getCaseLabelElementList(); + if (list == null) return null; + PsiCaseLabelElement element = list.getElements()[0]; + if (!(element instanceof PsiPattern pattern)) return null; + return pattern; + } + /** * Kinds of switch selector * @see #getSwitchSelectorKind(PsiType) diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties index b2415c8ae9af..b90ca93c402a 100644 --- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties +++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties @@ -37,6 +37,8 @@ dataflow.message.npe.methodref.invocation=Method reference invocation #ref dataflow.message.only.switch.label=Switch label #ref is the only reachable in the whole switch dataflow.message.passing.null.argument.nonannotated=Passing null argument to non-annotated parameter dataflow.message.passing.null.argument=Passing null argument to parameter annotated as @NotNull +dataflow.message.deconstruction.match.exception=Pattern matching will throw MatchException +dataflow.message.may.deconstruction.match.exception=Pattern matching may throw MatchException dataflow.message.passing.nullable.argument.methodref.nonannotated=Method reference argument might be null but passed to non-annotated parameter dataflow.message.passing.nullable.argument.methodref=Method reference argument might be null dataflow.message.passing.nullable.argument.nonannotated=Argument #ref might be null but passed to non-annotated parameter diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index bce270cb3661..7118f17284e1 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -8,6 +8,7 @@ import com.intellij.codeInsight.Nullability; import com.intellij.codeInsight.NullabilityAnnotationInfo; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.intention.AddAnnotationModCommandAction; +import com.intellij.codeInsight.intention.QuickFixFactory; import com.intellij.codeInsight.intention.impl.BaseIntentionAction; import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool; import com.intellij.codeInspection.InspectionsBundle; @@ -136,6 +137,7 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true; public boolean REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL = true; public boolean REPORT_UNSOUND_WARNINGS = true; + public boolean REPORT_MATCHED_EXCEPTION = true; @Override public void writeSettings(@NotNull Element node) throws WriteExternalException { @@ -154,6 +156,9 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec if (!REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL) { node.addContent(new Element("option").setAttribute("name", "REPORT_NULLABLE_METHODS_RETURNING_NOT_NULL").setAttribute("value", "false")); } + if (!REPORT_MATCHED_EXCEPTION) { + node.addContent(new Element("option").setAttribute("name", "CHECK_MATCHED_EXCEPTION").setAttribute("value", "false")); + } if (!REPORT_UNSOUND_WARNINGS) { node.addContent(new Element("option").setAttribute("name", "REPORT_UNSOUND_WARNINGS").setAttribute("value", "false")); } @@ -607,6 +612,11 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec Consumer reportNullability = expr -> reportNullabilityProblem(reporter, problem, expression); NullabilityProblemKind.assigningToNotNull.ifMyProblem(problem, reportNullability); NullabilityProblemKind.storingToNotNullArray.ifMyProblem(problem, reportNullability); + if (REPORT_MATCHED_EXCEPTION) { + NullabilityProblemKind.deconstructionMatchException.ifMyProblem(problem, pattern -> { + reportDeconstructionMatchExceptionProblem(reporter, problem, pattern); + }); + } if (SUGGEST_NULLABLE_ANNOTATIONS) { NullabilityProblemKind.passingToNonAnnotatedMethodRefParameter.ifMyProblem( problem, methodRef -> reportNullableArgumentPassedToNonAnnotatedMethodRef(reporter, problem, methodRef)); @@ -621,6 +631,26 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec } } + private void reportDeconstructionMatchExceptionProblem(@NotNull ProblemReporter reporter, + @NotNull NullabilityProblem problem, + @NotNull PsiPattern pattern) { + ModCommandAction modCommandAction = createSwitchAddDefault(pattern); + if (modCommandAction == null) { + reporter.registerProblem(pattern, problem.getMessage(IGNORE_ASSERT_STATEMENTS)); + return; + } + reporter.registerProblem(pattern, problem.getMessage(IGNORE_ASSERT_STATEMENTS), LocalQuickFix.from(modCommandAction)); + } + + private static @Nullable ModCommandAction createSwitchAddDefault(@NotNull PsiPattern pattern) { + QuickFixFactory quickFixFactory = QuickFixFactory.getInstance(); + PsiSwitchBlock switchBlock = PsiTreeUtil.getParentOfType(pattern, PsiSwitchBlock.class); + if (switchBlock == null) return null; + ModCommandAction modCommandAction = quickFixFactory.createAddSwitchDefaultFix(switchBlock, null).asModCommandAction(); + if (modCommandAction == null) return null; + return modCommandAction; + } + private void reportNullabilityProblem(ProblemReporter reporter, NullabilityProblem problem, PsiExpression expr) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblemKind.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblemKind.java index 8c0d46bd4f93..edda1af68d91 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblemKind.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblemKind.java @@ -41,6 +41,7 @@ import com.intellij.psi.PsiNewExpression; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiParameterList; import com.intellij.psi.PsiParenthesizedExpression; +import com.intellij.psi.PsiPattern; import com.intellij.psi.PsiPolyadicExpression; import com.intellij.psi.PsiPrimitiveType; import com.intellij.psi.PsiReferenceExpression; @@ -100,6 +101,7 @@ import static com.intellij.util.ObjectUtils.tryCast; public final class NullabilityProblemKind { private static final String NPE = JAVA_LANG_NULL_POINTER_EXCEPTION; private static final String RE = JAVA_LANG_RUNTIME_EXCEPTION; + private static final String MATCH_EXCEPTION = "java.lang.MatchException"; private final String myName; private final Supplier<@Nls String> myAlwaysNullMessage; @@ -169,6 +171,11 @@ public final class NullabilityProblemKind { "dataflow.message.passing.nullable.argument.methodref.nonannotated"); // assumeNotNull problem is not reported, just used to force the argument to be not null public static final NullabilityProblemKind assumeNotNull = new NullabilityProblemKind<>(RE, "assumeNotNull"); + public static final NullabilityProblemKind deconstructionMatchException = + new NullabilityProblemKind<>(MATCH_EXCEPTION, "deconstructionMatchException", + "dataflow.message.deconstruction.match.exception", + "dataflow.message.may.deconstruction.match.exception"); + /** * noProblem is not reported and used to override another problem * @see ControlFlowAnalyzer#addCustomNullabilityProblem(PsiExpression, NullabilityProblemKind) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java index ad0035b5014b..8aa6606e0693 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java @@ -117,6 +117,7 @@ import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; import com.intellij.codeInspection.dataFlow.value.RelationType; import com.intellij.codeInspection.dataFlow.value.VariableDescriptor; +import com.intellij.java.codeserver.core.JavaPsiSwitchUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.pom.java.JavaFeature; import com.intellij.psi.JavaCodeFragment; @@ -264,6 +265,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import static com.intellij.codeInspection.dataFlow.NullabilityProblemKind.deconstructionMatchException; import static com.intellij.psi.CommonClassNames.JAVA_LANG_ASSERTION_ERROR; import static com.intellij.psi.CommonClassNames.JAVA_LANG_ERROR; import static com.intellij.psi.CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION; @@ -1362,6 +1364,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { VariableDescriptor descriptor = field == null ? new GetterDescriptor(accessor) : new PlainDescriptor(field); DfaVariableValue accessorDfaVar = getFactory().getVarFactory().createVariableValue(descriptor, patternDfaVar); addInstruction(new PushInstruction(accessorDfaVar, null)); + if (JavaPsiSwitchUtil.mayCauseMatchExceptionDuringDeconstruction(deconstructionPattern, recordComponent, patternComponent, Set.of())) { + addNullCheck(deconstructionMatchException.problem(patternComponent, null)); + } processPattern(sourcePattern, patternComponent, substitutor.substitute(recordComponent.getType()), null, endPatternOffset); } } diff --git a/java/java-backend/resources/META-INF/Inspections.xml b/java/java-backend/resources/META-INF/Inspections.xml index 6cfa0f0eae59..b342b0003886 100644 --- a/java/java-backend/resources/META-INF/Inspections.xml +++ b/java/java-backend/resources/META-INF/Inspections.xml @@ -486,12 +486,6 @@ groupKey="group.names.nullability.problems" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING" implementationClass="com.intellij.codeInspection.nullable.NullableStuffInspection"/> - Use the Ignore assert statements option to control how the inspection treats assert statements. By default, the option is disabled, which means that the assertions are assumed to be executed (-ea mode). If the option is enabled, the assertions will be completely ignored (-da mode). +
  • Use the Reports patterns that may throw MatchException option to report patterns that may throw + MatchException at runtime due to null values in deconstruction patterns.
  • Use the Report problems that happen only on some code paths option to control whether to report problems that may happen only on some code path. If this option is disabled, warnings like exception is possible will not be reported. The inspection will report only warnings like exception will definitely occur. This mode may greatly reduce the number of false-positives, especially if the code diff --git a/java/java-impl/resources/inspectionDescriptions/MatchException.html b/java/java-impl/resources/inspectionDescriptions/MatchException.html deleted file mode 100644 index 22e4bcefe611..000000000000 --- a/java/java-impl/resources/inspectionDescriptions/MatchException.html +++ /dev/null @@ -1,35 +0,0 @@ - - -Reports patterns in switch expressions and statements that may throw -MatchException at runtime -due to null values in deconstruction patterns. - -

    - The inspection analyzes record components with @Nullable annotation to detect cases where - deconstruction patterns may not cover null values. -

    - -

    Example:

    -
    
    -sealed interface II {
    -    record AI() implements II {}
    -    record BI() implements II {}
    -}
    -
    -record RI(@Nullable II value) {}
    -
    -private static II getII(RI ri) {
    -    return switch (ri) {
    -        case RI(II.BI bi) -> bi; // MatchException if ri.value is null
    -        case RI(II.AI ai) -> ai;
    -    };
    -}
    -
    - -

    - Use the provided quick-fix to add a default branch to handle unmatched cases safely. -

    - -

    New in 2026.1 - - \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInspection/MatchExceptionInspection.java b/java/java-impl/src/com/intellij/codeInspection/MatchExceptionInspection.java deleted file mode 100644 index 42672566dd48..000000000000 --- a/java/java-impl/src/com/intellij/codeInspection/MatchExceptionInspection.java +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.codeInspection; - -import com.intellij.codeInsight.ExpressionUtil; -import com.intellij.codeInsight.Nullability; -import com.intellij.codeInsight.TypeNullability; -import com.intellij.codeInsight.intention.QuickFixFactory; -import com.intellij.java.JavaBundle; -import com.intellij.java.codeserver.core.JavaPsiSwitchUtil; -import com.intellij.java.syntax.parser.JavaKeywords; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.JavaElementVisitor; -import com.intellij.psi.PsiCaseLabelElement; -import com.intellij.psi.PsiCaseLabelElementList; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiDeconstructionPattern; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiElementFactory; -import com.intellij.psi.PsiElementVisitor; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiKeyword; -import com.intellij.psi.PsiModifier; -import com.intellij.psi.PsiPattern; -import com.intellij.psi.PsiRecordComponent; -import com.intellij.psi.PsiStatement; -import com.intellij.psi.PsiSwitchBlock; -import com.intellij.psi.PsiSwitchExpression; -import com.intellij.psi.PsiSwitchLabelStatementBase; -import com.intellij.psi.PsiSwitchStatement; -import com.intellij.psi.PsiType; -import com.intellij.psi.PsiTypeElement; -import com.intellij.psi.util.PsiUtil; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.ObjectUtils; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.Objects; -import java.util.Set; - -public final class MatchExceptionInspection extends AbstractBaseJavaLocalInspectionTool { - - @Override - public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { - - return new JavaElementVisitor() { - @Override - public void visitSwitchExpression(@NotNull PsiSwitchExpression expression) { - super.visitSwitchExpression(expression); - checkSwitchBlock(expression); - } - - @Override - public void visitSwitchStatement(@NotNull PsiSwitchStatement statement) { - super.visitSwitchStatement(statement); - checkSwitchBlock(statement); - } - - private void checkSwitchBlock(@NotNull PsiSwitchBlock switchBlock) { - //fast exit - if (JavaPsiSwitchUtil.findDefaultElement(switchBlock) != null) { - return; - } - - PsiPattern pattern = findPatternCanProduceMatchException(switchBlock, Set.of(), true); - if (pattern == null) { - return; - } - - QuickFixFactory quickFixFactory = QuickFixFactory.getInstance(); - holder.problem(pattern, JavaBundle.message("inspection.match.exception.problems.message")) - .fix(Objects.requireNonNull(quickFixFactory.createAddSwitchDefaultFix(switchBlock, null).asModCommandAction())) - .register(); - } - }; - } - - /** - * Identifies a pattern in the branches of a given {@code PsiSwitchBlock} that could potentially - * produce a match exception during execution. - * - * @param switchBlock the {@link PsiSwitchBlock} to analyze, representing a switch statement or - * switch expression in Java code. It must not be null. - * @param skipDominatingElements a set of elements that should be skipped when searching for a pattern. - * @param necessaryNullable flag indicating whether the pattern must be certainly nullable. - * @return a {@link PsiPattern} that could potentially cause a match exception, or {@code null} - * if no such pattern is found. - */ - public static @Nullable PsiPattern findPatternCanProduceMatchException(@NotNull PsiSwitchBlock switchBlock, - @NotNull Set<@NotNull PsiElement> skipDominatingElements, - boolean necessaryNullable) { - List branches = JavaPsiSwitchUtil.getSwitchBranches(switchBlock); - for (PsiElement branch : branches) { - if (!(branch instanceof PsiDeconstructionPattern psiDeconstructionPattern)) continue; - PsiPattern deconstructionComponent = findDeconstructionComponentCanProduceMatchException(switchBlock, - psiDeconstructionPattern, - psiDeconstructionPattern, - skipDominatingElements, - necessaryNullable); - if (deconstructionComponent != null) return deconstructionComponent; - } - return null; - } - - private static @Nullable PsiPattern findDeconstructionComponentCanProduceMatchException( - @NotNull PsiSwitchBlock switchBlock, - @NotNull PsiDeconstructionPattern psiDeconstructionPattern, - @NotNull PsiDeconstructionPattern topLevelDeconstructionPattern, - @NotNull Set<@NotNull PsiElement> skipDominatingElements, - boolean necessaryNullable) { - PsiTypeElement typeElement = psiDeconstructionPattern.getTypeElement(); - PsiType recordType = typeElement.getType(); - PsiClass recordClass = PsiUtil.resolveClassInClassTypeOnly(recordType); - if (recordClass == null || !recordClass.isRecord()) { - return null; - } - PsiRecordComponent[] recordComponents = recordClass.getRecordComponents(); - @NotNull PsiPattern @NotNull [] deconstructionComponents = - psiDeconstructionPattern.getDeconstructionList().getDeconstructionComponents(); - if (deconstructionComponents.length != recordComponents.length) { - return null; - } - - for (int i = 0; i < recordComponents.length; i++) { - PsiPattern deconstructionComponent = deconstructionComponents[i]; - if (deconstructionComponent instanceof PsiDeconstructionPattern nestedDeconstructionPattern) { - PsiPattern canProduceMatchException = - findDeconstructionComponentCanProduceMatchException(switchBlock, - nestedDeconstructionPattern, - topLevelDeconstructionPattern, - skipDominatingElements, necessaryNullable); - if (canProduceMatchException != null) return canProduceMatchException; - } - PsiRecordComponent component = recordComponents[i]; - PsiType componentType = component.getType(); - TypeNullability nullability = componentType.getNullability(); - PsiExpression expression = switchBlock.getExpression(); - if (expression == null) return null; - if (necessaryNullable && nullability.nullability() != Nullability.NULLABLE) continue; - if (!necessaryNullable && nullability.nullability() == Nullability.NOT_NULL) continue; - PsiClass componentClass = PsiUtil.resolveClassInClassTypeOnly(componentType); - if (componentClass == null) continue; - if (deconstructionComponent instanceof PsiDeconstructionPattern || - componentClass.hasModifierProperty(PsiModifier.SEALED)) { - if (!hasDominated(switchBlock, topLevelDeconstructionPattern, deconstructionComponent, componentClass, skipDominatingElements)) { - return deconstructionComponent; - } - } - } - return null; - } - - private static boolean hasDominated(@NotNull PsiSwitchBlock block, - @NotNull PsiDeconstructionPattern pattern, - @NotNull PsiPattern deconstructionComponent, - @NotNull PsiClass sealedClass, - @NotNull Set<@NotNull PsiElement> skipDominatingElements) { - String text = pattern.getText(); - TextRange textRange = pattern.getTextRange(); - TextRange componentTextRange = deconstructionComponent.getTextRange(); - TextRange toChange = componentTextRange.shiftLeft(textRange.getStartOffset()); - String newPatternTe = StringUtil.replaceSubstring(text, toChange, sealedClass.getQualifiedName() + " someVariable"); - PsiPattern newPattern = createPatternFromText(newPatternTe, block); - if (newPattern == null) return true; - List branches = JavaPsiSwitchUtil.getSwitchBranches(block); - PsiExpression expression = block.getExpression(); - if (expression == null) return true; - PsiType selectorType = expression.getType(); - if (selectorType == null) return true; - for (PsiElement branch : branches) { - if (skipDominatingElements.contains(branch) || - //case null, default - (isNullOrDefault(branch) && - ContainerUtil.exists(skipDominatingElements, e -> JavaPsiSwitchUtil.isInCaseNullDefaultLabel(e)))) { - continue; - } - boolean dominated = JavaPsiSwitchUtil.isDominated(newPattern, branch, selectorType); - if (dominated) return true; - } - return false; - } - - private static boolean isNullOrDefault(@NotNull PsiElement branch) { - return ExpressionUtil.isNullLiteral(branch) || - (branch instanceof PsiKeyword && JavaKeywords.DEFAULT.equals(branch.getText())); - } - - - private static @Nullable PsiPattern createPatternFromText(@NotNull String patternText, @NotNull PsiElement context) { - PsiElementFactory factory = PsiElementFactory.getInstance(context.getProject()); - String labelText = "case " + patternText + "->{}"; - PsiStatement statement; - try { - statement = factory.createStatementFromText(labelText, context); - } - catch (IncorrectOperationException e) { - return null; - } - PsiSwitchLabelStatementBase label = ObjectUtils.tryCast(statement, PsiSwitchLabelStatementBase.class); - if (label == null) return null; - PsiCaseLabelElementList list = label.getCaseLabelElementList(); - if (list == null) return null; - PsiCaseLabelElement element = list.getElements()[0]; - if (!(element instanceof PsiPattern pattern)) return null; - return pattern; - } -} diff --git a/java/java-impl/src/com/siyeh/ig/controlflow/UnnecessaryDefaultInspection.java b/java/java-impl/src/com/siyeh/ig/controlflow/UnnecessaryDefaultInspection.java index 901ee54e20ea..66e3bf223a3e 100644 --- a/java/java-impl/src/com/siyeh/ig/controlflow/UnnecessaryDefaultInspection.java +++ b/java/java-impl/src/com/siyeh/ig/controlflow/UnnecessaryDefaultInspection.java @@ -15,8 +15,9 @@ */ package com.siyeh.ig.controlflow; +import com.intellij.codeInsight.Nullability; +import com.intellij.codeInsight.TypeNullability; import com.intellij.codeInspection.LocalQuickFix; -import com.intellij.codeInspection.MatchExceptionInspection; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.dataFlow.fix.DeleteSwitchLabelFix; import com.intellij.codeInspection.options.OptPane; @@ -31,6 +32,7 @@ import com.intellij.psi.PsiClass; import com.intellij.psi.PsiClassInitializer; import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiCodeBlock; +import com.intellij.psi.PsiDeconstructionPattern; import com.intellij.psi.PsiDefaultCaseLabelElement; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; @@ -43,6 +45,8 @@ import com.intellij.psi.PsiMember; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiParenthesizedExpression; +import com.intellij.psi.PsiPattern; +import com.intellij.psi.PsiRecordComponent; import com.intellij.psi.PsiReferenceExpression; import com.intellij.psi.PsiStatement; import com.intellij.psi.PsiSwitchBlock; @@ -52,6 +56,7 @@ import com.intellij.psi.PsiSwitchLabeledRuleStatement; import com.intellij.psi.PsiSwitchStatement; import com.intellij.psi.PsiThrowStatement; import com.intellij.psi.PsiType; +import com.intellij.psi.PsiTypeElement; import com.intellij.psi.PsiTypes; import com.intellij.psi.PsiVariable; import com.intellij.psi.controlFlow.AnalysisCanceledException; @@ -72,6 +77,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import static com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR_OR_WARNING; @@ -294,9 +300,61 @@ public final class UnnecessaryDefaultInspection extends BaseInspection { return null; } - if (MatchExceptionInspection.findPatternCanProduceMatchException(switchBlock, Set.of(defaultElement), false) != null) { + if (findPatternCanProduceMatchException(switchBlock, Set.of(defaultElement)) != null) { return null; } return defaultElement; } + + private static @Nullable PsiPattern findPatternCanProduceMatchException(@NotNull PsiSwitchBlock switchBlock, + @NotNull Set<@NotNull PsiElement> skipDominatingElements) { + List branches = JavaPsiSwitchUtil.getSwitchBranches(switchBlock); + for (PsiElement branch : branches) { + if (!(branch instanceof PsiDeconstructionPattern psiDeconstructionPattern)) continue; + PsiPattern deconstructionComponent = findDeconstructionComponentCanProduceMatchException(switchBlock, + psiDeconstructionPattern, + skipDominatingElements); + if (deconstructionComponent != null) return deconstructionComponent; + } + return null; + } + + private static @Nullable PsiPattern findDeconstructionComponentCanProduceMatchException( + @NotNull PsiSwitchBlock switchBlock, + @NotNull PsiDeconstructionPattern psiDeconstructionPattern, + @NotNull Set<@NotNull PsiElement> skipDominatingElements) { + PsiTypeElement typeElement = psiDeconstructionPattern.getTypeElement(); + PsiType recordType = typeElement.getType(); + PsiClass recordClass = PsiUtil.resolveClassInClassTypeOnly(recordType); + if (recordClass == null || !recordClass.isRecord()) { + return null; + } + PsiRecordComponent[] recordComponents = recordClass.getRecordComponents(); + @NotNull PsiPattern @NotNull [] deconstructionComponents = + psiDeconstructionPattern.getDeconstructionList().getDeconstructionComponents(); + if (deconstructionComponents.length != recordComponents.length) { + return null; + } + + for (int i = 0; i < recordComponents.length; i++) { + PsiPattern deconstructionComponent = deconstructionComponents[i]; + if (deconstructionComponent instanceof PsiDeconstructionPattern nestedDeconstructionPattern) { + PsiPattern canProduceMatchException = + findDeconstructionComponentCanProduceMatchException(switchBlock, + nestedDeconstructionPattern, + skipDominatingElements); + if (canProduceMatchException != null) return canProduceMatchException; + } + PsiRecordComponent component = recordComponents[i]; + PsiType componentType = component.getType(); + TypeNullability nullability = componentType.getNullability(); + PsiExpression expression = switchBlock.getExpression(); + if (expression == null) return null; + if (nullability.nullability() == Nullability.NOT_NULL) continue; + if (JavaPsiSwitchUtil.mayCauseMatchExceptionDuringDeconstruction(psiDeconstructionPattern, component, deconstructionComponent, skipDominatingElements)) { + return deconstructionComponent; + } + } + return null; + } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/matchException/MatchExceptionDoubleNestedDeconstruction.java b/java/java-tests/testData/inspection/dataFlow/fixture/MatchExceptionDoubleNestedDeconstruction.java similarity index 98% rename from java/java-tests/testData/inspection/matchException/MatchExceptionDoubleNestedDeconstruction.java rename to java/java-tests/testData/inspection/dataFlow/fixture/MatchExceptionDoubleNestedDeconstruction.java index dfd7aa35923b..107ae0d5d924 100644 --- a/java/java-tests/testData/inspection/matchException/MatchExceptionDoubleNestedDeconstruction.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/MatchExceptionDoubleNestedDeconstruction.java @@ -6,6 +6,7 @@ class Test2 { System.out.println(getLevel3(nullLevel3)); } + @Nullable private static String getLevel3(@Nullable final Level1 level1) { return switch (level1) { case Level1(Level2(Level3(var something))) -> something; diff --git a/java/java-tests/testData/inspection/matchException/MatchExceptionNestedDeconstruction.java b/java/java-tests/testData/inspection/dataFlow/fixture/MatchExceptionNestedDeconstruction.java similarity index 98% rename from java/java-tests/testData/inspection/matchException/MatchExceptionNestedDeconstruction.java rename to java/java-tests/testData/inspection/dataFlow/fixture/MatchExceptionNestedDeconstruction.java index 70f021e5f662..887cf463ebc9 100644 --- a/java/java-tests/testData/inspection/matchException/MatchExceptionNestedDeconstruction.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/MatchExceptionNestedDeconstruction.java @@ -7,6 +7,7 @@ class Test { System.out.println(getLevel3(nullLevel2)); } + @Nullable private static Level3 getLevel3(@Nullable final Level1 level1) { return switch (level1) { case Level1(Level2(var something)) -> something; diff --git a/java/java-tests/testData/inspection/matchException/MatchExceptionNestedSealedClass.java b/java/java-tests/testData/inspection/dataFlow/fixture/MatchExceptionNestedSealedClass.java similarity index 93% rename from java/java-tests/testData/inspection/matchException/MatchExceptionNestedSealedClass.java rename to java/java-tests/testData/inspection/dataFlow/fixture/MatchExceptionNestedSealedClass.java index ba4caf728b01..17cf798749ba 100644 --- a/java/java-tests/testData/inspection/matchException/MatchExceptionNestedSealedClass.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/MatchExceptionNestedSealedClass.java @@ -19,7 +19,7 @@ class TestExample2 { record RI2(@Nullable II value) { } - private static II getII(RI ri) { + public static II getII(RI ri) { return switch (ri) { case RI(RI2(II.BI bi)) -> bi; case RI(RI2(II.AI ai)) -> ai; diff --git a/java/java-tests/testData/inspection/matchException/MatchExceptionSealedClass.java b/java/java-tests/testData/inspection/dataFlow/fixture/MatchExceptionSealedClass.java similarity index 100% rename from java/java-tests/testData/inspection/matchException/MatchExceptionSealedClass.java rename to java/java-tests/testData/inspection/dataFlow/fixture/MatchExceptionSealedClass.java diff --git a/java/java-tests/testData/inspection/matchException/NoMatchExceptionDoubleNestedDeconstructionWithDominated.java b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionDoubleNestedDeconstructionWithDominated.java similarity index 97% rename from java/java-tests/testData/inspection/matchException/NoMatchExceptionDoubleNestedDeconstructionWithDominated.java rename to java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionDoubleNestedDeconstructionWithDominated.java index 0b97a8068e37..93bab3f919eb 100644 --- a/java/java-tests/testData/inspection/matchException/NoMatchExceptionDoubleNestedDeconstructionWithDominated.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionDoubleNestedDeconstructionWithDominated.java @@ -6,6 +6,7 @@ class Test2 { System.out.println(getLevel3(nullLevel3)); } + @Nullable private static String getLevel3(@Nullable final Level1 level1) { return switch (level1) { case Level1(Level2(Level3(var something))) -> something; diff --git a/java/java-tests/testData/inspection/matchException/NoMatchExceptionMostNestedDeconstruction.java b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionMostNestedDeconstruction.java similarity index 97% rename from java/java-tests/testData/inspection/matchException/NoMatchExceptionMostNestedDeconstruction.java rename to java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionMostNestedDeconstruction.java index 9e61dd0ee013..03472e8adf41 100644 --- a/java/java-tests/testData/inspection/matchException/NoMatchExceptionMostNestedDeconstruction.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionMostNestedDeconstruction.java @@ -6,6 +6,7 @@ class Test2 { System.out.println(getLevel3(full)); } + @Nullable private static String getLevel3(@Nullable final Level1 level1) { return switch (level1) { case Level1(Level2(Level3(var something))) -> something; diff --git a/java/java-tests/testData/inspection/matchException/NoMatchExceptionNestedDeconstructionWithDefault.java b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionNestedDeconstructionWithDefault.java similarity index 97% rename from java/java-tests/testData/inspection/matchException/NoMatchExceptionNestedDeconstructionWithDefault.java rename to java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionNestedDeconstructionWithDefault.java index c972b07cb693..8828cd7a6a68 100644 --- a/java/java-tests/testData/inspection/matchException/NoMatchExceptionNestedDeconstructionWithDefault.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionNestedDeconstructionWithDefault.java @@ -7,6 +7,7 @@ class Test { System.out.println(getLevel3(nullLevel2)); } + @Nullable private static Level3 getLevel3(@Nullable final Level1 level1) { return switch (level1) { case Level1(Level2(var something)) -> something; diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionSealedClassDataFlow.java b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionSealedClassDataFlow.java new file mode 100644 index 000000000000..4d590045b50c --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionSealedClassDataFlow.java @@ -0,0 +1,22 @@ +import org.jetbrains.annotations.Nullable; + +class TestExample { + sealed interface II { + record AI() implements II {} + + record BI() implements II {} + } + + record RI(@Nullable II value) {} + + @Nullable + private static II getII(RI ri) { + if (ri.value == null) { + return null; + } + return switch (ri) { + case RI(II.BI bi) -> bi; + case RI(II.AI ai) -> ai; + }; + } +} diff --git a/java/java-tests/testData/inspection/matchException/NoMatchExceptionSealedClassWithDominated.java b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionSealedClassWithDominated.java similarity index 96% rename from java/java-tests/testData/inspection/matchException/NoMatchExceptionSealedClassWithDominated.java rename to java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionSealedClassWithDominated.java index 60f1e89603be..a95ac49827f9 100644 --- a/java/java-tests/testData/inspection/matchException/NoMatchExceptionSealedClassWithDominated.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionSealedClassWithDominated.java @@ -12,6 +12,7 @@ class TestExample { record RI(@Nullable II value) { } + @Nullable private static II getII(RI ri) { return switch (ri) { case RI(II.BI bi) -> bi; diff --git a/java/java-tests/testData/inspection/matchException/NoMatchExceptionSealedClassWithNullDefault.java b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionSealedClassWithNullDefault.java similarity index 96% rename from java/java-tests/testData/inspection/matchException/NoMatchExceptionSealedClassWithNullDefault.java rename to java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionSealedClassWithNullDefault.java index 9a9ef5dd90b0..38a8f66f8176 100644 --- a/java/java-tests/testData/inspection/matchException/NoMatchExceptionSealedClassWithNullDefault.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/NoMatchExceptionSealedClassWithNullDefault.java @@ -9,6 +9,7 @@ class TestExample { record RI(@Nullable II value) {} + @Nullable private static II getII(RI ri) { return switch (ri) { case RI(II.BI bi) -> bi; diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java index 5ee592ebd4e0..38854634308d 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java @@ -254,7 +254,27 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase { setupTypeUseAnnotations("org.jspecify.annotations", myFixture); doTest(); } - + + public void testMatchExceptionNestedDeconstruction() { doTest(); } + + public void testMatchExceptionSealedClass() { doTest(); } + + public void testNoMatchExceptionSealedClassDataFlow() { doTest(); } + + public void testMatchExceptionDoubleNestedDeconstruction() { doTest(); } + + public void testNoMatchExceptionMostNestedDeconstruction() { doTest(); } + + public void testMatchExceptionNestedSealedClass() { doTest(); } + + public void testNoMatchExceptionNestedDeconstructionWithDefault() { doTest(); } + + public void testNoMatchExceptionSealedClassWithNullDefault() { doTest(); } + + public void testNoMatchExceptionDoubleNestedDeconstructionWithDominated() { doTest(); } + + public void testNoMatchExceptionSealedClassWithDominated() { doTest(); } + public void testOptionalInference() { doTestWith((dfi, cvi) -> dfi.SUGGEST_NULLABLE_ANNOTATIONS = false); } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/MatchExceptionInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/MatchExceptionInspectionTest.java deleted file mode 100644 index efb8d7e8e5e7..000000000000 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/MatchExceptionInspectionTest.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.java.codeInspection; - -import com.intellij.JavaTestUtil; -import com.intellij.codeInspection.MatchExceptionInspection; -import com.intellij.testFramework.LightProjectDescriptor; -import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase; -import org.jetbrains.annotations.NotNull; - -public final class MatchExceptionInspectionTest extends LightJavaCodeInsightFixtureTestCase { - @Override - protected String getBasePath() { - return JavaTestUtil.getRelativeJavaTestDataPath() + "/inspection/matchException"; - } - - @NotNull - @Override - protected LightProjectDescriptor getProjectDescriptor() { - return JAVA_21; - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - myFixture.enableInspections(new MatchExceptionInspection()); - } - - private void doTest() { - myFixture.testHighlighting(getTestName(false) + ".java"); - } - - public void testMatchExceptionNestedDeconstruction() { doTest(); } - - public void testMatchExceptionSealedClass() { doTest(); } - - public void testMatchExceptionDoubleNestedDeconstruction() { doTest(); } - - public void testNoMatchExceptionMostNestedDeconstruction() { doTest(); } - - public void testMatchExceptionNestedSealedClass() { doTest(); } - - public void testNoMatchExceptionNestedDeconstructionWithDefault() { doTest(); } - - public void testNoMatchExceptionSealedClassWithNullDefault() { doTest(); } - - public void testNoMatchExceptionDoubleNestedDeconstructionWithDominated() { doTest(); } - - public void testNoMatchExceptionSealedClassWithDominated() { doTest(); } -} diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index c2b36b800dff..df5325d8e2b7 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -374,6 +374,7 @@ inspection.data.flow.ignore.assert.statements=Ignore assert statements inspection.data.flow.treat.non.annotated.members.and.parameters.as.nullable=Treat non-annotated members and parameters as @Nullable inspection.data.flow.report.not.null.required.parameter.with.null.literal.argument.usages=Report non-null required parameter with null-literal argument usages inspection.data.flow.report.nullable.methods.that.always.return.a.non.null.value=Report nullable methods that always return a non-null value +inspection.data.flow.report.match.exception.problem=Reports patterns that may throw MatchException inspection.data.flow.report.problems.that.happen.only.on.some.code.paths=Report problems that happen only on some code paths inspection.data.flow.use.computeifpresent.quickfix=Replace 'compute' with 'computeIfPresent' inspection.dead.code.option.applet=Applets @@ -568,8 +569,6 @@ inspection.nullable.problems.notnull.to.nullable.assignment.conflicts=Report ass inspection.nullable.problems.redundant.nullability.inside.container=Report redundant nullability annotation in the scope of annotated container inspection.optional.get.without.is.present.message={0}.#ref() without ''isPresent()'' check inspection.optional.get.without.is.present.method.reference.message=#ref without 'isPresent()' check -inspection.match.exception.problems.display.name=Possible MatchException in switch pattern matching -inspection.match.exception.problems.message=Pattern matching may throw 'MatchException' inspection.overflowing.loop.index.inspection.description=Loop executes zero or billions of times inspection.overflowing.loop.index.inspection.name=Loop executes zero or billions of times inspection.overwritten.key.map.message=Duplicate Map key