java completion: sort/filter suggestions after :: by their matching to the expected type (IDEA-166673)

GitOrigin-RevId: adaefc912e115826280976b23adee38483ca1f71
This commit is contained in:
Peter Gromov
2020-06-17 17:01:52 +03:00
committed by intellij-monorepo-bot
parent e3489f3445
commit b9525d143e
15 changed files with 131 additions and 115 deletions
@@ -7,6 +7,7 @@ import com.intellij.codeInsight.ExpectedTypesProvider;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.TailTypes;
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
import com.intellij.codeInsight.daemon.impl.analysis.LambdaHighlightingUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFix;
import com.intellij.codeInsight.lookup.*;
import com.intellij.featureStatistics.FeatureUsageTracker;
@@ -365,9 +366,6 @@ public class JavaCompletionContributor extends CompletionContributor {
if (CatchTypeProvider.CATCH_CLAUSE_TYPE.accepts(position)) {
CatchTypeProvider.addCompletions(parameters, result);
}
if (psiElement().afterLeaf("::").withParent(PsiMethodReferenceExpression.class).accepts(position)) {
MethodReferenceCompletionProvider.addCompletions(parameters, result);
}
}
private static boolean smartCompleteExpression(CompletionParameters parameters,
@@ -561,9 +559,16 @@ public class JavaCompletionContributor extends CompletionContributor {
}
}
TailType switchLabelTail = !smart && IN_SWITCH_LABEL.accepts(position)
? TailTypes.forSwitchLabel(Objects.requireNonNull(PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class)))
: null;
boolean inSwitchLabel = IN_SWITCH_LABEL.accepts(position);
TailType forcedTail = null;
if (!smart) {
if (inSwitchLabel) {
forcedTail = TailTypes.forSwitchLabel(Objects.requireNonNull(PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class)));
}
else if (shouldInsertSemicolon(position)) {
forcedTail = TailType.SEMICOLON;
}
}
List<LookupElement> items = new ArrayList<>();
if (INSIDE_CONSTRUCTOR.accepts(position) &&
@@ -589,8 +594,11 @@ public class JavaCompletionContributor extends CompletionContributor {
}
LookupItem<?> item = element.as(LookupItem.CLASS_CONDITION_KEY);
if (switchLabelTail != null) {
element = new IndentingDecorator(TailTypeDecorator.withTail(element, switchLabelTail));
if (forcedTail != null) {
element = TailTypeDecorator.withTail(element, forcedTail);
}
if (inSwitchLabel && !smart) {
element = new IndentingDecorator(element);
}
if (originalFile instanceof PsiJavaCodeReferenceCodeFragment &&
!((PsiJavaCodeReferenceCodeFragment)originalFile).isClassesAccepted() && item != null) {
@@ -617,6 +625,11 @@ public class JavaCompletionContributor extends CompletionContributor {
return items;
}
static boolean shouldInsertSemicolon(PsiElement position) {
return position.getParent() instanceof PsiMethodReferenceExpression &&
LambdaHighlightingUtil.insertSemicolon(position.getParent().getParent());
}
private static List<LookupElement> processLabelReference(PsiLabelReference reference) {
return ContainerUtil.map(reference.getVariants(), s -> TailTypeDecorator.withTail(LookupElementBuilder.create(s), TailType.SEMICOLON));
}
@@ -542,10 +542,6 @@ public class JavaCompletionUtil {
}
}
if (reference instanceof PsiMethodReferenceExpression && completion instanceof PsiMethod && ((PsiMethod)completion).isConstructor()) {
return Collections.singletonList(createConstructorReferenceItem((PsiMethod)completion, (PsiMethodReferenceExpression)reference));
}
PsiSubstitutor substitutor = completionElement.getSubstitutor();
if (substitutor == null) substitutor = PsiSubstitutor.EMPTY;
if (completion instanceof PsiClass) {
@@ -555,7 +551,8 @@ public class JavaCompletionUtil {
}
if (completion instanceof PsiMethod) {
if (reference instanceof PsiMethodReferenceExpression) {
return Collections.singleton(new JavaMethodReferenceElement((PsiMethod)completion, (PsiMethodReferenceExpression)reference));
return Collections.singleton((LookupElement)new JavaMethodReferenceElement(
(PsiMethod)completion, (PsiMethodReferenceExpression)reference, completionElement.getMethodRefType()));
}
JavaMethodCallElement item = new JavaMethodCallElement((PsiMethod)completion).setQualifierSubstitutor(substitutor);
@@ -572,15 +569,6 @@ public class JavaCompletionUtil {
return Collections.singletonList(LookupItemUtil.objectToLookupItem(completion));
}
@NotNull
private static LookupElement createConstructorReferenceItem(@NotNull PsiMethod completion, @NotNull PsiMethodReferenceExpression context) {
LookupElementBuilder item = JavaLookupElementBuilder
.forMethod(completion, "new", PsiSubstitutor.EMPTY, null)
.withPresentableText("new")
.bold();
return LambdaHighlightingUtil.insertSemicolon(context.getParent()) ? TailTypeDecorator.withTail(item, TailType.SEMICOLON) : item;
}
public static boolean hasAccessibleConstructor(@NotNull PsiType type, @NotNull PsiElement place) {
if (type instanceof PsiArrayType) return true;
@@ -821,7 +809,7 @@ public class JavaCompletionUtil {
}
}
public static boolean insertTail(InsertionContext context, LookupElement item, TailType tailType, boolean hasTail) {
private static boolean insertTail(InsertionContext context, LookupElement item, TailType tailType, boolean hasTail) {
TailType toInsert = tailType;
LookupItem<?> lookupItem = item.as(LookupItem.CLASS_CONDITION_KEY);
if (lookupItem == null || lookupItem.getAttribute(LookupItem.TAIL_TYPE_ATTR) != TailType.UNKNOWN) {
@@ -834,10 +822,7 @@ public class JavaCompletionUtil {
boolean insertAdditionalSemicolon = true;
PsiElement leaf = context.getFile().findElementAt(context.getStartOffset());
PsiElement composite = leaf == null ? null : leaf.getParent();
if (composite instanceof PsiMethodReferenceExpression && !LambdaHighlightingUtil.insertSemicolon(composite.getParent())) {
insertAdditionalSemicolon = false;
}
else if (composite instanceof PsiReferenceExpression) {
if (composite instanceof PsiReferenceExpression) {
PsiElement parent = composite.getParent();
if (parent instanceof PsiMethodCallExpression) {
parent = parent.getParent();
@@ -845,9 +830,6 @@ public class JavaCompletionUtil {
if (parent instanceof PsiLambdaExpression && !LambdaHighlightingUtil.insertSemicolonAfter((PsiLambdaExpression)parent)) {
insertAdditionalSemicolon = false;
}
if (parent instanceof PsiMethodReferenceExpression && !LambdaHighlightingUtil.insertSemicolon(parent.getParent())) {
insertAdditionalSemicolon = false;
}
}
if (insertAdditionalSemicolon) {
toInsert = TailType.SEMICOLON;
@@ -3,25 +3,30 @@ package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.codeInsight.lookup.LookupItem;
import com.intellij.codeInsight.lookup.TypedLookupItem;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Iconable;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiMethodReferenceExpression;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
class JavaMethodReferenceElement extends LookupElement {
class JavaMethodReferenceElement extends LookupElement implements TypedLookupItem {
private final PsiMethod myMethod;
private final PsiElement myRefPlace;
private final PsiType myType;
JavaMethodReferenceElement(PsiMethod method, PsiElement refPlace) {
JavaMethodReferenceElement(PsiMethod method, PsiElement refPlace, @Nullable PsiType type) {
myMethod = method;
myRefPlace = refPlace;
myType = type;
}
@Override
public @Nullable PsiType getType() {
return myType;
}
@Override
@@ -69,7 +74,5 @@ class JavaMethodReferenceElement extends LookupElement {
document.insertString(startOffset, qualifiedName + "::");
JavaCompletionUtil.shortenReference(context.getFile(), startOffset + qualifiedName.length() - 1);
}
JavaCompletionUtil
.insertTail(context, this, LookupItem.handleCompletionChar(context.getEditor(), this, context.getCompletionChar()), false);
}
}
@@ -16,10 +16,12 @@
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.completion.impl.BetterPrefixMatcher;
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.TailTypeDecorator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
@@ -154,7 +156,12 @@ public class JavaNoVariantsDelegator extends CompletionContributor {
if (ref != null) {
for (LookupElement item : JavaSmartCompletionContributor.completeReference(position, ref, filter, true, true, parameters,
result.getPrefixMatcher())) {
qualifiedCollector.addElement(JavaCompletionUtil.highlightIfNeeded(null, new JavaChainLookupElement(base, item, separator), item.getObject(), position));
LookupElement chain =
JavaCompletionUtil.highlightIfNeeded(null, new JavaChainLookupElement(base, item, separator), item.getObject(), position);
if (JavaCompletionContributor.shouldInsertSemicolon(position)) {
chain = TailTypeDecorator.withTail(chain, TailType.SEMICOLON);
}
qualifiedCollector.addElement(chain);
}
}
}
@@ -1,65 +0,0 @@
// Copyright 2000-2018 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.
package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.completion.scope.CompletionElement;
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
import com.intellij.codeInsight.daemon.impl.analysis.PsiMethodReferenceHighlightingUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Conditions;
import com.intellij.psi.*;
import com.intellij.psi.filters.types.AssignableFromFilter;
import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.JBIterable;
import org.jetbrains.annotations.NotNull;
class MethodReferenceCompletionProvider {
private static final Logger LOG = Logger.getInstance(MethodReferenceCompletionProvider.class);
static void addCompletions(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) {
if (!PsiUtil.isLanguageLevel8OrHigher(parameters.getOriginalFile())) return;
PsiElement position = parameters.getPosition();
PsiMethodReferenceExpression refPlace = (PsiMethodReferenceExpression)position.getParent();
if (refPlace == null || !LambdaUtil.isValidLambdaContext(refPlace.getParent())) return;
final ExpectedTypeInfo[] expectedTypes = JavaSmartCompletionContributor.getExpectedTypes(parameters);
for (ExpectedTypeInfo expectedType : expectedTypes) {
final PsiType defaultType = expectedType.getDefaultType();
if (LambdaUtil.isFunctionalType(defaultType)) {
final PsiType functionalType = FunctionalInterfaceParameterizationUtil.getGroundTargetType(defaultType);
final PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalType);
if (returnType != null && functionalType != null) {
JavaCompletionProcessor processor = new JavaCompletionProcessor(
position, new AssignableFromFilter(returnType), JavaCompletionProcessor.Options.DEFAULT_OPTIONS, Conditions.alwaysTrue());
refPlace.processVariants(processor);
Iterable<PsiMethod> matchingMethods = JBIterable.from(processor.getResults())
.map(CompletionElement::getElement)
.filter(PsiMethod.class);
for (PsiMethod method : matchingMethods) {
PsiMethodReferenceExpression referenceExpression = createMethodReferenceExpression(method, refPlace);
LambdaUtil.performWithTargetType(referenceExpression, functionalType, () -> {
if (referenceExpression.isReferenceTo(method) &&
PsiMethodReferenceHighlightingUtil.checkMethodReferenceContext(referenceExpression, method, functionalType) == null) {
result.addElement(new JavaMethodReferenceElement(method, refPlace));
}
return null;
});
}
}
}
}
}
private static PsiMethodReferenceExpression createMethodReferenceExpression(PsiMethod method, PsiMethodReferenceExpression place) {
PsiElementFactory factory = JavaPsiFacade.getElementFactory(method.getProject());
PsiMethodReferenceExpression copy = (PsiMethodReferenceExpression)place.copy();
PsiElement referenceNameElement = copy.getReferenceNameElement();
LOG.assertTrue(referenceNameElement != null, copy);
referenceNameElement.replace(method.isConstructor() ? factory.createKeyword("new") : factory.createIdentifier(method.getName()));
return copy;
}
}
@@ -1,17 +1,22 @@
// Copyright 2000-2019 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.
package com.intellij.codeInsight.completion.scope;
import com.intellij.codeInsight.daemon.impl.analysis.PsiMethodReferenceHighlightingUtil;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInspection.SuppressManager;
import com.intellij.codeInspection.accessStaticViaInstance.AccessStaticViaInstanceBase;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.filters.ElementFilter;
import com.intellij.psi.filters.getters.ExpectedTypesGetter;
import com.intellij.psi.impl.light.LightMethodBuilder;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.JavaScopeProcessorEvent;
@@ -28,6 +33,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
public class JavaCompletionProcessor implements PsiScopeProcessor, ElementClassHint {
private static final Logger LOG = Logger.getInstance(JavaCompletionProcessor.class);
private final boolean myInJavaDoc;
private boolean myStatic;
@@ -46,6 +52,7 @@ public class JavaCompletionProcessor implements PsiScopeProcessor, ElementClassH
private final Condition<? super String> myMatcher;
private final Options myOptions;
private final boolean myAllowStaticWithInstanceQualifier;
private final NotNullLazyValue<Collection<PsiType>> myExpectedGroundTypes;
public JavaCompletionProcessor(@NotNull PsiElement element, ElementFilter filter, Options options, @NotNull Condition<? super String> nameCondition) {
myOptions = options;
@@ -91,7 +98,9 @@ public class JavaCompletionProcessor implements PsiScopeProcessor, ElementClassH
}
myAllowStaticWithInstanceQualifier = !options.filterStaticAfterInstance || allowStaticAfterInstanceQualifier(element);
myExpectedGroundTypes = NotNullLazyValue.createValue(
() -> ContainerUtil.map(ExpectedTypesGetter.getExpectedTypes(element, false),
FunctionalInterfaceParameterizationUtil::getGroundTargetType));
}
private static boolean allowStaticAfterInstanceQualifier(@NotNull PsiElement position) {
@@ -175,7 +184,8 @@ public class JavaCompletionProcessor implements PsiScopeProcessor, ElementClassH
StaticProblem sp = myElement.getParent() instanceof PsiMethodReferenceExpression ? StaticProblem.none : getStaticProblem(element);
if (sp == StaticProblem.instanceAfterStatic) return true;
CompletionElement completion = new CompletionElement(element, state.get(PsiSubstitutor.KEY), getCallQualifierText(element));
CompletionElement completion = new CompletionElement(
element, state.get(PsiSubstitutor.KEY), getCallQualifierText(element), getMethodReferenceType(element));
CompletionElement prev = myResults.get(completion);
if (prev == null || completion.isMoreSpecificThan(prev)) {
myResults.put(completion, completion);
@@ -187,6 +197,36 @@ public class JavaCompletionProcessor implements PsiScopeProcessor, ElementClassH
return true;
}
@Nullable
private PsiType getMethodReferenceType(PsiElement completion) {
PsiElement parent = myElement.getParent();
if (completion instanceof PsiMethod && parent instanceof PsiMethodReferenceExpression) {
PsiType matchingType = ContainerUtil.find(myExpectedGroundTypes.getValue(), candidate ->
hasSuitableType((PsiMethodReferenceExpression)parent, (PsiMethod)completion, candidate));
return matchingType != null ? matchingType : new PsiMethodReferenceType((PsiMethodReferenceExpression)parent);
}
return null;
}
private static boolean hasSuitableType(PsiMethodReferenceExpression refPlace, PsiMethod method, PsiType expectedType) {
PsiMethodReferenceExpression referenceExpression = createMethodReferenceExpression(method, refPlace);
return LambdaUtil.performWithTargetType(referenceExpression, expectedType, () -> {
JavaResolveResult result = referenceExpression.advancedResolve(false);
return method.getManager().areElementsEquivalent(method, result.getElement()) &&
PsiMethodReferenceUtil.isReturnTypeCompatible(referenceExpression, result, expectedType) &&
PsiMethodReferenceHighlightingUtil.checkMethodReferenceContext(referenceExpression, method, expectedType) == null;
});
}
private static PsiMethodReferenceExpression createMethodReferenceExpression(PsiMethod method, PsiMethodReferenceExpression place) {
PsiElementFactory factory = JavaPsiFacade.getElementFactory(method.getProject());
PsiMethodReferenceExpression copy = (PsiMethodReferenceExpression)place.copy();
PsiElement referenceNameElement = copy.getReferenceNameElement();
LOG.assertTrue(referenceNameElement != null, copy);
referenceNameElement.replace(method.isConstructor() ? factory.createKeyword("new") : factory.createIdentifier(method.getName()));
return copy;
}
@NotNull
private String getCallQualifierText(@NotNull PsiElement element) {
if (element instanceof PsiMethod) {
@@ -22,6 +22,7 @@ import com.intellij.psi.*;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,15 +33,17 @@ public class CompletionElement{
private final PsiSubstitutor mySubstitutor;
private final Object myEqualityObject;
private final String myQualifierText;
private final @Nullable PsiType myMethodRefType;
public CompletionElement(Object element, PsiSubstitutor substitutor) {
this(element, substitutor, "");
this(element, substitutor, "", null);
}
public CompletionElement(Object element, PsiSubstitutor substitutor, @NotNull String qualifierText) {
CompletionElement(Object element, PsiSubstitutor substitutor, @NotNull String qualifierText, @Nullable PsiType methodRefType) {
myElement = element;
mySubstitutor = substitutor;
myQualifierText = qualifierText;
myMethodRefType = methodRefType;
myEqualityObject = getUniqueId();
}
@@ -67,6 +70,10 @@ public class CompletionElement{
return ((PsiPackage)myElement).getQualifiedName();
}
if(myElement instanceof PsiMethod){
if (myMethodRefType != null) {
return ((PsiMethod)myElement).isConstructor() ? PsiKeyword.NEW : ((PsiMethod)myElement).getName();
}
return Trinity.create(((PsiMethod)myElement).getName(),
Arrays.asList(MethodSignatureUtil.calcErasedParameterTypes(((PsiMethod)myElement).getSignature(mySubstitutor))),
myQualifierText);
@@ -99,10 +106,20 @@ public class CompletionElement{
return myEqualityObject != null ? myEqualityObject.hashCode() : 0;
}
@Nullable
@ApiStatus.Internal
public PsiType getMethodRefType() {
return myMethodRefType;
}
public boolean isMoreSpecificThan(@NotNull CompletionElement another) {
Object anotherElement = another.getElement();
if (!(anotherElement instanceof PsiMethod && myElement instanceof PsiMethod)) return false;
if (another.myMethodRefType instanceof PsiMethodReferenceType && myMethodRefType instanceof PsiClassType) {
return true;
}
if (anotherElement != myElement &&
((PsiMethod)myElement).hasModifierProperty(PsiModifier.ABSTRACT) &&
!((PsiMethod)anotherElement).hasModifierProperty(PsiModifier.ABSTRACT)) {
@@ -1,7 +1,7 @@
import java.util.*;
class Test {
void aaa(Test p) { return 1; }
int aaa(Test p) { return 1; }
void test() {
Comparator<Test> r2 = Test::<caret>
}
@@ -1,8 +1,8 @@
import java.util.*;
class Test {
void aaa(Test p) { return 1; }
int aaa(Test p) { return 1; }
void test() {
Comparator<Test> r2 = Test::test;
Comparator<Test> r2 = Test::aaa;<caret>
}
}
@@ -9,6 +9,6 @@ class Bar {
class Test88 {
void foo(Foo9 foo) {
foo(Bar::new<caret>);
foo(Bar::new);<caret>
}
}
@@ -8,6 +8,6 @@ class Test {
}
{
I i = Test::aa
I i = Test::aa;<caret>
}
}
@@ -0,0 +1,9 @@
class NewCompletion {
private static void vdf(java.util.function.Supplier<java.util.ArrayList<Integer>> supplier) {
}
public static void main(String[] args) {
vdf(java.util.ArrayList::<caret>);
}
}
@@ -100,7 +100,9 @@ public class KeywordCompletionTest extends LightCompletionTestCase {
@NeedsIndex.ForStandardLibrary
public void testNewInMethodRefs() {
doTest(1, "new", "null", "true", "false");
assertEquals("new", LookupElementPresentation.renderElement(myItems[0]).getItemText());
LookupElementPresentation presentation = LookupElementPresentation.renderElement(myItems[0]);
assertEquals("new", presentation.getItemText());
assertEmpty(presentation.getTailText());
selectItem(myItems[0]);
checkResultByTestName();
}
@@ -469,4 +469,11 @@ class Test88 {
myFixture.type('\n')
checkResultByFileName()
}
@NeedsIndex.ForStandardLibrary
void testPreferConstructorReferenceOfExpectedType() {
configureByTestName()
myFixture.assertPreferredCompletionItems 0, 'new'
}
}
@@ -1989,6 +1989,7 @@ class Bar {{
void testOnlyResourcesInResourceList5() { doTest('\n') }
@NeedsIndex.ForStandardLibrary
void testMethodReferenceNoStatic() { doTest('\n') }
void testMethodReferenceCallContext() { doTest('\n') }