diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index 834360d9fd76..7e4b1195470f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -51,6 +51,7 @@ import com.intellij.util.containers.MultiMap; import com.intellij.util.containers.hash.HashSet; import com.intellij.util.ui.UIUtil; import com.intellij.xml.util.XmlStringUtil; +import com.siyeh.ig.psiutils.ControlFlowUtils; import gnu.trove.THashMap; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -2017,6 +2018,36 @@ public class HighlightUtil extends HighlightUtilBase { return results; } + @Nullable + static Collection checkSwitchExpressionHasResult(@NotNull PsiSwitchExpression switchExpression) { + PsiCodeBlock switchBody = switchExpression.getBody(); + if (switchBody != null) { + PsiStatement lastStatement = PsiTreeUtil.getPrevSiblingOfType(switchBody.getRBrace(), PsiStatement.class); + if (lastStatement instanceof PsiSwitchLabeledRuleStatement) { + Collection results = new ArrayList<>(); + for (PsiSwitchLabeledRuleStatement rule = (PsiSwitchLabeledRuleStatement)lastStatement; + rule != null; + rule = PsiTreeUtil.getPrevSiblingOfType(rule, PsiSwitchLabeledRuleStatement.class)) { + + PsiStatement ruleBody = rule.getBody(); + // the expression and throw statements are fine, only the block statement could be an issue + if (ruleBody instanceof PsiBlockStatement && ControlFlowUtils.statementMayCompleteNormally(ruleBody)) { + PsiElement target = ObjectUtils.notNull(ObjectUtils.tryCast(rule.getFirstChild(), PsiKeyword.class), rule); + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(target) + .descriptionAndTooltip(JavaErrorMessages.message("switch.expr.rule.should.produce.result")).create()); + } + } + return results; + } + // previous statements may have no result as well but in that case they fall through to the last one which needs to be checked anyway + if (lastStatement != null && ControlFlowUtils.statementMayCompleteNormally(lastStatement)) { + PsiElement target = ObjectUtils.notNull(ObjectUtils.tryCast(switchExpression.getFirstChild(), PsiKeyword.class), switchExpression); + return Collections.singletonList(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(target) + .descriptionAndTooltip(JavaErrorMessages.message("switch.expr.should.produce.result")).create()); + } + } + return null; + } /** * see JLS 8.3.2.3 diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index cd927a48a707..ea05cb21bc30 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -1608,6 +1608,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh if (!myHolder.hasErrorResults()) myHolder.add(checkFeature(expression, Feature.SWITCH_EXPRESSION)); checkSwitchBlock(expression); if (!myHolder.hasErrorResults()) myHolder.addAll(HighlightUtil.checkSwitchExpressionReturnTypeCompatible(expression)); + if (!myHolder.hasErrorResults()) myHolder.addAll(HighlightUtil.checkSwitchExpressionHasResult(expression)); } private void checkSwitchBlock(PsiSwitchBlock switchBlock) { diff --git a/java/java-psi-impl/src/messages/JavaErrorMessages.properties b/java/java-psi-impl/src/messages/JavaErrorMessages.properties index b818ad6c3aea..631e4a55bc04 100644 --- a/java/java-psi-impl/src/messages/JavaErrorMessages.properties +++ b/java/java-psi-impl/src/messages/JavaErrorMessages.properties @@ -269,6 +269,8 @@ duplicate.switch.label=Duplicate label ''{0}'' switch.colon.expected.after.case.label=':' expected switch.expr.empty='switch' expression does not have any case clauses switch.expr.incomplete='switch' expression does not cover all possible input values +switch.expr.should.produce.result=Switch expression should produce result in all execution paths +switch.expr.rule.should.produce.result=Switch expression rule should produce result in all execution paths illegal.forward.reference=Illegal forward reference illegal.self.reference=Illegal self reference diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchDefinitelyAssigned.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchDefinitelyAssigned.java index 885bc9b05fd0..4ca9a4aee183 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchDefinitelyAssigned.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchDefinitelyAssigned.java @@ -177,4 +177,43 @@ class C { }; System.out.println(n); } + + void switchExpressionAssignedInFinally(int n) { + String s; + try { + } finally { + s = switch (n) { + case -1 -> throw new RuntimeException(); + case 0 -> "a"; + default -> "b"; + }; + } + System.out.println(s); + } + + void allSwitchRulesAssignInFinally(int n) { + String s; + try { + } finally { + String string = switch (n) { + case -1 -> throw new RuntimeException(); + case 0 -> s = "a"; + default -> { break s = "b"; } + }; + } + System.out.println(s); + } + + void notAllSwitchRulesAssignInFinally(int n) { + String s; + try { + } finally { + String t = switch (n) { + case -1 -> throw new RuntimeException(); + case 0 -> s = "a"; + default -> "b"; + }; + } + System.out.println(s); + } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchUnreachable.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchUnreachable.java index b9670ce15f08..9b79f029eacc 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchUnreachable.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchUnreachable.java @@ -86,6 +86,30 @@ class C { System.out.println(); } + void switchExpressionUnreachableInFinally(int n) { + String s; + try { + } finally { + return; + s = switch (n) { + case 0 -> "a"; + default -> "b"; + }; + } + } + + void switchExpressionReachableInFinally(int n) { + String s; + try { + return; + } finally { + s = switch (n) { + case 0 -> "a"; + default -> "b"; + }; + } + } + static class SwitchExpressionReturnedFromTry { int foo(String s) throws Exception { try { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/SwitchExpressionHasResult.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/SwitchExpressionHasResult.java new file mode 100644 index 000000000000..b679ce416b97 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/SwitchExpressionHasResult.java @@ -0,0 +1,149 @@ +class C { + void defaultBranchHasNoResult(int n) { + String s = switch (n) { + default: + }; + } + + void defaultRuleHasNoResult(int n) { + String s = switch (n) { + default -> {} + }; + } + + void defaultBranchSometimesHasNoResult(int n, boolean b) { + String s = switch (n) { + default: { + if (b) break ""; + } + }; + } + + void defaultRuleSometimesHasNoResult(int n, boolean b) { + String s = switch (n) { + default -> { + if (b) break ""; + } + }; + } + + void defaultBranchAlwaysThrows(int n) { + String s = switch (n) { + default: throw new RuntimeException(); + }; + } + + void defaultRuleAlwaysThrows(int n) { + String s = switch (n) { + default -> throw new RuntimeException(); + }; + } + + void defaultBranchSometimesThrows(int n, boolean b) { + String s = switch (n) { + default: + if (b) throw new RuntimeException(); + break ""; + }; + } + + void defaultRuleSometimesThrows(int n, boolean b) { + String s = switch (n) { + default -> { + if (b) throw new RuntimeException(); + break ""; + } + }; + } + + void defaultBranchHasManyResults(int n, int k) { + String s = switch (n) { + default: { + if (k < n) break "a"; + if (k > n) break "b"; + break "c"; + } + }; + } + + void defaultRuleHasManyResults(int n, int k) { + String s = switch (n) { + default -> { + if (k < n) break "a"; + if (k > n) break "b"; + break "c"; + } + }; + } + + void oneOfBranchesHasNoResult(int n) { + String s = switch (n) { + case 0: break ""; + default: + }; + } + + void oneOfRulesHasNoResult(int n) { + String s = switch (n) { + case 0 -> ""; + default -> { + } + }; + } + + void allBranchesHaveNoResult(int n) { + String s = switch (n) { + case 0: + default: + }; + } + + void allRulesHaveNoResult(int n) { + String s = switch (n) { + case 0 -> { + } + default -> { + } + }; + } + + void allBranchesDoHaveResult(int n) { + String s = switch (n) { + case -1: throw new RuntimeException(); + case 0: break "a"; + default: break "b"; + }; + } + + void allRulesDoHaveResult(int n) { + String s = switch (n) { + case -1 -> throw new RuntimeException(); + case 0 -> "a"; + default -> "b"; + }; + } + + void allBranchesDoHaveResultInFinally(int n) { + String s; + try { + } finally { + s = switch (n) { + case -1: throw new RuntimeException(); + case 0: break "a"; + default: break "b"; + }; + } + } + + void allRulesDoHaveResultInFinally(int n) { + String s; + try { + } finally { + s = switch (n) { + case -1 -> throw new RuntimeException(); + case 0 -> "a"; + default -> "b"; + }; + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightJava12HighlightingTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightJava12HighlightingTest.kt index d2ac4d1b8c78..2c7f46984c55 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightJava12HighlightingTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightJava12HighlightingTest.kt @@ -15,6 +15,7 @@ class LightJava12HighlightingTest : LightCodeInsightFixtureTestCase() { fun testSimpleInferenceCases() = doTest() fun testEnhancedSwitchDefinitelyAssigned() = doTest() fun testEnhancedSwitchUnreachable() = doTest() + fun testSwitchExpressionHasResult() = doTest() private fun doTest() { myFixture.configureByFile(getTestName(false) + ".java")