From a60d9d546da639c2429f7398e8ad046524f529fb Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Tue, 19 Jul 2016 18:09:23 +0300 Subject: [PATCH] KT-12798 DSL-friendly completion #KT-12798 Fixed --- .../kotlin/idea/completion/CompletionUtils.kt | 10 ++- .../idea/completion/LookupElementFactory.kt | 63 ++++++++++++------- .../kotlin/idea/completion/Weighers.kt | 50 ++++++++++++++- .../testData/weighers/basic/Callables.kt | 22 ++++++- 4 files changed, 118 insertions(+), 27 deletions(-) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt index 65b7c93f816..1c8cee552e3 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionUtils.kt @@ -124,7 +124,7 @@ fun LookupElementPresentation.prependTailText(text: String, grayed: Boolean) { tails.forEach { appendTailText(it.text, it.isGrayed) } } -enum class CallableWeight { +enum class CallableWeightEnum { local, // local non-extension thisClassMember, baseClassMember, @@ -135,6 +135,14 @@ enum class CallableWeight { receiverCastRequired } +class CallableWeight(val enum: CallableWeightEnum, val receiverIndex: Int?) { + companion object { + val local = CallableWeight(CallableWeightEnum.local, null) + val globalOrStatic = CallableWeight(CallableWeightEnum.globalOrStatic, null) + val receiverCastRequired = CallableWeight(CallableWeightEnum.receiverCastRequired, null) + } +} + val CALLABLE_WEIGHT_KEY = Key("CALLABLE_WEIGHT_KEY") fun InsertionContext.isAfterDot(): Boolean { diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt index e36aad0ddf7..9269ccb7c1b 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/LookupElementFactory.kt @@ -104,7 +104,7 @@ class LookupElementFactory( } // special "[]" item for get-operator - if (callType == CallType.DOT && descriptor is FunctionDescriptor && descriptor.isOperator() && descriptor.name == OperatorNameConventions.GET) { + if (callType == CallType.DOT && descriptor is FunctionDescriptor && descriptor.isOperator && descriptor.name == OperatorNameConventions.GET) { val baseLookupElement = createLookupElement(descriptor, useReceiverTypes) val lookupElement = object : LookupElementDecorator(baseLookupElement) { override fun getLookupString() = "[]" @@ -261,9 +261,9 @@ class LookupElementFactory( } private fun LookupElement.boldIfImmediate(weight: CallableWeight?): LookupElement { - val style = when (weight) { - CallableWeight.thisClassMember, CallableWeight.thisTypeExtension -> Style.BOLD - CallableWeight.receiverCastRequired -> Style.GRAYED + val style = when (weight?.enum) { + CallableWeightEnum.thisClassMember, CallableWeightEnum.thisTypeExtension -> Style.BOLD + CallableWeightEnum.receiverCastRequired -> Style.GRAYED else -> Style.NORMAL } return if (style != Style.NORMAL) { @@ -310,12 +310,15 @@ class LookupElementFactory( if (descriptor.overriddenDescriptors.isNotEmpty()) { // Optimization: when one of direct overridden fits, then nothing can fit better - descriptor.overriddenDescriptors.mapNotNull { - it.callableWeightBasedOnReceiver(receiverTypes, onReceiverTypeMismatch = null) - }.min()?.let { return it } + descriptor.overriddenDescriptors + .mapNotNull { it.callableWeightBasedOnReceiver(receiverTypes, onReceiverTypeMismatch = null) } + .minBy { it.enum } + ?.let { return it } val overridden = descriptor.overriddenTreeUniqueAsSequence(useOriginal = false) - return overridden.map { callableWeightBasic(it, receiverTypes)!! }.min()!! + return overridden + .map { callableWeightBasic(it, receiverTypes)!! } + .minBy { it.enum }!! } return callableWeightBasic(descriptor, receiverTypes) @@ -334,24 +337,38 @@ class LookupElementFactory( receiverTypes: Collection, onReceiverTypeMismatch: CallableWeight? ): CallableWeight? { - val receiverParameter = extensionReceiverParameter ?: dispatchReceiverParameter - if (receiverParameter != null) { - return if (receiverTypes.any { TypeUtils.equalTypes(it, receiverParameter.type) }) { - when { - isExtensionForTypeParameter() -> CallableWeight.typeParameterExtension - isExtension -> CallableWeight.thisTypeExtension - else -> CallableWeight.thisClassMember - } - } - else if (receiverTypes.any { it.isSubtypeOf(receiverParameter.type) }) { - if (isExtension) CallableWeight.baseTypeExtension else CallableWeight.baseClassMember - } - else { - onReceiverTypeMismatch + val receiverParameter = extensionReceiverParameter + ?: dispatchReceiverParameter + ?: return null + + for ((receiverIndex, receiverType) in receiverTypes.withIndex()) { + val weight = callableWeightForReceiverType(receiverType, receiverParameter.type) + if (weight != null) { + // if descriptor is matching all receivers then use null as receiverIndex - otherwise e.g. all members of Any would have too high priority + val receiverIndexToUse = if (receiverIndex == 0 && receiverTypes.drop(1).all { callableWeightForReceiverType(it, receiverParameter.type) != null }) + null + else + receiverIndex + return CallableWeight(weight, receiverIndexToUse) } } + return onReceiverTypeMismatch + } - return null + private fun CallableDescriptor.callableWeightForReceiverType(receiverType: KotlinType, receiverParameterType: KotlinType): CallableWeightEnum? { + if (TypeUtils.equalTypes(receiverType, receiverParameterType)) { + return when { + isExtensionForTypeParameter() -> CallableWeightEnum.typeParameterExtension + isExtension -> CallableWeightEnum.thisTypeExtension + else -> CallableWeightEnum.thisClassMember + } + } + else if (receiverType.isSubtypeOf(receiverParameterType)) { + return if (isExtension) CallableWeightEnum.baseTypeExtension else CallableWeightEnum.baseClassMember + } + else { + return null + } } private fun CallableDescriptor.isExtensionForTypeParameter(): Boolean { diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt index 1d025268209..7493def7e09 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/Weighers.kt @@ -138,7 +138,55 @@ object KindWeigher : LookupElementWeigher("kotlin.kind") { } object CallableWeigher : LookupElementWeigher("kotlin.callableWeight") { - override fun weigh(element: LookupElement) = element.getUserData(CALLABLE_WEIGHT_KEY) + private enum class Weight1 { + local, + memberOrExtension, + globalOrStatic, + typeParameterExtension, + receiverCastRequired + } + + private enum class Weight2 { + thisClassMember, + baseClassMember, + thisTypeExtension, + baseTypeExtension, + other + } + + private class CompoundWeight(val weight1: Weight1, val receiverIndex: Int, val weight2: Weight2) : Comparable { + override fun compareTo(other: CompoundWeight): Int { + if (weight1 != other.weight1) return weight1.compareTo(other.weight1) + if (receiverIndex != other.receiverIndex) return receiverIndex.compareTo(other.receiverIndex) + return weight2.compareTo(other.weight2) + } + } + + override fun weigh(element: LookupElement): Comparable<*>? { + val weight = element.getUserData(CALLABLE_WEIGHT_KEY) ?: return null + val w1 = when (weight.enum) { + CallableWeightEnum.local -> Weight1.local + + CallableWeightEnum.thisClassMember, + CallableWeightEnum.baseClassMember, + CallableWeightEnum.thisTypeExtension, + CallableWeightEnum.baseTypeExtension -> Weight1.memberOrExtension + + CallableWeightEnum.globalOrStatic -> Weight1.globalOrStatic + + CallableWeightEnum.typeParameterExtension -> Weight1.typeParameterExtension + + CallableWeightEnum.receiverCastRequired -> Weight1.receiverCastRequired + } + val w2 = when (weight.enum) { + CallableWeightEnum.thisClassMember -> Weight2.thisClassMember + CallableWeightEnum.baseClassMember -> Weight2.baseClassMember + CallableWeightEnum.thisTypeExtension -> Weight2.thisTypeExtension + CallableWeightEnum.baseTypeExtension -> Weight2.baseTypeExtension + else -> Weight2.other + } + return CompoundWeight(w1, weight.receiverIndex ?: Int.MAX_VALUE, w2) + } } object VariableOrFunctionWeigher : LookupElementWeigher("kotlin.variableOrFunction"){ diff --git a/idea/idea-completion/testData/weighers/basic/Callables.kt b/idea/idea-completion/testData/weighers/basic/Callables.kt index 1ed734ed68a..96be95e6715 100644 --- a/idea/idea-completion/testData/weighers/basic/Callables.kt +++ b/idea/idea-completion/testData/weighers/basic/Callables.kt @@ -1,3 +1,5 @@ +public inline fun with(receiver: T, block: T.() -> R): R = receiver.block() + fun aaGlobalFun(){} val aaGlobalProp = 1 @@ -10,14 +12,23 @@ class Derived : Base() { fun aaDerivedFun(){} val aaDerivedProp = 1 - fun foo() { + fun foo(y: Y) { val aaLocalVal = 1 fun aaLocalFun(){} - aa + with (y) { + aa + } } } +interface X { + fun aaX() +} +interface Y : X { + fun aaY() +} + fun Any.aaAnyExtensionFun(){} fun Derived.aaExtensionFun(){} @@ -26,8 +37,15 @@ val Derived.aaExtensionProp: Int get() = 1 fun T.aaTypeParamExt(){} +fun X.aaXExt(){} +fun Y.aaYExt(){} + // ORDER: aaLocalVal // ORDER: aaLocalFun +// ORDER: aaY +// ORDER: aaX +// ORDER: aaYExt +// ORDER: aaXExt // ORDER: aaDerivedProp // ORDER: aaDerivedFun // ORDER: aaBaseProp