mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-inspections] IDEA-349198 IJ-CR-190983 Catch deconstruction on null value
- use dfa GitOrigin-RevId: 19cdc3be30263c4b3dbbfff173c557c62c376d67
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4983585e6a
commit
c6ca035622
@@ -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<PsiElement> 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)
|
||||
|
||||
@@ -37,6 +37,8 @@ dataflow.message.npe.methodref.invocation=Method reference invocation <code>#ref
|
||||
dataflow.message.only.switch.label=Switch label <code>#ref</code> is the only reachable in the whole switch
|
||||
dataflow.message.passing.null.argument.nonannotated=Passing <code>null</code> argument to non-annotated parameter
|
||||
dataflow.message.passing.null.argument=Passing <code>null</code> argument to parameter annotated as @NotNull
|
||||
dataflow.message.deconstruction.match.exception=Pattern matching will throw <code>MatchException</code>
|
||||
dataflow.message.may.deconstruction.match.exception=Pattern matching may throw <code>MatchException</code>
|
||||
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 <code>#ref</code> might be null but passed to non-annotated parameter
|
||||
|
||||
+30
@@ -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<PsiExpression> 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) {
|
||||
|
||||
+7
@@ -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<T extends PsiElement> {
|
||||
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<T extends PsiElement> {
|
||||
"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<PsiExpression> assumeNotNull = new NullabilityProblemKind<>(RE, "assumeNotNull");
|
||||
public static final NullabilityProblemKind<PsiPattern> 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)
|
||||
|
||||
+5
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,12 +486,6 @@
|
||||
groupKey="group.names.nullability.problems" groupBundle="messages.InspectionsBundle" enabledByDefault="true"
|
||||
level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.nullable.NullableStuffInspection"/>
|
||||
<localInspection groupPathKey="group.path.names.probable.bugs" language="JAVA" bundle="messages.JavaBundle"
|
||||
key="inspection.match.exception.problems.display.name"
|
||||
hasStaticDescription="true"
|
||||
groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle" enabledByDefault="true"
|
||||
level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.MatchExceptionInspection"/>
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="UnsatisfiedRange" bundle="messages.JavaAnalysisBundle"
|
||||
key="inspection.unsatisfied.range.display.name"
|
||||
groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING"
|
||||
|
||||
+2
@@ -245,6 +245,8 @@ public final class DataFlowInspection extends DataFlowInspectionBase {
|
||||
message("inspection.data.flow.report.nullable.methods.that.always.return.a.non.null.value")),
|
||||
checkbox("IGNORE_ASSERT_STATEMENTS",
|
||||
message("inspection.data.flow.ignore.assert.statements")),
|
||||
checkbox("REPORT_MATCHED_EXCEPTION",
|
||||
message("inspection.data.flow.report.match.exception.problem")),
|
||||
checkbox("REPORT_UNSOUND_WARNINGS",
|
||||
message("inspection.data.flow.report.problems.that.happen.only.on.some.code.paths")),
|
||||
JavaInspectionControls.button(
|
||||
|
||||
@@ -37,6 +37,8 @@ Integer square(@Nullable Integer input) {
|
||||
<li>Use the <b>Ignore assert statements</b> option to control how the inspection treats <code>assert</code> 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).</li>
|
||||
<li>Use the <b>Reports patterns that may throw MatchException</b> option to report patterns that may throw
|
||||
<code>MatchException</code> at runtime due to null values in deconstruction patterns.</li>
|
||||
<li>Use the <b>Report problems that happen only on some code paths</b> option to control whether to report problems that may happen only
|
||||
on some code path. If this option is disabled, warnings like <i>exception is possible</i> will not be reported. The inspection will report
|
||||
only warnings like <i>exception will definitely occur</i>. This mode may greatly reduce the number of false-positives, especially if the code
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports patterns in switch expressions and statements that may throw
|
||||
<code>MatchException</code> at runtime
|
||||
due to <code>null</code> values in deconstruction patterns.
|
||||
|
||||
<p>
|
||||
The inspection analyzes record components with <code>@Nullable</code> annotation to detect cases where
|
||||
deconstruction patterns may not cover <code>null</code> values.
|
||||
</p>
|
||||
|
||||
<p><b>Example:</b></p>
|
||||
<pre><code>
|
||||
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;
|
||||
};
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>
|
||||
Use the provided quick-fix to add a <code>default</code> branch to handle unmatched cases safely.
|
||||
</p>
|
||||
<!-- tooltip end -->
|
||||
<p><small>New in 2026.1</small>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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<PsiElement> 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<PsiElement> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<PsiElement> 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;
|
||||
}
|
||||
}
|
||||
+1
@@ -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(<warning descr="Pattern matching may throw 'MatchException'">Level3(var something)</warning>)) -> something;
|
||||
+1
@@ -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(<warning descr="Pattern matching may throw 'MatchException'">Level2(var something)</warning>) -> something;
|
||||
+1
-1
@@ -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(<warning descr="Pattern matching may throw 'MatchException'">II.BI bi</warning>)) -> bi;
|
||||
case RI(RI2(II.AI ai)) -> ai;
|
||||
+1
@@ -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;
|
||||
+1
@@ -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;
|
||||
+1
@@ -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;
|
||||
+22
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
+1
@@ -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;
|
||||
+1
@@ -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;
|
||||
+21
-1
@@ -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);
|
||||
}
|
||||
|
||||
-49
@@ -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(); }
|
||||
}
|
||||
@@ -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=<code>{0}.#ref()</code> without ''isPresent()'' check
|
||||
inspection.optional.get.without.is.present.method.reference.message=<code>#ref</code> 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
|
||||
|
||||
Reference in New Issue
Block a user