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:
Roman Golyshev
2019-09-26 17:51:11 +03:00
committed by Roman Golyshev
parent b2d2165342
commit c463fad3b7
14 changed files with 294 additions and 27 deletions
@@ -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(
@@ -0,0 +1,12 @@
// "Import" "true"
package p
class T {
companion object {
fun T.foobar() {}
}
}
fun usage(t: T) {
t.<caret>foobar()
}
@@ -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()
}
@@ -0,0 +1,12 @@
// "Import" "true"
package p
class T {
companion object {
val T.foobar get = 10
}
}
fun usage(t: T) {
t.<caret>foobar
}
@@ -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
}
@@ -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()
}
@@ -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()
}
@@ -0,0 +1,12 @@
// "Import" "true"
package p
class T
object TopLevelObject1 {
fun <A> A.foobar() {}
}
fun usage(t: T) {
t.<caret>foobar()
}
@@ -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()
}
@@ -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>
}
@@ -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
}
@@ -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
}
@@ -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");