[java-highlighting] IDEA-324880 Exhaustive switches can produce definitely assigned variables

GitOrigin-RevId: 7e644a2565803fdd900830d986dba481aeb127db
This commit is contained in:
Mikhail Pyltsin
2023-07-11 17:23:37 +02:00
committed by intellij-monorepo-bot
parent ff09dfa2e1
commit 5237df5eb3
6 changed files with 107 additions and 11 deletions

View File

@@ -358,8 +358,8 @@ public class SwitchBlockHighlightingModel {
}
private boolean isEnhancedSwitch(@NotNull List<? extends PsiCaseLabelElement> labelElements) {
if (getSwitchSelectorKind() == SelectorKind.CLASS_OR_ARRAY) return true;
return ContainerUtil.exists(labelElements, st -> st instanceof PsiPattern || st instanceof PsiPatternGuard || isNullType(st));
boolean selectorIsTypeOrClass = getSwitchSelectorKind() == SelectorKind.CLASS_OR_ARRAY;
return JavaPsiSwitchUtil.isEnhancedSwitch(labelElements, selectorIsTypeOrClass);
}
private static boolean isNullType(@NotNull PsiElement element) {

View File

@@ -9,10 +9,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.JavaPsiPatternUtil;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.*;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.Stack;
import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -963,7 +960,7 @@ final class ControlFlowAnalyzer extends JavaElementVisitor {
PsiCodeBlock body = statement.getBody();
if (body != null) {
PsiStatement[] statements = body.getStatements();
boolean needToCreateDefault = false;
boolean needToCreateDefault = JavaPsiSwitchUtil.isEnhancedSwitchStatement(statement);
PsiType exprType = expr == null ? null : expr.getType();
for (PsiStatement aStatement : statements) {
ProgressManager.checkCanceled();
@@ -973,7 +970,7 @@ final class ControlFlowAnalyzer extends JavaElementVisitor {
needToCreateDefault = true;
}
PsiCaseLabelElementList labelElementList = labelStatement.getCaseLabelElementList();
if (labelElementList != null) {
if (!needToCreateDefault && labelElementList != null) {
for (PsiCaseLabelElement element : labelElementList.getElements()) {
if (element instanceof PsiDefaultCaseLabelElement ||
exprType != null && JavaPsiPatternUtil.isUnconditionalForType(element, exprType)) {

View File

@@ -0,0 +1,77 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi.util;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* Utility methods to support switch
*/
public final class JavaPsiSwitchUtil {
/**
* Checks if the given switch statement is enhanced.
*
* @param statement the switch statement to check
* @return true if the switch statement is an enhanced switch statement, false otherwise
*/
public static boolean isEnhancedSwitchStatement(@NotNull PsiSwitchBlock statement) {
if(!(statement instanceof PsiSwitchStatement)) return false;
PsiExpression selector = statement.getExpression();
if (selector == null) {
return false;
}
PsiType selectorType = selector.getType();
if (selectorType == null) {
return false;
}
PsiCodeBlock body = statement.getBody();
if (body == null) {
return false;
}
List<PsiCaseLabelElement> cases = new ArrayList<>();
for (PsiStatement psiStatement : body.getStatements()) {
if (psiStatement instanceof PsiSwitchLabelStatementBase) {
PsiSwitchLabelStatementBase labelStatementBase = (PsiSwitchLabelStatementBase)psiStatement;
PsiCaseLabelElementList elementList = labelStatementBase.getCaseLabelElementList();
if (elementList == null) {
continue;
}
PsiCaseLabelElement[] elements = elementList.getElements();
for (PsiCaseLabelElement caseLabelElement : elements) {
if (caseLabelElement != null) {
cases.add(caseLabelElement);
}
}
}
}
return isEnhancedSwitch(cases, isClassSelectorType(selectorType));
}
public static boolean isEnhancedSwitch(@NotNull List<? extends PsiCaseLabelElement> labelElements, boolean selectorIsTypeOrClass) {
if (selectorIsTypeOrClass) return true;
return ContainerUtil.exists(labelElements, st -> st instanceof PsiPattern || st instanceof PsiPatternGuard || isNullType(st));
}
private static boolean isNullType(@NotNull PsiElement element) {
return element instanceof PsiExpression && TypeConversionUtil.isNullType(((PsiExpression)element).getType());
}
private static boolean isClassSelectorType(@NotNull PsiType type) {
if (TypeConversionUtil.isPrimitiveAndNotNull(type)) return false;
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (psiClass != null) {
if (psiClass.isEnum()) return false;
String fqn = psiClass.getQualifiedName();
if (Comparing.strEqual(fqn, CommonClassNames.JAVA_LANG_STRING)) return false;
}
return true;
}
}

View File

@@ -36,7 +36,7 @@ class Test {
case Second s:
throw new IllegalArgumentException();
}
System.out.println();
<error descr="Unreachable statement">System.out.println();</error>
}
void testSealedClassUnreachable1(I i) {

View File

@@ -216,4 +216,26 @@ class C {
}
System.out.println(<error descr="Variable 's' might not have been initialized">s</error>);
}
sealed interface T permits T1, T2 {}
final class T1 implements T {}
final class T2 implements T {}
private void testStatement1(T obj) {
int i;
switch (obj) {
case T1 t1 -> i = 1;
case T2 t2 -> i = 2;
};
System.out.println(i);
}
private void testStatement2(int obj) {
int i;
switch (obj) {
case 1 -> i = 1;
case 2 -> i = 2;
};
System.out.println(<error descr="Variable 'i' might not have been initialized">i</error>);
}
}

View File

@@ -17,8 +17,8 @@ class JavaSwitchExpressionsHighlightingTest : LightJavaCodeInsightFixtureTestCas
fun testSwitchExpressionsEnumResolve() = doTest()
fun testSwitchNumericPromotion() = doTest()
fun testSimpleInferenceCases() = doTest()
fun testEnhancedSwitchDefinitelyAssigned() = doTest()
fun testEnhancedSwitchUnreachable() = IdeaTestUtil.withLevel(module, LanguageLevel.JDK_20_PREVIEW) { doTest() }
fun testEnhancedSwitchDefinitelyAssigned() = IdeaTestUtil.withLevel(module, LanguageLevel.JDK_21) { doTest() }
fun testEnhancedSwitchUnreachable() = IdeaTestUtil.withLevel(module, LanguageLevel.JDK_21) { doTest() }
fun testSwitchExpressionHasResult() = doTest()
fun testYieldStatements() = doTest()
fun testAssignToFinalInSwitchExpression() = doTest()