IDEA-155383 Auto-complete for method references should use the type where the method...
authorpeter <peter@jetbrains.com>
Fri, 29 Apr 2016 17:06:55 +0000 (19:06 +0200)
committerpeter <peter@jetbrains.com>
Fri, 29 Apr 2016 20:56:18 +0000 (22:56 +0200)
java/java-impl/src/com/intellij/codeInsight/completion/FunctionalExpressionCompletionProvider.java
java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java
java/java-tests/testData/codeInsight/completion/smartType/MethodReferenceOnAncestor-out.java [new file with mode: 0644]
java/java-tests/testData/codeInsight/completion/smartType/MethodReferenceOnAncestor.java [new file with mode: 0644]
java/java-tests/testSrc/com/intellij/codeInsight/completion/SmartType18CompletionTest.java

index 5ad4543848c3cbb65a06ce0da37ffdaf97a5c3b9..0f7903bdaf147dc017687595677d0b8861d5cb11 100644 (file)
@@ -21,14 +21,10 @@ import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
 import com.intellij.codeInsight.lookup.LookupElement;
 import com.intellij.codeInsight.lookup.LookupElementBuilder;
 import com.intellij.icons.AllIcons;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.EditorModificationUtil;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Comparing;
 import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.patterns.ElementPattern;
-import com.intellij.patterns.PatternCondition;
 import com.intellij.psi.*;
 import com.intellij.psi.codeStyle.CodeStyleManager;
 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
@@ -37,27 +33,17 @@ import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfacePa
 import com.intellij.psi.util.PsiTreeUtil;
 import com.intellij.psi.util.PsiUtil;
 import com.intellij.psi.util.TypeConversionUtil;
-import com.intellij.util.Function;
+import com.intellij.util.ObjectUtils;
 import com.intellij.util.ProcessingContext;
 import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import static com.intellij.patterns.PlatformPatterns.psiElement;
