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 8f2a23b7e85..5d5693a1397 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 @@ -21,20 +21,15 @@ import com.intellij.codeInsight.lookup.LookupElementWeigher import com.intellij.codeInsight.lookup.WeighingContext import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor -import org.jetbrains.kotlin.idea.completion.smart.SMART_COMPLETION_ITEM_PRIORITY_KEY -import org.jetbrains.kotlin.idea.completion.smart.SmartCompletionItemPriority -import org.jetbrains.kotlin.idea.completion.smart.fuzzyTypesForSmartCompletion +import org.jetbrains.kotlin.idea.completion.smart.* import org.jetbrains.kotlin.idea.core.SmartCastCalculator import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject import org.jetbrains.kotlin.idea.util.FuzzyType import org.jetbrains.kotlin.idea.util.ImportInsertHelper -import org.jetbrains.kotlin.idea.util.makeNotNullable -import org.jetbrains.kotlin.idea.util.nullability import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.platform.JavaToKotlinClassMap import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.resolve.ImportPath -import org.jetbrains.kotlin.types.typeUtil.TypeNullability import java.util.HashSet object PriorityWeigher : LookupElementWeigher("kotlin.priority") { @@ -194,30 +189,47 @@ class ExpectedInfoMatchWeigher( private val expectedInfos: Collection, private val smartCastCalculator: SmartCastCalculator ) : LookupElementWeigher("kotlin.expectedInfoMatch") { - private enum class Weight { - matches, - matchesIfNotNull, - doesNotMatch + + private val NO_MATCH = 0L + + private fun fullMatch(nameSimilarity: Int): Long { + return -((2L shl 32) + nameSimilarity) } - override fun weigh(element: LookupElement): Weight { + private fun ifNotNullMatch(nameSimilarity: Int): Long { + return -((1L shl 32) + nameSimilarity) + } + + override fun weigh(element: LookupElement): Long { // TODO: keywords with type val o = element.`object` - val fuzzyTypes = when (o) { + val (fuzzyTypes, name) = when (o) { is DeclarationLookupObject -> { - val descriptor = o.descriptor ?: return Weight.doesNotMatch - descriptor.fuzzyTypesForSmartCompletion(smartCastCalculator) + val descriptor = o.descriptor ?: return NO_MATCH + descriptor.fuzzyTypesForSmartCompletion(smartCastCalculator) to descriptor.name } - is ThisItemLookupObject -> smartCastCalculator.types(o.receiverParameter).map { FuzzyType(it, emptyList()) } + is ThisItemLookupObject -> smartCastCalculator.types(o.receiverParameter).map { FuzzyType(it, emptyList()) } to null - else -> return Weight.doesNotMatch + else -> return NO_MATCH } - return when { - fuzzyTypes.any { type -> expectedInfos.any { it.matchingSubstitutor(type) != null } } -> Weight.matches - fuzzyTypes.any { type -> type.nullability() == TypeNullability.NULLABLE && expectedInfos.any { it.matchingSubstitutor(type.makeNotNullable()) != null } } -> Weight.matchesIfNotNull - else -> Weight.doesNotMatch + if (fuzzyTypes.isEmpty()) return NO_MATCH + + val classified: Collection> = expectedInfos.map { it to fuzzyTypes.classifyExpectedInfo(it) } + if (classified.all { it.second == ExpectedInfoClassification.noMatch }) return NO_MATCH + + val nameSimilarity = if (name != null) { + val matchingInfos = classified.filter { it.second != ExpectedInfoClassification.noMatch }.map { it.first } + calcNameSimilarity(name.asString(), matchingInfos) } + else { + 0 + } + + return if (classified.any { it.second.isMatch() }) + fullMatch(nameSimilarity) + else + ifNotNullMatch(nameSimilarity) } } \ No newline at end of file diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/KeywordValues.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/KeywordValues.kt index a14b2ee4495..deeaddf485e 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/KeywordValues.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/KeywordValues.kt @@ -54,7 +54,7 @@ object KeywordValues { if (!skipTrueFalse) { val booleanInfoClassifier = { info: ExpectedInfo -> - if (info.fuzzyType?.type == KotlinBuiltIns.getInstance().getBooleanType()) ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.notMatches + if (info.fuzzyType?.type == KotlinBuiltIns.getInstance().getBooleanType()) ExpectedInfoClassification.match(TypeSubstitutor.EMPTY) else ExpectedInfoClassification.noMatch } collection.addLookupElements(null, expectedInfos, booleanInfoClassifier) { LookupElementBuilder.create("true").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.TRUE) } collection.addLookupElements(null, expectedInfos, booleanInfoClassifier) { LookupElementBuilder.create("false").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.FALSE) } @@ -62,9 +62,9 @@ object KeywordValues { val classifier = { info: ExpectedInfo -> if (info.fuzzyType != null && info.fuzzyType!!.type.isMarkedNullable()) - ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) + ExpectedInfoClassification.match(TypeSubstitutor.EMPTY) else - ExpectedInfoClassification.notMatches + ExpectedInfoClassification.noMatch } collection.addLookupElements(null, expectedInfos, classifier) { LookupElementBuilder.create("null").bold().assignSmartCompletionPriority(SmartCompletionItemPriority.NULL) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/StaticMembers.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/StaticMembers.kt index d660439ac83..29b217d20fb 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/StaticMembers.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/StaticMembers.kt @@ -75,7 +75,7 @@ class StaticMembers( classifier = { expectedInfo -> returnType.classifyExpectedInfo(expectedInfo) } } else if (DescriptorUtils.isEnumEntry(descriptor) && !enumEntriesToSkip.contains(descriptor)) { - classifier = { ExpectedInfoClassification.matches(TypeSubstitutor.EMPTY) } /* we do not need to check type of enum entry because it's taken from proper enum */ + classifier = { ExpectedInfoClassification.match(TypeSubstitutor.EMPTY) } /* we do not need to check type of enum entry because it's taken from proper enum */ } else { return diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt index 598f3707df7..d6f9f6b813a 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/Utils.kt @@ -124,10 +124,12 @@ private constructor( val substitutor: TypeSubstitutor?, val makeNotNullable: Boolean ) { + fun isMatch() = substitutor != null && !makeNotNullable + companion object { - val notMatches = ExpectedInfoClassification(null, false) - fun matches(substitutor: TypeSubstitutor) = ExpectedInfoClassification(substitutor, false) - fun matchesIfNotNullable(substitutor: TypeSubstitutor) = ExpectedInfoClassification(substitutor, true) + val noMatch = ExpectedInfoClassification(null, false) + fun match(substitutor: TypeSubstitutor) = ExpectedInfoClassification(substitutor, false) + fun ifNotNullMatch(substitutor: TypeSubstitutor) = ExpectedInfoClassification(substitutor, true) } } @@ -135,17 +137,17 @@ fun Collection.classifyExpectedInfo(expectedInfo: ExpectedInfo): Expe val sequence = asSequence() val substitutor = sequence.map { expectedInfo.matchingSubstitutor(it) }.firstOrNull() if (substitutor != null) { - return ExpectedInfoClassification.matches(substitutor) + return ExpectedInfoClassification.match(substitutor) } if (sequence.any { it.nullability() == TypeNullability.NULLABLE }) { val substitutor2 = sequence.map { expectedInfo.matchingSubstitutor(it.makeNotNullable()) }.firstOrNull() if (substitutor2 != null) { - return ExpectedInfoClassification.matchesIfNotNullable(substitutor2) + return ExpectedInfoClassification.ifNotNullMatch(substitutor2) } } - return ExpectedInfoClassification.notMatches + return ExpectedInfoClassification.noMatch } fun FuzzyType.classifyExpectedInfo(expectedInfo: ExpectedInfo) = listOf(this).classifyExpectedInfo(expectedInfo) diff --git a/idea/idea-completion/testData/weighers/basic/expectedInfo/NameSimilarity.kt b/idea/idea-completion/testData/weighers/basic/expectedInfo/NameSimilarity.kt new file mode 100644 index 00000000000..12d96ebd9ea --- /dev/null +++ b/idea/idea-completion/testData/weighers/basic/expectedInfo/NameSimilarity.kt @@ -0,0 +1,10 @@ +fun foo(xxx1: String?, xxx2: String, xxx3: Any, xp: String?) { + bar(x) +} + +fun bar(pXxx: String){} + +// ORDER: xxx2 +// ORDER: xxx1 +// ORDER: xp +// ORDER: xxx3 diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java index 4b63e0535aa..5e17ef86d42 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/weighers/BasicCompletionWeigherTestGenerated.java @@ -157,6 +157,12 @@ public class BasicCompletionWeigherTestGenerated extends AbstractBasicCompletion doTest(fileName); } + @TestMetadata("NameSimilarity.kt") + public void testNameSimilarity() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/weighers/basic/expectedInfo/NameSimilarity.kt"); + doTest(fileName); + } + @TestMetadata("PreferMatchingThis.kt") public void testPreferMatchingThis() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/idea-completion/testData/weighers/basic/expectedInfo/PreferMatchingThis.kt");