Smart completion: "::functionName" items when value of function type is expected

This commit is contained in:
Valentin Kipyatkov
2014-04-11 22:07:39 +04:00
parent 7d5b86e000
commit 6b1ecc3fb0
16 changed files with 252 additions and 24 deletions
@@ -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<DeclarationDescriptor, Boolean>() {
@Override
public Boolean invoke(DeclarationDescriptor descriptor) {
return isVisibleDescriptor(descriptor);
}
});
if (data != null) {
addReferenceVariants(new Function1<DeclarationDescriptor, LookupElement>() {
addReferenceVariants(new Function1<DeclarationDescriptor, Iterable<LookupElement>>(){
@Override
public LookupElement invoke(DeclarationDescriptor descriptor) {
return data.toElement(descriptor);
public Iterable<LookupElement> invoke(DeclarationDescriptor descriptor) {
return data.toElements(descriptor);
}
});
for (LookupElement element : data.getAdditionalElements()) {
@@ -242,22 +248,25 @@ class CompletionSession {
}
private void addReferenceVariants(@NotNull final Condition<DeclarationDescriptor> filterCondition) {
addReferenceVariants(new Function1<DeclarationDescriptor, LookupElement>() {
addReferenceVariants(new Function1<DeclarationDescriptor, Iterable<LookupElement>>(){
@Override
public LookupElement invoke(DeclarationDescriptor descriptor) {
return filterCondition.value(descriptor) ? DescriptorLookupConverter.createLookupElement(getResolveSession(), getExpressionBindingContext(), descriptor) : null;
public Iterable<LookupElement> invoke(DeclarationDescriptor descriptor) {
return filterCondition.value(descriptor)
? Collections.singletonList(
DescriptorLookupConverter.createLookupElement(getResolveSession(), getExpressionBindingContext(), descriptor))
: Collections.<LookupElement>emptyList();
}
});
}
private void addReferenceVariants(@NotNull Function1<DeclarationDescriptor, LookupElement> filter) {
private void addReferenceVariants(@NotNull Function1<DeclarationDescriptor, Iterable<LookupElement>> filter) {
Collection<DeclarationDescriptor> 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<LookupElement> elements = filter.invoke(descriptor);
for (LookupElement element : elements) {
jetResult.addElement(element);
}
}
@@ -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<LookupElement>
val additionalElements: Iterable<LookupElement>
}
@@ -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<LookupElement> {
if (itemsToSkip.contains(descriptor)) return listOf()
val result = ArrayList<LookupElement>()
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>(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<LookupElement>.addTypeInstantiationItems(expectedT
}
private fun MutableCollection<LookupElement>.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<LookupElement>.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<LookupElement>.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<LookupElement>.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?>): 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 : Any> T?.toList(): List<T> = if (this != null) listOf(this) else listOf()
private fun String?.isNullOrEmpty() = this == null || this.isEmpty()
@@ -0,0 +1,9 @@
fun foo(p: () -> Unit){}
fun bar() {
foo(<caret>)
}
fun f(){}
// ELEMENT: ::f
@@ -0,0 +1,9 @@
fun foo(p: () -> Unit){}
fun bar() {
foo(::f)<caret>
}
fun f(){}
// ELEMENT: ::f
@@ -0,0 +1,9 @@
fun foo(){}
class C{}
val v: Any = <caret>
// ABSENT: ::foo
// ABSENT: ::C
// ABSENT: ::Runnable
@@ -0,0 +1,14 @@
fun foo(p: () -> Unit){}
fun bar() {
foo(<caret>)
}
fun f1(){}
fun f2(i: Int){}
// EXIST: { lookupString:"::f1", itemText:"::f1", tailText:"", typeText:"" }
// ABSENT: ::f2
// ABSENT: ::Unit
// ABSENT: ::Nothing
// ABSENT: { lookupString:"object" }
@@ -0,0 +1,10 @@
fun foo(p: () -> Unit){}
fun bar() {
foo(<caret>)
}
fun f(){}
fun f(i: Int){}
// ABSENT: ::f
@@ -0,0 +1,11 @@
fun foo(p: () -> Unit)){}
fun foo(p: () -> (() -> Unit)){}
fun bar() {
foo(<caret>)
}
fun f(): () -> Unit {}
// EXIST: ::f
// EXIST: f
@@ -0,0 +1,11 @@
fun foo(p: (() -> Unit)?){}
fun bar() {
foo(<caret>)
}
fun f1(){}
fun f2(i: Int){}
// EXIST: ::f1
// ABSENT: ::f2
@@ -0,0 +1,9 @@
class C(i: Int){}
fun foo(p: (Int) -> C){}
fun bar(){
foo(<caret>)
}
// EXIST: ::C
@@ -0,0 +1,7 @@
fun foo(p: (() -> Unit) -> Runnable){}
fun bar(){
foo(<caret>)
}
// EXIST: ::Runnable
@@ -0,0 +1,9 @@
import java.util.Date
fun foo(p: (Long) -> Date){}
fun bar(){
foo(<caret>)
}
// ABSENT: ::Date
@@ -0,0 +1,11 @@
fun foo(p: String.() -> Unit){}
fun bar() {
foo(<caret>)
}
fun String.f1(){}
fun f2(){}
// EXIST: ::f1
// ABSENT: ::f2
@@ -0,0 +1,9 @@
abstract class C(i: Int){}
fun foo(p: (Int) -> C){}
fun bar(){
foo(<caret>)
}
// ABSENT: ::C
@@ -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");
@@ -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");