From e8c35e16e030750c712fd60515fd60c135b03741 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Thu, 24 Mar 2016 16:42:05 +0300 Subject: [PATCH] Smart completion after "by" and "in": non-imported operators supported --- .../idea/completion/BasicCompletionSession.kt | 2 +- .../completion/handlers/GenerateLambda.kt | 2 +- .../idea/completion/smart/SmartCompletion.kt | 3 ++- .../smart/SmartCompletionSession.kt | 4 +-- .../NotImportedContains/Declarations.kt | 8 ++++++ .../NotImportedContains.kt | 11 ++++++++ .../NotImportedGetValue/Declarations.kt | 22 ++++++++++++++++ .../NotImportedGetValue.kt | 26 +++++++++++++++++++ ...MultiFileSmartCompletionTestGenerated.java | 12 +++++++++ .../kotlin/idea/core/ExpectedInfos.kt | 9 ++++--- .../kotlin/idea/core/KotlinIndicesHelper.kt | 19 +++++++++----- .../idea/core/TypesWithOperatorDetector.kt | 26 ++++++++++++++----- .../macro/SuitableVariableMacro.kt | 2 +- 13 files changed, 122 insertions(+), 24 deletions(-) create mode 100644 idea/idea-completion/testData/smartMultiFile/NotImportedContains/Declarations.kt create mode 100644 idea/idea-completion/testData/smartMultiFile/NotImportedContains/NotImportedContains.kt create mode 100644 idea/idea-completion/testData/smartMultiFile/NotImportedGetValue/Declarations.kt create mode 100644 idea/idea-completion/testData/smartMultiFile/NotImportedGetValue/NotImportedGetValue.kt diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt index 820234c7e86..430947f5bef 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/BasicCompletionSession.kt @@ -78,7 +78,7 @@ class BasicCompletionSession( private val smartCompletion = expression?.let { SmartCompletion( - it, resolutionFacade, bindingContext, moduleDescriptor, isVisibleFilter, prefixMatcher, + it, resolutionFacade, bindingContext, moduleDescriptor, isVisibleFilter, indicesHelper(false), prefixMatcher, GlobalSearchScope.EMPTY_SCOPE, toFromOriginalFileMapper, callTypeAndReceiver, isJvmModule, forBasicCompletion = true ) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/GenerateLambda.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/GenerateLambda.kt index 7f58d1cf2cc..9e5a1cba84e 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/GenerateLambda.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/handlers/GenerateLambda.kt @@ -82,7 +82,7 @@ private fun needExplicitParameterTypes(context: InsertionContext, placeholderRan val resolutionFacade = file.getResolutionFacade() val bindingContext = resolutionFacade.analyze(expression, BodyResolveMode.PARTIAL) - val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, useHeuristicSignatures = false).calculate(expression) + val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, indicesHelper = null, useHeuristicSignatures = false).calculate(expression) val functionTypes = expectedInfos .mapNotNull { it.fuzzyType?.type } diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt index 308d9f80fc2..4a707138a5f 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletion.kt @@ -55,6 +55,7 @@ class SmartCompletion( private val bindingContext: BindingContext, private val moduleDescriptor: ModuleDescriptor, private val visibilityFilter: (DeclarationDescriptor) -> Boolean, + private val indicesHelper: KotlinIndicesHelper, private val prefixMatcher: PrefixMatcher, private val inheritorSearchScope: GlobalSearchScope, private val toFromOriginalFileMapper: ToFromOriginalFileMapper, @@ -302,7 +303,7 @@ class SmartCompletion( // if expected types are too general, try to use expected type from outer calls var count = 0 while (true) { - val infos = ExpectedInfos(bindingContext, resolutionFacade, useOuterCallsExpectedTypeCount = count) + val infos = ExpectedInfos(bindingContext, resolutionFacade, indicesHelper, useOuterCallsExpectedTypeCount = count) .calculate(expression) if (count == 2 /* use two outer calls maximum */ || infos.none { it.fuzzyType?.isAlmostEverything() ?: false }) { return if (forBasicCompletion) diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt index c6d31f3aca2..9617088e22f 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/smart/SmartCompletionSession.kt @@ -59,7 +59,7 @@ class SmartCompletionSession( private val smartCompletion by lazy(LazyThreadSafetyMode.NONE) { expression?.let { - SmartCompletion(it, resolutionFacade, bindingContext, moduleDescriptor, isVisibleFilter, + SmartCompletion(it, resolutionFacade, bindingContext, moduleDescriptor, isVisibleFilter, indicesHelper(false), prefixMatcher, originalSearchScope, toFromOriginalFileMapper, callTypeAndReceiver, isJvmModule) } @@ -192,7 +192,7 @@ class SmartCompletionSession( override fun getValueArgumentList() = throw UnsupportedOperationException() } - val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade) + val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade, indicesHelper(false)) .calculateForArgument(dummyCall, dummyArgument) collector.addElements(LambdaItems.collect(expectedInfos)) } diff --git a/idea/idea-completion/testData/smartMultiFile/NotImportedContains/Declarations.kt b/idea/idea-completion/testData/smartMultiFile/NotImportedContains/Declarations.kt new file mode 100644 index 00000000000..54ab13a01a0 --- /dev/null +++ b/idea/idea-completion/testData/smartMultiFile/NotImportedContains/Declarations.kt @@ -0,0 +1,8 @@ +package dependency + +interface X +interface Y : X +interface Z + +operator fun X.contains(s: String): Boolean = true +fun Z.contains(s: String): Boolean = true diff --git a/idea/idea-completion/testData/smartMultiFile/NotImportedContains/NotImportedContains.kt b/idea/idea-completion/testData/smartMultiFile/NotImportedContains/NotImportedContains.kt new file mode 100644 index 00000000000..77877baeca5 --- /dev/null +++ b/idea/idea-completion/testData/smartMultiFile/NotImportedContains/NotImportedContains.kt @@ -0,0 +1,11 @@ +import dependency.X +import dependency.Y +import dependency.Z + +fun foo(s: String, x: X, y: Y, z: Z) { + if (s in ) +} + +// EXIST: x +// EXIST: y +// ABSENT: z diff --git a/idea/idea-completion/testData/smartMultiFile/NotImportedGetValue/Declarations.kt b/idea/idea-completion/testData/smartMultiFile/NotImportedGetValue/Declarations.kt new file mode 100644 index 00000000000..88b49048d73 --- /dev/null +++ b/idea/idea-completion/testData/smartMultiFile/NotImportedGetValue/Declarations.kt @@ -0,0 +1,22 @@ +package dependency + +import kotlin.reflect.KProperty +import main.C + +class X1 { + operator fun getValue(thisRef: C, property: KProperty<*>): String = "" +} +class X2 { + operator fun getValue(thisRef: String, property: KProperty<*>): String = "" +} +class X3 { + operator fun getValue(thisRef: Any, property: KProperty<*>): String = "" +} + +class Y1 +class Y2 +class Y3 + +operator fun Y1.getValue(thisRef: C, property: KProperty<*>): String = "" +operator fun Y2.getValue(thisRef: String, property: KProperty<*>): String = "" +operator fun Y3.getValue(thisRef: Any, property: KProperty<*>): String = "" diff --git a/idea/idea-completion/testData/smartMultiFile/NotImportedGetValue/NotImportedGetValue.kt b/idea/idea-completion/testData/smartMultiFile/NotImportedGetValue/NotImportedGetValue.kt new file mode 100644 index 00000000000..1e77bb99d29 --- /dev/null +++ b/idea/idea-completion/testData/smartMultiFile/NotImportedGetValue/NotImportedGetValue.kt @@ -0,0 +1,26 @@ +package main + +import dependency.X1 +import dependency.X2 +import dependency.X3 +import dependency.Y1 +import dependency.Y2 +import dependency.Y3 + +fun createX1() = X1() +fun createX2() = X2() +fun createX3() = X3() +fun createY1() = Y1() +fun createY2() = Y2() +fun createY3() = Y3() + +class C { + val property by +} + +// EXIST: createX1 +// ABSENT: createX2 +// EXIST: createX3 +// EXIST: createY1 +// ABSENT: createY2 +// EXIST: createY3 diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileSmartCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileSmartCompletionTestGenerated.java index 4da7f591636..de906ad8def 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileSmartCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/MultiFileSmartCompletionTestGenerated.java @@ -137,6 +137,18 @@ public class MultiFileSmartCompletionTestGenerated extends AbstractMultiFileSmar doTest(fileName); } + @TestMetadata("NotImportedContains") + public void testNotImportedContains() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smartMultiFile/NotImportedContains/"); + doTest(fileName); + } + + @TestMetadata("NotImportedGetValue") + public void testNotImportedGetValue() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smartMultiFile/NotImportedGetValue/"); + doTest(fileName); + } + @TestMetadata("StaticMembers1") public void testStaticMembers1() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/smartMultiFile/StaticMembers1/"); diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ExpectedInfos.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ExpectedInfos.kt index 4ded54c4050..6450cbd4c31 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ExpectedInfos.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/ExpectedInfos.kt @@ -140,6 +140,7 @@ object IfConditionAdditionalData : ExpectedInfo.AdditionalData class ExpectedInfos( private val bindingContext: BindingContext, private val resolutionFacade: ResolutionFacade, + private val indicesHelper: KotlinIndicesHelper?, private val useHeuristicSignatures: Boolean = true, private val useOuterCallsExpectedTypeCount: Int = 0 ) { @@ -207,7 +208,7 @@ class ExpectedInfos( if (useOuterCallsExpectedTypeCount > 0 && results.any(::makesSenseToUseOuterCallExpectedType)) { val callExpression = (call.callElement as? KtExpression)?.getQualifiedExpressionForSelectorOrThis() ?: return results - val expectedFuzzyTypes = ExpectedInfos(bindingContext, resolutionFacade, useHeuristicSignatures, useOuterCallsExpectedTypeCount - 1) + val expectedFuzzyTypes = ExpectedInfos(bindingContext, resolutionFacade, indicesHelper, useHeuristicSignatures, useOuterCallsExpectedTypeCount - 1) .calculate(callExpression) .mapNotNull { it.fuzzyType } if (expectedFuzzyTypes.isEmpty() || expectedFuzzyTypes.any { it.freeParameters.isNotEmpty() }) return results @@ -587,7 +588,7 @@ class ExpectedInfos( val leftOperandType = binaryExpression.left?.let { bindingContext.getType(it) } ?: return null val scope = expressionWithType.getResolutionScope(bindingContext, resolutionFacade) - val detector = TypesWithContainsDetector(scope, leftOperandType) + val detector = TypesWithContainsDetector(scope, indicesHelper, leftOperandType) val byTypeFilter = object : ByTypeFilter { override fun matchingSubstitutor(descriptorType: FuzzyType): TypeSubstitutor? { @@ -608,8 +609,8 @@ class ExpectedInfos( ?: return null val explicitPropertyType = property.returnType?.check { propertyDeclaration.typeReference != null } - val typesWithGetDetector = TypesWithGetValueDetector(scope, propertyOwnerType, explicitPropertyType) - val typesWithSetDetector = if (property.isVar) TypesWithSetValueDetector(scope, propertyOwnerType) else null + val typesWithGetDetector = TypesWithGetValueDetector(scope, indicesHelper, propertyOwnerType, explicitPropertyType) + val typesWithSetDetector = if (property.isVar) TypesWithSetValueDetector(scope, indicesHelper, propertyOwnerType) else null val byTypeFilter = object : ByTypeFilter { override fun matchingSubstitutor(descriptorType: FuzzyType): TypeSubstitutor? { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt index 3f89a73ca1a..7e116f5bfe3 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt @@ -16,7 +16,6 @@ package org.jetbrains.kotlin.idea.core -import com.intellij.codeInsight.CodeInsightSettings import com.intellij.codeInsight.JavaProjectCodeInsightSettings import com.intellij.openapi.progress.ProgressManager import com.intellij.psi.PsiFile @@ -26,10 +25,7 @@ import com.intellij.psi.search.PsiShortNamesCache import com.intellij.psi.stubs.StringStubIndexExtension import com.intellij.util.Processor import com.intellij.util.indexing.IdFilter -import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.ClassKind -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.caches.resolve.getJavaFieldDescriptor import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference @@ -78,12 +74,12 @@ class KotlinIndicesHelper( } fun getTopLevelCallablesByName(name: String): Collection { - val declarations = HashSet() + val declarations = LinkedHashSet() declarations.addTopLevelNonExtensionCallablesByName(KotlinFunctionShortNameIndex.getInstance(), name) declarations.addTopLevelNonExtensionCallablesByName(KotlinPropertyShortNameIndex.getInstance(), name) return declarations .flatMap { it.resolveToDescriptorsWithHack() } - .filter { it.extensionReceiverParameter == null && descriptorFilter(it) } + .filter { descriptorFilter(it) } } private fun MutableSet.addTopLevelNonExtensionCallablesByName( @@ -93,6 +89,15 @@ class KotlinIndicesHelper( index.get(name, project, scope).filterTo(this) { it.parent is KtFile && it.receiverTypeReference == null } } + fun getTopLevelExtensionOperatorsByName(name: String): Collection { + return KotlinFunctionShortNameIndex.getInstance().get(name, project, scope) + .filter { it.parent is KtFile && it.receiverTypeReference != null && it.hasModifier(KtTokens.OPERATOR_KEYWORD) } + .flatMap { it.resolveToDescriptorsWithHack() } + .filterIsInstance() + .filter { descriptorFilter(it) } + .distinct() + } + fun processTopLevelCallables(nameFilter: (String) -> Boolean, processor: (CallableDescriptor) -> Unit) { fun processIndex(index: StringStubIndexExtension) { for (key in index.getAllKeys(project)) { diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithOperatorDetector.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithOperatorDetector.kt index d1bbe2aba71..a0f49a6ca31 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithOperatorDetector.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/TypesWithOperatorDetector.kt @@ -30,22 +30,30 @@ import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.util.isValidOperator import java.util.* -//TODO: support not imported extensions abstract class TypesWithOperatorDetector( private val name: Name, - private val scope: LexicalScope + private val scope: LexicalScope, + private val indicesHelper: KotlinIndicesHelper? ) { protected abstract fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): Boolean private val cache = HashMap() - private val typesWithExtension: Map by lazy { + private val typesWithExtensionFromScope: Map by lazy { scope.collectFunctions(name, NoLookupLocation.FROM_IDE) .filter { it.extensionReceiverParameter != null && it.isValidOperator() && isSuitableByType(it, it.typeParameters) } .map { it.extensionReceiverParameter!!.type to it } .toMap() } + private val typesWithExtensionFromIndices: Map by lazy { + indicesHelper?.getTopLevelExtensionOperatorsByName(name.asString()) + ?.filter { it.extensionReceiverParameter != null && it.isValidOperator() && isSuitableByType(it, it.typeParameters) } + ?.map { it.extensionReceiverParameter!!.type to it } + ?.filter { it.first !in typesWithExtensionFromScope.keys } + ?.toMap() ?: emptyMap() + } + fun findOperator(type: FuzzyType): FunctionDescriptor? { if (cache.containsKey(type)) { return cache[type] @@ -64,19 +72,21 @@ abstract class TypesWithOperatorDetector( if (memberFunction != null) return memberFunction } - for ((typeWithExtension, operator) in typesWithExtension) { + for ((typeWithExtension, operator) in typesWithExtensionFromScope + typesWithExtensionFromIndices) { if (type.checkIsSubtypeOf(typeWithExtension) != null) { return operator //TODO: substitution } } + return null } } class TypesWithContainsDetector( scope: LexicalScope, + indicesHelper: KotlinIndicesHelper?, private val argumentType: KotlinType -) : TypesWithOperatorDetector(OperatorNameConventions.CONTAINS, scope) { +) : TypesWithOperatorDetector(OperatorNameConventions.CONTAINS, scope, indicesHelper) { override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): Boolean { val parameter = function.valueParameters.single() @@ -87,9 +97,10 @@ class TypesWithContainsDetector( class TypesWithGetValueDetector( scope: LexicalScope, + indicesHelper: KotlinIndicesHelper?, private val propertyOwnerType: KotlinType, private val propertyType: KotlinType? -) : TypesWithOperatorDetector(OperatorNameConventions.GET_VALUE, scope) { +) : TypesWithOperatorDetector(OperatorNameConventions.GET_VALUE, scope, indicesHelper) { override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): Boolean { val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams) @@ -106,8 +117,9 @@ class TypesWithGetValueDetector( class TypesWithSetValueDetector( scope: LexicalScope, + indicesHelper: KotlinIndicesHelper?, private val propertyOwnerType: KotlinType -) : TypesWithOperatorDetector(OperatorNameConventions.SET_VALUE, scope) { +) : TypesWithOperatorDetector(OperatorNameConventions.SET_VALUE, scope, indicesHelper) { override fun isSuitableByType(function: FunctionDescriptor, freeTypeParams: Collection): Boolean { val paramType = FuzzyType(function.valueParameters.first().type, freeTypeParams) diff --git a/idea/idea-live-templates/src/org/jetbrains/kotlin/idea/liveTemplates/macro/SuitableVariableMacro.kt b/idea/idea-live-templates/src/org/jetbrains/kotlin/idea/liveTemplates/macro/SuitableVariableMacro.kt index de07f5b04ac..f835904be53 100644 --- a/idea/idea-live-templates/src/org/jetbrains/kotlin/idea/liveTemplates/macro/SuitableVariableMacro.kt +++ b/idea/idea-live-templates/src/org/jetbrains/kotlin/idea/liveTemplates/macro/SuitableVariableMacro.kt @@ -40,7 +40,7 @@ class SuitableVariableMacro : BaseKotlinVariableMacro