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
This commit is contained in:
committed by
Roman Golyshev
parent
b2d2165342
commit
c463fad3b7
@@ -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<DeclarationDescriptor> {
|
||||
|
||||
val element = element ?: return emptyList()
|
||||
if (element.isImportDirectiveExpression() || isSelectorInQualified(element)) return emptyList()
|
||||
if (element.isImportDirectiveExpression()) return emptyList()
|
||||
|
||||
val result = ArrayList<DeclarationDescriptor>()
|
||||
|
||||
@@ -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<KotlinType>, val allReceivers: Collection<KotlinType>)
|
||||
|
||||
private fun CallableDescriptor.isValidByReceiversFor(
|
||||
explicitReceiverTypes: Collection<ReceiverType>,
|
||||
allReceiverTypes: Collection<ReceiverType>,
|
||||
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(
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Import" "true"
|
||||
package p
|
||||
|
||||
class T {
|
||||
companion object {
|
||||
fun T.foobar() {}
|
||||
}
|
||||
}
|
||||
|
||||
fun usage(t: T) {
|
||||
t.<caret>foobar()
|
||||
}
|
||||
+14
@@ -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.<caret>foobar()
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Import" "true"
|
||||
package p
|
||||
|
||||
class T {
|
||||
companion object {
|
||||
val T.foobar get = 10
|
||||
}
|
||||
}
|
||||
|
||||
fun usage(t: T) {
|
||||
t.<caret>foobar
|
||||
}
|
||||
+14
@@ -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.<caret>foobar
|
||||
}
|
||||
idea/testData/quickfix/autoImports/objectMemberExtensionFunctionImportWhenExplicitReceiverPresent.kt
Vendored
+26
@@ -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.<caret>foobar()
|
||||
}
|
||||
+28
@@ -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()
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Import" "true"
|
||||
package p
|
||||
|
||||
class T
|
||||
|
||||
object TopLevelObject1 {
|
||||
fun <A> A.foobar() {}
|
||||
}
|
||||
|
||||
fun usage(t: T) {
|
||||
t.<caret>foobar()
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Import" "true"
|
||||
package p
|
||||
|
||||
import p.TopLevelObject1.foobar
|
||||
|
||||
class T
|
||||
|
||||
object TopLevelObject1 {
|
||||
fun <A> A.foobar() {}
|
||||
}
|
||||
|
||||
fun usage(t: T) {
|
||||
t.foobar()
|
||||
}
|
||||
idea/testData/quickfix/autoImports/objectMemberExtensionPropertyImportWhenExplicitReceiverPresent.kt
Vendored
+26
@@ -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<caret>
|
||||
}
|
||||
+28
@@ -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
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Import" "true"
|
||||
package p
|
||||
|
||||
class T
|
||||
|
||||
object TopLevelObject1 {
|
||||
val <A> A.foobar get() = 10
|
||||
}
|
||||
|
||||
fun usage(t: T) {
|
||||
t.<caret>foobar
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// "Import" "true"
|
||||
package p
|
||||
|
||||
import p.TopLevelObject1.foobar
|
||||
|
||||
class T
|
||||
|
||||
object TopLevelObject1 {
|
||||
val <A> A.foobar get() = 10
|
||||
}
|
||||
|
||||
fun usage(t: T) {
|
||||
t.<caret>foobar
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user