[java-highlighting] IDEA-326939 Support multi-pattern switch labels that define no variables

GitOrigin-RevId: fb0360f00314417c17566637db6bbda4c21e6faa
This commit is contained in:
Tagir Valeev
2023-08-14 17:10:16 +02:00
committed by intellij-monorepo-bot
parent fdcb966cf3
commit 5618627fa4
7 changed files with 108 additions and 23 deletions

View File

@@ -949,18 +949,30 @@ public class SwitchBlockHighlightingModel {
if (problem != null) {
addIllegalFallThroughError(problem.element(), problem.message(), holder, alreadyFallThroughElements);
}
else if (JavaPsiPatternUtil.containsPatternVariable(first)) {
PsiElement nextNotLabel = PsiTreeUtil.skipSiblingsForward(switchLabelElement, PsiWhiteSpace.class, PsiComment.class,
PsiSwitchLabelStatement.class);
//there is no statement, it is allowed to go through (14.11.1 JEP 440-441)
if (!(nextNotLabel instanceof PsiStatement)) {
continue;
else {
if (elements.length > 1) {
for (int i = 0; i < elements.length - 1; i++) {
if (elements[i] instanceof PsiPatternGuard guard) {
PsiExpression expression = guard.getGuardingExpression();
if (expression != null) {
holder.add(createError(expression, "Guard expression is allowed only after the last label element").create());
}
}
}
}
if (PsiTreeUtil.skipWhitespacesAndCommentsForward(switchLabelElement) instanceof PsiSwitchLabelStatement) {
addIllegalFallThroughError(first, "multiple.switch.labels", holder, alreadyFallThroughElements);
}
else if (PsiTreeUtil.skipWhitespacesAndCommentsBackward(switchLabelElement) instanceof PsiSwitchLabelStatement) {
addIllegalFallThroughError(first, "multiple.switch.labels", holder, alreadyFallThroughElements);
if (JavaPsiPatternUtil.containsNamedPatternVariable(first)) {
PsiElement nextNotLabel = PsiTreeUtil.skipSiblingsForward(switchLabelElement, PsiWhiteSpace.class, PsiComment.class,
PsiSwitchLabelStatement.class);
//there is no statement, it is allowed to go through (14.11.1 JEP 440-441)
if (!(nextNotLabel instanceof PsiStatement)) {
continue;
}
if (PsiTreeUtil.skipWhitespacesAndCommentsForward(switchLabelElement) instanceof PsiSwitchLabelStatement) {
addIllegalFallThroughError(first, "multiple.switch.labels", holder, alreadyFallThroughElements);
}
else if (PsiTreeUtil.skipWhitespacesAndCommentsBackward(switchLabelElement) instanceof PsiSwitchLabelStatement) {
addIllegalFallThroughError(first, "multiple.switch.labels", holder, alreadyFallThroughElements);
}
}
}
}
@@ -1021,9 +1033,15 @@ public class SwitchBlockHighlightingModel {
}
else if (firstElement instanceof PsiPattern || firstElement instanceof PsiPatternGuard) {
if (elements[1] instanceof PsiPattern || elements[1] instanceof PsiPatternGuard) {
return new CaseLabelCombinationProblem(elements[1], "invalid.case.label.combination.several.patterns");
if (ContainerUtil.exists(elements, JavaPsiPatternUtil::containsNamedPatternVariable)) {
String messageKey = HighlightingFeature.UNNAMED_PATTERNS_AND_VARIABLES.isAvailable(firstElement)
? "invalid.case.label.combination.several.patterns.unnamed"
: "invalid.case.label.combination.several.patterns";
return new CaseLabelCombinationProblem(elements[1], messageKey);
}
} else {
return new CaseLabelCombinationProblem(elements[1], "invalid.case.label.combination.constants.and.patterns");
}
return new CaseLabelCombinationProblem(elements[1], "invalid.case.label.combination.constants.and.patterns");
}
return null;
}
@@ -1050,7 +1068,7 @@ public class SwitchBlockHighlightingModel {
PsiCaseLabelElementList labelElementList = switchLabel.getCaseLabelElementList();
if (labelElementList == null) continue;
List<PsiCaseLabelElement> patternElements = ContainerUtil.filter(labelElementList.getElements(),
labelElement -> JavaPsiPatternUtil.containsPatternVariable(labelElement));
labelElement -> JavaPsiPatternUtil.containsNamedPatternVariable(labelElement));
if (patternElements.isEmpty()) continue;
PsiStatement prevStatement = PsiTreeUtil.getPrevSiblingOfType(firstSwitchLabelInGroup, PsiStatement.class);
if (prevStatement == null) continue;

View File

@@ -807,7 +807,7 @@ public final class DuplicateBranchesInSwitchInspection extends LocalInspectionTo
PsiCaseLabelElement[] elements = labelElementList.getElements();
for (PsiCaseLabelElement element : elements) {
if (element instanceof PsiPatternGuard ||
element instanceof PsiPattern && (!java20plus || JavaPsiPatternUtil.containsPatternVariable(element))) {
element instanceof PsiPattern && (!java20plus || JavaPsiPatternUtil.containsNamedPatternVariable(element))) {
return false;
}
}

View File

@@ -165,21 +165,22 @@ public final class JavaPsiPatternUtil {
* @return {@code true} if the pattern declares one or more pattern variables, {@code false} otherwise.
*/
@Contract(value = "null -> false", pure = true)
public static boolean containsPatternVariable(@Nullable PsiCaseLabelElement pattern) {
public static boolean containsNamedPatternVariable(@Nullable PsiCaseLabelElement pattern) {
if (pattern instanceof PsiPatternGuard) {
return containsPatternVariable(((PsiPatternGuard)pattern).getPattern());
return containsNamedPatternVariable(((PsiPatternGuard)pattern).getPattern());
}
else if (pattern instanceof PsiTypeTestPattern) {
return ((PsiTypeTestPattern)pattern).getPatternVariable() != null;
PsiPatternVariable variable = ((PsiTypeTestPattern)pattern).getPatternVariable();
return variable != null && !variable.isUnnamed();
}
else if (pattern instanceof PsiParenthesizedPattern) {
return containsPatternVariable(((PsiParenthesizedPattern)pattern).getPattern());
return containsNamedPatternVariable(((PsiParenthesizedPattern)pattern).getPattern());
}
else if (pattern instanceof PsiDeconstructionPattern) {
PsiDeconstructionPattern deconstructionPattern = (PsiDeconstructionPattern)pattern;
return deconstructionPattern.getPatternVariable() != null ||
ContainerUtil.exists(deconstructionPattern.getDeconstructionList().getDeconstructionComponents(),
component -> containsPatternVariable(component));
component -> containsNamedPatternVariable(component));
}
return false;
}

View File

@@ -239,7 +239,8 @@ valid.switch.selector.types=byte, char, short or int
valid.switch.1_7.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.constants.and.patterns=Invalid case label combination: A case label must consist of either a list of case constants or a single case pattern
invalid.case.label.combination.constants.and.patterns=Invalid case label combination: a case label must consist of either a list of case constants or a single case pattern
invalid.case.label.combination.several.patterns.unnamed=Invalid case label combination: Multiple patterns are allowed only if none of them declare any pattern variables
invalid.case.label.combination.several.patterns=Invalid case label combination: A case label must not consist of more than one case pattern
null.label.not.allowed.here=Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'
default.label.must.not.contains.case.keyword=The label for the default case must only use the 'default' keyword, without 'case'

View File

@@ -127,7 +127,7 @@ public class Main {
void test12(Integer integer) {
switch (integer) {
case 1, 2, <error descr="Invalid case label combination: A case label must consist of either a list of case constants or a single case pattern">Integer i1 when i1 > 5</error>:
case 1, 2, <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a single case pattern">Integer i1 when i1 > 5</error>:
case null:
System.out.println("blah blah blah");
break;
@@ -253,7 +253,7 @@ public class Main {
void test28(String s) {
switch (s) {
case String str, <error descr="Invalid case label combination: A case label must consist of either a list of case constants or a single case pattern">"hello"</error>, "world" -> {}
case String str, <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a single case pattern">"hello"</error>, "world" -> {}
}
}

View File

@@ -0,0 +1,61 @@
class Test {
enum X{A, B}
record R(int x, int y) {}
record R1(int z) {}
void test(Object obj) {
switch (obj) {
case Integer _, String _ -> System.out.println("string or int");
case R(_, _), R1 _ -> System.out.println("R or R1");
default -> System.out.println("other");
}
}
void testRepeat(Object obj) {
switch (obj) {
case Integer _, String _ -> System.out.println("string or int");
case Double _, <error descr="Label is dominated by a preceding case label 'Integer _'">Integer _</error> -> System.out.println("double or int");
default -> System.out.println("other");
}
}
void testEnum(Object obj) {
switch (obj) {
case X.A, <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a single case pattern">String _</error> -> System.out.println("string or int");
case Integer _, <error descr="Invalid case label combination: a case label must consist of either a list of case constants or a single case pattern">X.B</error> -> System.out.println("string or int");
default -> System.out.println("other");
}
}
void test2(Object obj) {
switch (obj) {
case Integer x, <error descr="Invalid case label combination: Multiple patterns are allowed only if none of them declare any pattern variables">String _</error> -> System.out.println("string or int");
case R(_, var i), <error descr="Invalid case label combination: Multiple patterns are allowed only if none of them declare any pattern variables">R1 _</error> -> System.out.println("R or R1");
default -> System.out.println("other");
}
}
void testGuards(Object obj) {
switch (obj) {
case Integer _ when <error descr="Guard expression is allowed only after the last label element">((Integer)obj) > 0</error>,
String _ when !((String)obj).isEmpty() -> System.out.println("Positive integer or non-empty string");
default -> System.out.println("other");
}
}
void testFallthrough(Object obj) {
switch (obj) {
case Integer _:
case String _:
System.out.println("Number or string");
break;
case Double _:
case <error descr="Multiple switch labels are permitted for a switch labeled statement group only if none of them declare any pattern variables">Float f</error>:
System.out.println("double or float");
break;
default:
System.out.println("other");
}
}
}

View File

@@ -90,6 +90,10 @@ public class LightPatternsForSwitchHighlightingTest extends LightJavaCodeInsight
public void testSwitchExhaustivenessWithGenericsIn21Java() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_21, this::doTest);
}
public void testSwitchSeveralPatternsUnnamed() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_21_PREVIEW, this::doTest);
}
public void testSwitchDominanceIn21Java() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_21, this::doTest);