From 80ad8e9ebdfbd00866ba7a1b8a8574a1e810dd78 Mon Sep 17 00:00:00 2001 From: peter Date: Sun, 1 May 2016 21:45:56 +0200 Subject: [PATCH] IDEA-154276 Suggest method references to constructors of implementing classes when Supplier is expected --- ...unctionalExpressionCompletionProvider.java | 109 +++++++++++------- .../completion/JavaCompletionContributor.java | 6 +- .../InheritorConstructorRef-out.java | 7 ++ .../smartType/InheritorConstructorRef.java | 5 + .../normal/InheritorConstructorRef.java | 5 + .../completion/Normal8CompletionTest.groovy | 7 +- .../completion/SmartType18CompletionTest.java | 13 +++ 7 files changed, 106 insertions(+), 46 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/completion/smartType/InheritorConstructorRef-out.java create mode 100644 java/java-tests/testData/codeInsight/completion/smartType/InheritorConstructorRef.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/InheritorConstructorRef.java diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/FunctionalExpressionCompletionProvider.java b/java/java-impl/src/com/intellij/codeInsight/completion/FunctionalExpressionCompletionProvider.java index 0f7903bdaf14..b5301c510afe 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/FunctionalExpressionCompletionProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/FunctionalExpressionCompletionProvider.java @@ -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 { + private static final InsertHandler 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 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 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 result.addElement(smart ? JavaSmartCompletionContributor.decorate(element, Arrays.asList(expectedTypes)) : element)); } } } - return result; } - private static List getMethodReferenceVariants(boolean smart, - PsiType functionalInterfaceType, - PsiMethod functionalInterfaceMethod, - PsiParameter[] params, - PsiElement originalPosition, - PsiSubstitutor substitutor) { - List 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 result) { 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)); - } + if (expectedReturnType == null) return; + + if (params.length > 0) { + for (LookupElement element : collectVariantsByReceiver(!smart, functionalInterfaceType, params, originalPosition, substitutor, expectedReturnType)) { + result.consume(element); } } - return result; + for (LookupElement element : collectThisVariants(functionalInterfaceType, params, originalPosition, substitutor, expectedReturnType)) { + result.consume(element); + } + + Consumer 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)); + } + } + 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); + } } - 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); } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java index 295a9cdf2992..fbac2bb0e9bc 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionContributor.java @@ -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 index 000000000000..f53cf9cb4c90 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/smartType/InheritorConstructorRef-out.java @@ -0,0 +1,7 @@ +import foo.ImplBar; + +class Test { + void foo() { + java.util.function.Supplier> s = ImplBar::new; + } +} \ 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 index 000000000000..2556c5c4d613 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/smartType/InheritorConstructorRef.java @@ -0,0 +1,5 @@ +class Test { + void foo() { + java.util.function.Supplier> s = + } +} \ 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 index 000000000000..ce873b902883 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/lambda/completion/normal/InheritorConstructorRef.java @@ -0,0 +1,5 @@ +class Foo { + { + java.util.function.Supplier> s = ArrayL + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/Normal8CompletionTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/Normal8CompletionTest.groovy index 7cf3edae78e0..59d79b00073b 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/Normal8CompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/Normal8CompletionTest.groovy @@ -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 { diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/SmartType18CompletionTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/completion/SmartType18CompletionTest.java index 94a459998e09..1e1e4eca0a0f 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/SmartType18CompletionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/SmartType18CompletionTest.java @@ -85,6 +85,19 @@ public class SmartType18CompletionTest extends LightFixtureCompletionTestCase { doTest(false); } + public void testInheritorConstructorRef() { + myFixture.addClass("package intf; public interface Intf {}"); + myFixture.addClass("package foo; public class ImplBar implements intf.Intf {}"); + myFixture.addClass("package foo; public class ImplFoo implements intf.Intf {}"); + myFixture.addClass("package foo; public class ImplIncompatible implements intf.Intf {}"); + myFixture.addClass("package foo; class ImplInaccessible implements intf.Intf {}"); + + configureByTestName(); + myFixture.assertPreferredCompletionItems(0, "ImplBar::new", "ImplFoo::new", "()"); + myFixture.type('\n'); + checkResultByFile("/" + getTestName(false) + "-out.java"); + } + public void testFilteredMethodReference() throws Exception { doTest(false); }