[java][switch completion] IDEA-278622 Fix completion variants in switch case label

Use the `inheritorsFilter` when the element that is being filtered is of the PsiClass type regardless if the selector class of a switch block is sealed or not. All the inheritors of the sealed class get added in JavaCompletionProcessor.

GitOrigin-RevId: 28e70b44444edad329f086a9dca0557baea03bec
This commit is contained in:
Nikita Eshkeev
2021-09-27 13:36:08 +00:00
committed by intellij-monorepo-bot
parent 13dec23fbc
commit 90db265d7a
6 changed files with 89 additions and 44 deletions
@@ -64,7 +64,6 @@ import com.intellij.util.DocumentUtil;
import com.intellij.util.ProcessingContext;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.SealedUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import gnu.trove.THashSet;
import one.util.streamex.EntryStream;
@@ -109,22 +108,6 @@ public final class JavaCompletionContributor extends CompletionContributor imple
return aClass != null && aClass.isEnum();
}
}))));
private static final ElementPattern<PsiElement> IN_SEALED_SWITCH = psiElement()
.withSuperParent(2, psiElement(PsiCaseLabelElementList.class)
.withParent(psiElement(PsiSwitchLabelStatementBase.class)
.withSuperParent(2, psiElement(PsiSwitchBlock.class)
.with(new PatternCondition<>("sealedExpressionType") {
@Override
public boolean accepts(@NotNull PsiSwitchBlock psiSwitchBlock, ProcessingContext context) {
final PsiExpression expression = psiSwitchBlock.getExpression();
if (expression == null) return false;
final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
return aClass != null && aClass.hasModifierProperty(PsiModifier.SEALED);
}
})
)
)
);
private static final PsiJavaElementPattern.Capture<PsiElement> IN_CASE_LABEL_ELEMENT_LIST =
psiElement().withSuperParent(2, psiElement(PsiCaseLabelElementList.class));
@@ -308,28 +291,12 @@ public final class JavaCompletionContributor extends CompletionContributor imple
return new OrFilter(new ClassFilter(PsiClass.class), constantVariablesFilter);
}
final PsiResolveHelper resolver = JavaPsiFacade.getInstance(position.getProject()).getResolveHelper();
final PsiClass selectorClass = resolver.resolveReferencedClass(selectorType.getCanonicalText(), null);
if (selectorClass == null) return TrueFilter.INSTANCE;
if (IN_SEALED_SWITCH.accepts(position)) {
final Collection<PsiClass> classes = SealedUtils.findSameFileInheritorsClasses(selectorClass);
return new ClassFilter(PsiClass.class) {
@Override
public boolean isAcceptable(Object element, PsiElement context) {
if (!(element instanceof PsiClass)) return false;
final PsiClass aClass = (PsiClass)element;
return selectorClass == aClass || classes.contains(aClass);
}
};
}
final PsiClass typeClass = PsiUtil.resolveClassInType(selectorType);
final ClassFilter inheritorsFilter = new ClassFilter(PsiClass.class) {
@Override
public boolean isAcceptable(Object element, PsiElement context) {
return element instanceof PsiClass && InheritanceUtil.isInheritorOrSelf((PsiClass)element, selectorClass, true);
return element instanceof PsiClass && InheritanceUtil.isInheritorOrSelf((PsiClass)element, typeClass, true);
}
};
@@ -337,9 +304,9 @@ public final class JavaCompletionContributor extends CompletionContributor imple
return new OrFilter(constantVariablesFilter, inheritorsFilter);
}
if (selectorType instanceof PsiClassType) return inheritorsFilter;
return TrueFilter.INSTANCE;
return selectorType instanceof PsiClassType
? inheritorsFilter
: TrueFilter.INSTANCE;
}
@Contract(pure = true)
@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.completion.scope;
import com.intellij.codeInsight.daemon.impl.analysis.PsiMethodReferenceHighlightingUtil;
@@ -26,13 +26,17 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.LinkedHashMap;
import com.siyeh.ig.psiutils.SealedUtils;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature.PATTERNS_IN_SWITCH;
public final class JavaCompletionProcessor implements PsiScopeProcessor, ElementClassHint {
private static final Logger LOG = Logger.getInstance(JavaCompletionProcessor.class);
@@ -192,9 +196,28 @@ public final class JavaCompletionProcessor implements PsiScopeProcessor, Element
}
}
if (!(element instanceof PsiClass) || !PATTERNS_IN_SWITCH.isAvailable(myElement)) return true;
final PsiClass psiClass = (PsiClass)element;
if (psiClass.hasModifierProperty(PsiModifier.SEALED)) {
addSealedHierarchy(state, psiClass);
}
return true;
}
@Contract(pure = true)
private void addSealedHierarchy(@NotNull ResolveState state, @NotNull PsiClass psiClass) {
final Collection<PsiClass> sealedInheritors = SealedUtils.findSameFileInheritorsClasses(psiClass);
for (PsiClass inheritor : sealedInheritors) {
final CompletionElement completion = new CompletionElement(inheritor, state.get(PsiSubstitutor.KEY));
final CompletionElement prev = myResults.get(completion);
if (prev == null || completion.isMoreSpecificThan(prev)) {
myResults.put(completion, completion);
}
}
}
@Nullable
private PsiType getMethodReferenceType(PsiElement completion) {
PsiElement parent = myElement.getParent();
@@ -252,7 +275,7 @@ public final class JavaCompletionProcessor implements PsiScopeProcessor, Element
PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)element;
if (myStatic) {
if (!(element instanceof PsiClass) && !modifierListOwner.hasModifierProperty(PsiModifier.STATIC)) {
// we don't need non static method in static context.
// we don't need non-static method in static context.
return StaticProblem.instanceAfterStatic;
}
}
@@ -294,7 +317,7 @@ public final class JavaCompletionProcessor implements PsiScopeProcessor, Element
public boolean isAccessible(@Nullable final PsiElement element) {
// if checkAccess is false, we only show inaccessible source elements because their access modifiers can be changed later by the user.
// compiled element can't be changed so we don't pollute the completion with them. In Javadoc, everything is allowed.
// compiled element can't be changed, so we don't pollute the completion with them. In Javadoc, everything is allowed.
if (!myOptions.checkAccess && myInJavaDoc) return true;
if (isAccessibleForResolve(element)) {
@@ -0,0 +1,24 @@
class Main {
public static abstract sealed class Sealed {
public static final class SealedInheritor extends Sealed {}
}
int f(Sealed o) {
return switch(o) {
case Sealed.SealedIn<caret>, null
}
}
int g(Sealed o) {
return switch(o) {
case null, Sealed.SealedIn<caret>
}
}
int h(Sealed o) {
return switch(o) {
case Sealed.SealedIn<caret>
}
}
}
@@ -0,0 +1,24 @@
class Main {
public static abstract sealed class Sealed {
public static final class SealedInheritor extends Sealed {}
}
int f(Sealed o) {
return switch(o) {
case Sealed.SealedInheritor, null
}
}
int g(Sealed o) {
return switch(o) {
case null, Sealed.SealedInheritor
}
}
int h(Sealed o) {
return switch(o) {
case Sealed.SealedInheritor
}
}
}
@@ -3,6 +3,7 @@ package com.intellij.java.codeInsight.completion
import com.intellij.codeInsight.template.impl.LiveTemplateCompletionContributor
import com.intellij.testFramework.LightProjectDescriptor
import com.intellij.testFramework.NeedsIndex
import groovy.transform.CompileStatic
import org.jetbrains.annotations.NotNull
@@ -24,12 +25,16 @@ class NormalSwitchCompletionTest extends NormalCompletionTestCase {
void testInsideRuleInSwitchExpression() { doTest() }
void testBreakDeepInsideSwitchExpression() { doTest() }
@NeedsIndex.Full
void testCompletePatternVariableInSwitchExpr() { doTest() }
@NeedsIndex.Full
void testCompletePatternVariableInSwitchStmt() { doTest() }
void testCompleteReturnInSwitch() { doTest() }
@NeedsIndex.Full
void testCompleteConstantInSwitchExpr() { doTest() }
@NeedsIndex.Full
void testCompleteConstantInSwitchStmt() { doTest() }
void testCompleteNullInSwitchStmt() { doTest() }
@@ -41,13 +46,15 @@ class NormalSwitchCompletionTest extends NormalCompletionTestCase {
void testCompletePatternVariableSwitchStmt() { doTest() }
void testCompletePatternVariableSwitchExpr() { doTest() }
void testCompleteSealedLabelSwitch() { doTest() }
void testCompleteSwitchObjectSelectorPostfix() { doTestPostfixCompletion() }
void testCompleteSwitchSealedSelectorPostfix() { doTestPostfixCompletion() }
private void doTestPostfixCompletion() {
LiveTemplateCompletionContributor.setShowTemplatesInTests(true, myFixture.testRootDisposable)
configure();
configure()
myFixture.type('\t' as char)
checkResult();
checkResult()
}
}
@@ -67,7 +67,7 @@ public final class SealedUtils {
private static @NotNull <T> Collection<T> getClasses(@NotNull PsiClass psiClass,
Function<PsiClass, T> mapper,
PsiClass @NotNull ... classesToExclude) {
GlobalSearchScope fileScope = GlobalSearchScope.fileScope(psiClass.getContainingFile());
GlobalSearchScope fileScope = GlobalSearchScope.fileScope(psiClass.getContainingFile().getOriginalFile());
return DirectClassInheritorsSearch.search(psiClass, fileScope)
.filtering(inheritor -> !ArrayUtil.contains(inheritor, classesToExclude))
.mapping(mapper)