From 51ab1f234cf6a370d6f47ca2e57b36e12fd5493a Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Tue, 11 Jul 2023 13:18:08 +0200 Subject: [PATCH] [java-highlighting] IDEA-324654 Fixes to find missed and covered classes GitOrigin-RevId: 07311efb7bd08b055b5b662be12ea59b9fdfc7d5 --- .../analysis/PatternHighlightingModel.java | 42 +++++++------------ .../SwitchBlockHighlightingModel.java | 32 ++++++-------- 2 files changed, 29 insertions(+), 45 deletions(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternHighlightingModel.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternHighlightingModel.java index 5168af4487ca..6da7159b3dd3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternHighlightingModel.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/PatternHighlightingModel.java @@ -29,7 +29,7 @@ import java.util.*; import java.util.function.BiPredicate; import java.util.stream.Collectors; -import static com.intellij.codeInsight.daemon.impl.analysis.SwitchBlockHighlightingModel.findMissedClassesForSealed; +import static com.intellij.codeInsight.daemon.impl.analysis.SwitchBlockHighlightingModel.findMissedClasses; import static com.intellij.codeInsight.daemon.impl.analysis.SwitchBlockHighlightingModel.oneOfUnconditional; final class PatternHighlightingModel { @@ -503,7 +503,7 @@ final class PatternHighlightingModel { /** * Try to reduce sealed classes to their supertypes or if selectorType is covered any of types,then return selectorType. * Previous sealed classes are not excluded because they can be used in another combination. - * This method uses {@link SwitchBlockHighlightingModel#findMissedClassesForSealed(PsiType, List, List, PsiElement) findMissedClassesForSealed} + * This method uses {@link SwitchBlockHighlightingModel#findMissedClasses(PsiType, List, List, PsiElement) findMissedClassesForSealed} * To prevent recursive calls, only TypeTest descriptions are passed to this method. */ @NotNull @@ -532,30 +532,20 @@ final class PatternHighlightingModel { StreamEx.of(patterns).select(PatternTypeTestDescription.class).collect(Collectors.toSet()); Set toAdd = new HashSet<>(); Set existedTypes = typeTestDescriptions.stream().map(t -> t.type()).collect(Collectors.toSet()); - boolean unconditionalCovers = false; - for (PatternTypeTestDescription description : typeTestDescriptions) { - if (oneOfUnconditional(description.type(), selectorType)) { - toAdd.add(new PatternTypeTestDescription(selectorType)); - changed = true; - unconditionalCovers = true; - break; - } - } - if (!unconditionalCovers) { - Set visitedCovered = findMissedClassesForSealed(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; - } + Set 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; } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java index 88f2ffb09b39..612be171b604 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java @@ -515,6 +515,7 @@ public class SwitchBlockHighlightingModel { /** * Finds the missed and covered classes for a sealed selector type. + * If a selector type is not sealed classes, it will be checked if it is covered by one of elements or enumConstants * * @param selectorType the selector type * @param elements the pattern descriptions, unconditional @@ -522,10 +523,10 @@ public class SwitchBlockHighlightingModel { * @param context the context element (parent of pattern descriptions) * @return the container of missed and covered classes (may contain classes outside the selector type hierarchy) */ - static @NotNull SealedResult findMissedClassesForSealed(@NotNull PsiType selectorType, - @NotNull List elements, - @NotNull List enumConstants, - @NotNull PsiElement context) { + static @NotNull SealedResult findMissedClasses(@NotNull PsiType selectorType, + @NotNull List elements, + @NotNull List enumConstants, + @NotNull PsiElement context) { //Used to keep dependencies. The last dependency is one of the selector types. record ClassWithDependencies(PsiClass mainClass, List dependencies) { } @@ -535,19 +536,13 @@ public class SwitchBlockHighlightingModel { Set missingClasses = new LinkedHashSet<>(); LinkedHashMap permittedPatternClasses = findPermittedClasses(elements); - HashSet usedTypes = new HashSet<>(permittedPatternClasses.values()); //according JEP 440-441 only direct abstract sealed classes are allowed (14.11.1.1) Set sealedUpperClasses = findSealedUpperClasses(permittedPatternClasses.keySet()); - List typeTestPatternsOutOfSealed = StreamEx.of(elements) - .select(PatternTypeTestDescription.class) - .filter(pattern -> !usedTypes.contains(pattern.type())) - .toList(); + List typeTestPatterns = ContainerUtil.filterIsInstance(elements, PatternTypeTestDescription.class); - Set selectorClasses = StreamEx.of(getAllTypes(selectorType)) - .map(type -> PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(type))) - .nonNull() - .toSet(); + Set selectorClasses = ContainerUtil.map2SetNotNull(getAllTypes(selectorType), + type -> PsiUtil.resolveClassInClassTypeOnly(TypeConversionUtil.erasure(type))); if (selectorClasses.isEmpty()) return new SealedResult(Collections.emptySet(), Collections.emptySet()); Queue nonVisited = new ArrayDeque<>(); @@ -589,13 +584,12 @@ public class SwitchBlockHighlightingModel { if (TypeConversionUtil.areTypesConvertible(targetType, selectorType) || //we should consider items from the intersections in the usual way oneOfUnconditional(targetType, selectorType)) { - if (//check a case, when we have something, which not in sealed hierarchy, but covers some leaves - !ContainerUtil.exists(typeTestPatternsOutOfSealed, pattern -> oneOfUnconditional(pattern.type(), targetType)) && - //check if it is an enum and it is covered by all enums + 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())) { + !(psiClass.isRecord() && checkRecordPatternExhaustivenessForDescription(elements, targetType, context).isExhaustive()) && + //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); visitedNotCovered.addAll(peeked.dependencies); } @@ -1231,7 +1225,7 @@ public class SwitchBlockHighlightingModel { }else{ List descriptions = preparePatternDescription(elements); List enumConstants = StreamEx.of(elements).map(element -> getEnumConstant(element)).nonNull().toList(); - List missedSealedClasses = StreamEx.of(findMissedClassesForSealed(selectorType, descriptions, enumConstants, myBlock).missedClasses()) + List missedSealedClasses = StreamEx.of(findMissedClasses(selectorType, descriptions, enumConstants, myBlock).missedClasses()) .sortedBy(t->t.getQualifiedName()) .toList(); missedClasses = new LinkedHashSet<>();