IDEA-145958 Suggest chained calls of expected type in basic & autopopup completion

GitOrigin-RevId: b2d1ed10839ffac236b724c224f856e6bb99cb14
This commit is contained in:
Peter Gromov
2020-06-15 15:02:03 +03:00
committed by intellij-monorepo-bot
parent 60db1bd88c
commit 3aea481c1f
12 changed files with 203 additions and 137 deletions
@@ -245,6 +245,13 @@ public class JavaCompletionContributor extends CompletionContributor {
if (position instanceof PsiIdentifier) {
addIdentifierVariants(parameters, position, result, session, matcher);
Set<ExpectedTypeInfo> expectedInfos = ContainerUtil.newHashSet(JavaSmartCompletionContributor.getExpectedTypes(parameters));
boolean shouldAddExpressionVariants = shouldAddExpressionVariants(parameters);
boolean hasTypeMatchingSuggestions =
shouldAddExpressionVariants && addExpectedTypeMembers(parameters, false, expectedInfos,
item -> session.registerBatchItems(Collections.singleton(item)));
if (!smart) {
PsiAnnotation anno = findAnnotationWhoseAttributeIsCompleted(position);
if (anno != null) {
@@ -264,21 +271,30 @@ public class JavaCompletionContributor extends CompletionContributor {
result.stopHere();
}
List<LookupElement> refBasedSuggestions = parent instanceof PsiJavaCodeReferenceElement && mayCompleteReference
? completeReference(parameters, (PsiJavaCodeReferenceElement)parent, session)
: Collections.emptyList();
if (!smart) {
TailType switchLabelTail = IN_SWITCH_LABEL.accepts(position)
? TailTypes.forSwitchLabel(Objects.requireNonNull(PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class)))
: null;
session.registerBatchItems(ContainerUtil.map(refBasedSuggestions, e -> switchLabelTail != null ? new IndentingDecorator(TailTypeDecorator.withTail(e, switchLabelTail)) : e));
List<LookupElement> refSuggestions = Collections.emptyList();
if (parent instanceof PsiJavaCodeReferenceElement && mayCompleteReference) {
refSuggestions = completeReference(parameters, (PsiJavaCodeReferenceElement)parent, session, expectedInfos);
List<LookupElement> filtered = filterReferenceSuggestions(smart, result, (PsiJavaCodeReferenceElement)parent, expectedInfos, refSuggestions);
hasTypeMatchingSuggestions |= ContainerUtil.exists(filtered, item ->
ReferenceExpressionCompletionContributor.matchesExpectedType(item, expectedInfos));
session.registerBatchItems(filtered);
result.stopHere();
}
session.flushBatchItems();
if (smart) {
addSmartCompletionSuggestions(parameters, result, refBasedSuggestions);
hasTypeMatchingSuggestions |= smartCompleteExpression(parameters, result, expectedInfos);
smartCompleteNonExpression(parameters, result);
}
if ((!hasTypeMatchingSuggestions || parameters.getInvocationCount() >= 2) &&
JavaSmartCompletionContributor.INSIDE_EXPRESSION.accepts(position)) {
SlowerTypeConversions.addChainedSuggestions(parameters, result, expectedInfos, refSuggestions);
}
if (smart && parameters.getInvocationCount() > 1 && shouldAddExpressionVariants) {
addExpectedTypeMembers(parameters, true, expectedInfos, result);
}
}
@@ -321,31 +337,23 @@ public class JavaCompletionContributor extends CompletionContributor {
}
}
private static void addSmartCompletionSuggestions(CompletionParameters parameters,
CompletionResultSet result,
List<LookupElement> allRefSuggestions) {
private static List<LookupElement> filterReferenceSuggestions(boolean smart,
CompletionResultSet result,
PsiJavaCodeReferenceElement parent,
Set<ExpectedTypeInfo> expectedInfos,
List<LookupElement> refSuggestions) {
if (smart) {
refSuggestions = ReferenceExpressionCompletionContributor.smartCompleteReference(refSuggestions, expectedInfos);
}
List<LookupElement> matching = ContainerUtil.findAll(refSuggestions, result.getPrefixMatcher()::prefixMatches);
return JavaCompletionProcessor.dispreferStaticAfterInstance(parent, matching);
}
private static void smartCompleteNonExpression(CompletionParameters parameters, CompletionResultSet result) {
PsiElement position = parameters.getPosition();
Set<ExpectedTypeInfo> infos = ContainerUtil.newHashSet(JavaSmartCompletionContributor.getExpectedTypes(parameters));
Set<ExpectedTypeInfo> mergedInfos = new THashSet<>(infos, JavaSmartCompletionContributor.EXPECTED_TYPE_INFO_STRATEGY);
List<SlowerTypeConversions> chainedEtc = new ArrayList<>();
PsiElement parent = position.getParent();
if (!SmartCastProvider.shouldSuggestCast(parameters) && parent instanceof PsiJavaCodeReferenceElement) {
JavaSmartCompletionContributor.addClassReferenceSuggestions(parameters, result, position, (PsiJavaCodeReferenceElement)parent);
if (JavaSmartCompletionContributor.INSIDE_EXPRESSION.accepts(position)) {
ReferenceExpressionCompletionContributor.addSmartReferenceSuggestions(
parameters, allRefSuggestions, mergedInfos, chainedEtc,
e -> result.addElement(JavaSmartCompletionContributor.decorate(e, infos)));
for (ExpectedTypeInfo info : mergedInfos) {
BasicExpressionCompletionContributor.fillCompletionVariants(new JavaSmartCompletionParameters(parameters, info), lookupElement -> {
final PsiType psiType = JavaCompletionUtil.getLookupElementType(lookupElement);
if (psiType != null && info.getType().isAssignableFrom(psiType)) {
result.addElement(JavaSmartCompletionContributor.decorate(lookupElement, infos));
}
}, result.getPrefixMatcher());
}
}
}
if (InstanceofTypeProvider.AFTER_INSTANCEOF.accepts(position)) {
InstanceofTypeProvider.addCompletions(parameters, result);
@@ -359,14 +367,29 @@ public class JavaCompletionContributor extends CompletionContributor {
if (psiElement().afterLeaf("::").withParent(PsiMethodReferenceExpression.class).accepts(position)) {
MethodReferenceCompletionProvider.addCompletions(parameters, result);
}
}
for (Runnable runnable : chainedEtc) {
runnable.run();
private static boolean smartCompleteExpression(CompletionParameters parameters,
CompletionResultSet result,
Set<ExpectedTypeInfo> infos) {
PsiElement position = parameters.getPosition();
if (SmartCastProvider.shouldSuggestCast(parameters) ||
!JavaSmartCompletionContributor.INSIDE_EXPRESSION.accepts(position) ||
!(position.getParent() instanceof PsiJavaCodeReferenceElement)) {
return false;
}
if (parameters.getInvocationCount() > 1 && shouldAddExpressionVariants(parameters)) {
addExpectedTypeMembers(parameters, true, result);
boolean[] hadItems = new boolean[1];
for (ExpectedTypeInfo info : new THashSet<>(infos, JavaSmartCompletionContributor.EXPECTED_TYPE_INFO_STRATEGY)) {
BasicExpressionCompletionContributor.fillCompletionVariants(new JavaSmartCompletionParameters(parameters, info), lookupElement -> {
final PsiType psiType = JavaCompletionUtil.getLookupElementType(lookupElement);
if (psiType != null && info.getType().isAssignableFrom(psiType)) {
hadItems[0] = true;
result.addElement(JavaSmartCompletionContributor.decorate(lookupElement, infos));
}
}, result.getPrefixMatcher());
}
return hadItems[0];
}
@Nullable
@@ -487,7 +510,6 @@ public class JavaCompletionContributor extends CompletionContributor {
private static void addExpressionVariants(@NotNull CompletionParameters parameters, PsiElement position, Consumer<? super LookupElement> result) {
if (shouldAddExpressionVariants(parameters)) {
addExpectedTypeMembers(parameters, false, result);
if (SameSignatureCallParametersProvider.IN_CALL_ARGUMENT.accepts(position)) {
new SameSignatureCallParametersProvider().addSignatureItems(position, result);
}
@@ -524,11 +546,28 @@ public class JavaCompletionContributor extends CompletionContributor {
private static List<LookupElement> completeReference(CompletionParameters parameters,
PsiJavaCodeReferenceElement ref,
JavaCompletionSession session) {
JavaCompletionSession session,
Set<ExpectedTypeInfo> expectedTypes) {
PsiElement position = parameters.getPosition();
ElementFilter filter = getReferenceFilter(position);
if (filter == null) return Collections.emptyList();
boolean smart = parameters.getCompletionType() == CompletionType.SMART;
if (smart) {
if (JavaSmartCompletionContributor.INSIDE_TYPECAST_EXPRESSION.accepts(position) || SmartCastProvider.shouldSuggestCast(parameters)) {
return Collections.emptyList();
}
ElementFilter smartRestriction = ReferenceExpressionCompletionContributor.getReferenceFilter(position, false);
if (smartRestriction != TrueFilter.INSTANCE) {
filter = new AndFilter(filter, smartRestriction);
}
}
TailType switchLabelTail = !smart && IN_SWITCH_LABEL.accepts(position)
? TailTypes.forSwitchLabel(Objects.requireNonNull(PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class)))
: null;
List<LookupElement> items = new ArrayList<>();
if (INSIDE_CONSTRUCTOR.accepts(position) &&
(parameters.getInvocationCount() <= 1 || CheckInitialized.isInsideConstructorCall(position))) {
@@ -537,25 +576,25 @@ public class JavaCompletionContributor extends CompletionContributor {
PsiFile originalFile = parameters.getOriginalFile();
boolean first = parameters.getInvocationCount() <= 1;
PsiType[] expectedTypes = ExpectedTypesGetter.getExpectedTypes(parameters.getPosition(), true);
boolean smart = parameters.getCompletionType() == CompletionType.SMART;
JavaCompletionProcessor.Options options =
JavaCompletionProcessor.Options.DEFAULT_OPTIONS
.withCheckAccess(first)
.withFilterStaticAfterInstance(first)
.withFilterStaticAfterInstance(false)
.withShowInstanceInStaticContext(!first && !smart);
PrefixMatcher matcher = smart ? PrefixMatcher.ALWAYS_TRUE : session.getMatcher();
for (LookupElement element : JavaCompletionUtil.processJavaReference(position,
ref,
new ElementExtractorFilter(filter),
options,
matcher, parameters)) {
PrefixMatcher.ALWAYS_TRUE, parameters)) {
if (session.alreadyProcessed(element)) {
continue;
}
LookupItem<?> item = element.as(LookupItem.CLASS_CONDITION_KEY);
if (switchLabelTail != null) {
element = new IndentingDecorator(TailTypeDecorator.withTail(element, switchLabelTail));
}
if (originalFile instanceof PsiJavaCodeReferenceCodeFragment &&
!((PsiJavaCodeReferenceCodeFragment)originalFile).isClassesAccepted() && item != null) {
item.setTailType(TailType.NONE);
@@ -565,10 +604,11 @@ public class JavaCompletionContributor extends CompletionContributor {
final PsiMethod method = call.getObject();
if (method.getTypeParameters().length > 0) {
PsiType returned = TypeConversionUtil.erasure(method.getReturnType());
PsiType matchingExpectation = returned == null ? null : ContainerUtil.find(expectedTypes, type ->
type.isAssignableFrom(returned) || AssignableFromFilter.isAcceptable(method, position, type, call.getSubstitutor()));
ExpectedTypeInfo matchingExpectation = returned == null ? null : ContainerUtil.find(expectedTypes, info ->
info.getDefaultType().isAssignableFrom(returned) ||
AssignableFromFilter.isAcceptable(method, position, info.getDefaultType(), call.getSubstitutor()));
if (matchingExpectation != null) {
call.setInferenceSubstitutorFromExpectedType(position, matchingExpectation);
call.setInferenceSubstitutorFromExpectedType(position, matchingExpectation.getDefaultType());
}
}
}
@@ -999,11 +1039,17 @@ public class JavaCompletionContributor extends CompletionContributor {
return null;
}
private static void addExpectedTypeMembers(CompletionParameters parameters, boolean searchInheritors, final Consumer<? super LookupElement> result) {
private static boolean addExpectedTypeMembers(CompletionParameters parameters,
boolean searchInheritors,
Collection<ExpectedTypeInfo> types,
Consumer<? super LookupElement> result) {
boolean[] added = new boolean[1];
boolean smart = parameters.getCompletionType() == CompletionType.SMART;
if (smart || parameters.getInvocationCount() <= 1) { // on second basic completion, StaticMemberProcessor will suggest those
ExpectedTypeInfo[] types = JavaSmartCompletionContributor.getExpectedTypes(parameters);
Consumer<LookupElement> consumer = e -> result.consume(smart ? JavaSmartCompletionContributor.decorate(e, Arrays.asList(types)) : e);
Consumer<LookupElement> consumer = e -> {
added[0] = true;
result.consume(smart ? JavaSmartCompletionContributor.decorate(e, types) : e);
};
for (ExpectedTypeInfo info : types) {
new JavaMembersGetter(info.getType(), parameters).addMembers(searchInheritors, consumer);
if (!info.getType().equals(info.getDefaultType())) {
@@ -1011,6 +1057,7 @@ public class JavaCompletionContributor extends CompletionContributor {
}
}
}
return added[0];
}
private static void addModuleReferences(PsiElement moduleRef, PsiFile originalFile, CompletionResultSet result) {
@@ -12,7 +12,6 @@ import com.intellij.psi.filters.element.ModifierFilter;
import com.intellij.psi.filters.types.AssignableFromFilter;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Consumer;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
@@ -67,32 +66,21 @@ public class ReferenceExpressionCompletionContributor {
return TrueFilter.INSTANCE;
}
static void addSmartReferenceSuggestions(CompletionParameters parameters,
List<LookupElement> allRefSuggestions,
Set<ExpectedTypeInfo> infos,
List<SlowerTypeConversions> chainedEtc,
Consumer<? super LookupElement> result) {
final PsiElement element = parameters.getPosition();
if (JavaSmartCompletionContributor.INSIDE_TYPECAST_EXPRESSION.accepts(element)) return;
ElementFilter filter = getReferenceFilter(element, false);
allRefSuggestions = ContainerUtil.filter(allRefSuggestions, item -> filter.isAcceptable(item.getObject(), element));
for (ExpectedTypeInfo info : infos) {
for (LookupElement item : allRefSuggestions) {
if (matchesExpectedType(item, info.getType())) {
if (item instanceof JavaMethodCallElement) {
checkTooGeneric((JavaMethodCallElement)item);
}
result.consume(item);
static List<LookupElement> smartCompleteReference(List<LookupElement> allRefSuggestions, Set<ExpectedTypeInfo> infos) {
List<LookupElement> result = new ArrayList<>();
for (LookupElement item : allRefSuggestions) {
if (matchesExpectedType(item, infos)) {
if (item instanceof JavaMethodCallElement) {
checkTooGeneric((JavaMethodCallElement)item);
}
}
if (parameters.getInvocationCount() >= 2) {
chainedEtc.add(new SlowerTypeConversions(new HashSet<>(allRefSuggestions), element, (PsiJavaCodeReferenceElement) element.getParent(),
new JavaSmartCompletionParameters(parameters, info), result));
result.add(JavaSmartCompletionContributor.decorate(item, infos));
}
}
return result;
}
static boolean matchesExpectedType(LookupElement item, Set<ExpectedTypeInfo> infos) {
return ContainerUtil.exists(infos, info -> matchesExpectedType(item, info.getType()));
}
private static boolean matchesExpectedType(LookupElement item, PsiType type) {
@@ -2,6 +2,7 @@
package com.intellij.codeInsight.completion;
import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
@@ -12,61 +13,47 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.getSpace;
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
/**
* @author peter
*/
class SlowerTypeConversions implements Runnable {
private static final PrefixMatcher TRUE_MATCHER = new PrefixMatcher("") {
@Override
public boolean prefixMatches(@NotNull String name) {
return true;
}
class SlowerTypeConversions {
static void addChainedSuggestions(CompletionParameters parameters,
CompletionResultSet result,
Set<ExpectedTypeInfo> expectedInfos,
List<LookupElement> base) {
PsiElement position = parameters.getPosition();
PsiJavaCodeReferenceElement reference = (PsiJavaCodeReferenceElement)position.getParent();
List<LookupElement> chainable = ContainerUtil.filter(base, item -> isChainable(item.getObject()));
if (chainable.isEmpty()) return;
@NotNull
@Override
public PrefixMatcher cloneWithPrefix(@NotNull String prefix) {
return this;
}
};
private final Set<? extends LookupElement> myBase;
private final PsiElement myElement;
private final PsiJavaCodeReferenceElement myReference;
private final JavaSmartCompletionParameters myParameters;
private final Consumer<? super LookupElement> myResult;
for (ExpectedTypeInfo info : expectedInfos) {
Set<Pair<LookupElement, String>> processedChains = new HashSet<>();
JavaSmartCompletionParameters smartParams = new JavaSmartCompletionParameters(parameters, info);
for (LookupElement item : chainable) {
addSecondCompletionVariants(position, reference, item, smartParams, lookupElement -> {
ContainerUtil.addIfNotNull(processedChains, chainInfo(lookupElement));
result.consume(JavaSmartCompletionContributor.decorate(lookupElement, expectedInfos));
});
}
if (!reference.isQualified()) {
BasicExpressionCompletionContributor.processDataflowExpressionTypes(smartParams, null, PrefixMatcher.ALWAYS_TRUE,
baseItem -> addSecondCompletionVariants(position, reference, baseItem, smartParams, lookupElement -> {
if (!processedChains.contains(chainInfo(lookupElement))) {
result.consume(JavaSmartCompletionContributor.decorate(lookupElement, expectedInfos));
}
}));
}
SlowerTypeConversions(Set<? extends LookupElement> base,
PsiElement element,
PsiJavaCodeReferenceElement reference,
JavaSmartCompletionParameters parameters, Consumer<? super LookupElement> result) {
myBase = base;
myElement = element;
myReference = reference;
myParameters = parameters;
myResult = result;
}
}
@Override
public void run() {
final Set<Pair<LookupElement, String>> processedChains = new HashSet<>();
for (final LookupElement item : myBase) {
addSecondCompletionVariants(myElement, myReference, item, myParameters, lookupElement -> {
ContainerUtil.addIfNotNull(processedChains, chainInfo(lookupElement));
myResult.consume(lookupElement);
});
}
if (!psiElement().afterLeaf(".").accepts(myElement)) {
BasicExpressionCompletionContributor.processDataflowExpressionTypes(myParameters, null, TRUE_MATCHER,
baseItem -> addSecondCompletionVariants(myElement, myReference, baseItem, myParameters, lookupElement -> {
if (!processedChains.contains(chainInfo(lookupElement))) {
myResult.consume(lookupElement);
}
}));
}
private static boolean isChainable(Object object) {
return object instanceof PsiVariable || object instanceof PsiMethod || object instanceof PsiExpression;
}
private static void addSecondCompletionVariants(PsiElement element, PsiReference reference, LookupElement baseItem,
@@ -1,6 +1,7 @@
// 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.lookup.LookupElement;
import com.intellij.codeInspection.SuppressManager;
import com.intellij.codeInspection.accessStaticViaInstance.AccessStaticViaInstanceBase;
import com.intellij.openapi.util.Condition;
@@ -24,10 +25,7 @@ import com.intellij.util.containers.hash.LinkedHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
public class JavaCompletionProcessor implements PsiScopeProcessor, ElementClassHint {
@@ -92,12 +90,34 @@ public class JavaCompletionProcessor implements PsiScopeProcessor, ElementClassH
myQualifierType = JavaPsiFacade.getElementFactory(element.getProject()).createType(qualifierClass);
}
myAllowStaticWithInstanceQualifier = !options.filterStaticAfterInstance ||
SuppressManager.getInstance().isSuppressedFor(element, AccessStaticViaInstanceBase.ACCESS_STATIC_VIA_INSTANCE) ||
Registry.is("ide.java.completion.suggest.static.after.instance");
myAllowStaticWithInstanceQualifier = !options.filterStaticAfterInstance || allowStaticAfterInstanceQualifier(element);
}
private static boolean allowStaticAfterInstanceQualifier(@NotNull PsiElement position) {
return SuppressManager.getInstance().isSuppressedFor(position, AccessStaticViaInstanceBase.ACCESS_STATIC_VIA_INSTANCE) ||
Registry.is("ide.java.completion.suggest.static.after.instance");
}
public static List<LookupElement> dispreferStaticAfterInstance(PsiJavaCodeReferenceElement position, List<LookupElement> items) {
if (allowStaticAfterInstanceQualifier(position)) return items;
PsiElement qualifier = position.getQualifier();
if (qualifier == null ||
qualifier instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement)qualifier).resolve() instanceof PsiClass) {
return items;
}
List<LookupElement> preferred = new ArrayList<>();
for (LookupElement item : items) {
Object object = item.getObject();
if (!(object instanceof PsiModifierListOwner) || !((PsiModifierListOwner)object).hasModifierProperty(PsiModifier.STATIC)) {
preferred.add(item);
}
}
return preferred.isEmpty() ? items : preferred;
}
@Override
public void handleEvent(@NotNull Event event, Object associated){
if (JavaScopeProcessorEvent.isEnteringStaticScope(event, associated)) {
@@ -0,0 +1,13 @@
class Bar {
Goo getGoo();
}
class Goo {}
class Foo {
Bar getBar() {}
void x(Goo unmatched) {
Goo g = getG<caret>
}
}
@@ -0,0 +1,13 @@
class Bar {
Goo getGoo();
}
class Goo {}
class Foo {
Bar getBar() {}
void x(Goo unmatched) {
Goo g = getBar().getGoo();<caret>
}
}
@@ -2,7 +2,7 @@ import foo.bar.*;
import foo.baz.Baz;
class Main {
void foo() throws ReflectiveOperationException {
Class<Annotation> aType = Baz.class;
Class<Baz> aType = Baz.class;
Test.class.getAnnotation(<caret>);
}
}
@@ -2,7 +2,7 @@ import foo.bar.*;
import foo.baz.Baz;
class Main {
void foo() throws ReflectiveOperationException {
Class<Annotation> aType = Baz.class;
Class<Baz> aType = Baz.class;
Test.class.getAnnotation(aType);
}
}
@@ -1,12 +1,7 @@
class A{
class B{
int fooo(){
}
}
int fooo(){
A b = null;
A a = null;
return fo<caret>
return a.fooo();<caret>
}
}
@@ -1,11 +1,6 @@
class A{
class B{
int fooo(){
}
}
int fooo(){
A b = null;
A a = null;
return fo<caret>
}
@@ -2019,4 +2019,6 @@ class Abc {
selectItem(myItems[1])
checkResult()
}
void testSuggestChainsOfExpectedType() { doTest() }
}
@@ -1344,4 +1344,10 @@ public class SmartTypeCompletionTest extends LightFixtureCompletionTestCase {
assertTrue(PsiUtil.getLanguageLevel(getProject()).isLessThan(LanguageLevel.JDK_1_8));
doTest();
}
public void testSuggestChainsWhenNoDirectMatches() {
myFixture.configureFromExistingVirtualFile(myFixture.copyFileToProject("second/MethodAsQualifier.java", "a.java"));
myFixture.complete(CompletionType.SMART);
checkResultByFile("second/MethodAsQualifier-out.java");
}
}