[java-highlighting] IDEA-324625 fix case with nested record patterns

GitOrigin-RevId: c88ce329fd28cf12a6b27f752a6d96040061aa17
This commit is contained in:
Mikhail Pyltsin
2023-07-12 19:10:08 +02:00
committed by intellij-monorepo-bot
parent c5be5727f2
commit c09ed3984b
3 changed files with 130 additions and 57 deletions

View File

@@ -30,13 +30,12 @@ import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import static com.intellij.codeInsight.daemon.impl.analysis.SwitchBlockHighlightingModel.findMissedClasses;
import static com.intellij.codeInsight.daemon.impl.analysis.SwitchBlockHighlightingModel.oneOfUnconditional;
final class PatternHighlightingModel {
private static final Logger LOG = Logger.getInstance(PatternHighlightingModel.class);
private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance();
public static final int MAX_ITERATION_COVERAGE = 1_000;
private static final int MAX_ITERATION_COVERAGE = 1_000;
static void createDeconstructionErrors(@Nullable PsiDeconstructionPattern deconstructionPattern, @NotNull HighlightInfoHolder holder) {
if (deconstructionPattern == null) return;
@@ -223,11 +222,13 @@ final class PatternHighlightingModel {
* This method tries to rewrite the existing set of patterns to equivalent
*/
@NotNull
static RecordExhaustivenessResult checkRecordPatternExhaustivenessForDescription(@NotNull List<PatternDescription> elements,
static RecordExhaustivenessResult checkRecordPatternExhaustivenessForDescription(@NotNull List<? extends PatternDescription> elements,
@NotNull PsiType targetType,
@NotNull PsiElement context) {
List<PatternDeconstructionDescription> descriptions =
ContainerUtil.filterIsInstance(elements, PatternDeconstructionDescription.class);
StreamEx.of(elements).select(PatternDeconstructionDescription.class)
.filter(element -> TypeConversionUtil.areTypesConvertible(element.type(), targetType))
.toList();
if (descriptions.isEmpty()) {
return RecordExhaustivenessResult.createNotBeAdded();
@@ -281,6 +282,29 @@ final class PatternHighlightingModel {
return new ReduceResult(currentPatterns, changed);
}
@NotNull
static List<PatternTypeTestDescription> reduceToTypeTest(@NotNull List<? extends PatternDescription> elements,
@NotNull PsiElement context) {
List<PatternTypeTestDescription> reducedToTypeTest = new ArrayList<>();
List<PatternDeconstructionDescription> deconstructionDescriptions = new ArrayList<>();
for (PatternDescription element : elements) {
if (element instanceof PatternTypeTestDescription typeTestDescription) {
reducedToTypeTest.add(typeTestDescription);
}
if(element instanceof PatternDeconstructionDescription deconstructionDescription) {
deconstructionDescriptions.add(deconstructionDescription);
}
}
Map<PsiType, List<PatternDeconstructionDescription>> groupedByType =
deconstructionDescriptions.stream().collect(Collectors.groupingBy(t -> t.type()));
for (Map.Entry<PsiType, List<PatternDeconstructionDescription>> entry : groupedByType.entrySet()) {
if (checkRecordPatternExhaustivenessForDescription(entry.getValue(), entry.getKey(), context).isExhaustive()) {
reducedToTypeTest.add(new PatternTypeTestDescription(entry.getKey()));
}
}
return reducedToTypeTest;
}
record ReduceResultCacheContext(@NotNull PsiType selectorType,
@NotNull Set<? extends PatternDescription> currentPatterns) {
}
@@ -347,6 +371,7 @@ final class PatternHighlightingModel {
.groupingBy(t -> t.type(), Collectors.toSet());
Set<PatternDescription> toRemove = new HashSet<>();
Set<PatternDescription> toAdd = new HashSet<>();
Map<PsiType, List<PsiType>> componentCaches = new HashMap<>();
for (Map.Entry<PsiType, Set<PatternDeconstructionDescription>> entry : byType.entrySet()) {
Set<PatternDeconstructionDescription> descriptions = entry.getValue();
if (descriptions.isEmpty()) {
@@ -354,13 +379,7 @@ final class PatternHighlightingModel {
}
PatternDeconstructionDescription first = descriptions.iterator().next();
for (int i = 0; i < first.list().size(); i++) {
MultiMap<List<PatternDescription>, PatternDeconstructionDescription> groupWithoutOneComponent = new MultiMap<>();
for (PatternDeconstructionDescription description : descriptions) {
if (description.list().size() <= i) {
return new ReduceResult(patterns, false);
}
groupWithoutOneComponent.putValue(getWithoutComponent(description, i), description);
}
MultiMap<List<PatternDescription>, PatternDeconstructionDescription> groupWithoutOneComponent = getGroupWithoutOneComponent(descriptions, i);
for (Map.Entry<List<PatternDescription>, Collection<PatternDeconstructionDescription>> value :
groupWithoutOneComponent.entrySet()) {
@@ -372,11 +391,16 @@ final class PatternHighlightingModel {
Set<PatternDescription> nestedDescriptions = setWithOneDifferentElement.stream()
.map(t -> t.list().get(finalI))
.collect(Collectors.toSet());
List<PsiType> types = getComponentTypes(context, entry.getKey());
if (types == null || types.size() <= i) {
PsiType recordType = entry.getKey();
List<PsiType> componentTypes = componentCaches.get(recordType);
if (componentTypes == null) {
componentTypes = getComponentTypes(context, recordType);
componentCaches.put(recordType, componentTypes);
}
if (componentTypes == null || componentTypes.size() <= i) {
continue;
}
ReduceResult result = reduceInLoop(types.get(i), context, nestedDescriptions, (set, type) -> false, cache);
ReduceResult result = reduceInLoop(componentTypes.get(i), context, nestedDescriptions, (set, type) -> false, cache);
if (result.changed()) {
changed = true;
toRemove.addAll(setWithOneDifferentElement);
@@ -451,7 +475,7 @@ final class PatternHighlightingModel {
for (int i = 0; i < recordComponentTypes.size(); i++) {
PsiType recordComponentType = recordComponentTypes.get(i);
PsiType descriptionComponentType = descriptionTypes.get(i);
if (!oneOfUnconditional(descriptionComponentType, recordComponentType)) {
if (!SwitchBlockHighlightingModel.oneOfUnconditional(descriptionComponentType, recordComponentType)) {
allCovered = false;
break;
}
@@ -527,28 +551,13 @@ final class PatternHighlightingModel {
private static ReduceResult reduceClassesInner(@NotNull Set<? extends PatternDescription> patterns,
@NotNull PsiType selectorType,
@NotNull PsiElement context) {
boolean changed = false;
Set<PatternTypeTestDescription> typeTestDescriptions =
StreamEx.of(patterns).select(PatternTypeTestDescription.class).collect(Collectors.toSet());
Set<PatternTypeTestDescription> toAdd = new HashSet<>();
Set<PsiType> existedTypes = typeTestDescriptions.stream().map(t -> t.type()).collect(Collectors.toSet());
Set<PsiClass> visitedCovered =
findMissedClasses(selectorType, new ArrayList<>(typeTestDescriptions), new ArrayList<>(), context).coveredClasses();
for (PsiClass covered : visitedCovered) {
PsiClassType classType = TypeUtils.getType(covered);
if (!existedTypes.contains(classType)) {
if (oneOfUnconditional(selectorType, classType)) {
toAdd.add(new PatternTypeTestDescription(classType));
changed = true;
}
//find something upper. let's change to selectorType
if (oneOfUnconditional(classType, selectorType)) {
toAdd.add(new PatternTypeTestDescription(selectorType));
changed = true;
break;
}
}
}
boolean changed = addNewClasses(selectorType, visitedCovered, existedTypes, toAdd);
if (!changed) {
return new ReduceResult(patterns, false);
}
@@ -559,10 +568,50 @@ final class PatternHighlightingModel {
return new ReduceResult(newPatterns, true);
}
private static boolean addNewClasses(@NotNull PsiType selectorType,
@NotNull Set<PsiClass> visitedCovered,
@NotNull Set<PsiType> existedTypes,
@NotNull Collection<PatternTypeTestDescription> toAdd) {
boolean changed = false;
for (PsiClass covered : visitedCovered) {
PsiClassType classType = TypeUtils.getType(covered);
if (!existedTypes.contains(classType)) {
if (SwitchBlockHighlightingModel.oneOfUnconditional(selectorType, classType)) {
toAdd.add(new PatternTypeTestDescription(classType));
changed = true;
}
//find something upper. let's change to selectorType
if (SwitchBlockHighlightingModel.oneOfUnconditional(classType, selectorType)) {
toAdd.add(new PatternTypeTestDescription(selectorType));
changed = true;
break;
}
}
}
return changed;
}
@NotNull
private static MultiMap<List<PatternDescription>, PatternDeconstructionDescription> getGroupWithoutOneComponent(
@NotNull Set<? extends PatternDescription> combinedPatterns,
int i) {
MultiMap<List<PatternDescription>, PatternDeconstructionDescription> groupWithoutOneComponent = new MultiMap<>();
for (PatternDescription description : combinedPatterns) {
if (!(description instanceof PatternDeconstructionDescription deconstructionDescription)) {
continue;
}
if (deconstructionDescription.list().size() <= i) {
continue;
}
groupWithoutOneComponent.putValue(getWithoutComponent(deconstructionDescription, i), deconstructionDescription);
}
return groupWithoutOneComponent;
}
private static boolean coverSelectorType(@NotNull Set<? extends PatternDescription> patterns,
@NotNull PsiType selectorType) {
for (PatternDescription pattern : patterns) {
if (pattern instanceof PatternTypeTestDescription && oneOfUnconditional(pattern.type(), selectorType)) {
if (pattern instanceof PatternTypeTestDescription && SwitchBlockHighlightingModel.oneOfUnconditional(pattern.type(), selectorType)) {
return true;
}
}
@@ -594,7 +643,7 @@ final class PatternHighlightingModel {
}
record PatternDeconstructionDescription(@NotNull PsiType type,
@NotNull List<PatternDescription> list)
@NotNull List<? extends PatternDescription> list)
implements PatternDescription {
PatternDeconstructionDescription createFor(int element, PatternDescription pattern) {
ArrayList<PatternDescription> descriptions = new ArrayList<>(list);
@@ -614,7 +663,7 @@ final class PatternHighlightingModel {
canBeAdded = added;
}
public Map<PsiType, Set<List<PsiType>>> getMissedBranchesByType() {
Map<PsiType, Set<List<PsiType>>> getMissedBranchesByType() {
Map<PsiType, Set<List<PsiType>>> result = new HashMap<>();
for (Map.Entry<PsiType, Set<List<PsiType>>> missedBranches : missedBranchesByType.entrySet()) {
Set<List<PsiType>> branchSet = new HashSet<>();
@@ -628,15 +677,15 @@ final class PatternHighlightingModel {
return result;
}
public boolean isExhaustive() {
boolean isExhaustive() {
return isExhaustive;
}
public boolean canBeAdded() {
boolean canBeAdded() {
return canBeAdded;
}
public void merge(RecordExhaustivenessResult result) {
void merge(RecordExhaustivenessResult result) {
if (!this.isExhaustive && !this.canBeAdded) {
return;
}
@@ -660,7 +709,7 @@ final class PatternHighlightingModel {
}
}
public void addNextType(PsiType recordType, PsiType nextClass) {
void addNextType(PsiType recordType, PsiType nextClass) {
if (!this.canBeAdded) {
return;
}
@@ -673,9 +722,9 @@ final class PatternHighlightingModel {
}
}
public void addNewBranch(@NotNull PsiType recordType,
@Nullable PsiType classForNextBranch,
@NotNull List<? extends PsiType> types) {
void addNewBranch(@NotNull PsiType recordType,
@Nullable PsiType classForNextBranch,
@NotNull List<? extends PsiType> types) {
if (!this.canBeAdded) {
return;
}

View File

@@ -454,19 +454,16 @@ public class SwitchBlockHighlightingModel {
return sealedUpperClasses;
}
private static @NotNull LinkedHashMap<PsiClass, PsiType> findPermittedClasses(@NotNull List<PatternDescription> elements) {
LinkedHashMap<PsiClass, PsiType> patternClasses = new LinkedHashMap<>();
private static @NotNull MultiMap<PsiClass, PsiType> findPermittedClasses(@NotNull List<PatternTypeTestDescription> elements) {
MultiMap<PsiClass, PsiType> patternClasses = new MultiMap<>();
for (PatternDescription element : elements) {
if (element instanceof PatternDeconstructionDescription) {
continue;
}
PsiType patternType = element.type();
PsiClass patternClass = PsiUtil.resolveClassInClassTypeOnly(patternType);
if (patternClass != null) {
patternClasses.put(patternClass, patternType);
patternClasses.putValue(patternClass, element.type());
Set<PsiClass> classes = returnAllPermittedClasses(patternClass);
for (PsiClass aClass : classes) {
patternClasses.put(aClass, patternType);
patternClasses.putValue(aClass, element.type());
}
}
}
@@ -524,7 +521,7 @@ public class SwitchBlockHighlightingModel {
* @return the container of missed and covered classes (may contain classes outside the selector type hierarchy)
*/
static @NotNull SealedResult findMissedClasses(@NotNull PsiType selectorType,
@NotNull List<PatternDescription> elements,
@NotNull List<? extends PatternDescription> elements,
@NotNull List<PsiEnumConstant> enumConstants,
@NotNull PsiElement context) {
//Used to keep dependencies. The last dependency is one of the selector types.
@@ -535,8 +532,8 @@ public class SwitchBlockHighlightingModel {
Set<PsiClass> visitedNotCovered = new HashSet<>();
Set<PsiClass> missingClasses = new LinkedHashSet<>();
LinkedHashMap<PsiClass, PsiType> permittedPatternClasses = findPermittedClasses(elements);
//according JEP 440-441 only direct abstract sealed classes are allowed (14.11.1.1)
MultiMap<PsiClass, PsiType> permittedPatternClasses = findPermittedClasses(reduceToTypeTest(elements, context));
//according JEP 440-441 only direct abstract-sealed classes are allowed (14.11.1.1)
Set<PsiClass> sealedUpperClasses = findSealedUpperClasses(permittedPatternClasses.keySet());
List<PatternTypeTestDescription> typeTestPatterns = ContainerUtil.filterIsInstance(elements, PatternTypeTestDescription.class);
@@ -563,16 +560,19 @@ public class SwitchBlockHighlightingModel {
//used to generate missed classes when the switch is empty
(selectorClasses.contains(psiClass) && elements.isEmpty())) {
for (PsiClass permittedClass : PatternsInSwitchBlockHighlightingModel.getPermittedClasses(psiClass)) {
PsiType patternType = permittedPatternClasses.get(permittedClass);
Collection<PsiType> patternTypes = permittedPatternClasses.get(permittedClass);
PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(selectorClass, permittedClass, PsiSubstitutor.EMPTY);
PsiType permittedType = JavaPsiFacade.getElementFactory(psiClass.getProject()).createType(psiClass, substitutor);
if (patternType == null && TypeConversionUtil.areTypesConvertible(selectorType, permittedType) ||
patternType != null && !oneOfUnconditional(patternType, TypeUtils.getType(permittedClass))) {
//if we don't have patternType and tree goes away from a target type, let's skip it
if (patternTypes.isEmpty() && TypeConversionUtil.areTypesConvertible(selectorType, permittedType) ||
//if permittedClass is covered by existed patternType, we don't have to go further
!patternTypes.isEmpty() && !ContainerUtil.exists(patternTypes,
patternType -> oneOfUnconditional(patternType, TypeUtils.getType(permittedClass)))) {
List<PsiClass> dependentClasses = new ArrayList<>(peeked.dependencies);
dependentClasses.add(permittedClass);
nonVisited.add(new ClassWithDependencies(permittedClass, dependentClasses));
} else {
if (patternType != null) {
if (!patternTypes.isEmpty()) {
coveredClasses.addAll(peeked.dependencies);
}
}
@@ -586,8 +586,7 @@ public class SwitchBlockHighlightingModel {
oneOfUnconditional(targetType, selectorType)) {
if (//check if it is an enum and it is covered by all enums
!(psiClass.isEnum() && findMissingEnumConstant(psiClass, enumConstants).isEmpty()) &&
//check if it is a record, and it is covered by record patterns (deconstruction)
!(psiClass.isRecord() && checkRecordPatternExhaustivenessForDescription(elements, targetType, context).isExhaustive()) &&
//check if it is a record, and it is covered by record patterns was done before
//check a case, when we have something, which not in sealed hierarchy, but covers some leaves
!ContainerUtil.exists(typeTestPatterns, pattern -> oneOfUnconditional(pattern.type(), targetType))) {
missingClasses.add(psiClass);

View File

@@ -77,4 +77,29 @@ class Main {
case E e -> 43;
};
}
class NestedGenerics {
sealed interface JB {
}
record A<X>(X x) implements JB {
}
record Wrap(A<String> a) implements JB {
static void main(JB jb) {
switch (jb) {
case A<?>(var x) -> System.out.println(x);
case Wrap(A(String s)) -> System.out.println(s);
}
}
static void main2(JB jb) {
switch (<error descr="'switch' statement does not cover all possible input values">jb</error>) {
case A<?>(var x) -> System.out.println(x);
case Wrap(A<?>(String s)) -> System.out.println(s);
}
}
}
}
}