From 094125c97056bb8703b1c4c620f7e94ec3496933 Mon Sep 17 00:00:00 2001 From: Simon Ogorodnik Date: Fri, 21 Jul 2017 19:38:54 +0300 Subject: [PATCH] KT-19154: Fix auto-import to check receiver for members properly #KT-19154 fixed --- .../jetbrains/kotlin/idea/util/CallType.kt | 19 ++++++++ .../idea/completion/CompletionSession.kt | 15 +----- .../kotlin/idea/quickfix/ImportFix.kt | 47 ++++++++++++++----- .../quickfix/autoImports/dslMarkers.after.kt | 13 +++++ .../dslMarkers.before.Dependency.kt | 25 ++++++++++ .../autoImports/dslMarkers.before.Main.kt | 11 +++++ ...FunctionImportWhenReceiverPresent.after.kt | 11 +++++ ...rtWhenReceiverPresent.before.Dependency.kt | 5 ++ ...onImportWhenReceiverPresent.before.Main.kt | 9 ++++ .../QuickFixMultiFileTestGenerated.java | 12 +++++ 10 files changed, 141 insertions(+), 26 deletions(-) create mode 100644 idea/testData/quickfix/autoImports/dslMarkers.after.kt create mode 100644 idea/testData/quickfix/autoImports/dslMarkers.before.Dependency.kt create mode 100644 idea/testData/quickfix/autoImports/dslMarkers.before.Main.kt create mode 100644 idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.after.kt create mode 100644 idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.before.Dependency.kt create mode 100644 idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.before.Main.kt 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 a6347e8bc36..2638e21cce6 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 @@ -20,12 +20,14 @@ import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.resolve.ResolutionFacade 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 import org.jetbrains.kotlin.psi.psiUtil.isPackageDirectiveExpression import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore +import org.jetbrains.kotlin.resolve.calls.checkers.DslScopeViolationCallChecker.extractDslMarkerFqNames import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory import org.jetbrains.kotlin.resolve.calls.smartcasts.SmartCastManager @@ -328,3 +330,20 @@ private fun receiverValueTypes( listOf(receiverValue.type) } } + + +fun Collection.shadowedByDslMarkers(): Set { + val typesByDslScopes = LinkedHashMap>() + + this + .mapNotNull { receiver -> + val dslMarkers = receiver.type.extractDslMarkerFqNames() + (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 323fbff6109..376fefc7663 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 @@ -403,20 +403,7 @@ abstract class CompletionSession( } if (receiverTypes != null && nameExpression.languageVersionSettings.supportsFeature(LanguageFeature.DslMarkersSupport)) { - - val typesByDslScopes = LinkedHashMap>() - - receiverTypes - .mapNotNull { receiver -> - val dslMarkers = receiver.type.extractDslMarkerFqNames() - (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() } - - receiverTypes -= shadowedDslReceivers + receiverTypes -= receiverTypes.shadowedByDslMarkers() } return receiverTypes diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt index c4bf26453e7..e9d5a96ad1c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt @@ -31,6 +31,7 @@ import com.intellij.psi.PsiErrorElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiModifier import com.intellij.psi.util.PsiModificationTracker +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.DiagnosticFactory @@ -51,10 +52,9 @@ import org.jetbrains.kotlin.idea.core.isVisible import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.project.TargetPlatformDetector +import org.jetbrains.kotlin.idea.project.languageVersionSettings import org.jetbrains.kotlin.idea.references.mainReference -import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver -import org.jetbrains.kotlin.idea.util.getResolutionScope -import org.jetbrains.kotlin.idea.util.receiverTypes +import org.jetbrains.kotlin.idea.util.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.js.resolve.JsPlatform import org.jetbrains.kotlin.name.FqName @@ -74,6 +74,7 @@ import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScope import org.jetbrains.kotlin.resolve.scopes.utils.collectFunctions import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf import org.jetbrains.kotlin.util.OperatorNameConventions +import org.jetbrains.kotlin.utils.ifEmpty import java.util.* /** @@ -300,17 +301,27 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix indicesHelper.getKotlinEnumsByName(name).filterTo(result, filterByCallType) val resolutionFacade = element.getResolutionFacade() - val actualReceiverTypes = - callTypeAndReceiver.receiverTypes(bindingContext, element, resolutionFacade.moduleDescriptor, resolutionFacade, false).orEmpty() + var 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 } + + val checkDispatchReceiver = when(callTypeAndReceiver) { + is CallTypeAndReceiver.OPERATOR, is CallTypeAndReceiver.INFIX -> true + else -> false + } val processor = { descriptor: CallableDescriptor -> - if (descriptor.canBeReferencedViaImport() && filterByCallType(descriptor)) { - val extensionReceiverType = descriptor.extensionReceiverParameter?.type - - if ((actualReceiverTypes.isEmpty() && extensionReceiverType == null) || - (extensionReceiverType != null && actualReceiverTypes.any { it.isSubtypeOf(extensionReceiverType) })) { - result.add(descriptor) - } + if (descriptor.canBeReferencedViaImport() && filterByCallType(descriptor) + && descriptor.isValidByReceiversFor(explicitReceiverTypes, actualReceiverTypes, checkDispatchReceiver)) { + result.add(descriptor) } } @@ -331,6 +342,18 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix } + private fun CallableDescriptor.isValidByReceiversFor(explicitReceiverTypes: Collection, + allReceiverTypes: Collection, + checkDispatchReceiver: Boolean): Boolean { + val bothReceivers = listOfNotNull(extensionReceiverParameter, dispatchReceiverParameter.takeIf { checkDispatchReceiver }) + + val receiverTypesPerReceiver = generateSequence(explicitReceiverTypes.ifEmpty { allReceiverTypes }) { allReceiverTypes } + + return bothReceivers + .zip(receiverTypesPerReceiver.asIterable()) + .all { (receiver, possibleTypes) -> possibleTypes.any { it.type.isSubtypeOf(receiver.type) } } + } + override fun fillCandidates( name: String, callTypeAndReceiver: CallTypeAndReceiver<*, *>, diff --git a/idea/testData/quickfix/autoImports/dslMarkers.after.kt b/idea/testData/quickfix/autoImports/dslMarkers.after.kt new file mode 100644 index 00000000000..ba01b4ed61e --- /dev/null +++ b/idea/testData/quickfix/autoImports/dslMarkers.after.kt @@ -0,0 +1,13 @@ +import BExtSpace.aaa + +// "Import" "true" +// WITH_RUNTIME +// ERROR: Unresolved reference: aaa + +fun test() { + AAA().apply { + sub { + aaa() + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/dslMarkers.before.Dependency.kt b/idea/testData/quickfix/autoImports/dslMarkers.before.Dependency.kt new file mode 100644 index 00000000000..369fd57e92e --- /dev/null +++ b/idea/testData/quickfix/autoImports/dslMarkers.before.Dependency.kt @@ -0,0 +1,25 @@ + +@DslMarker +annotation class DSL + +@DSL +class AAA { + fun sub(l: BBB.() -> Unit) { + l(BBB()) + } +} + +@DSL +class BBB + +object AExtSpace { + fun AAA.aaa() { + + } +} + +object BExtSpace { + fun BBB.aaa() { + + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/dslMarkers.before.Main.kt b/idea/testData/quickfix/autoImports/dslMarkers.before.Main.kt new file mode 100644 index 00000000000..49ffc4be76f --- /dev/null +++ b/idea/testData/quickfix/autoImports/dslMarkers.before.Main.kt @@ -0,0 +1,11 @@ +// "Import" "true" +// WITH_RUNTIME +// ERROR: Unresolved reference: aaa + +fun test() { + AAA().apply { + sub { + aaa() + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.after.kt b/idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.after.kt new file mode 100644 index 00000000000..8483fa277d8 --- /dev/null +++ b/idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.after.kt @@ -0,0 +1,11 @@ +// "Import" "true" +// ERROR: Unresolved reference: foobar +package p2 + +import p1.Some.foobar + +class A { + fun some() { + foobar() + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.before.Dependency.kt b/idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.before.Dependency.kt new file mode 100644 index 00000000000..4d0f6fcf45b --- /dev/null +++ b/idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.before.Dependency.kt @@ -0,0 +1,5 @@ +package p1 + +object Some { + fun foobar() {} +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.before.Main.kt b/idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.before.Main.kt new file mode 100644 index 00000000000..1dce860b369 --- /dev/null +++ b/idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.before.Main.kt @@ -0,0 +1,9 @@ +// "Import" "true" +// ERROR: Unresolved reference: foobar +package p2 + +class A { + fun some() { + foobar() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index 4e4ba0d6eed..0ff0a70397c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -131,6 +131,12 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes doTestWithExtraFile(fileName); } + @TestMetadata("dslMarkers.before.Main.kt") + public void testDslMarkers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/dslMarkers.before.Main.kt"); + doTestWithExtraFile(fileName); + } + @TestMetadata("extensionFunctionImport.before.Main.kt") public void testExtensionFunctionImport() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/extensionFunctionImport.before.Main.kt"); @@ -545,6 +551,12 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes doTestWithExtraFile(fileName); } + @TestMetadata("objectMemberFunctionImportWhenReceiverPresent.before.Main.kt") + public void testObjectMemberFunctionImportWhenReceiverPresent() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/objectMemberFunctionImportWhenReceiverPresent.before.Main.kt"); + doTestWithExtraFile(fileName); + } + @TestMetadata("operatorAssignPlus.test") public void testOperatorAssignPlus() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/autoImports/operatorAssignPlus.test");