smart completion of method references (IDEA-123293; IDEA-123291)

This commit is contained in:
Anna Kozlova
2014-04-03 17:15:57 +02:00
parent c33aa267f5
commit 7bb8868ef2
14 changed files with 177 additions and 31 deletions
@@ -83,7 +83,7 @@ public class JavaSmartCompletionContributor extends CompletionContributor {
new GeneratorFilter(AssignableGroupFilter.class, new ThrowsListGetter()),
new AssignableFromFilter(CommonClassNames.JAVA_LANG_THROWABLE));
public static final ElementPattern<PsiElement> INSIDE_EXPRESSION = or(
psiElement().withParent(PsiExpression.class).andNot(psiElement().withParent(PsiLiteralExpression.class)),
psiElement().withParent(PsiExpression.class).andNot(psiElement().withParent(PsiLiteralExpression.class)).andNot(psiElement().withParent(PsiMethodReferenceExpression.class)),
psiElement().inside(PsiClassObjectAccessExpression.class),
psiElement().inside(PsiThisExpression.class),
psiElement().inside(PsiSuperExpression.class)
@@ -19,14 +19,18 @@ import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.ExpectedTypeInfoImpl;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.psi.LambdaUtil;
import com.intellij.psi.PsiType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.Consumer;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
public class MethodReferenceCompletionProvider extends CompletionProvider<CompletionParameters> {
private static final Logger LOG = Logger.getInstance("#" + MethodReferenceCompletionProvider.class.getName());
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@@ -38,13 +42,33 @@ public class MethodReferenceCompletionProvider extends CompletionProvider<Comple
if (LambdaUtil.isFunctionalType(defaultType)) {
final PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(defaultType);
if (returnType != null) {
final PsiMethodReferenceExpression ref = (PsiMethodReferenceExpression)parameters.getPosition().getParent();
final ExpectedTypeInfoImpl typeInfo =
new ExpectedTypeInfoImpl(returnType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, returnType, TailType.UNKNOWN, null,
ExpectedTypeInfoImpl.NULL);
final Map<PsiMethodReferenceExpression, PsiType> map = PsiMethodReferenceUtil.getFunctionalTypeMap();
Consumer<LookupElement> noTypeCheck = new Consumer<LookupElement>() {
@Override
public void consume(final LookupElement lookupElement) {
result.addElement(lookupElement);
final PsiElement element = lookupElement.getPsiElement();
if (element instanceof PsiMethod) {
final PsiMethodReferenceExpression referenceExpression = (PsiMethodReferenceExpression)ref.copy();
final PsiElement referenceNameElement = referenceExpression.getReferenceNameElement();
LOG.assertTrue(referenceNameElement != null, referenceExpression);
referenceNameElement.replace(JavaPsiFacade.getElementFactory(element.getProject()).createIdentifier(((PsiMethod)element).getName()));
final PsiType added = map.put(referenceExpression, defaultType);
try {
final PsiElement resolve = referenceExpression.resolve();
if (resolve == element && PsiMethodReferenceUtil.checkMethodReferenceContext(referenceExpression, resolve, defaultType) == null) {
result.addElement(lookupElement);
}
}
finally {
if (added == null) {
map.remove(referenceExpression);
}
}
}
}
};
@@ -17,6 +17,7 @@ package com.intellij.psi;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.util.*;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -86,6 +87,16 @@ public class PsiMethodReferenceUtil {
return true;
}
@NotNull
public static Map<PsiMethodReferenceExpression, PsiType> getFunctionalTypeMap() {
Map<PsiMethodReferenceExpression, PsiType> map = ourRefs.get();
if (map == null) {
map = new HashMap<PsiMethodReferenceExpression, PsiType>();
ourRefs.set(map);
}
return map;
}
public static class QualifierResolveResult {
private final PsiClass myContainingClass;
private final PsiSubstitutor mySubstitutor;
@@ -232,6 +243,12 @@ public class PsiMethodReferenceUtil {
final PsiElement resolve = methodRef.resolve();
if (resolve == null) return null;
return checkMethodReferenceContext(methodRef, resolve, methodRef.getFunctionalInterfaceType());
}
public static String checkMethodReferenceContext(PsiMethodReferenceExpression methodRef,
PsiElement resolve,
PsiType functionalInterfaceType) {
final PsiClass containingClass = resolve instanceof PsiMethod ? ((PsiMethod)resolve).getContainingClass() : (PsiClass)resolve;
final boolean isStaticSelector = isStaticallyReferenced(methodRef);
final PsiElement qualifier = methodRef.getQualifier();
@@ -245,7 +262,7 @@ public class PsiMethodReferenceUtil {
isMethodStatic = method.hasModifierProperty(PsiModifier.STATIC);
isConstructor = method.isConstructor();
receiverReferenced = hasReceiver(methodRef, method);
receiverReferenced = hasReceiver(methodRef, method, functionalInterfaceType);
if (method.hasModifierProperty(PsiModifier.ABSTRACT) && qualifier instanceof PsiSuperExpression) {
return "Abstract method '" + method.getName() + "' cannot be accessed directly";
@@ -285,8 +302,15 @@ public class PsiMethodReferenceUtil {
return null;
}
public static boolean hasReceiver(@NotNull PsiMethodReferenceExpression methodRef, @NotNull PsiMethod method) {
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(methodRef.getFunctionalInterfaceType());
public static boolean hasReceiver(@NotNull PsiMethodReferenceExpression methodRef,
@NotNull PsiMethod method) {
return hasReceiver(methodRef, method, methodRef.getFunctionalInterfaceType());
}
private static boolean hasReceiver(@NotNull PsiMethodReferenceExpression methodRef,
@NotNull PsiMethod method,
PsiType functionalInterfaceType) {
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult);
final MethodSignature signature = interfaceMethod != null ? interfaceMethod.getSignature(LambdaUtil.getSubstitutor(interfaceMethod, resolveResult)) : null;
LOG.assertTrue(signature != null);
@@ -24,7 +24,6 @@ import com.intellij.psi.impl.source.tree.java.PsiMethodReferenceExpressionImpl;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import java.util.List;
import java.util.Map;
@@ -110,11 +109,7 @@ public class PsiMethodReferenceCompatibilityConstraint implements ConstraintForm
return true;
}
Map<PsiMethodReferenceExpression, PsiType> map = PsiMethodReferenceUtil.ourRefs.get();
if (map == null) {
map = new HashMap<PsiMethodReferenceExpression, PsiType>();
PsiMethodReferenceUtil.ourRefs.set(map);
}
final Map<PsiMethodReferenceExpression, PsiType> map = PsiMethodReferenceUtil.getFunctionalTypeMap();
final PsiType added = map.put(myExpression, groundTargetType);
final PsiElement resolve;
try {
@@ -41,7 +41,6 @@ import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase implements PsiMethodReferenceExpression {
@@ -398,12 +397,7 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase
// the result of applying capture conversion (5.1.10) to the return type of the invocation type (15.12.2.6) of the chosen declaration is R',
// where R is the target type that may be used to infer R'; neither R nor R' is void; and R' is compatible with R in an assignment context.
Map<PsiMethodReferenceExpression, PsiType> map = PsiMethodReferenceUtil.ourRefs.get();
if (map == null) {
map = new HashMap<PsiMethodReferenceExpression, PsiType>();
PsiMethodReferenceUtil.ourRefs.set(map);
}
Map<PsiMethodReferenceExpression, PsiType> map = PsiMethodReferenceUtil.getFunctionalTypeMap();
final JavaResolveResult result;
try {
if (map.put(this, left) != null) {
@@ -19,7 +19,7 @@ interface I {
public class Test {
public int m() {
I i = s -> {
return s.contains();
return s.isEmpty();
};
return 0;
}
@@ -18,7 +18,7 @@ interface I {
}
public class Test {
public int m() {
I i = s -> s.contains();
I i = s -> s.isEmpty();
return 0;
}
}
@@ -0,0 +1,14 @@
import java.util.ArrayList;
import java.util.List;
class A {
{
List<A> s = new ArrayList<>();
s.stream().filter(A::<caret>);
}
public boolean accept(String s) {
return true;
}
}
@@ -0,0 +1,18 @@
import java.util.ArrayList;
import java.util.List;
class A {
{
List<A> s = new ArrayList<>();
s.stream().filter(A::accept);
}
static <K> boolean accept(K k) {
return false;
}
public boolean accept1(String s) {
return true;
}
}
@@ -0,0 +1,18 @@
import java.util.ArrayList;
import java.util.List;
class A {
{
List<A> s = new ArrayList<>();
s.stream().filter(A::<caret>);
}
static <K> boolean accept(K k) {
return false;
}
public boolean accept1(String s) {
return true;
}
}
@@ -0,0 +1,14 @@
import java.util.ArrayList;
import java.util.List;
class A {
{
List<A> s = new ArrayList<>();
s.stream().filter(A::accept);
}
public boolean accept() {
return true;
}
}
@@ -0,0 +1,14 @@
import java.util.ArrayList;
import java.util.List;
class A {
{
List<A> s = new ArrayList<>();
s.stream().filter(A::<caret>);
}
public boolean accept() {
return true;
}
}
@@ -37,7 +37,7 @@ public class SmartType18CompletionTest extends LightFixtureCompletionTestCase {
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_LATEST;
return JAVA_8;
}
@@ -82,7 +82,21 @@ public class SmartType18CompletionTest extends LightFixtureCompletionTestCase {
}
public void testFilteredMethodReference() throws Exception {
doTest();
doTest(false);
}
public void testFilteredStaticMethods() throws Exception {
doTest(false);
}
public void testFilterWrongParamsMethods() throws Exception {
doTest(false);
}
public void testFilterAmbiguity() throws Exception {
configureByFile("/" + getTestName(false) + ".java");
assertNotNull(myItems);
assertTrue(myItems.length == 0);
}
public void testNotAvailableInLambdaPositionAfterQualifier() throws Exception {
@@ -105,17 +119,22 @@ public class SmartType18CompletionTest extends LightFixtureCompletionTestCase {
}
public void testDiamondsInsideMethodCall() throws Exception {
configureByFile("/" + getTestName(false) + ".java");
checkResultByFile("/" + getTestName(false) + "-out.java");
doTest(false);
}
private void doTest() {
doTest(true);
}
private void doTest(boolean checkItems) {
configureByFile("/" + getTestName(false) + ".java");
assertNotNull(myItems);
assertTrue(myItems.length > 0);
final Lookup lookup = getLookup();
if (lookup != null) {
selectItem(lookup.getCurrentItem(), Lookup.NORMAL_SELECT_CHAR);
if (checkItems) {
assertNotNull(myItems);
assertTrue(myItems.length > 0);
final Lookup lookup = getLookup();
if (lookup != null) {
selectItem(lookup.getCurrentItem(), Lookup.NORMAL_SELECT_CHAR);
}
}
checkResultByFile("/" + getTestName(false) + "-out.java");
}
@@ -19,6 +19,7 @@ import com.intellij.lang.Language;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ContentEntry;
import com.intellij.openapi.roots.LanguageLevelModuleExtension;
import com.intellij.openapi.roots.ModifiableRootModel;
@@ -47,6 +48,17 @@ public abstract class LightCodeInsightFixtureTestCase extends UsefulTestCase{
model.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(LanguageLevel.JDK_1_7);
}
};
public static final LightProjectDescriptor JAVA_8 = new DefaultLightProjectDescriptor() {
@Override
public Sdk getSdk() {
return IdeaTestUtil.getMockJdk18();
}
@Override
public void configureModule(Module module, ModifiableRootModel model, ContentEntry contentEntry) {
model.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(LanguageLevel.JDK_1_8);
}
};
public static final LightProjectDescriptor JAVA_LATEST = new DefaultLightProjectDescriptor();