From a3da1ea1a0392fb62631348ccd9279d7f5ca297d Mon Sep 17 00:00:00 2001 From: Roman Golyshev Date: Wed, 24 Jun 2020 19:39:51 +0300 Subject: [PATCH] Iterate on symbols, not on scopes - This way it is easier to get the correct results --- ...FunctionsFromInnerClassExplicitReceiver.kt | 18 +++ ...sionPropertyAndFunctionImplicitReceiver.kt | 25 +++++ .../test/JSBasicCompletionTestGenerated.java | 10 ++ .../test/JvmBasicCompletionTestGenerated.java | 10 ++ .../KotlinFirCompletionContributor.kt | 104 +++++++++--------- 5 files changed, 118 insertions(+), 49 deletions(-) create mode 100644 idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInnerClassExplicitReceiver.kt create mode 100644 idea/idea-completion/testData/basic/common/primitiveCompletion/extensionPropertyAndFunctionImplicitReceiver.kt diff --git a/idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInnerClassExplicitReceiver.kt b/idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInnerClassExplicitReceiver.kt new file mode 100644 index 00000000000..3a8aed20a07 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInnerClassExplicitReceiver.kt @@ -0,0 +1,18 @@ +class A { + fun aa() {} + val aaa = 10 + + inner class AA { + fun bb() {} + val bbb = 20 + + fun test() { + this. + } + } +} + +// ABSENT: aa +// ABSENT: aaa +// EXIST: bb +// EXIST: bbb \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/primitiveCompletion/extensionPropertyAndFunctionImplicitReceiver.kt b/idea/idea-completion/testData/basic/common/primitiveCompletion/extensionPropertyAndFunctionImplicitReceiver.kt new file mode 100644 index 00000000000..e2034958862 --- /dev/null +++ b/idea/idea-completion/testData/basic/common/primitiveCompletion/extensionPropertyAndFunctionImplicitReceiver.kt @@ -0,0 +1,25 @@ +fun Any.anyFun() {} +val Any.anyVal: Int get() = 10 + +class A +fun A.aFun() {} +val A.aVal: Int get() = 10 + +class B +fun B.bFun() {} +val B.bVal: Int get() = 10 + +fun test(a: A) { + a.run { + + + Unit + } +} + +// EXIST: anyFun +// EXIST: anyVal +// EXIST: aVal +// EXIST: aFun +// ABSENT: bVal +// ABSENT: bFun \ No newline at end of file diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java index fa740d2728c..15361b9902c 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JSBasicCompletionTestGenerated.java @@ -2247,6 +2247,11 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInnerClass.kt"); } + @TestMetadata("classFieldsAndFunctionsFromInnerClassExplicitReceiver.kt") + public void testClassFieldsAndFunctionsFromInnerClassExplicitReceiver() throws Exception { + runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInnerClassExplicitReceiver.kt"); + } + @TestMetadata("explicitReceiverCompletion.kt") public void testExplicitReceiverCompletion() throws Exception { runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/explicitReceiverCompletion.kt"); @@ -2257,6 +2262,11 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/extensionPropertyAndFunctionExplicitReceiver.kt"); } + @TestMetadata("extensionPropertyAndFunctionImplicitReceiver.kt") + public void testExtensionPropertyAndFunctionImplicitReceiver() throws Exception { + runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/extensionPropertyAndFunctionImplicitReceiver.kt"); + } + @TestMetadata("genericExtensionPropertyAndFunctionExplicitReceiver.kt") public void testGenericExtensionPropertyAndFunctionExplicitReceiver() throws Exception { runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/genericExtensionPropertyAndFunctionExplicitReceiver.kt"); diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java index 166b34187b1..a5d47118752 100644 --- a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/JvmBasicCompletionTestGenerated.java @@ -2247,6 +2247,11 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInnerClass.kt"); } + @TestMetadata("classFieldsAndFunctionsFromInnerClassExplicitReceiver.kt") + public void testClassFieldsAndFunctionsFromInnerClassExplicitReceiver() throws Exception { + runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/classFieldsAndFunctionsFromInnerClassExplicitReceiver.kt"); + } + @TestMetadata("explicitReceiverCompletion.kt") public void testExplicitReceiverCompletion() throws Exception { runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/explicitReceiverCompletion.kt"); @@ -2257,6 +2262,11 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/extensionPropertyAndFunctionExplicitReceiver.kt"); } + @TestMetadata("extensionPropertyAndFunctionImplicitReceiver.kt") + public void testExtensionPropertyAndFunctionImplicitReceiver() throws Exception { + runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/extensionPropertyAndFunctionImplicitReceiver.kt"); + } + @TestMetadata("genericExtensionPropertyAndFunctionExplicitReceiver.kt") public void testGenericExtensionPropertyAndFunctionExplicitReceiver() throws Exception { runTest("idea/idea-completion/testData/basic/common/primitiveCompletion/genericExtensionPropertyAndFunctionExplicitReceiver.kt"); diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt index 0ecf9328074..e0c72f55732 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/completion/KotlinFirCompletionContributor.kt @@ -17,7 +17,6 @@ import org.jetbrains.kotlin.fir.scopes.FirScope import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.typeContext import org.jetbrains.kotlin.fir.types.ConeKotlinType -import org.jetbrains.kotlin.fir.types.classId import org.jetbrains.kotlin.fir.types.coneTypeUnsafe import org.jetbrains.kotlin.idea.fir.getFirOfClosestParent import org.jetbrains.kotlin.idea.fir.getOrBuildFirSafe @@ -27,6 +26,7 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.types.AbstractTypeChecker +import org.jetbrains.kotlin.types.model.TypeCheckerProviderContext class KotlinFirCompletionContributor : CompletionContributor() { init { @@ -53,65 +53,71 @@ private object KotlinFirCompletionProvider : CompletionProvider() - - val scopes: Sequence = sequence { + val symbols: Sequence> = sequence { + val explicitReceiver = element.explicitReceiver + val explicitReceiverType = explicitReceiver?.typeRef?.coneTypeUnsafe() val explicitReceiverScope = explicitReceiverType?.scope(completionContext.session, ScopeSession()) - if (explicitReceiverScope != null) { - yield(explicitReceiverScope) - } - yieldAll(towerDataContext.localScopes) - - val implicitReceiversScopes = towerDataContext.nonLocalTowerDataElements.mapNotNull { it.implicitReceiver?.implicitScope } - yieldAll(implicitReceiversScopes) + val implicitReceivers = towerDataContext.nonLocalTowerDataElements.mapNotNull { it.implicitReceiver } + val implicitReceiversTypes = implicitReceivers.map { it.type } + val localScopes = towerDataContext.localScopes val nonLocalScopes = towerDataContext.nonLocalTowerDataElements.mapNotNull { it.scope } - yieldAll(nonLocalScopes) - } + val implicitReceiversScopes = implicitReceivers.mapNotNull { it.implicitScope } - for (scope in scopes) { - for (symbol in scope.collectCallableSymbols()) { - val symbolName = symbol.callableId.callableName - if (symbolName.isConstructor) continue + val allNonExplicitScopes = localScopes.asSequence() + nonLocalScopes + implicitReceiversScopes - val expectedReceiverType = symbol.fir.receiverTypeRef?.coneTypeUnsafe() + val typeContext = completionContext.session.typeContext - if (expectedReceiverType != null) { - val receiverTypes = if (explicitReceiverType != null) { - listOf(explicitReceiverType) - } else { - towerDataContext.nonLocalTowerDataElements.mapNotNull { it.implicitReceiver?.type } - } + if (explicitReceiverScope != null) { + yieldAll(explicitReceiverScope.collectCallableSymbols()) - val expectedReceiverTypeIsPresent = receiverTypes.any { - AbstractTypeChecker.isSubtypeOf(completionContext.session.typeContext, it, expectedReceiverType) - } + val allApplicableExtensions = allNonExplicitScopes + .flatMap { it.collectCallableSymbols() } + .filter { it.isExtension && it.isExtensionThatCanBeCalledOnTypes(listOf(explicitReceiverType), typeContext) } - if (expectedReceiverTypeIsPresent) { - result.addElement(LookupElementBuilder.create(symbolName.toString())) - } - } else if (explicitReceiverType == null || symbol.callableId.classId == explicitReceiverType.classId) { - result.addElement(LookupElementBuilder.create(symbolName.toString())) - } + yieldAll(allApplicableExtensions) + } else { + val allAvailableSymbols = allNonExplicitScopes + .flatMap { it.collectCallableSymbols() } + .filter { !it.isExtension || it.isExtensionThatCanBeCalledOnTypes(implicitReceiversTypes, typeContext) } + + yieldAll(allAvailableSymbols) } } + + for (symbol in symbols) { + val symbolName = symbol.callableId.callableName + if (symbolName.isConstructor) continue + + result.addElement(LookupElementBuilder.create(symbolName.toString())) + } } - private fun FirScope.collectCallableSymbols(): MutableList> { - val allCallableSymbols = mutableListOf>() - fun symbolsCollector(symbol: FirCallableSymbol<*>) { - allCallableSymbols.add(symbol) - } - - for (name in getCallableNames()) { - processFunctionsByName(name, ::symbolsCollector) - processPropertiesByName(name, ::symbolsCollector) - } - - return allCallableSymbols - } - - private val Name.isConstructor get() = this == Name.special("") +} + +private val FirCallableSymbol<*>.isExtension get() = fir.receiverTypeRef != null + +private val Name.isConstructor get() = this == Name.special("") + +private fun FirScope.collectCallableSymbols(): MutableList> { + val allCallableSymbols = mutableListOf>() + fun symbolsCollector(symbol: FirCallableSymbol<*>) { + allCallableSymbols.add(symbol) + } + + for (name in getCallableNames()) { + processFunctionsByName(name, ::symbolsCollector) + processPropertiesByName(name, ::symbolsCollector) + } + + return allCallableSymbols +} + +private fun FirCallableSymbol<*>.isExtensionThatCanBeCalledOnTypes( + receivers: List, + session: TypeCheckerProviderContext +): Boolean { + val expectedReceiverType = fir.receiverTypeRef?.coneTypeUnsafe() ?: return false + return receivers.any { AbstractTypeChecker.isSubtypeOf(session, it, expectedReceiverType) } }