mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
[java-highlighting] IDEA-324625 fix case with nested record patterns
GitOrigin-RevId: c88ce329fd28cf12a6b27f752a6d96040061aa17
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c5be5727f2
commit
c09ed3984b
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user