From 6b1ecc3fb0e6925a95b6e9b7ea04a05c6140c257 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Fri, 11 Apr 2014 22:07:39 +0400 Subject: [PATCH] Smart completion: "::functionName" items when value of function type is expected --- .../plugin/completion/CompletionSession.java | 29 ++++--- .../jet/plugin/completion/SmartCompletion.kt | 84 +++++++++++++++---- .../handlers/smart/FunctionReference.kt | 9 ++ .../handlers/smart/FunctionReference.kt.after | 9 ++ idea/testData/completion/smart/AnyExpected.kt | 9 ++ .../completion/smart/FunctionExpected1.kt | 14 ++++ .../smart/FunctionExpected2.kt.todo | 10 +++ .../completion/smart/FunctionExpected3.kt | 11 +++ .../completion/smart/FunctionExpected4.kt | 11 +++ .../completion/smart/FunctionExpected5.kt | 9 ++ .../completion/smart/FunctionExpected6.kt | 7 ++ .../completion/smart/FunctionExpected7.kt | 9 ++ .../smart/FunctionExpected8.kt.todo | 11 +++ .../completion/smart/FunctionExpected9.kt | 9 ++ .../JvmSmartCompletionTestGenerated.java | 40 +++++++++ .../SmartCompletionHandlerTestGenerated.java | 5 ++ 16 files changed, 252 insertions(+), 24 deletions(-) create mode 100644 idea/testData/completion/handlers/smart/FunctionReference.kt create mode 100644 idea/testData/completion/handlers/smart/FunctionReference.kt.after create mode 100644 idea/testData/completion/smart/AnyExpected.kt create mode 100644 idea/testData/completion/smart/FunctionExpected1.kt create mode 100644 idea/testData/completion/smart/FunctionExpected2.kt.todo create mode 100644 idea/testData/completion/smart/FunctionExpected3.kt create mode 100644 idea/testData/completion/smart/FunctionExpected4.kt create mode 100644 idea/testData/completion/smart/FunctionExpected5.kt create mode 100644 idea/testData/completion/smart/FunctionExpected6.kt create mode 100644 idea/testData/completion/smart/FunctionExpected7.kt create mode 100644 idea/testData/completion/smart/FunctionExpected8.kt.todo create mode 100644 idea/testData/completion/smart/FunctionExpected9.kt diff --git a/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java b/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java index 6298b3badc1..c0ad01c3140 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java +++ b/idea/src/org/jetbrains/jet/plugin/completion/CompletionSession.java @@ -43,6 +43,7 @@ import org.jetbrains.jet.plugin.project.ResolveSessionForBodies; import org.jetbrains.jet.plugin.references.JetSimpleNameReference; import java.util.Collection; +import java.util.Collections; class CompletionSession { @Nullable @@ -133,12 +134,17 @@ class CompletionSession { public void completeSmart() { assert parameters.getCompletionType() == CompletionType.SMART; - final SmartCompletionData data = CompletionPackage.buildSmartCompletionData(jetReference.getExpression(), getResolveSession()); + final SmartCompletionData data = CompletionPackage.buildSmartCompletionData(jetReference.getExpression(), getResolveSession(), new Function1() { + @Override + public Boolean invoke(DeclarationDescriptor descriptor) { + return isVisibleDescriptor(descriptor); + } + }); if (data != null) { - addReferenceVariants(new Function1() { + addReferenceVariants(new Function1>(){ @Override - public LookupElement invoke(DeclarationDescriptor descriptor) { - return data.toElement(descriptor); + public Iterable invoke(DeclarationDescriptor descriptor) { + return data.toElements(descriptor); } }); for (LookupElement element : data.getAdditionalElements()) { @@ -242,22 +248,25 @@ class CompletionSession { } private void addReferenceVariants(@NotNull final Condition filterCondition) { - addReferenceVariants(new Function1() { + addReferenceVariants(new Function1>(){ @Override - public LookupElement invoke(DeclarationDescriptor descriptor) { - return filterCondition.value(descriptor) ? DescriptorLookupConverter.createLookupElement(getResolveSession(), getExpressionBindingContext(), descriptor) : null; + public Iterable invoke(DeclarationDescriptor descriptor) { + return filterCondition.value(descriptor) + ? Collections.singletonList( + DescriptorLookupConverter.createLookupElement(getResolveSession(), getExpressionBindingContext(), descriptor)) + : Collections.emptyList(); } }); } - private void addReferenceVariants(@NotNull Function1 filter) { + private void addReferenceVariants(@NotNull Function1> filter) { Collection descriptors = TipsManager.getReferenceVariants( jetReference.getExpression(), getExpressionBindingContext()); for (DeclarationDescriptor descriptor : descriptors) { if (descriptor != null && descriptorFilter.value(descriptor)) { - LookupElement element = filter.invoke(descriptor); - if (element != null) { + Iterable elements = filter.invoke(descriptor); + for (LookupElement element : elements) { jetResult.addElement(element); } } diff --git a/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt b/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt index 406878eacf1..3927afd51c0 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt +++ b/idea/src/org/jetbrains/jet/plugin/completion/SmartCompletion.kt @@ -35,7 +35,7 @@ import com.intellij.lang.ASTNode import org.jetbrains.jet.lang.resolve.scopes.JetScope trait SmartCompletionData{ - fun toElement(descriptor: DeclarationDescriptor): LookupElement? + fun toElements(descriptor: DeclarationDescriptor): Iterable val additionalElements: Iterable } @@ -46,7 +46,9 @@ enum class Tail { data class ExpectedTypeInfo(val `type`: JetType, val tail: Tail?) -fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession: ResolveSessionForBodies): SmartCompletionData? { +fun buildSmartCompletionData(expression: JetSimpleNameExpression, + resolveSession: ResolveSessionForBodies, + visibilityFilter: (DeclarationDescriptor) -> Boolean): SmartCompletionData? { val parent = expression.getParent() val expressionWithType: JetExpression; val receiver: JetExpression? @@ -107,12 +109,58 @@ fun buildSmartCompletionData(expression: JetSimpleNameExpression, resolveSession } return object: SmartCompletionData { - override fun toElement(descriptor: DeclarationDescriptor): LookupElement? { - if (itemsToSkip.contains(descriptor)) return null - val matchedExpectedTypes = expectedTypes.filter { expectedType -> typesOf(descriptor).any { descriptorType -> isSubtypeOf(descriptorType, expectedType.`type`) } } - if (matchedExpectedTypes.isEmpty()) return null - val tail = mergeTails(matchedExpectedTypes.map { it.tail }) - return decorateLookupElement(DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor), tail) + override fun toElements(descriptor: DeclarationDescriptor): Iterable { + if (itemsToSkip.contains(descriptor)) return listOf() + + val result = ArrayList() + + run { + val matchedExpectedTypes = expectedTypes.filter { expectedType -> typesOf(descriptor).any { descriptorType -> isSubtypeOf(descriptorType, expectedType.`type`) } } + if (matchedExpectedTypes.isNotEmpty()) { + val tail = mergeTails(matchedExpectedTypes.map { it.tail }) + result.add(addTailToLookupElement(DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor), tail)) + } + } + + val functionExpectedTypes = expectedTypes.filter { KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(it.`type`) } + if (functionExpectedTypes.isNotEmpty()) { + fun functionReferenceLookupElement(descriptor: FunctionDescriptor): LookupElement? { + val functionType = functionType(descriptor) + if (functionType == null) return null + + val matchedExpectedTypes = functionExpectedTypes.filter { isSubtypeOf(functionType, it.`type`) } + if (matchedExpectedTypes.isEmpty()) return null + val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor) + val text = "::" + (if (descriptor is ConstructorDescriptor) descriptor.getContainingDeclaration().getName() else descriptor.getName()) + val lookupElementDecorated = object: LookupElementDecorator(lookupElement) { + override fun getLookupString() = text + + override fun renderElement(presentation: LookupElementPresentation) { + super.renderElement(presentation) + presentation.setItemText(text) + presentation.setTypeText("") + } + + override fun handleInsert(context: InsertionContext) { + } + } + + val tail = mergeTails(matchedExpectedTypes.map { it.tail }) + return addTailToLookupElement(lookupElementDecorated, tail) + } + + if (descriptor is SimpleFunctionDescriptor) { + functionReferenceLookupElement(descriptor)?.let { result.add(it) } + } + else if (descriptor is ClassDescriptor && descriptor.getModality() != Modality.ABSTRACT) { + val constructors = descriptor.getConstructors().filter(visibilityFilter) + if (constructors.size == 1) { //TODO: this code is to be changed if overloads to start work after :: + functionReferenceLookupElement(constructors.single())?.let { result.add(it) } + } + } + } + + return result } override val additionalElements = additionalElements @@ -210,6 +258,8 @@ private fun MutableCollection.addTypeInstantiationItems(expectedT } private fun MutableCollection.addTypeInstantiationItems(jetType: JetType, tail: Tail?, resolveSession: ResolveSessionForBodies, bindingContext: BindingContext) { + if (KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(jetType)) return // do not show "object: ..." for function types + val classifier = jetType.getConstructor().getDeclarationDescriptor() if (!(classifier is ClassDescriptor)) return //TODO: check for constructor's visibility @@ -289,7 +339,7 @@ private fun MutableCollection.addTypeInstantiationItems(jetType: } } - val lookupElementWithTail = decorateLookupElement(lookupElementDecorated, tail) + val lookupElementWithTail = addTailToLookupElement(lookupElementDecorated, tail) if (suppressAutoInsertion) { add(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(lookupElementWithTail)) @@ -315,7 +365,7 @@ private fun MutableCollection.addThisItems(context: JetExpression val expressionText = if (qualifier == null) "this" else "this@" + qualifier val lookupElement = LookupElementBuilder.create(expressionText).withTypeText(DescriptorRenderer.TEXT.renderType(thisType)) val tailType = mergeTails(matchedExpectedTypes.map { it.tail }) - add(decorateLookupElement(lookupElement, tailType)) + add(addTailToLookupElement(lookupElement, tailType)) } } } @@ -501,7 +551,7 @@ private fun MutableCollection.addStaticMembers(classDescriptor: C } val tail = mergeTails(descriptorExpectedTypes.map { it.tail }) - add(decorateLookupElement(lookupElementDecorated, tail)) + add(addTailToLookupElement(lookupElementDecorated, tail)) } } @@ -510,7 +560,7 @@ private fun mergeTails(tails: Collection): Tail? { return if (HashSet(tails).size == 1) tails.first() else null } -private fun decorateLookupElement(lookupElement: LookupElement, tail: Tail?): LookupElement { +private fun addTailToLookupElement(lookupElement: LookupElement, tail: Tail?): LookupElement { return when (tail) { null -> lookupElement @@ -528,10 +578,16 @@ private fun decorateLookupElement(lookupElement: LookupElement, tail: Tail?): Lo } } -private fun isSubtypeOf(t: JetType, expectedType: JetType): Boolean{ - return !t.isError() && JetTypeChecker.INSTANCE.isSubtypeOf(t, expectedType) +private fun functionType(function: FunctionDescriptor): JetType? { + return KotlinBuiltIns.getInstance().getKFunctionType(function.getAnnotations(), + null, + function.getValueParameters().map { it.getType() }, + function.getReturnType() ?: return null, + function.getReceiverParameter() != null) } +private fun isSubtypeOf(t: JetType, expectedType: JetType) = !t.isError() && JetTypeChecker.INSTANCE.isSubtypeOf(t, expectedType) + private fun T?.toList(): List = if (this != null) listOf(this) else listOf() private fun String?.isNullOrEmpty() = this == null || this.isEmpty() diff --git a/idea/testData/completion/handlers/smart/FunctionReference.kt b/idea/testData/completion/handlers/smart/FunctionReference.kt new file mode 100644 index 00000000000..93ca249b729 --- /dev/null +++ b/idea/testData/completion/handlers/smart/FunctionReference.kt @@ -0,0 +1,9 @@ +fun foo(p: () -> Unit){} + +fun bar() { + foo() +} + +fun f(){} + +// ELEMENT: ::f \ No newline at end of file diff --git a/idea/testData/completion/handlers/smart/FunctionReference.kt.after b/idea/testData/completion/handlers/smart/FunctionReference.kt.after new file mode 100644 index 00000000000..9093966215a --- /dev/null +++ b/idea/testData/completion/handlers/smart/FunctionReference.kt.after @@ -0,0 +1,9 @@ +fun foo(p: () -> Unit){} + +fun bar() { + foo(::f) +} + +fun f(){} + +// ELEMENT: ::f \ No newline at end of file diff --git a/idea/testData/completion/smart/AnyExpected.kt b/idea/testData/completion/smart/AnyExpected.kt new file mode 100644 index 00000000000..211098980db --- /dev/null +++ b/idea/testData/completion/smart/AnyExpected.kt @@ -0,0 +1,9 @@ +fun foo(){} + +class C{} + +val v: Any = + +// ABSENT: ::foo +// ABSENT: ::C +// ABSENT: ::Runnable \ No newline at end of file diff --git a/idea/testData/completion/smart/FunctionExpected1.kt b/idea/testData/completion/smart/FunctionExpected1.kt new file mode 100644 index 00000000000..d334c5f794f --- /dev/null +++ b/idea/testData/completion/smart/FunctionExpected1.kt @@ -0,0 +1,14 @@ +fun foo(p: () -> Unit){} + +fun bar() { + foo() +} + +fun f1(){} +fun f2(i: Int){} + +// EXIST: { lookupString:"::f1", itemText:"::f1", tailText:"", typeText:"" } +// ABSENT: ::f2 +// ABSENT: ::Unit +// ABSENT: ::Nothing +// ABSENT: { lookupString:"object" } diff --git a/idea/testData/completion/smart/FunctionExpected2.kt.todo b/idea/testData/completion/smart/FunctionExpected2.kt.todo new file mode 100644 index 00000000000..9bb6083a0c3 --- /dev/null +++ b/idea/testData/completion/smart/FunctionExpected2.kt.todo @@ -0,0 +1,10 @@ +fun foo(p: () -> Unit){} + +fun bar() { + foo() +} + +fun f(){} +fun f(i: Int){} + +// ABSENT: ::f diff --git a/idea/testData/completion/smart/FunctionExpected3.kt b/idea/testData/completion/smart/FunctionExpected3.kt new file mode 100644 index 00000000000..bf5baa414dc --- /dev/null +++ b/idea/testData/completion/smart/FunctionExpected3.kt @@ -0,0 +1,11 @@ +fun foo(p: () -> Unit)){} +fun foo(p: () -> (() -> Unit)){} + +fun bar() { + foo() +} + +fun f(): () -> Unit {} + +// EXIST: ::f +// EXIST: f diff --git a/idea/testData/completion/smart/FunctionExpected4.kt b/idea/testData/completion/smart/FunctionExpected4.kt new file mode 100644 index 00000000000..f24fe7bc041 --- /dev/null +++ b/idea/testData/completion/smart/FunctionExpected4.kt @@ -0,0 +1,11 @@ +fun foo(p: (() -> Unit)?){} + +fun bar() { + foo() +} + +fun f1(){} +fun f2(i: Int){} + +// EXIST: ::f1 +// ABSENT: ::f2 diff --git a/idea/testData/completion/smart/FunctionExpected5.kt b/idea/testData/completion/smart/FunctionExpected5.kt new file mode 100644 index 00000000000..b9eddf6d7bd --- /dev/null +++ b/idea/testData/completion/smart/FunctionExpected5.kt @@ -0,0 +1,9 @@ +class C(i: Int){} + +fun foo(p: (Int) -> C){} + +fun bar(){ + foo() +} + +// EXIST: ::C diff --git a/idea/testData/completion/smart/FunctionExpected6.kt b/idea/testData/completion/smart/FunctionExpected6.kt new file mode 100644 index 00000000000..85e41e1a822 --- /dev/null +++ b/idea/testData/completion/smart/FunctionExpected6.kt @@ -0,0 +1,7 @@ +fun foo(p: (() -> Unit) -> Runnable){} + +fun bar(){ + foo() +} + +// EXIST: ::Runnable diff --git a/idea/testData/completion/smart/FunctionExpected7.kt b/idea/testData/completion/smart/FunctionExpected7.kt new file mode 100644 index 00000000000..c1976752393 --- /dev/null +++ b/idea/testData/completion/smart/FunctionExpected7.kt @@ -0,0 +1,9 @@ +import java.util.Date + +fun foo(p: (Long) -> Date){} + +fun bar(){ + foo() +} + +// ABSENT: ::Date diff --git a/idea/testData/completion/smart/FunctionExpected8.kt.todo b/idea/testData/completion/smart/FunctionExpected8.kt.todo new file mode 100644 index 00000000000..e995e62a6bf --- /dev/null +++ b/idea/testData/completion/smart/FunctionExpected8.kt.todo @@ -0,0 +1,11 @@ +fun foo(p: String.() -> Unit){} + +fun bar() { + foo() +} + +fun String.f1(){} +fun f2(){} + +// EXIST: ::f1 +// ABSENT: ::f2 diff --git a/idea/testData/completion/smart/FunctionExpected9.kt b/idea/testData/completion/smart/FunctionExpected9.kt new file mode 100644 index 00000000000..05510bc5357 --- /dev/null +++ b/idea/testData/completion/smart/FunctionExpected9.kt @@ -0,0 +1,9 @@ +abstract class C(i: Int){} + +fun foo(p: (Int) -> C){} + +fun bar(){ + foo() +} + +// ABSENT: ::C diff --git a/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java index 8fe0ee28c3e..c113cd982d2 100644 --- a/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/JvmSmartCompletionTestGenerated.java @@ -51,6 +51,11 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT doTest("idea/testData/completion/smart/AnonymousObjectForJavaInterface.kt"); } + @TestMetadata("AnyExpected.kt") + public void testAnyExpected() throws Exception { + doTest("idea/testData/completion/smart/AnyExpected.kt"); + } + @TestMetadata("AutoCastedType.kt") public void testAutoCastedType() throws Exception { doTest("idea/testData/completion/smart/AutoCastedType.kt"); @@ -126,6 +131,41 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT doTest("idea/testData/completion/smart/EnumMembers.kt"); } + @TestMetadata("FunctionExpected1.kt") + public void testFunctionExpected1() throws Exception { + doTest("idea/testData/completion/smart/FunctionExpected1.kt"); + } + + @TestMetadata("FunctionExpected3.kt") + public void testFunctionExpected3() throws Exception { + doTest("idea/testData/completion/smart/FunctionExpected3.kt"); + } + + @TestMetadata("FunctionExpected4.kt") + public void testFunctionExpected4() throws Exception { + doTest("idea/testData/completion/smart/FunctionExpected4.kt"); + } + + @TestMetadata("FunctionExpected5.kt") + public void testFunctionExpected5() throws Exception { + doTest("idea/testData/completion/smart/FunctionExpected5.kt"); + } + + @TestMetadata("FunctionExpected6.kt") + public void testFunctionExpected6() throws Exception { + doTest("idea/testData/completion/smart/FunctionExpected6.kt"); + } + + @TestMetadata("FunctionExpected7.kt") + public void testFunctionExpected7() throws Exception { + doTest("idea/testData/completion/smart/FunctionExpected7.kt"); + } + + @TestMetadata("FunctionExpected9.kt") + public void testFunctionExpected9() throws Exception { + doTest("idea/testData/completion/smart/FunctionExpected9.kt"); + } + @TestMetadata("InsideIdentifier.kt") public void testInsideIdentifier() throws Exception { doTest("idea/testData/completion/smart/InsideIdentifier.kt"); diff --git a/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java b/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java index fdc3c86e104..7b4592ea2b0 100644 --- a/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/completion/handlers/SmartCompletionHandlerTestGenerated.java @@ -161,6 +161,11 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion doTest("idea/testData/completion/handlers/smart/EnumMember.kt"); } + @TestMetadata("FunctionReference.kt") + public void testFunctionReference() throws Exception { + doTest("idea/testData/completion/handlers/smart/FunctionReference.kt"); + } + @TestMetadata("JavaEnumMemberInsertsImport.kt") public void testJavaEnumMemberInsertsImport() throws Exception { doTest("idea/testData/completion/handlers/smart/JavaEnumMemberInsertsImport.kt");