[java-highlighting] IDEA-324544 Support intersections for exhaustiveness

GitOrigin-RevId: c87035335aa16dd8ff50d903cc9de814fe7b6a30
This commit is contained in:
Mikhail Pyltsin
2023-07-06 17:35:29 +02:00
committed by intellij-monorepo-bot
parent 56128f71f7
commit cb3916ded4
4 changed files with 111 additions and 33 deletions

View File

@@ -607,7 +607,7 @@ public class SwitchBlockHighlightingModel {
}
if (!TypeConversionUtil.areTypesConvertible(mySelectorType, patternType) ||
// 14.30.3 A type pattern that declares a pattern variable of a reference type U is
// applicable at another reference type T if T is checkcast convertible to U (JEP 427)
// applicable at another reference type T if T is checkcast convertible to U (JEP 440-441)
// There is no rule that says that a reference type applies to a primitive type
(mySelectorType instanceof PsiPrimitiveType && HighlightingFeature.PATTERN_GUARDS_AND_RECORD_PATTERNS.isAvailable(label))) {
HighlightInfo error =
@@ -934,8 +934,26 @@ public class SwitchBlockHighlightingModel {
*/
private void checkCompleteness(@NotNull List<? extends PsiCaseLabelElement> elements, @NotNull HighlightInfoHolder results,
boolean inclusiveUnconditionalAndDefault) {
List<PsiType> selectorTypes = new ArrayList<>();
PsiClass resolvedClass = PsiUtil.resolveClassInClassTypeOnly(mySelectorType);
//T is an intersection type T1& ... &Tn and P covers Ti, for one of the types Ti (1≤i≤n)
if (resolvedClass instanceof PsiTypeParameter typeParameter) {
PsiClassType[] types = typeParameter.getExtendsListTypes();
Arrays.stream(types)
.filter(t -> t != null)
.forEach(t -> selectorTypes.add(t));
}
if (selectorTypes.isEmpty()) {
selectorTypes.add(mySelectorType);
}
if (inclusiveUnconditionalAndDefault) {
PsiCaseLabelElement elementCoversType = findUnconditionalPatternForType(elements, mySelectorType);
PsiCaseLabelElement elementCoversType =
selectorTypes.stream()
.map(type -> findUnconditionalPatternForType(elements, type))
.filter(t -> t != null)
.findAny()
.orElse(null);
PsiElement defaultElement = SwitchUtils.findDefaultElement(myBlock);
if (defaultElement != null && elementCoversType != null) {
HighlightInfo.Builder defaultInfo =
@@ -951,6 +969,7 @@ public class SwitchBlockHighlightingModel {
}
if (defaultElement != null || elementCoversType != null) return;
}
//enums are final, checking intersections are not needed
PsiClass selectorClass = PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(mySelectorType));
if (selectorClass != null && getSwitchSelectorKind() == SelectorKind.ENUM) {
List<String> enumElements = new SmartList<>();
@@ -966,18 +985,37 @@ public class SwitchBlockHighlightingModel {
}
}
checkEnumCompleteness(selectorClass, enumElements, results);
return;
}
else if (selectorClass != null && selectorClass.hasModifierProperty(SEALED)) {
HighlightInfo.Builder info = checkSealedClassCompleteness(mySelectorType, elements);
if (info != null) {
results.add(info.create());
List<PsiType> sealedTypes = selectorTypes.stream()
.filter(type -> {
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(type));
return psiClass != null && psiClass.hasModifierProperty(SEALED);
})
.toList();
if (!sealedTypes.isEmpty()) {
if (selectorClass != null) {
//records are final, checking intersections are not needed
checkRecordExhaustiveness(elements, selectorClass, results);
}
for (int i = 0; i < sealedTypes.size(); i++) {
PsiType currentSelectorType = sealedTypes.get(i);
HighlightInfo.Builder info = checkSealedClassCompleteness(currentSelectorType, elements);
if (info == null) {
return;
}
if (i == sealedTypes.size() - 1) {
results.add(info.create());
return;
}
}
checkRecordExhaustiveness(elements, selectorClass, results);
}
//records are final, checking intersections are not needed
else if (selectorClass != null && selectorClass.isRecord()) {
if (!checkRecordCaseSetNotEmpty(elements)) {
results.add(createCompletenessInfoForSwitch(!elements.isEmpty()).create());
}else{
}
else {
checkRecordExhaustiveness(elements, selectorClass, results);
}
}
@@ -989,12 +1027,14 @@ public class SwitchBlockHighlightingModel {
private void checkRecordExhaustiveness(@NotNull List<? extends PsiCaseLabelElement> elements,
@NotNull PsiClass selectorClass,
@NotNull HighlightInfoHolder results) {
PatternHighlightingModel.RecordExhaustivenessResult exhaustivenessResult = PatternHighlightingModel.checkRecordExhaustiveness(elements);
PatternHighlightingModel.RecordExhaustivenessResult exhaustivenessResult =
PatternHighlightingModel.checkRecordExhaustiveness(elements);
if (!exhaustivenessResult.isExhaustive()) {
HighlightInfo.Builder builder = createCompletenessInfoForSwitch(!elements.isEmpty());
if (exhaustivenessResult.canBeAdded() && selectorClass.isRecord()) {
IntentionAction fix =
getFixFactory().createAddMissingRecordClassBranchesFix(myBlock, selectorClass, exhaustivenessResult.getMissedBranchesByType(), elements);
getFixFactory().createAddMissingRecordClassBranchesFix(myBlock, selectorClass, exhaustivenessResult.getMissedBranchesByType(),
elements);
if (fix != null) {
builder.registerFix(fix, null, null, null, null);
}
@@ -1032,7 +1072,9 @@ public class SwitchBlockHighlightingModel {
}
}
private void registerDeleteFixForDefaultElement(@NotNull HighlightInfo.Builder info, PsiElement defaultElement, @NotNull PsiElement duplicateElement) {
private void registerDeleteFixForDefaultElement(@NotNull HighlightInfo.Builder info,
PsiElement defaultElement,
@NotNull PsiElement duplicateElement) {
if (defaultElement instanceof PsiCaseLabelElement caseElement) {
IntentionAction action = getFixFactory().createDeleteSwitchLabelFix(caseElement);
info.registerFix(action, null, null, null, null);
@@ -1044,7 +1086,7 @@ public class SwitchBlockHighlightingModel {
@Nullable
private HighlightInfo.Builder checkSealedClassCompleteness(@NotNull PsiType selectorType,
@NotNull List<? extends PsiCaseLabelElement> elements) {
@NotNull List<? extends PsiCaseLabelElement> elements) {
LinkedHashMap<PsiClass, PsiPattern> patternClasses = findPatternClasses(elements);
Set<PsiClass> missingClasses = findMissedClasses(selectorType, patternClasses);
if (missingClasses.isEmpty()) return null;
@@ -1167,7 +1209,8 @@ public class SwitchBlockHighlightingModel {
* then dominated label elements will be also included in the result set.
*/
public static @NotNull Set<PsiElement> findSuspiciousLabelElements(@NotNull PsiSwitchBlock switchBlock) {
SwitchBlockHighlightingModel switchModel = createInstance(PsiUtil.getLanguageLevel(switchBlock), switchBlock, switchBlock.getContainingFile());
SwitchBlockHighlightingModel switchModel =
createInstance(PsiUtil.getLanguageLevel(switchBlock), switchBlock, switchBlock.getContainingFile());
if (switchModel == null) return Collections.emptySet();
List<PsiCaseLabelElement> labelElements =
ContainerUtil.filterIsInstance(SwitchUtils.getSwitchBranches(switchBlock), PsiCaseLabelElement.class);

View File

@@ -1,3 +1,5 @@
package com.test;
import java.util.List;
record LongRecord(String s1, String s2, String s3) {}
@@ -14,7 +16,7 @@ final class D implements I {}
record TypedRecord<T>(T x) {}
class Incompatible {
public class Incompatible {
Object object;
Integer integer;
@@ -23,29 +25,29 @@ class Incompatible {
<T extends IntegerRecord> void incompatible() {
switch (object) {
case LongRecord<error descr="Incorrect number of nested patterns: expected 3 but found 2">(String s1, int y)</error> -> {}
case RecordWithInterface<error descr="Incorrect number of nested patterns: expected 2 but found 1">(Integer y)</error> when true -> {}
case RecordWithInterface(<error descr="Incompatible types. Found: 'java.lang.Integer', required: 'I'">Integer x</error>, D y) when true -> {}
case RecordWithInterface(I x, D y) when true -> {}
case RecordWithInterface<error descr="Incorrect number of nested patterns: expected 2 but found 1">(Integer y)</error> when true -> {}
case RecordWithInterface(<error descr="Incompatible types. Found: 'java.lang.Integer', required: 'com.test.I'">Integer x</error>, D y) when true -> {}
case RecordWithInterface(I x, D y) when true -> {}
case <error descr="Deconstruction pattern can only be applied to a record, 'java.lang.Integer' is not a record">Integer</error>(double x) -> {}
case PrimitiveRecord(<error descr="Incompatible types. Found: 'java.lang.Integer', required: 'int'">Integer x</error>) when true -> {}
case PrimitiveRecord(int x) when true -> {}
case IntegerRecord(Integer x) when true -> {}
case IntegerRecord(<error descr="Incompatible types. Found: 'int', required: 'java.lang.Integer'">int x</error>) when true -> {}
case PrimitiveRecord(<error descr="Incompatible types. Found: 'java.lang.Integer', required: 'int'">Integer x</error>) when true -> {}
case PrimitiveRecord(int x) when true -> {}
case IntegerRecord(Integer x) when true -> {}
case IntegerRecord(<error descr="Incompatible types. Found: 'int', required: 'java.lang.Integer'">int x</error>) when true -> {}
case <error descr="'Object' cannot be safely cast to 'T'">T(Integer x)</error> -> {}
}
switch (integer){
case <error descr="Incompatible types. Found: 'PrimitiveRecord', required: 'java.lang.Integer'">PrimitiveRecord(int x)</error> -> {}
case <error descr="Incompatible types. Found: 'com.test.PrimitiveRecord', required: 'java.lang.Integer'">PrimitiveRecord(int x)</error> -> {}
default -> {}
}
switch (typedRecord){
case TypedRecord<I>(I x) -> {}
case TypedRecord<I>(I x)-> {}
default -> {}
}
switch (typedRecord){
case TypedRecord(C x) -> {}
case TypedRecord(I x) -> {}
case TypedRecord<I>(<error descr="Incompatible types. Found: 'java.lang.Integer', required: 'I'">Integer t</error>) -> {}
case TypedRecord(C x)-> {}
case TypedRecord(I x)-> {}
case TypedRecord<I>(<error descr="Incompatible types. Found: 'java.lang.Integer', required: 'com.test.I'">Integer t</error>) -> {}
case TypedRecord<?>(<error descr="'Object' cannot be safely cast to 'List<Number>'">List<Number> nums</error>) -> {}
case TypedRecord<?>(List<?> list) -> {}
case TypedRecord<?>(<error descr="'Object' cannot be safely cast to 'T'">T t</error>) -> {}
@@ -54,9 +56,9 @@ class Incompatible {
default -> {}
}
switch (object){
case Top(Child c1, Child(I x, <error descr="Incompatible types. Found: 'int', required: 'I'">int y</error>)) -> { }
case Top(Child c1, <error descr="Incompatible types. Found: 'Wrong', required: 'Child'">Wrong(int y)</error>) -> { }
case Top(Child c1, Child(C a, I i)) -> { }
case Top(Child c1, Child(I x, <error descr="Incompatible types. Found: 'int', required: 'com.test.I'">int y</error>)) -> { }
case Top(Child c1, <error descr="Incompatible types. Found: 'com.test.Wrong', required: 'com.test.Child'">Wrong(int y)</error>) -> { }
case Top(Child c1, Child(C a, I i)) -> { }
default -> {}
}
}

View File

@@ -145,16 +145,16 @@ class Main {
};
switch (ii) {
case Object obj, <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error>:
case <error descr="'switch' has both an unconditional pattern and a default label">Object obj</error>:
System.out.println("int");
break;
default:
<error descr="'switch' has both an unconditional pattern and a default label">default</error>:
System.out.println("def");
break;
}
str = switch (ii) {
case Object obj, <error descr="Invalid case label combination: 'null' can only be used as a single case label or paired only with 'default'">null</error> -> "int";
default -> "def";
case <error descr="'switch' has both an unconditional pattern and a default label">Object obj</error> -> "int";
<error descr="'switch' has both an unconditional pattern and a default label">default</error> -> "def";
};
switch (ii) {

View File

@@ -1,3 +1,5 @@
import java.util.Optional;
record PrimitiveAndSealed(int x, I y) {}
record NormalAndSealed(Integer x, I y) {}
@@ -244,4 +246,35 @@ class Basic {
case Pair<? extends I>(D fst, I snd) -> {}
}
}
sealed interface Parent {}
record AAA() implements Parent {}
record BBB() implements Parent {}
void test(Optional<? extends Parent> optional) {
switch (optional.get()) {
case AAA a -> {}
case BBB b -> {}
}
}
class CCCC{}
public <T extends CCCC & Comparable<T>> void test(T c) {
switch (c) {
case Comparable t -> System.out.println(13);
}
}
interface II1{}
sealed interface II2{}
non-sealed class Cl1 implements II2{}
public <T extends II1 & II2> void test(T c) {
switch (c) {
case Cl1 t:
System.out.println(21);
;
}
}
}