mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-inspections] IDEA-349198 Catch deconstruction on null value
GitOrigin-RevId: 6eac3a29ad4cefa69c3a7fa69a85c46d98187eb6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2d970ca167
commit
6d178d8680
@@ -974,12 +974,9 @@ public final class SwitchUtils {
|
||||
* Evaluates the exhaustiveness state of a switch block.
|
||||
*
|
||||
* @param switchBlock the PsiSwitchBlock to evaluate
|
||||
* @param considerNestedDeconstructionPatterns flag indicating whether to consider nested deconstruction patterns. It is necessary to take into account,
|
||||
* because nested deconstruction patterns don't cover null values
|
||||
* @return exhaustiveness state.
|
||||
*/
|
||||
public static @NotNull SwitchExhaustivenessState evaluateSwitchCompleteness(@NotNull PsiSwitchBlock switchBlock,
|
||||
boolean considerNestedDeconstructionPatterns) {
|
||||
public static @NotNull SwitchExhaustivenessState evaluateSwitchCompleteness(@NotNull PsiSwitchBlock switchBlock) {
|
||||
PsiExpression selector = switchBlock.getExpression();
|
||||
if (selector == null) return SwitchExhaustivenessState.MALFORMED;
|
||||
PsiType selectorType = selector.getType();
|
||||
@@ -998,15 +995,6 @@ public final class SwitchUtils {
|
||||
return SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT;
|
||||
}
|
||||
if (!needToCheckCompleteness && !isEnumSelector) return SwitchExhaustivenessState.INCOMPLETE;
|
||||
// It is necessary because deconstruction patterns don't cover cases
|
||||
// when some of their components are null and deconstructionPattern too
|
||||
if (!considerNestedDeconstructionPatterns) {
|
||||
labelElements = ContainerUtil.filter(
|
||||
labelElements, label -> !(label instanceof PsiDeconstructionPattern deconstructionPattern &&
|
||||
ContainerUtil.or(
|
||||
deconstructionPattern.getDeconstructionList().getDeconstructionComponents(),
|
||||
component -> component instanceof PsiDeconstructionPattern)));
|
||||
}
|
||||
boolean hasError = hasExhaustivenessError(switchBlock, labelElements);
|
||||
// if a switch block is needed to check completeness and switch is incomplete we let highlighting to inform about it as it's a compilation error
|
||||
if (!hasError) {
|
||||
|
||||
@@ -486,6 +486,12 @@
|
||||
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"
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<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
-1
@@ -340,7 +340,7 @@ public final class EnhancedSwitchMigrationInspection extends AbstractBaseJavaLoc
|
||||
if (branch.isDefault()) return true;
|
||||
if (existsDefaultLabelElement(branch.myLabelStatement)) return true;
|
||||
}
|
||||
SwitchUtils.SwitchExhaustivenessState completenessResult = evaluateSwitchCompleteness(switchStatement, true);
|
||||
SwitchUtils.SwitchExhaustivenessState completenessResult = evaluateSwitchCompleteness(switchStatement);
|
||||
return completenessResult == SwitchUtils.SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT ||
|
||||
completenessResult == SwitchUtils.SwitchExhaustivenessState.EXHAUSTIVE_NO_DEFAULT;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -73,7 +73,7 @@ public final class SwitchStatementsWithoutDefaultInspection extends AbstractBase
|
||||
infoMode = true;
|
||||
}
|
||||
else {
|
||||
SwitchExhaustivenessState completenessResult = evaluateSwitchCompleteness(statement, true);
|
||||
SwitchExhaustivenessState completenessResult = evaluateSwitchCompleteness(statement);
|
||||
if (completenessResult == SwitchExhaustivenessState.UNNECESSARY ||
|
||||
completenessResult == SwitchExhaustivenessState.MALFORMED ||
|
||||
completenessResult == SwitchExhaustivenessState.EMPTY ||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.siyeh.ig.controlflow;
|
||||
|
||||
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;
|
||||
@@ -283,12 +284,19 @@ public final class UnnecessaryDefaultInspection extends BaseInspection {
|
||||
if (!(type instanceof PsiClassType)) {
|
||||
return null;
|
||||
}
|
||||
final PsiElement result = JavaPsiSwitchUtil.findDefaultElement(switchBlock);
|
||||
if (result == null) {
|
||||
final PsiElement defaultElement = JavaPsiSwitchUtil.findDefaultElement(switchBlock);
|
||||
if (defaultElement == null) {
|
||||
return null;
|
||||
}
|
||||
final SwitchExhaustivenessState completenessResult = evaluateSwitchCompleteness(switchBlock, false);
|
||||
return completenessResult == SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT
|
||||
? result : null;
|
||||
final SwitchExhaustivenessState completenessResult = evaluateSwitchCompleteness(switchBlock);
|
||||
|
||||
if (completenessResult != SwitchExhaustivenessState.EXHAUSTIVE_CAN_ADD_DEFAULT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (MatchExceptionInspection.findPatternCanProduceMatchException(switchBlock, Set.of(defaultElement), false) != null) {
|
||||
return null;
|
||||
}
|
||||
return defaultElement;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class Test2 {
|
||||
private static void testSwitchPattern() {
|
||||
final var nullLevel3 = new Level1(new Level2(null));
|
||||
System.out.println(getLevel3(nullLevel3));
|
||||
}
|
||||
|
||||
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;
|
||||
case null -> null;
|
||||
};
|
||||
}
|
||||
|
||||
record Level3(String text) {
|
||||
}
|
||||
|
||||
record Level2(@Nullable Level3 something) {
|
||||
}
|
||||
|
||||
record Level1(Level2 level2) {
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class Test {
|
||||
private static void testSwitchPattern() {
|
||||
final var nullLevel2 = new Level1(null);
|
||||
System.out.println(getLevel3(null));
|
||||
System.out.println(getLevel3(nullLevel2));
|
||||
}
|
||||
|
||||
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;
|
||||
case null -> null;
|
||||
};
|
||||
}
|
||||
|
||||
static class Level3 {
|
||||
}
|
||||
|
||||
record Level2(Level3 something) {
|
||||
}
|
||||
|
||||
record Level1(@Nullable Level2 level2) {
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class TestExample2 {
|
||||
public static void main(String[] args) {
|
||||
getII(new RI(new RI2(null)));
|
||||
}
|
||||
|
||||
sealed interface II {
|
||||
record AI() implements II {
|
||||
}
|
||||
|
||||
record BI() implements II {
|
||||
}
|
||||
}
|
||||
|
||||
record RI(RI2 value) {
|
||||
}
|
||||
|
||||
record RI2(@Nullable II value) {
|
||||
}
|
||||
|
||||
private 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class TestExample {
|
||||
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(<warning descr="Pattern matching may throw 'MatchException'">II.BI bi</warning>) -> bi;
|
||||
case RI(II.AI ai) -> ai;
|
||||
};
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class Test2 {
|
||||
private static void testSwitchPattern() {
|
||||
final var nullLevel3 = new Level1(new Level2(null));
|
||||
System.out.println(getLevel3(nullLevel3));
|
||||
}
|
||||
|
||||
private static String getLevel3(@Nullable final Level1 level1) {
|
||||
return switch (level1) {
|
||||
case Level1(Level2(Level3(var something))) -> something;
|
||||
case Level1(Level2 t) -> "some";
|
||||
case null -> null;
|
||||
};
|
||||
}
|
||||
|
||||
record Level3(String text) {
|
||||
}
|
||||
|
||||
record Level2(@Nullable Level3 something) {
|
||||
}
|
||||
|
||||
record Level1(Level2 level2) {
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class Test2 {
|
||||
private static void testSwitchPattern() {
|
||||
final var full = new Level1(new Level2(new Level3(null)));
|
||||
System.out.println(getLevel3(full));
|
||||
}
|
||||
|
||||
private static String getLevel3(@Nullable final Level1 level1) {
|
||||
return switch (level1) {
|
||||
case Level1(Level2(Level3(var something))) -> something;
|
||||
case null -> null;
|
||||
};
|
||||
}
|
||||
|
||||
record Level3(@Nullable String text) {
|
||||
}
|
||||
|
||||
record Level2(Level3 something) {
|
||||
}
|
||||
|
||||
record Level1(Level2 level2) {
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class Test {
|
||||
private static void testSwitchPattern() {
|
||||
final var nullLevel2 = new Level1(null);
|
||||
System.out.println(getLevel3(null));
|
||||
System.out.println(getLevel3(nullLevel2));
|
||||
}
|
||||
|
||||
private static Level3 getLevel3(@Nullable final Level1 level1) {
|
||||
return switch (level1) {
|
||||
case Level1(Level2(var something)) -> something;
|
||||
case null -> null;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
static class Level3 {
|
||||
}
|
||||
|
||||
record Level2(Level3 something) {
|
||||
}
|
||||
|
||||
record Level1(@Nullable Level2 level2) {
|
||||
}
|
||||
}
|
||||
+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) {
|
||||
}
|
||||
|
||||
private static II getII(RI ri) {
|
||||
return switch (ri) {
|
||||
case RI(II.BI bi) -> bi;
|
||||
case RI(II.AI ai) -> ai;
|
||||
case RI(II a) -> a;
|
||||
};
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class TestExample {
|
||||
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;
|
||||
case RI(II.AI ai) -> ai;
|
||||
case null, default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// 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(); }
|
||||
}
|
||||
+46
@@ -179,6 +179,52 @@ public class UnnecessaryDefaultInspectionTest extends LightJavaInspectionTestCas
|
||||
"}");
|
||||
}
|
||||
|
||||
public void testDefaultWithDominance() {
|
||||
doTest("""
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
class TestExample {
|
||||
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;
|
||||
case RI(II.AI ai) -> ai;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
public void testNullDefaultWithDominance() {
|
||||
doTest("""
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
class TestExample {
|
||||
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;
|
||||
case RI(II.AI ai) -> ai;
|
||||
case null, default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getEnvironmentClasses() {
|
||||
return new String[] {
|
||||
|
||||
@@ -568,6 +568,8 @@ 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