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.ExplicitImportsScope
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScope
|
import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScope
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.collectFunctions
|
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.types.typeUtil.isSubtypeOf
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
import org.jetbrains.kotlin.utils.ifEmpty
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -288,7 +288,7 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix
|
|||||||
): List<DeclarationDescriptor> {
|
): List<DeclarationDescriptor> {
|
||||||
|
|
||||||
val element = element ?: return emptyList()
|
val element = element ?: return emptyList()
|
||||||
if (element.isImportDirectiveExpression() || isSelectorInQualified(element)) return emptyList()
|
if (element.isImportDirectiveExpression()) return emptyList()
|
||||||
|
|
||||||
val result = ArrayList<DeclarationDescriptor>()
|
val result = ArrayList<DeclarationDescriptor>()
|
||||||
|
|
||||||
@@ -296,17 +296,7 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix
|
|||||||
|
|
||||||
indicesHelper.getKotlinEnumsByName(name).filterTo(result, filterByCallType)
|
indicesHelper.getKotlinEnumsByName(name).filterTo(result, filterByCallType)
|
||||||
|
|
||||||
val resolutionFacade = element.getResolutionFacade()
|
val actualReceivers = getReceiversForExpression(element, callTypeAndReceiver, bindingContext)
|
||||||
val actualReceiverTypes = callTypeAndReceiver
|
|
||||||
.receiverTypesWithIndex(
|
|
||||||
bindingContext, element,
|
|
||||||
resolutionFacade.moduleDescriptor, resolutionFacade,
|
|
||||||
stableSmartCastsOnly = false,
|
|
||||||
withImplicitReceiversWhenExplicitPresent = true
|
|
||||||
).orEmpty()
|
|
||||||
|
|
||||||
|
|
||||||
val explicitReceiverTypes = actualReceiverTypes.filterNot { it.implicit }
|
|
||||||
|
|
||||||
val checkDispatchReceiver = when (callTypeAndReceiver) {
|
val checkDispatchReceiver = when (callTypeAndReceiver) {
|
||||||
is CallTypeAndReceiver.OPERATOR, is CallTypeAndReceiver.INFIX -> true
|
is CallTypeAndReceiver.OPERATOR, is CallTypeAndReceiver.INFIX -> true
|
||||||
@@ -314,10 +304,12 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix
|
|||||||
}
|
}
|
||||||
|
|
||||||
val processor = { descriptor: CallableDescriptor ->
|
val processor = { descriptor: CallableDescriptor ->
|
||||||
if (descriptor.canBeReferencedViaImport() && filterByCallType(descriptor)
|
if (descriptor.canBeReferencedViaImport() && filterByCallType(descriptor)) {
|
||||||
&& descriptor.isValidByReceiversFor(explicitReceiverTypes, actualReceiverTypes, checkDispatchReceiver)
|
if (descriptor.extensionReceiverParameter != null) {
|
||||||
) {
|
result.addAll(descriptor.substituteExtensionIfCallable(actualReceivers.allReceivers, callTypeAndReceiver.callType))
|
||||||
result.add(descriptor)
|
} else if (descriptor.isValidByReceiversFor(actualReceivers, checkDispatchReceiver)) {
|
||||||
|
result.add(descriptor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,19 +329,52 @@ internal class ImportFix(expression: KtSimpleNameExpression) : OrdinaryImportFix
|
|||||||
return result
|
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(
|
private fun getReceiversForExpression(
|
||||||
explicitReceiverTypes: Collection<ReceiverType>,
|
element: KtSimpleNameExpression,
|
||||||
allReceiverTypes: Collection<ReceiverType>,
|
callTypeAndReceiver: CallTypeAndReceiver<*, *>,
|
||||||
checkDispatchReceiver: Boolean
|
bindingContext: BindingContext
|
||||||
): Boolean {
|
): Receivers {
|
||||||
val bothReceivers = listOfNotNull(extensionReceiverParameter, dispatchReceiverParameter.takeIf { checkDispatchReceiver })
|
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
|
return Receivers(
|
||||||
.zip(receiverTypesPerReceiver.asIterable())
|
explicitReceiverType.map { it.type },
|
||||||
.all { (receiver, possibleTypes) -> possibleTypes.any { it.type.isSubtypeOf(receiver.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(
|
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");
|
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")
|
@TestMetadata("conflictWithClassIdentifier.kt")
|
||||||
public void testConflictWithClassIdentifier() throws Exception {
|
public void testConflictWithClassIdentifier() throws Exception {
|
||||||
runTest("idea/testData/quickfix/autoImports/conflictWithClassIdentifier.kt");
|
runTest("idea/testData/quickfix/autoImports/conflictWithClassIdentifier.kt");
|
||||||
@@ -1886,6 +1896,26 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
runTest("idea/testData/quickfix/autoImports/notForThisLabel.kt");
|
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")
|
@TestMetadata("sAMConstructorFromLambda.kt")
|
||||||
public void testSAMConstructorFromLambda() throws Exception {
|
public void testSAMConstructorFromLambda() throws Exception {
|
||||||
runTest("idea/testData/quickfix/autoImports/sAMConstructorFromLambda.kt");
|
runTest("idea/testData/quickfix/autoImports/sAMConstructorFromLambda.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user