From c463fad3b74b95a7acf711758d2514241bb1b157 Mon Sep 17 00:00:00 2001 From: Roman Golyshev Date: Thu, 26 Sep 2019 17:51:11 +0300 Subject: [PATCH] KT-34000: Allow autoimport to suggest fixes in qualified expressions - it is required to be able to autoimport extensions from objects, not only from the top level - use `substituteExtensionIfCallable` to handle generics for extension methods - move finding expression receivers to the separate method, add `Receivers` value class to hold found receivers - change `CallableDescriptor.isValidByReceiversFor` to return false if explicit receiver is not required for the descriptor - ^KT-34000 Fixed --- .../kotlin/idea/quickfix/ImportFix.kt | 79 ++++++++++++------- ...nctionImportWhenExplicitReceiverPresent.kt | 12 +++ ...ImportWhenExplicitReceiverPresent.kt.after | 14 ++++ ...opertyImportWhenExplicitReceiverPresent.kt | 12 +++ ...ImportWhenExplicitReceiverPresent.kt.after | 14 ++++ ...nctionImportWhenExplicitReceiverPresent.kt | 26 ++++++ ...ImportWhenExplicitReceiverPresent.kt.after | 28 +++++++ ...enericImportWhenExplicitReceiverPresent.kt | 12 +++ ...ImportWhenExplicitReceiverPresent.kt.after | 14 ++++ ...opertyImportWhenExplicitReceiverPresent.kt | 26 ++++++ ...ImportWhenExplicitReceiverPresent.kt.after | 28 +++++++ ...enericImportWhenExplicitReceiverPresent.kt | 12 +++ ...ImportWhenExplicitReceiverPresent.kt.after | 14 ++++ .../idea/quickfix/QuickFixTestGenerated.java | 30 +++++++ 14 files changed, 294 insertions(+), 27 deletions(-) create mode 100644 idea/testData/quickfix/autoImports/companionObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt create mode 100644 idea/testData/quickfix/autoImports/companionObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt.after create mode 100644 idea/testData/quickfix/autoImports/companionObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt create mode 100644 idea/testData/quickfix/autoImports/companionObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt.after create mode 100644 idea/testData/quickfix/autoImports/objectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt create mode 100644 idea/testData/quickfix/autoImports/objectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt.after create mode 100644 idea/testData/quickfix/autoImports/objectMemberExtensionFunctionWithGenericImportWhenExplicitReceiverPresent.kt create mode 100644 idea/testData/quickfix/autoImports/objectMemberExtensionFunctionWithGenericImportWhenExplicitReceiverPresent.kt.after create mode 100644 idea/testData/quickfix/autoImports/objectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt create mode 100644 idea/testData/quickfix/autoImports/objectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt.after create mode 100644 idea/testData/quickfix/autoImports/objectMemberExtensionPropertyWithGenericImportWhenExplicitReceiverPresent.kt create mode 100644 idea/testData/quickfix/autoImports/objectMemberExtensionPropertyWithGenericImportWhenExplicitReceiverPresent.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt index 26bb3e31bc9..9086db34352 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ImportFix.kt @@ -59,9 +59,9 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.ExplicitImportsScope import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScope import org.jetbrains.kotlin.resolve.scopes.utils.collectFunctions +import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf import org.jetbrains.kotlin.util.OperatorNameConventions -import org.jetbrains.kotlin.utils.ifEmpty import java.util.* /** @@ -288,7 +288,7 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix ): List { val element = element ?: return emptyList() - if (element.isImportDirectiveExpression() || isSelectorInQualified(element)) return emptyList() + if (element.isImportDirectiveExpression()) return emptyList() val result = ArrayList() @@ -296,17 +296,7 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix indicesHelper.getKotlinEnumsByName(name).filterTo(result, filterByCallType) - val resolutionFacade = element.getResolutionFacade() - val actualReceiverTypes = callTypeAndReceiver - .receiverTypesWithIndex( - bindingContext, element, - resolutionFacade.moduleDescriptor, resolutionFacade, - stableSmartCastsOnly = false, - withImplicitReceiversWhenExplicitPresent = true - ).orEmpty() - - - val explicitReceiverTypes = actualReceiverTypes.filterNot { it.implicit } + val actualReceivers = getReceiversForExpression(element, callTypeAndReceiver, bindingContext) val checkDispatchReceiver = when (callTypeAndReceiver) { is CallTypeAndReceiver.OPERATOR, is CallTypeAndReceiver.INFIX -> true @@ -314,10 +304,12 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix } val processor = { descriptor: CallableDescriptor -> - if (descriptor.canBeReferencedViaImport() && filterByCallType(descriptor) - && descriptor.isValidByReceiversFor(explicitReceiverTypes, actualReceiverTypes, checkDispatchReceiver) - ) { - result.add(descriptor) + if (descriptor.canBeReferencedViaImport() && filterByCallType(descriptor)) { + if (descriptor.extensionReceiverParameter != null) { + result.addAll(descriptor.substituteExtensionIfCallable(actualReceivers.allReceivers, callTypeAndReceiver.callType)) + } else if (descriptor.isValidByReceiversFor(actualReceivers, checkDispatchReceiver)) { + result.add(descriptor) + } } } @@ -337,19 +329,52 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix return result } + /** + * Currently at most one explicit receiver can be used in expression, but it can change in the future, + * so we use `Collection` to represent explicit receivers. + */ + private class Receivers(val explicitReceivers: Collection, val allReceivers: Collection) - private fun CallableDescriptor.isValidByReceiversFor( - explicitReceiverTypes: Collection, - allReceiverTypes: Collection, - checkDispatchReceiver: Boolean - ): Boolean { - val bothReceivers = listOfNotNull(extensionReceiverParameter, dispatchReceiverParameter.takeIf { checkDispatchReceiver }) + private fun getReceiversForExpression( + element: KtSimpleNameExpression, + callTypeAndReceiver: CallTypeAndReceiver<*, *>, + bindingContext: BindingContext + ): Receivers { + val resolutionFacade = element.getResolutionFacade() + val actualReceiverTypes = callTypeAndReceiver + .receiverTypesWithIndex( + bindingContext, element, + resolutionFacade.moduleDescriptor, resolutionFacade, + stableSmartCastsOnly = false, + withImplicitReceiversWhenExplicitPresent = true + ).orEmpty() - val receiverTypesPerReceiver = generateSequence(explicitReceiverTypes.ifEmpty { allReceiverTypes }) { allReceiverTypes } + val explicitReceiverType = actualReceiverTypes.filterNot { it.implicit } - return bothReceivers - .zip(receiverTypesPerReceiver.asIterable()) - .all { (receiver, possibleTypes) -> possibleTypes.any { it.type.isSubtypeOf(receiver.type) } } + return Receivers( + explicitReceiverType.map { it.type }, + actualReceiverTypes.map { it.type } + ) + } + + /** + * This methods accepts only callables with no extension receiver because it ignores generics + * and does not perform any substitution. + * + * @return true iff [this] descriptor can be called given [actualReceivers] present in scope AND + * passed [Receivers.explicitReceivers] are satisfied if present. + */ + private fun CallableDescriptor.isValidByReceiversFor(actualReceivers: Receivers, checkDispatchReceiver: Boolean): Boolean { + require(extensionReceiverParameter == null) { "This method works only on non-extension callables, got $this" } + + val dispatcherReceiver = dispatchReceiverParameter.takeIf { checkDispatchReceiver } + + return if (dispatcherReceiver == null) { + actualReceivers.explicitReceivers.isEmpty() + } else { + val typesToCheck = with(actualReceivers) { explicitReceivers.ifEmpty { allReceivers } } + typesToCheck.any { it.isSubtypeOf(dispatcherReceiver.type) } + } } override fun fillCandidates( diff --git a/idea/testData/quickfix/autoImports/companionObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt b/idea/testData/quickfix/autoImports/companionObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt new file mode 100644 index 00000000000..5a70f70feb5 --- /dev/null +++ b/idea/testData/quickfix/autoImports/companionObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt @@ -0,0 +1,12 @@ +// "Import" "true" +package p + +class T { + companion object { + fun T.foobar() {} + } +} + +fun usage(t: T) { + t.foobar() +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/companionObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt.after b/idea/testData/quickfix/autoImports/companionObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt.after new file mode 100644 index 00000000000..1c53a739c16 --- /dev/null +++ b/idea/testData/quickfix/autoImports/companionObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt.after @@ -0,0 +1,14 @@ +// "Import" "true" +package p + +import p.T.Companion.foobar + +class T { + companion object { + fun T.foobar() {} + } +} + +fun usage(t: T) { + t.foobar() +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/companionObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt b/idea/testData/quickfix/autoImports/companionObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt new file mode 100644 index 00000000000..0cabe6be050 --- /dev/null +++ b/idea/testData/quickfix/autoImports/companionObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt @@ -0,0 +1,12 @@ +// "Import" "true" +package p + +class T { + companion object { + val T.foobar get = 10 + } +} + +fun usage(t: T) { + t.foobar +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/companionObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt.after b/idea/testData/quickfix/autoImports/companionObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt.after new file mode 100644 index 00000000000..b07cab23a13 --- /dev/null +++ b/idea/testData/quickfix/autoImports/companionObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt.after @@ -0,0 +1,14 @@ +// "Import" "true" +package p + +import p.T.Companion.foobar + +class T { + companion object { + val T.foobar get = 10 + } +} + +fun usage(t: T) { + t.foobar +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt b/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt new file mode 100644 index 00000000000..2e0201d176a --- /dev/null +++ b/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt @@ -0,0 +1,26 @@ +// "Import" "true" +package p + +class T +class NotT +class NotTT + +object TopLevelObject1 { + fun T.foobar() {} +} + +object TopLevelObject2 { + fun NotT.foobar() {} // other type, should not be imported +} + +object TopLevelObject3 { + fun foobar() {} // not an extension, should not be imported +} + +object TopLevelObject4 { + fun NotTT.foobar() {} // NotTT present as receiver, but explicit receiver shuts him down +} + +fun NotTT.usage(t: T) { + t.foobar() +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt.after b/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt.after new file mode 100644 index 00000000000..e2c62234d74 --- /dev/null +++ b/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt.after @@ -0,0 +1,28 @@ +// "Import" "true" +package p + +import p.TopLevelObject1.foobar + +class T +class NotT +class NotTT + +object TopLevelObject1 { + fun T.foobar() {} +} + +object TopLevelObject2 { + fun NotT.foobar() {} // other type, should not be imported +} + +object TopLevelObject3 { + fun foobar() {} // not an extension, should not be imported +} + +object TopLevelObject4 { + fun NotTT.foobar() {} // NotTT present as receiver, but explicit receiver shuts him down +} + +fun NotTT.usage(t: T) { + t.foobar() +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionWithGenericImportWhenExplicitReceiverPresent.kt b/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionWithGenericImportWhenExplicitReceiverPresent.kt new file mode 100644 index 00000000000..eba2db4ae86 --- /dev/null +++ b/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionWithGenericImportWhenExplicitReceiverPresent.kt @@ -0,0 +1,12 @@ +// "Import" "true" +package p + +class T + +object TopLevelObject1 { + fun A.foobar() {} +} + +fun usage(t: T) { + t.foobar() +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionWithGenericImportWhenExplicitReceiverPresent.kt.after b/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionWithGenericImportWhenExplicitReceiverPresent.kt.after new file mode 100644 index 00000000000..48a0b94cb0d --- /dev/null +++ b/idea/testData/quickfix/autoImports/objectMemberExtensionFunctionWithGenericImportWhenExplicitReceiverPresent.kt.after @@ -0,0 +1,14 @@ +// "Import" "true" +package p + +import p.TopLevelObject1.foobar + +class T + +object TopLevelObject1 { + fun A.foobar() {} +} + +fun usage(t: T) { + t.foobar() +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt b/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt new file mode 100644 index 00000000000..d62c22b7c81 --- /dev/null +++ b/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt @@ -0,0 +1,26 @@ +// "Import" "true" +package p + +class T +class NotT +class NotTT + +object TopLevelObject1 { + val T.foobar get() = 10 +} + +object TopLevelObject2 { + val NotT.foobar get() = 10 // other type, should not be imported +} + +object TopLevelObject3 { + val foobar get() = 10 // not an extension, should not be imported +} + +object TopLevelObject4 { + val NotTT.foobar get() = 10 // NotTT present as receiver, but explicit receiver shuts him down +} + +fun NotTT.usage(t: T) { + t.foobar +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt.after b/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt.after new file mode 100644 index 00000000000..a9d323aeea6 --- /dev/null +++ b/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt.after @@ -0,0 +1,28 @@ +// "Import" "true" +package p + +import p.TopLevelObject1.foobar + +class T +class NotT +class NotTT + +object TopLevelObject1 { + val T.foobar get() = 10 +} + +object TopLevelObject2 { + val NotT.foobar get() = 10 // other type, should not be imported +} + +object TopLevelObject3 { + val foobar get() = 10 // not an extension, should not be imported +} + +object TopLevelObject4 { + val NotTT.foobar get() = 10 // NotTT present as receiver, but explicit receiver shuts him down +} + +fun NotTT.usage(t: T) { + t.foobar +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyWithGenericImportWhenExplicitReceiverPresent.kt b/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyWithGenericImportWhenExplicitReceiverPresent.kt new file mode 100644 index 00000000000..b9d2fd84e9a --- /dev/null +++ b/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyWithGenericImportWhenExplicitReceiverPresent.kt @@ -0,0 +1,12 @@ +// "Import" "true" +package p + +class T + +object TopLevelObject1 { + val A.foobar get() = 10 +} + +fun usage(t: T) { + t.foobar +} \ No newline at end of file diff --git a/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyWithGenericImportWhenExplicitReceiverPresent.kt.after b/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyWithGenericImportWhenExplicitReceiverPresent.kt.after new file mode 100644 index 00000000000..9f074fef21a --- /dev/null +++ b/idea/testData/quickfix/autoImports/objectMemberExtensionPropertyWithGenericImportWhenExplicitReceiverPresent.kt.after @@ -0,0 +1,14 @@ +// "Import" "true" +package p + +import p.TopLevelObject1.foobar + +class T + +object TopLevelObject1 { + val A.foobar get() = 10 +} + +fun usage(t: T) { + t.foobar +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 8ab259a3b98..a220cb7cf8c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -1766,6 +1766,16 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { runTest("idea/testData/quickfix/autoImports/checkNoStackOverflowInImportInnerClassInCurrentFile.kt"); } + @TestMetadata("companionObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt") + public void testCompanionObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent() throws Exception { + runTest("idea/testData/quickfix/autoImports/companionObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt"); + } + + @TestMetadata("companionObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt") + public void testCompanionObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent() throws Exception { + runTest("idea/testData/quickfix/autoImports/companionObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt"); + } + @TestMetadata("conflictWithClassIdentifier.kt") public void testConflictWithClassIdentifier() throws Exception { runTest("idea/testData/quickfix/autoImports/conflictWithClassIdentifier.kt"); @@ -1886,6 +1896,26 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { runTest("idea/testData/quickfix/autoImports/notForThisLabel.kt"); } + @TestMetadata("objectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt") + public void testObjectMemberExtensionFunctionImportWhenExplicitReceiverPresent() throws Exception { + runTest("idea/testData/quickfix/autoImports/objectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt"); + } + + @TestMetadata("objectMemberExtensionFunctionWithGenericImportWhenExplicitReceiverPresent.kt") + public void testObjectMemberExtensionFunctionWithGenericImportWhenExplicitReceiverPresent() throws Exception { + runTest("idea/testData/quickfix/autoImports/objectMemberExtensionFunctionWithGenericImportWhenExplicitReceiverPresent.kt"); + } + + @TestMetadata("objectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt") + public void testObjectMemberExtensionPropertyImportWhenExplicitReceiverPresent() throws Exception { + runTest("idea/testData/quickfix/autoImports/objectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt"); + } + + @TestMetadata("objectMemberExtensionPropertyWithGenericImportWhenExplicitReceiverPresent.kt") + public void testObjectMemberExtensionPropertyWithGenericImportWhenExplicitReceiverPresent() throws Exception { + runTest("idea/testData/quickfix/autoImports/objectMemberExtensionPropertyWithGenericImportWhenExplicitReceiverPresent.kt"); + } + @TestMetadata("sAMConstructorFromLambda.kt") public void testSAMConstructorFromLambda() throws Exception { runTest("idea/testData/quickfix/autoImports/sAMConstructorFromLambda.kt");