From b59f134f78b8ea5fe4393fbac2b05a66caef7674 Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Tue, 7 Aug 2018 21:48:26 +0200 Subject: [PATCH] Fix completing members for receivers shadowed by DslMarker #KT-20396 Fixed --- .../codeInsight/ReferenceVariantsHelper.kt | 19 ++++++---- .../jetbrains/kotlin/idea/util/CallType.kt | 23 +++--------- .../idea/util/implicitReceiversUtils.kt | 34 +++++++++++++++--- .../idea/completion/CompletionSession.kt | 11 +++--- .../basic/common/dslMarker/2receivers.kt | 4 +-- .../testData/basic/common/dslMarker/child.kt | 4 +-- .../basic/common/dslMarker/compositeDsl.kt | 9 +++-- .../basic/common/dslMarker/container.kt | 4 +-- .../basic/UnavailableDslReceiver.Data1.kt | 6 ++++ .../weighers/basic/UnavailableDslReceiver.kt | 35 +++++++++++++++++++ .../BasicCompletionWeigherTestGenerated.java | 5 +++ .../kotlin/idea/quickfix/ImportFix.kt | 15 ++++---- 12 files changed, 115 insertions(+), 54 deletions(-) create mode 100644 idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.Data1.kt create mode 100644 idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.kt diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt index 9b37eeb1cd0..ea87a74d162 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/codeInsight/ReferenceVariantsHelper.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.codeInsight import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.resolve.ResolutionFacade @@ -177,14 +178,18 @@ class ReferenceVariantsHelper( val containingDeclaration = resolutionScope.ownerDescriptor val smartCastManager = resolutionFacade.frontendService() - val implicitReceiverTypes = resolutionScope.getImplicitReceiversWithInstance().flatMap { + val languageVersionSettings = resolutionFacade.frontendService() + + val implicitReceiverTypes = resolutionScope.getImplicitReceiversWithInstance( + languageVersionSettings.supportsFeature(LanguageFeature.DslMarkersSupport) + ).flatMap { smartCastManager.getSmartCastVariantsWithLessSpecificExcluded( - it.value, - bindingContext, - containingDeclaration, - dataFlowInfo, - resolutionFacade.frontendService(), - resolutionFacade.frontendService() + it.value, + bindingContext, + containingDeclaration, + dataFlowInfo, + languageVersionSettings, + resolutionFacade.frontendService() ) }.toSet() diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt index 8c4658b1bd7..060b5bbe8cb 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/CallType.kt @@ -17,12 +17,12 @@ package org.jetbrains.kotlin.idea.util import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.resolve.frontendService import org.jetbrains.kotlin.lexer.KtTokens -import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression import org.jetbrains.kotlin.psi.psiUtil.isImportDirectiveExpression @@ -306,7 +306,9 @@ fun CallTypeAndReceiver<*, *>.receiverTypesWithIndex( ExpressionReceiver.create(receiverExpression, receiverType, bindingContext) } - val implicitReceiverValues = resolutionScope.getImplicitReceiversWithInstance().map { it.value } + val implicitReceiverValues = resolutionScope.getImplicitReceiversWithInstance( + excludeShadowedByDslMarkers = languageVersionSettings.supportsFeature(LanguageFeature.DslMarkersSupport) + ).map { it.value } val dataFlowInfo = bindingContext.getDataFlowInfoBefore(contextElement) @@ -357,20 +359,3 @@ private fun receiverValueTypes( listOf(receiverValue.type) } } - - -fun Collection.shadowedByDslMarkers(): Set { - val typesByDslScopes = LinkedHashMap>() - - this - .mapNotNull { receiver -> - val dslMarkers = receiver.extractDslMarkers() - (receiver to dslMarkers).takeIf { dslMarkers.isNotEmpty() } - } - .forEach { (v, dslMarkers) -> dslMarkers.forEach { typesByDslScopes.getOrPut(it, { mutableListOf() }) += v } } - - val shadowedDslReceivers = mutableSetOf() - typesByDslScopes.flatMapTo(shadowedDslReceivers) { (_, v) -> v.asSequence().drop(1).asIterable() } - - return shadowedDslReceivers -} diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/implicitReceiversUtils.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/implicitReceiversUtils.kt index d63b877d3d3..ad74c955894 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/implicitReceiversUtils.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/implicitReceiversUtils.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.util import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtFunctionLiteral @@ -24,14 +25,16 @@ import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.renderer.render import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.calls.DslMarkerUtils import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf import java.util.* +import kotlin.collections.LinkedHashSet -fun LexicalScope.getImplicitReceiversWithInstance(): Collection - = getImplicitReceiversWithInstanceToExpression().keys +fun LexicalScope.getImplicitReceiversWithInstance(excludeShadowedByDslMarkers: Boolean = false): Collection = + getImplicitReceiversWithInstanceToExpression(excludeShadowedByDslMarkers).keys interface ReceiverExpressionFactory { val isImmediate: Boolean @@ -48,9 +51,18 @@ fun LexicalScope.getFactoryForImplicitReceiverWithSubtypeOf(receiverType: Kotlin ?.value } -fun LexicalScope.getImplicitReceiversWithInstanceToExpression(): Map { +fun LexicalScope.getImplicitReceiversWithInstanceToExpression( + excludeShadowedByDslMarkers: Boolean = false +): Map { + val allReceivers = getImplicitReceiversHierarchy() // we use a set to workaround a bug with receiver for companion object present twice in the result of getImplicitReceiversHierarchy() - val receivers = LinkedHashSet(getImplicitReceiversHierarchy()) + val receivers = LinkedHashSet( + if (excludeShadowedByDslMarkers) { + allReceivers - allReceivers.shadowedByDslMarkers() + } else { + allReceivers + } + ) val outerDeclarationsWithInstance = LinkedHashSet() var current: DeclarationDescriptor? = ownerDescriptor @@ -109,3 +121,17 @@ private fun thisQualifierName(receiver: ReceiverParameterDescriptor): Name? { val functionLiteral = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as? KtFunctionLiteral return functionLiteral?.findLabelAndCall()?.first } + +private fun List.shadowedByDslMarkers(): Set { + val typesByDslScopes = LinkedHashMap>() + + this.mapNotNull { receiver -> + val dslMarkers = DslMarkerUtils.extractDslMarkerFqNames(receiver.value).all() + (receiver to dslMarkers).takeIf { dslMarkers.isNotEmpty() } + }.forEach { (v, dslMarkers) -> dslMarkers.forEach { typesByDslScopes.getOrPut(it, { mutableListOf() }) += v } } + + val shadowedDslReceivers = mutableSetOf() + typesByDslScopes.flatMapTo(shadowedDslReceivers) { (_, v) -> v.asSequence().drop(1).asIterable() } + + return shadowedDslReceivers +} \ No newline at end of file diff --git a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt index e1b90d47c3f..ff16e83b727 100644 --- a/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt +++ b/idea/idea-completion/src/org/jetbrains/kotlin/idea/completion/CompletionSession.kt @@ -406,18 +406,15 @@ abstract class CompletionSession( nameExpression: KtSimpleNameExpression, callTypeAndReceiver: CallTypeAndReceiver<*, *>): Collection? { var receiverTypes = callTypeAndReceiver.receiverTypesWithIndex( - bindingContext, nameExpression, moduleDescriptor, resolutionFacade, - stableSmartCastsOnly = true, /* we don't include smart cast receiver types for "unstable" receiver value to mark members grayed */ - withImplicitReceiversWhenExplicitPresent = true) + bindingContext, nameExpression, moduleDescriptor, resolutionFacade, + stableSmartCastsOnly = true, /* we don't include smart cast receiver types for "unstable" receiver value to mark members grayed */ + withImplicitReceiversWhenExplicitPresent = true + ) if (callTypeAndReceiver is CallTypeAndReceiver.SAFE || isDebuggerContext) { receiverTypes = receiverTypes?.map { ReceiverType(it.type.makeNotNullable(), it.receiverIndex) } } - if (receiverTypes != null && nameExpression.languageVersionSettings.supportsFeature(LanguageFeature.DslMarkersSupport)) { - receiverTypes -= receiverTypes.shadowedByDslMarkers() - } - return receiverTypes } } diff --git a/idea/idea-completion/testData/basic/common/dslMarker/2receivers.kt b/idea/idea-completion/testData/basic/common/dslMarker/2receivers.kt index 47c935366ba..0f8e27eacf1 100644 --- a/idea/idea-completion/testData/basic/common/dslMarker/2receivers.kt +++ b/idea/idea-completion/testData/basic/common/dslMarker/2receivers.kt @@ -43,5 +43,5 @@ fun test() { } } -// EXIST: {"lookupString":"two", "attributes":""} -// EXIST: {"lookupString":"one", "attributes":["grayed"]} +// EXIST: two +// ABSENT: one diff --git a/idea/idea-completion/testData/basic/common/dslMarker/child.kt b/idea/idea-completion/testData/basic/common/dslMarker/child.kt index fe45440099b..14b11156e18 100644 --- a/idea/idea-completion/testData/basic/common/dslMarker/child.kt +++ b/idea/idea-completion/testData/basic/common/dslMarker/child.kt @@ -37,5 +37,5 @@ fun test() { } } -// EXIST: {"lookupString":"container", "attributes":["grayed"]} -// EXIST: {"lookupString":"child", "attributes":["grayed"]} \ No newline at end of file +// ABSENT: container +// ABSENT: lookupString \ No newline at end of file diff --git a/idea/idea-completion/testData/basic/common/dslMarker/compositeDsl.kt b/idea/idea-completion/testData/basic/common/dslMarker/compositeDsl.kt index 73d3dede9ac..106b2383a46 100644 --- a/idea/idea-completion/testData/basic/common/dslMarker/compositeDsl.kt +++ b/idea/idea-completion/testData/basic/common/dslMarker/compositeDsl.kt @@ -42,7 +42,9 @@ class DslOtherChild { @SimpleOtherDsl @SimpleDsl class DslAnotherChild { + fun foo () { + } } fun dsl(body: DslRoot.() -> Unit) { @@ -66,6 +68,7 @@ fun test() { } } -// EXIST: {lookupString:"otherThing", attributes:["grayed"]} -// EXIST: {lookupString:"child", attributes:["grayed"]} -// EXIST: {lookupString:"otherChild", attributes:["grayed"]} +// EXIST: foo +// ABSENT: otherThing +// ABSENT: child +// ABSENT: otherChild diff --git a/idea/idea-completion/testData/basic/common/dslMarker/container.kt b/idea/idea-completion/testData/basic/common/dslMarker/container.kt index 4c9e1b3613b..5f5ca4f81fe 100644 --- a/idea/idea-completion/testData/basic/common/dslMarker/container.kt +++ b/idea/idea-completion/testData/basic/common/dslMarker/container.kt @@ -34,5 +34,5 @@ fun test() { } } -// EXIST: {"lookupString":"child", "attributes":["bold"]} -// EXIST: {"lookupString":"container", "attributes":["grayed"]} \ No newline at end of file +// EXIST: child +// ABSENT: container \ No newline at end of file diff --git a/idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.Data1.kt b/idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.Data1.kt new file mode 100644 index 00000000000..5310b19c452 --- /dev/null +++ b/idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.Data1.kt @@ -0,0 +1,6 @@ +package bar + +import baz.* + +fun B.booExtension() {} +fun A.booExtensionToA() {} \ No newline at end of file diff --git a/idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.kt b/idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.kt new file mode 100644 index 00000000000..e7c1c69684f --- /dev/null +++ b/idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.kt @@ -0,0 +1,35 @@ +// RUNTIME + +package baz + +@DslMarker +annotation class Marker + +@Marker +class A { + val booFromA = "" + + fun booFromA2() {} + + fun section(block: B.() -> Unit) {} +} + +@Marker +class B { + fun booFromB() {} +} + +fun x() { + A().apply { + section { + boo + } + } + +} + +fun booUnrelated() { + +} + +// ORDER: booFromB, booUnrelated, booleanArrayOf, booExtension \ No newline at end of file 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 7b235105677..c7cda5ae045 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 @@ -204,6 +204,11 @@ public class BasicCompletionWeigherTestGenerated extends AbstractBasicCompletion runTest("idea/idea-completion/testData/weighers/basic/SuperMembers.kt"); } + @TestMetadata("UnavailableDslReceiver.kt") + public void testUnavailableDslReceiver() throws Exception { + runTest("idea/idea-completion/testData/weighers/basic/UnavailableDslReceiver.kt"); + } + @TestMetadata("idea/idea-completion/testData/weighers/basic/expectedInfo") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt index 777ce941313..b7c6d06c664 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt @@ -303,15 +303,14 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix indicesHelper.getKotlinEnumsByName(name).filterTo(result, filterByCallType) val resolutionFacade = element.getResolutionFacade() - var actualReceiverTypes = callTypeAndReceiver - .receiverTypesWithIndex(bindingContext, element, - resolutionFacade.moduleDescriptor, resolutionFacade, - stableSmartCastsOnly = false, - withImplicitReceiversWhenExplicitPresent = true).orEmpty() + val actualReceiverTypes = callTypeAndReceiver + .receiverTypesWithIndex( + bindingContext, element, + resolutionFacade.moduleDescriptor, resolutionFacade, + stableSmartCastsOnly = false, + withImplicitReceiversWhenExplicitPresent = true + ).orEmpty() - if (element.languageVersionSettings.supportsFeature(LanguageFeature.DslMarkersSupport)) { - actualReceiverTypes -= actualReceiverTypes.shadowedByDslMarkers() - } val explicitReceiverTypes = actualReceiverTypes.filterNot { it.implicit }