IDEA-154276 Suggest method references to constructors of implementing classes when...
authorpeter <peter@jetbrains.com>
Sun, 1 May 2016 19:45:56 +0000 (21:45 +0200)
committerpeter <peter@jetbrains.com>
Mon, 2 May 2016 06:58:57 +0000 (08:58 +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/InheritorConstructorRef-out.java [new file with mode: 0644]
java/java-tests/testData/codeInsight/completion/smartType/InheritorConstructorRef.java [new file with mode: 0644]
java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/InheritorConstructorRef.java [new file with mode: 0644]
java/java-tests/testSrc/com/intellij/codeInsight/completion/Normal8CompletionTest.groovy
java/java-tests/testSrc/com/intellij/codeInsight/completion/SmartType18CompletionTest.java

index 0f7903bdaf147dc017687595677d0b8861d5cb11..b5301c510afe377e685e6452b6e2a2111f25d3f4 100644 (file)
@@ -33,9 +33,9 @@ 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.Consumer;
 import com.intellij.util.ObjectUtils;
 import com.intellij.util.ProcessingContext;
-import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.*;
@@ -45,6 +45,15 @@ import java.util.*;
  */
 public class FunctionalExpressionCompletionProvider extends CompletionProvider<CompletionParameters> {
 
+  private static final InsertHandler<LookupElement> CONSTRUCTOR_REF_INSERT_HANDLER = (context, item) -> {
+    int start = context.getStartOffset();
+    PsiClass psiClass = PsiUtil.resolveClassInType((PsiType)item.getObject());
+    if (psiClass != null) {
+      JavaCompletionUtil.insertClassReference(psiClass, context.getFile(), start,
+                                              start + StringUtil.trimEnd(item.getLookupString(), "::new").length());
+    }
+  };
+
   private static boolean isLambdaContext(@NotNull PsiElement element) {
     final PsiElement rulezzRef = element.getParent();
     return rulezzRef != null &&
@@ -57,13 +66,12 @@ public class FunctionalExpressionCompletionProvider extends CompletionProvider<C
   protected void addCompletions(@NotNull CompletionParameters parameters,
                                 ProcessingContext context,
                                 @NotNull CompletionResultSet result) {
-    result.addAllElements(getLambdaVariants(parameters, true));
+    addFunctionalVariants(parameters, true, true, result);
   }
 
-  static List<LookupElement> getLambdaVariants(@NotNull CompletionParameters parameters, boolean smart) {
-    if (!PsiUtil.isLanguageLevel8OrHigher(parameters.getOriginalFile()) || !isLambdaContext(parameters.getPosition())) return Collections.emptyList();
+  static void addFunctionalVariants(@NotNull CompletionParameters parameters, boolean smart, boolean addInheritors, CompletionResultSet result) {
+    if (!PsiUtil.isLanguageLevel8OrHigher(parameters.getOriginalFile()) || !isLambdaContext(parameters.getPosition())) return;
 
-    List<LookupElement> result = ContainerUtil.newArrayList();
     ExpectedTypeInfo[] expectedTypes = JavaSmartCompletionContributor.getExpectedTypes(parameters);
     for (ExpectedTypeInfo expectedType : expectedTypes) {
       final PsiType defaultType = expectedType.getDefaultType();
@@ -99,60 +107,73 @@ public class FunctionalExpressionCompletionProvider extends CompletionProvider<C
                 .withTypeText(functionalInterfaceType.getPresentableText())
                 .withIcon(AllIcons.Nodes.Function);
             LookupElement lambdaElement = builder.withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
-            if (!smart) {
-              lambdaElement = PrioritizedLookupElement.withPriority(lambdaElement, 1);
-            }
-            result.add(lambdaElement);
+            result.addElement(smart ? lambdaElement : PrioritizedLookupElement.withPriority(lambdaElement, 1));
           }
 
-          for (LookupElement element : getMethodReferenceVariants(smart, functionalInterfaceType, functionalInterfaceMethod, params, originalPosition, substitutor)) {
-            result.add(smart ? JavaSmartCompletionContributor.decorate(element, Arrays.asList(expectedTypes)) : element);
-          }
+          addMethodReferenceVariants(
+            smart, addInheritors, parameters, result.getPrefixMatcher(), functionalInterfaceType, functionalInterfaceMethod, params, originalPosition, substitutor,
+            element -> result.addElement(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<>();
+  private static void addMethodReferenceVariants(boolean smart,
+                                                 boolean addInheritors,
+                                                 CompletionParameters parameters,
+                                                 PrefixMatcher matcher,
+                                                 PsiType functionalInterfaceType,
+                                                 PsiMethod functionalInterfaceMethod,
+                                                 PsiParameter[] params,
+                                                 PsiElement originalPosition,
+                                                 PsiSubstitutor substitutor,
+                                                 Consumer<LookupElement> result) {
     final PsiType expectedReturnType = substitutor.substitute(functionalInterfaceMethod.getReturnType());
-    if (expectedReturnType != null) {
-      if (params.length > 0) {
-        result.addAll(collectVariantsByReceiver(!smart, functionalInterfaceType, params, originalPosition, substitutor, expectedReturnType));
+    if (expectedReturnType == null) return;
+
+    if (params.length > 0) {
+      for (LookupElement element : collectVariantsByReceiver(!smart, functionalInterfaceType, params, originalPosition, substitutor, expectedReturnType)) {
+        result.consume(element);
       }
-      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));
+    }
+    for (LookupElement element : collectThisVariants(functionalInterfaceType, params, originalPosition, substitutor, expectedReturnType)) {
+      result.consume(element);
+    }
+
+    Consumer<PsiType> consumer = eachReturnType -> {
+      PsiClass psiClass = PsiUtil.resolveClassInType(eachReturnType);
+      if (psiClass == null || psiClass instanceof PsiTypeParameter) return;
+
+      if (eachReturnType.getArrayDimensions() == 0) {
+        PsiMethod[] constructors = psiClass.getConstructors();
+        for (PsiMethod psiMethod : constructors) {
+          if (areParameterTypesAppropriate(psiMethod, params, substitutor, 0)) {
+            result.consume(createConstructorReferenceLookup(functionalInterfaceType, eachReturnType));
           }
         }
-        else if (params.length == 1 && PsiType.INT.equals(params[0].getType())){
-          result.add(createConstructorReferenceLookup(functionalInterfaceType, expectedReturnType));
+        if (constructors.length == 0 && params.length == 0 && !psiClass.isInterface() && !psiClass.isEnum()) {
+          result.consume(createConstructorReferenceLookup(functionalInterfaceType, eachReturnType));
         }
       }
+      else if (params.length == 1 && PsiType.INT.equals(params[0].getType())) {
+        result.consume(createConstructorReferenceLookup(functionalInterfaceType, eachReturnType));
+      }
+    };
+    if (addInheritors && expectedReturnType instanceof PsiClassType) {
+      JavaInheritorsGetter.processInheritors(parameters, Collections.singletonList((PsiClassType)expectedReturnType), matcher, consumer);
+    } else {
+      consumer.consume(expectedReturnType);
     }
-    return result;
   }
 
-  private static LookupElement createConstructorReferenceLookup(PsiType functionalInterfaceType, PsiType expectedReturnType) {
+  private static LookupElement createConstructorReferenceLookup(@NotNull PsiType functionalInterfaceType, 
+                                                                @NotNull PsiType constructedType) {
+    constructedType = TypeConversionUtil.erasure(constructedType);
     return LookupElementBuilder
-                      .create(expectedReturnType.getPresentableText() + "::new")
+                      .create(constructedType, constructedType.getPresentableText() + "::new")
                       .withTypeText(functionalInterfaceType.getPresentableText())
                       .withIcon(AllIcons.Nodes.MethodReference)
+                      .withInsertHandler(CONSTRUCTOR_REF_INSERT_HANDLER)
                       .withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
   }
 
index 295a9cdf2992a52acbba086943f248c69cd7c121..fbac2bb0e9bc719fd600ab41bc50f4602c854a0d 100644 (file)
@@ -246,6 +246,10 @@ public class JavaCompletionContributor extends CompletionContributor {
 
     addAllClasses(parameters, result, session);
 
+    if (position instanceof PsiIdentifier) {
+      FunctionalExpressionCompletionProvider.addFunctionalVariants(parameters, false, true, result);
+    }
+
     if (position instanceof PsiIdentifier &&
         parent instanceof PsiReferenceExpression &&
         !((PsiReferenceExpression)parent).isQualified() &&
@@ -264,7 +268,7 @@ public class JavaCompletionContributor extends CompletionContributor {
       new TypeArgumentCompletionProvider(false, session).addCompletions(parameters, new ProcessingContext(), result);
     }
 
-    result.addAllElements(FunctionalExpressionCompletionProvider.getLambdaVariants(parameters, false));
+    FunctionalExpressionCompletionProvider.addFunctionalVariants(parameters, false, false, result);
 
     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/InheritorConstructorRef-out.java b/java/java-tests/testData/codeInsight/completion/smartType/InheritorConstructorRef-out.java
new file mode 100644 (file)
index 0000000..f53cf9c
--- /dev/null
@@ -0,0 +1,7 @@
+import foo.ImplBar;
+
+class Test {
+  void foo() {
+    java.util.function.Supplier<intf.Intf<String>> s = ImplBar::new;<caret>
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/completion/smartType/InheritorConstructorRef.java b/java/java-tests/testData/codeInsight/completion/smartType/InheritorConstructorRef.java
new file mode 100644 (file)
index 0000000..2556c5c
--- /dev/null
@@ -0,0 +1,5 @@
+class Test {
+  void foo() {
+    java.util.function.Supplier<intf.Intf<String>> s = <caret>
+  }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/InheritorConstructorRef.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/InheritorConstructorRef.java
new file mode 100644 (file)
index 0000000..ce873b9
--- /dev/null
@@ -0,0 +1,5 @@
+class Foo {
+  {
+    java.util.function.Supplier<java.util.List<String>> s = ArrayL<caret>
+  }
+}
\ No newline at end of file
index 7cf3edae78e018cc2a1bb9dc6a34836779727686..59d79b00073bf2f7b5d58b2f9b0bd0bb93253c6e 100644 (file)
@@ -174,7 +174,12 @@ class Test88 {
 }
 """
   }
-  
+
+  public void testInheritorConstructorRef() {
+    configureByTestName()
+    myFixture.assertPreferredCompletionItems 0, 'ArrayList::new', 'ArrayList'
+  }
+
   public void "test constructor ref without start"() {
     myFixture.configureByText "a.java", """
 interface Foo9 {
index 94a459998e09b39193053551115bb3e5f9a31cba..1e1e4eca0a0fc168198f07b571c95f940196da1a 100644 (file)
@@ -85,6 +85,19 @@ public class SmartType18CompletionTest extends LightFixtureCompletionTestCase {
     doTest(false);
   }
 
+  public void testInheritorConstructorRef() {
+    myFixture.addClass("package intf; public interface Intf<T> {}");
+    myFixture.addClass("package foo; public class ImplBar implements intf.Intf<String> {}");
+    myFixture.addClass("package foo; public class ImplFoo<T> implements intf.Intf<T> {}");
+    myFixture.addClass("package foo; public class ImplIncompatible implements intf.Intf<Integer> {}");
+    myFixture.addClass("package foo; class ImplInaccessible implements intf.Intf<String> {}");
+
+    configureByTestName();
+    myFixture.assertPreferredCompletionItems(0, "ImplBar::new", "ImplFoo::new", "()");
+    myFixture.type('\n');
+    checkResultByFile("/" + getTestName(false) + "-out.java");
+  }
+
   public void testFilteredMethodReference() throws Exception {
     doTest(false);
   }