+import java.util.*;
 
 /**
  * User: anna
  */
 public class FunctionalExpressionCompletionProvider extends CompletionProvider<CompletionParameters> {
-  static final ElementPattern<PsiElement> LAMBDA = psiElement().with(new PatternCondition<PsiElement>("LAMBDA_CONTEXT") {
-    @Override
-    public boolean accepts(@NotNull PsiElement element, ProcessingContext context) {
-      return isLambdaContext(element);
-    }});
 
   private static boolean isLambdaContext(@NotNull PsiElement element) {
     final PsiElement rulezzRef = element.getParent();
@@ -71,20 +57,20 @@ public class FunctionalExpressionCompletionProvider extends CompletionProvider<C
   protected void addCompletions(@NotNull CompletionParameters parameters,
                                 ProcessingContext context,
                                 @NotNull CompletionResultSet result) {
-    result.addAllElements(getLambdaVariants(parameters, false));
+    result.addAllElements(getLambdaVariants(parameters, true));
   }
 
-  static List<LookupElement> getLambdaVariants(@NotNull CompletionParameters parameters, boolean prioritize) {
+  static List<LookupElement> getLambdaVariants(@NotNull CompletionParameters parameters, boolean smart) {
     if (!PsiUtil.isLanguageLevel8OrHigher(parameters.getOriginalFile()) || !isLambdaContext(parameters.getPosition())) return Collections.emptyList();
 
     List<LookupElement> result = ContainerUtil.newArrayList();
-    for (ExpectedTypeInfo expectedType : JavaSmartCompletionContributor.getExpectedTypes(parameters)) {
+    ExpectedTypeInfo[] expectedTypes = JavaSmartCompletionContributor.getExpectedTypes(parameters);
+    for (ExpectedTypeInfo expectedType : expectedTypes) {
       final PsiType defaultType = expectedType.getDefaultType();
       if (LambdaUtil.isFunctionalType(defaultType)) {
         final PsiType functionalInterfaceType = FunctionalInterfaceParameterizationUtil.getGroundTargetType(defaultType);
         final PsiMethod functionalInterfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
         if (functionalInterfaceMethod != null) {
-          assert functionalInterfaceType != null;
           PsiParameter[] params = new PsiParameter[0];
           final PsiElement originalPosition = parameters.getPosition();
           final PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(functionalInterfaceMethod, PsiUtil.resolveGenericsClassInType(functionalInterfaceType));
@@ -98,12 +84,8 @@ public class FunctionalExpressionCompletionProvider extends CompletionProvider<C
             }
 
             String paramsString =
-              params.length == 1 ? getParamName(params[0], javaCodeStyleManager, originalPosition) : "(" + StringUtil.join(params, new Function<PsiParameter, String>() {
-              @Override
-              public String fun(PsiParameter parameter) {
-                return getParamName(parameter, javaCodeStyleManager, originalPosition);
-              }
-              }, ",") + ")";
+              params.length == 1 ? getParamName(params[0], originalPosition)
+                                 : "(" + StringUtil.join(params, parameter -> getParamName(parameter, originalPosition), ",") + ")";
 
             final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
             PsiLambdaExpression lambdaExpression = (PsiLambdaExpression)JavaPsiFacade.getElementFactory(project)
@@ -112,46 +94,54 @@ public class FunctionalExpressionCompletionProvider extends CompletionProvider<C
             paramsString = lambdaExpression.getParameterList().getText();
             final LookupElementBuilder builder =
               LookupElementBuilder.create(functionalInterfaceMethod, paramsString)
-                .withPresentableText(paramsString + " -> {}").withInsertHandler(new InsertHandler<LookupElement>() {
-                @Override
-                public void handleInsert(InsertionContext context, LookupElement item) {
-                  final Editor editor = context.getEditor();
-                  EditorModificationUtil.insertStringAtCaret(editor, " -> ");
-                }
-              })
+                .withPresentableText(paramsString + " -> {}")
+                .withInsertHandler((context, item) -> EditorModificationUtil.insertStringAtCaret(context.getEditor(), " -> "))
                 .withTypeText(functionalInterfaceType.getPresentableText())
                 .withIcon(AllIcons.Nodes.Function);
             LookupElement lambdaElement = builder.withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
-            if (prioritize) {
+            if (!smart) {
               lambdaElement = PrioritizedLookupElement.withPriority(lambdaElement, 1);
             }
             result.add(lambdaElement);
           }
 
-          final PsiType expectedReturnType = substitutor.substitute(functionalInterfaceMethod.getReturnType());
-          if (expectedReturnType != null) {
-            if (params.length > 0) {
-              collectVariantsByReceiver(prioritize, functionalInterfaceType, params, originalPosition, substitutor, expectedReturnType, result);
-            }
-            collectThisVariants(functionalInterfaceType, params, originalPosition, substitutor, expectedReturnType, result);
-            final PsiClass psiClass = PsiUtil.resolveClassInType(expectedReturnType);
-            if (psiClass != null && !(psiClass instanceof PsiTypeParameter)) {
-              if (expectedReturnType.getArrayDimensions() == 0) {
-                final PsiMethod[] constructors = psiClass.getConstructors();
-                for (PsiMethod psiMethod : constructors) {
-                  if (areParameterTypesAppropriate(psiMethod, params, substitutor, 0)) {
-                    result.add(createConstructorReferenceLookup(functionalInterfaceType, expectedReturnType));
-                  }
-                }
-                if (constructors.length == 0 && params.length == 0) {
-                  result.add(createConstructorReferenceLookup(functionalInterfaceType, expectedReturnType));
-                }
-              }
-              else if (params.length == 1 && PsiType.INT.equals(params[0].getType())){
-                result.add(createConstructorReferenceLookup(functionalInterfaceType, expectedReturnType));
-              }
+          for (LookupElement element : getMethodReferenceVariants(smart, functionalInterfaceType, functionalInterfaceMethod, params, originalPosition, substitutor)) {
+            result.add(smart ? JavaSmartCompletionContributor.decorate(element, Arrays.asList(expectedTypes)) : element);
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  private static List<LookupElement> getMethodReferenceVariants(boolean smart,
+                                                                PsiType functionalInterfaceType,
+                                                                PsiMethod functionalInterfaceMethod,
+                                                                PsiParameter[] params,
+                                                                PsiElement originalPosition,
+                                                                PsiSubstitutor substitutor) {
+    List<LookupElement> result = new ArrayList<>();
+    final PsiType expectedReturnType = substitutor.substitute(functionalInterfaceMethod.getReturnType());
+    if (expectedReturnType != null) {
+      if (params.length > 0) {
+        result.addAll(collectVariantsByReceiver(!smart, functionalInterfaceType, params, originalPosition, substitutor, expectedReturnType));
+      }
+      result.addAll(collectThisVariants(functionalInterfaceType, params, originalPosition, substitutor, expectedReturnType));
+      final PsiClass psiClass = PsiUtil.resolveClassInType(expectedReturnType);
+      if (psiClass != null && !(psiClass instanceof PsiTypeParameter)) {
+        if (expectedReturnType.getArrayDimensions() == 0) {
+          final PsiMethod[] constructors = psiClass.getConstructors();
+          for (PsiMethod psiMethod : constructors) {
+            if (areParameterTypesAppropriate(psiMethod, params, substitutor, 0)) {
+              result.add(createConstructorReferenceLookup(functionalInterfaceType, expectedReturnType));
             }
           }
+          if (constructors.length == 0 && params.length == 0) {
+            result.add(createConstructorReferenceLookup(functionalInterfaceType, expectedReturnType));
+          }
+        }
+        else if (params.length == 1 && PsiType.INT.equals(params[0].getType())){
+          result.add(createConstructorReferenceLookup(functionalInterfaceType, expectedReturnType));
         }
       }
     }
@@ -166,10 +156,11 @@ public class FunctionalExpressionCompletionProvider extends CompletionProvider<C
                       .withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
   }
 
-  private static void collectThisVariants(PsiType functionalInterfaceType,
-                                          PsiParameter[] params,
-                                          PsiElement originalPosition,
-                                          PsiSubstitutor substitutor, PsiType expectedReturnType, List<LookupElement> result) {
+  private static List<LookupElement> collectThisVariants(PsiType functionalInterfaceType,
+                                                         PsiParameter[] params,
+                                                         PsiElement originalPosition,
+                                                         PsiSubstitutor substitutor, PsiType expectedReturnType) {
+    List<LookupElement> result = new ArrayList<>();
     final PsiClass psiClass = PsiTreeUtil.getParentOfType(originalPosition, PsiClass.class);
     if (psiClass != null) {
       for (PsiMethod psiMethod : psiClass.getMethods()) {
@@ -179,14 +170,7 @@ public class FunctionalExpressionCompletionProvider extends CompletionProvider<C
           LookupElement methodRefLookupElement = LookupElementBuilder
             .create(psiMethod)
             .withPresentableText("this::" + psiMethod.getName())
-            .withInsertHandler(new InsertHandler<LookupElement>() {
-              @Override
-              public void handleInsert(InsertionContext context, LookupElement item) {
-                final int startOffset = context.getStartOffset();
-                final Document document = context.getDocument();
-                document.insertString(startOffset, "this::");
-              }
-            })
+            .withInsertHandler((context, item) -> context.getDocument().insertString(context.getStartOffset(), "this::"))
             .withTypeText(functionalInterfaceType.getPresentableText())
             .withIcon(AllIcons.Nodes.MethodReference)
             .withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
@@ -194,48 +178,47 @@ public class FunctionalExpressionCompletionProvider extends CompletionProvider<C
         }
       }
     }
+    return result;
   }
 
-  private static void collectVariantsByReceiver(boolean prioritize,
-                                                PsiType functionalInterfaceType,
-                                                PsiParameter[] params,
-                                                PsiElement originalPosition,
-                                                PsiSubstitutor substitutor, 
-                                                PsiType expectedReturnType,
-                                                List<LookupElement> result) {
+  private static List<LookupElement> collectVariantsByReceiver(boolean prioritize,
+                                                               PsiType functionalInterfaceType,
+                                                               PsiParameter[] params,
+                                                               PsiElement originalPosition,
+                                                               PsiSubstitutor substitutor,
+                                                               PsiType expectedReturnType) {
+    List<LookupElement> result = new ArrayList<>();
     final PsiType functionalInterfaceParamType = substitutor.substitute(params[0].getType());
     final PsiClass paramClass = PsiUtil.resolveClassInClassTypeOnly(functionalInterfaceParamType);
     if (paramClass != null && !paramClass.hasTypeParameters()) {
       final Set<String> visited = new HashSet<String>();
       for (PsiMethod psiMethod : paramClass.getAllMethods()) {
         final PsiType returnType = psiMethod.getReturnType();
+        PsiClass containingClass = psiMethod.getContainingClass();
+        PsiClass qualifierClass = containingClass != null ? containingClass : paramClass;
         if (visited.add(psiMethod.getName()) &&
             isInstanceMethodWithAppropriateReturnType(expectedReturnType, psiMethod, returnType) &&
             areParameterTypesAppropriate(psiMethod, params, substitutor, 1) &&
             JavaResolveUtil.isAccessible(psiMethod, null, psiMethod.getModifierList(), originalPosition, null, null)) {
           LookupElement methodRefLookupElement = LookupElementBuilder
             .create(psiMethod)
-            .withPresentableText(paramClass.getName() + "::" + psiMethod.getName())
-            .withInsertHandler(new InsertHandler<LookupElement>() {
-              @Override
-              public void handleInsert(InsertionContext context, LookupElement item) {
-                final int startOffset = context.getStartOffset();
-                final Document document = context.getDocument();
-                final PsiFile file = context.getFile();
-                document.insertString(startOffset, "::");
-                JavaCompletionUtil.insertClassReference(paramClass, file, startOffset);
-              }
+            .withPresentableText(qualifierClass.getName() + "::" + psiMethod.getName())
+            .withInsertHandler((context, item) -> {
+              int startOffset = context.getStartOffset();
+              context.getDocument().insertString(startOffset, "::");
+              JavaCompletionUtil.insertClassReference(qualifierClass, context.getFile(), startOffset);
             })
             .withTypeText(functionalInterfaceType.getPresentableText())
             .withIcon(AllIcons.Nodes.MethodReference)
             .withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
-          if (prioritize && psiMethod.getContainingClass() == paramClass) {
+          if (prioritize && containingClass == paramClass) {
             methodRefLookupElement = PrioritizedLookupElement.withPriority(methodRefLookupElement, 1);
           }
           result.add(methodRefLookupElement);
         }
       }
     }
+    return result;
   }
 
   private static boolean isInstanceMethodWithAppropriateReturnType(PsiType expectedReturnType, PsiMethod psiMethod, PsiType returnType) {
@@ -258,7 +241,8 @@ public class FunctionalExpressionCompletionProvider extends CompletionProvider<C
     return false;
   }
 
-  private static String getParamName(PsiParameter param, JavaCodeStyleManager javaCodeStyleManager, PsiElement originalPosition) {
-    return javaCodeStyleManager.suggestUniqueVariableName(param.getName(), originalPosition, false);
+  private static String getParamName(PsiParameter param, PsiElement originalPosition) {
+    return JavaCodeStyleManager.getInstance(originalPosition.getProject()).suggestUniqueVariableName(
+      ObjectUtils.assertNotNull(param.getName()), originalPosition, false);
   }
 }
index 24caed65aabd2ab64f1cb67998904769600e9eec..295a9cdf2992a52acbba086943f248c69cd7c121 100644 (file)
@@ -264,7 +264,7 @@ public class JavaCompletionContributor extends CompletionContributor {
       new TypeArgumentCompletionProvider(false, session).addCompletions(parameters, new ProcessingContext(), result);
     }
 
-    result.addAllElements(FunctionalExpressionCompletionProvider.getLambdaVariants(parameters, true));
+    result.addAllElements(FunctionalExpressionCompletionProvider.getLambdaVariants(parameters, false));
 
     if (JavaSmartCompletionContributor.AFTER_NEW.accepts(position)) {
       new JavaInheritorsGetter(ConstructorInsertHandler.BASIC_INSTANCE).generateVariants(parameters, matcher, session);
diff --git a/java/java-tests/testData/codeInsight/completion/smartType/MethodReferenceOnAncestor-out.java b/java/java-tests/testData/codeInsight/completion/smartType/MethodReferenceOnAncestor-out.java
new file mode 100644 (file)
index 0000000..df8ad20
--- /dev/null
@@ -0,0 +1,16 @@
+import java.util.*;
+
+class MyTest {
+
+  public interface Parent {
+    boolean hasFlag();
+  }
+
+  public interface Child extends Parent {}
+
+  public static void main(String[] args) {
+    List<Child> children = new ArrayList<>();
+    children.stream().filter(Parent::hasFlag)<caret>
+  }
+
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/completion/smartType/MethodReferenceOnAncestor.java b/java/java-tests/testData/codeInsight/completion/smartType/MethodReferenceOnAncestor.java
new file mode 100644 (file)
index 0000000..780cad9
--- /dev/null
@@ -0,0 +1,16 @@
+import java.util.*;
+
+class MyTest {
+
+  public interface Parent {
+    boolean hasFlag();
+  }
+
+  public interface Child extends Parent {}
+
+  public static void main(String[] args) {
+    List<Child> children = new ArrayList<>();
+    children.stream().filter(hasF<caret>)
+  }
+
+}
\ No newline at end of file
index 6ff7aa17c75d69d59a456fe402b81b2f1a98c2b4..94a459998e09b39193053551115bb3e5f9a31cba 100644 (file)
@@ -127,6 +127,8 @@ public class SmartType18CompletionTest extends LightFixtureCompletionTestCase {
     doTest(true);
   }
 
+  public void testMethodReferenceOnAncestor() { doTest(true); }
+
   public void testNoLambdaSuggestionForGenericsFunctionalInterfaceMethod() throws Exception {
     configureByFile("/" + getTestName(false) + ".java");
     assertEmpty(myItems);