mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-21 22:11:40 +07:00
[java-highlighting] Pattern matching for switch (fourth preview): illegal falling-through
IDEA-309572 GitOrigin-RevId: 2dbf9102e0302fa84ad358c9afb0b21df7a877eb
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1a8af338c3
commit
4c2d17acba
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user