[java-highlighting] Pattern matching for switch (fourth preview): illegal falling-through

IDEA-309572

GitOrigin-RevId: 2dbf9102e0302fa84ad358c9afb0b21df7a877eb
This commit is contained in:
Andrey Cherkasov
2023-01-04 19:39:42 +04:00
committed by intellij-monorepo-bot
parent 1a8af338c3
commit 4c2d17acba
4 changed files with 309 additions and 5 deletions

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
@@ -533,7 +533,12 @@ public class SwitchBlockHighlightingModel {
checkDuplicates(elementsToCheckDuplicates, holder);
if (holder.hasErrorResults()) return;
checkFallThroughFromToPattern(elementsToCheckFallThroughLegality, holder);
if (PsiUtil.getLanguageLevel(holder.getProject()).isAtLeast(LanguageLevel.JDK_20_PREVIEW)) {
checkFallThroughFromToPatternJava20(elementsToCheckFallThroughLegality, holder);
}
else {
checkFallThroughFromToPattern(elementsToCheckFallThroughLegality, holder);
}
if (holder.hasErrorResults()) return;
checkDominance(elementsToCheckDominance, holder);
@@ -810,6 +815,53 @@ public class SwitchBlockHighlightingModel {
checkFallThroughInSwitchLabels(switchBlockGroup, holder, alreadyFallThroughElements);
}
private static void checkFallThroughFromToPatternJava20(@NotNull List<? extends List<PsiSwitchLabelStatementBase>> switchBlockGroup,
@NotNull HighlightInfoHolder holder) {
if (switchBlockGroup.isEmpty()) return;
Set<PsiElement> alreadyFallThroughElements = new HashSet<>();
for (var switchLabel : switchBlockGroup) {
boolean canPrecedingStatementCompleteNormally = false;
for (PsiSwitchLabelStatementBase switchLabelElement : switchLabel) {
PsiCaseLabelElementList labelElementList = switchLabelElement.getCaseLabelElementList();
if (labelElementList == null) continue;
boolean existPattern = false;
boolean existsConst = false;
boolean existsNull = false;
PsiCaseLabelElement[] elements = labelElementList.getElements();
for (int i = 0; i < elements.length; i++) {
PsiCaseLabelElement currentElement = elements[i];
if (isInCaseNullDefaultLabel(currentElement)) continue;
if (currentElement instanceof PsiExpression expr && ExpressionUtils.isNullLiteral(expr) && i != 0 && !existPattern ||
existsConst && !isConstantLabelElement(currentElement) ||
existsNull) {
addIllegalFallThroughError(currentElement, "invalid.case.label.combination", holder, alreadyFallThroughElements);
break;
}
if (containsPatternVariable(currentElement)) {
if (existPattern || PsiTreeUtil.skipWhitespacesAndCommentsForward(switchLabelElement) instanceof PsiSwitchLabelStatement) {
addIllegalFallThroughError(currentElement, "switch.illegal.fall.through.from", holder, alreadyFallThroughElements);
break;
}
else if (canPrecedingStatementCompleteNormally ||
PsiTreeUtil.skipWhitespacesAndCommentsBackward(switchLabelElement) instanceof PsiSwitchLabelStatement) {
addIllegalFallThroughError(currentElement, "switch.illegal.fall.through.to", holder, alreadyFallThroughElements);
break;
}
}
if (existPattern) {
addIllegalFallThroughError(currentElement, "switch.illegal.fall.through.from", holder, alreadyFallThroughElements);
break;
}
existPattern = currentElement instanceof PsiPattern || currentElement instanceof PsiPatternGuard;
existsConst |= isConstantLabelElement(currentElement);
existsNull = currentElement instanceof PsiExpression expr && ExpressionUtils.isNullLiteral(expr);
}
canPrecedingStatementCompleteNormally = true;
}
}
checkFallThroughInSwitchLabels(switchBlockGroup, holder, alreadyFallThroughElements);
}
private static void addIllegalFallThroughError(@NotNull PsiElement element,
@NotNull @PropertyKey(resourceBundle = JavaErrorBundle.BUNDLE) String key,
@NotNull HighlightInfoHolder holder,
@@ -832,8 +884,7 @@ public class SwitchBlockHighlightingModel {
PsiCaseLabelElementList labelElementList = switchLabel.getCaseLabelElementList();
if (labelElementList == null) continue;
List<PsiCaseLabelElement> patternElements = ContainerUtil.filter(labelElementList.getElements(),
labelElement -> labelElement instanceof PsiPattern || labelElement instanceof PsiPatternGuard
);
labelElement -> containsPatternVariable(labelElement));
if (patternElements.isEmpty()) continue;
PsiStatement prevStatement = PsiTreeUtil.getPrevSiblingOfType(firstSwitchLabelInGroup, PsiStatement.class);
if (prevStatement == null) continue;
@@ -845,6 +896,28 @@ public class SwitchBlockHighlightingModel {
}
}
private static boolean containsPatternVariable(@NotNull PsiCaseLabelElement element) {
if (element instanceof PsiPatternGuard patternGuard) {
return containsPatternVariable(patternGuard.getPattern());
}
else if (element instanceof PsiGuardedPattern guardedPattern) {
return containsPatternVariable(guardedPattern.getPrimaryPattern());
}
else if (element instanceof PsiTypeTestPattern typeTestPattern) {
return typeTestPattern.getPatternVariable() != null;
}
else if (element instanceof PsiParenthesizedPattern parenthesizedPattern) {
PsiPattern pattern = parenthesizedPattern.getPattern();
return pattern != null && containsPatternVariable(pattern);
}
else if (element instanceof PsiDeconstructionPattern deconstructionPattern) {
return deconstructionPattern.getPatternVariable() != null ||
ContainerUtil.exists(deconstructionPattern.getDeconstructionList().getDeconstructionComponents(),
pattern -> containsPatternVariable(pattern));
}
return false;
}
/**
* 14.11.1 Switch Blocks
* To ensure the absence of unreachable statements, domination rules provide a possible order

View File

@@ -236,6 +236,8 @@ valid.switch.selector.types=byte, char, short or int
valid.switch.17.selector.types=char, byte, short, int, Character, Byte, Short, Integer, String, or an enum
switch.illegal.fall.through.from=Illegal fall-through from a pattern
switch.illegal.fall.through.to=Illegal fall-through to a pattern
invalid.case.label.combination=Invalid case label combination
default.label.not.allowed.here=default label not allowed here
switch.dominance.of.preceding.label=Label is dominated by a preceding case label ''{0}''
switch.total.pattern.and.default.exist='switch' has both a total pattern and a default label
switch.class.or.array.type.expected=class or array

View File

@@ -0,0 +1,225 @@
public class Main {
record R() {}
record S() {}
void test0(Object obj) {
switch (obj) {
case <error descr="Illegal fall-through from a pattern">String s</error>:
case <error descr="Illegal fall-through to a pattern">Integer i</error>:
System.out.println(i + 1);
default:
}
}
void test1(String s) {
switch (s) {
case "hello":
System.out.println("hello");
case "world":
System.out.println("world");
case <error descr="Illegal fall-through to a pattern">String str when str.isEmpty()</error>:
System.out.println("an empty string");
case null:
System.out.println("null");
}
}
void test2(Object obj) {
switch (obj) {
case <error descr="Illegal fall-through from a pattern">String s</error>:
case R():
case S():
System.out.println(42);
break;
default:
}
}
void test3(Object obj) {
switch (obj) {
case <error descr="Illegal fall-through from a pattern">String s</error>:
default:
System.out.println(42);
}
}
void test4(Object obj) {
switch (obj) {
case null:
case <error descr="Illegal fall-through to a pattern">String s</error>:
System.out.println(s);
default:
}
}
void test5(String s) {
switch (s) {
case null:
case "hello":
System.out.println("hello");
break;
default:
}
}
void test6(Object obj) {
switch (obj) {
case S():
case null:
case R():
System.out.println("blah blah blah");
break;
default:
}
}
void test7(Object obj) {
switch (obj) {
case String s:
System.out.println(s);
case R():
System.out.println("It's either an R or a string");
break;
default:
}
}
void test8(Object obj) {
switch (obj) {
case String s:
System.out.println("String: " + s);
case <error descr="Illegal fall-through to a pattern">Integer i</error>:
System.out.println(i + 1);
default:
}
}
void test9(Object obj) {
switch (obj) {
case R():
case S():
System.out.println("Either R or an S");
break;
default:
}
}
void test10(Object obj) {
switch (obj) {
case null:
case R():
System.out.println("Either null or an R");
break;
default:
}
}
void test11(Integer integer) {
switch (integer) {
case 1, 2:
case null, <error descr="Invalid case label combination">Integer i when i == 42</error>:
System.out.println("blah blah blah");
break;
default: System.out.println("null");
}
}
void test12(Integer integer) {
switch (integer) {
case 1, 2, <error descr="Invalid case label combination">Integer i1 when i1 > 5</error>:
case null:
System.out.println("blah blah blah");
break;
default:
}
}
void test13(Object obj) {
switch (obj) {
case String s, <error descr="Illegal fall-through from a pattern">null</error> -> {}
default -> {}
}
}
void test14(Object obj) {
switch (obj) {
case null, <error descr="Invalid case label combination">String s when s.isEmpty()</error>, Integer i when i == 42 -> {}
default -> {}
}
}
void test15(Object obj) {
switch (obj) {
case String s when s.isEmpty(), <error descr="Illegal fall-through from a pattern">null</error>, Integer i -> {}
default -> {}
}
}
void test16(Object obj) {
switch (obj) {
case String s, <error descr="Illegal fall-through from a pattern">Integer i</error>, null -> {}
default -> {}
}
}
void test17(String s) {
switch (s) {
case null, <error descr="Invalid case label combination">"hello"</error>, "world" -> {}
default -> {}
}
}
void test18(String s) {
switch (s) {
case "hello", "world", <error descr="Invalid case label combination">null</error>, String str when str.isEmpty() -> {}
default -> {}
}
}
void test19(String s) {
switch (s) {
case "hello", "world", <error descr="Invalid case label combination">null</error>, String str -> {}
}
}
void test20(Object obj) {
switch (obj) {
case null, <error descr="Invalid case label combination">S()</error>, R() -> {}
default -> {}
}
}
void test21(Object obj) {
switch (obj) {
case S(), <error descr="Illegal fall-through from a pattern">null</error>, R() -> {}
default -> {}
}
}
void test22(Object obj) {
switch (obj) {
case String s when s.isEmpty(), <error descr="Illegal fall-through from a pattern">null</error>, Integer i -> {}
default -> {}
}
}
void test23(Object obj) {
switch (obj) {
case String s, <error descr="Illegal fall-through from a pattern">Integer i</error>, null -> {}
default -> {}
}
}
void test24(String s) {
switch (s) {
case "hello", "world", <error descr="Invalid case label combination">null</error>, String str -> {}
}
}
void too25(String s) {
switch (s) {
case "hello", "world", <error descr="Invalid case label combination">String str</error>, null -> {}
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.java.codeInsight.daemon;
import com.intellij.JavaTestUtil;
@@ -49,6 +49,10 @@ public class LightPatternsForSwitchHighlightingTest extends LightJavaCodeInsight
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_19_PREVIEW, this::doTest);
}
public void testIllegalFallthroughIn20Java() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_20_PREVIEW, this::doTest);
}
public void testUnconditionalDestructuringAndDefaultIn19Java() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_19_PREVIEW, this::doTest);
}