IDEA-283367 - [Patterns for switch] - fixed completeness for parameterized sealed hierarchies

GitOrigin-RevId: 4f5c59fe1e38b08f86974682b8adf7d70f0274b5
This commit is contained in:
Ilyas Selimov
2022-01-24 12:28:36 +00:00
committed by intellij-monorepo-bot
parent c97f39dd58
commit 3d5bbea95f
5 changed files with 234 additions and 7 deletions
@@ -807,7 +807,9 @@ public class SwitchBlockHighlightingModel {
for (PsiClass permittedClass : getPermittedClasses(psiClass)) {
if (!visited.add(permittedClass)) continue;
PsiPattern pattern = patternClasses.get(permittedClass);
if (pattern == null || !JavaPsiPatternUtil.isTotalForType(pattern, TypeUtils.getType(permittedClass))) {
if (pattern == null && (PsiUtil.getLanguageLevel(permittedClass).isLessThan(LanguageLevel.JDK_18_PREVIEW) ||
TypeConversionUtil.areTypesConvertible(mySelectorType, TypeUtils.getType(permittedClass))) ||
pattern != null && !JavaPsiPatternUtil.isTotalForType(pattern, TypeUtils.getType(permittedClass))) {
nonVisited.add(permittedClass);
}
}
@@ -724,7 +724,7 @@ class Main {
}
}
void completeness(Day d, I i, I2 i2, I3 i3, AorBorC abc, J1 j) {
void completeness(Day d, I i, I2 i2, I3 i3, AorBorC abc, J1 j, II<Integer> ii) {
// old style switch, no completeness check
switch (d) {
case MONDAY, TUESDAY -> System.out.println("ok");
@@ -826,7 +826,7 @@ class Main {
break;
}
str = switch(<error descr="'switch' expression does not cover all possible input values">i</error>) {
case I ii && ii != null -> "ok";
case I in && in != null -> "ok";
};
switch (i3) {
@@ -886,6 +886,10 @@ class Main {
}
str = switch (<error descr="'switch' expression does not have any case clauses">i2</error>) {
};
switch (<error descr="'switch' statement does not cover all possible input values">ii</error>) {
case BB b -> {}
}
}
}
@@ -948,4 +952,8 @@ final class C implements AorC, BorC {}
sealed interface J1 {}
sealed interface J2 extends J1 permits R1 {}
record R1() implements J1, J2 {}
record R2() implements J1 {}
record R2() implements J1 {}
sealed interface II<T> {}
final class AA implements II<String> {}
final class BB<T> implements II<Object> {}
@@ -221,6 +221,175 @@ class Main {
case <error descr="Label is dominated by a preceding case label '(Day dd && true)'">MONDAY</error>: break;
}
}
void completeness(Day d, I i, I2 i2, I3 i3, AorBorC abc, J1 j, II<Integer> ii) {
// old style switch, no completeness check
switch (d) {
case MONDAY, TUESDAY -> System.out.println("ok");
}
// If the type of the selector expression is an enum type E
String str;
switch (d) {
case Day dd && dd != null:
System.out.println("ok");
case <error descr="Label is dominated by a preceding case label 'Day dd && dd != null'">MONDAY</error>:
System.out.println("mon");
};
switch (<error descr="'switch' statement does not cover all possible input values">d</error>) {
case Day dd && dd != null:
System.out.println("ok");
};
str = switch (<error descr="'switch' expression does not cover all possible input values">d</error>) {
case MONDAY, TUESDAY -> System.out.println("ok");
};
str = switch (d) {
case MONDAY, TUESDAY, WEDNESDAY -> "ok";
};
str = switch (d) {
case MONDAY, TUESDAY, default -> "ok";
};
switch (d) {
case <error descr="'switch' has both a total pattern and a default label">((Day dd && true))</error>:
System.out.println("ok");
<error descr="'switch' has both a total pattern and a default label">default</error>: // blah blah blah
System.out.println("mon");
};
switch (d) {
case ((Day dd && dd != null)):
System.out.println("ok");
default:
System.out.println("mon");
};
// If the type of the selector expression, T, names a sealed interface or a sealed class that is abstract
switch(i) {
case Sub1 s1:
System.out.println("ok");
break;
case Sub2 s2:
System.out.println("ok");
break;
case Sub3 s3:
System.out.println("ok");
break;
}
str = switch(i) {
case Sub1 s1 -> "ok";
case Sub2 s2 -> "ok";
case Sub3 s3 && true -> "ok";
};
switch (<error descr="'switch' statement does not cover all possible input values">i</error>) {
case Sub1 s1:
System.out.println("ok");
break;
case Sub2 s2:
System.out.println("ok");
break;
}
str = switch(<error descr="'switch' expression does not cover all possible input values">i</error>) {
case Sub1 s1 -> "ok";
case Sub2 s2 -> "ok";
};
switch (<error descr="'switch' statement does not cover all possible input values">i</error>) {
case Sub1 s1:
System.out.println("ok");
break;
case Sub2 s2:
System.out.println("ok");
break;
case Sub4 s4:
System.out.println("ok");
break;
case Sub6 s6:
System.out.println("ok");
break;
}
str = switch(<error descr="'switch' expression does not cover all possible input values">i</error>) {
case Sub1 s1 -> "ok";
case Sub2 s2 -> "ok";
case Sub4 s4 -> "ok";
case Sub6 s6 -> "ok";
};
switch (<error descr="'switch' statement does not cover all possible input values">i</error>) {
case Sub1 s1:
break;
case (Sub2 s2 && false):
break;
case Sub3 s3:
break;
}
str = switch(<error descr="'switch' expression does not cover all possible input values">i</error>) {
case I in && in != null -> "ok";
};
switch (i3) {
case (Sub9 s && true):
break;
case Sub11 s:
break;
case Sub12 s && true:
break;
}
str = switch (abc) {
case A a -> "1";
case B b -> "2";
case C c -> "3";
};
str = switch (abc) {
case A a -> "1";
case C c -> "2";
case AorB ab -> "3";
case BorC bc -> "4";
};
switch (j) {
case R1 r1:
break;
case R2 r2:
break;
}
// If the type of the selector expression, T, is not an enum type and also does not name a sealed interface or a sealed class that is abstract
switch (<error descr="'switch' statement does not cover all possible input values">i2</error>) {
case Sub7 s1:
System.out.println("ok");
break;
case Sub8 s2:
System.out.println("ok");
break;
}
str = switch (<error descr="'switch' expression does not cover all possible input values">i2</error>) {
case Sub7 s1 -> "ok";
case Sub8 s2 -> "ok";
};
// empty switches
switch (d) {
}
str = switch (<error descr="'switch' expression does not have any case clauses">d</error>) {
};
switch (<error descr="'switch' statement does not have any case clauses">i</error>) {
}
str = switch (<error descr="'switch' expression does not have any case clauses">i</error>) {
};
switch (<error descr="'switch' statement does not have any case clauses">i2</error>) {
}
str = switch (<error descr="'switch' expression does not have any case clauses">i2</error>) {
};
// 'case AA' is redundant brach here as 'AA' is not castable to II<Integer>, so the code compiles correctly
switch (ii) {
case BB b -> {}
}
}
}
sealed interface I {
@@ -282,4 +451,8 @@ final class C implements AorC, BorC {}
sealed interface J1 {}
sealed interface J2 extends J1 permits R1 {}
record R1() implements J1, J2 {}
record R2() implements J1 {}
record R2() implements J1 {}
sealed interface II<T> {}
final class AA implements II<String> {}
final class BB<T> implements II<Object> {}
@@ -10,11 +10,18 @@ public class Test {
}
}
int testDominatedConstLabel(Integer i) {
int testDominatedConstLabel(Integer i, E e) {
switch (e) {
case (E d && d == E.A): return 1;
case <error descr="Label is dominated by a preceding case label '(E d && d == E.A)'">A</error>: return -1;
}
return switch (i) {
case (Integer ii && ii > 2) -> 1;
case <error descr="Label is dominated by a preceding case label '(Integer ii && ii > 2)'">2</error> -> 2;
case default -> 3;
};
}
enum E {A, B}
}
@@ -2,6 +2,8 @@
package com.siyeh.ig.controlflow;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.siyeh.ig.LightJavaInspectionTestCase;
import org.jetbrains.annotations.NotNull;
@@ -184,13 +186,48 @@ public class UnnecessaryDefaultInspectionTest extends LightJavaInspectionTestCas
"}");
}
public void testDefaultInParameterizedSealedHierarchy() {
doTest("class X {" +
" void x(J<Integer> j) {" +
" switch (j) {" +
" case D2 d2 -> {}" +
" case default -> {}" +
" }" +
" }" +
"}");
}
public void testDefaultInParameterizedSealedHierarchyJava18() {
IdeaTestUtil.withLevel(getModule(), LanguageLevel.JDK_18_PREVIEW, () -> {
doTest("class X {" +
" void x(J<Integer> j) {" +
" switch (j) {" +
" case D2 d2 -> {}" +
" case /*'default' branch is unnecessary*/default/*_*//**/ -> {}" +
" }" +
" }" +
"}");
checkQuickFix("Remove 'default' branch",
"class X {" +
" void x(J<Integer> j) {" +
" switch (j) {" +
" case D2 d2 -> {}\n" +
"}" +
" }" +
"}");
});
}
@Override
protected String[] getEnvironmentClasses() {
return new String[] {
"enum E { A, B }\n" +
"sealed interface I {}\n" +
"final class C1 implements I {}\n" +
"final class C2 implements I {}\n"
"final class C2 implements I {}\n" +
"sealed interface J<T>\n" +
"final class D1 implements J<String> {}\n" +
"final class D2<T> implements J<T> {}\n"
};
}