Move: Fix processing of implicit extension 'invoke' calls

Also fix bogus "unused import" inspection reported on such calls

 #KT-17496 Fixed
This commit is contained in:
Alexey Sedunov
2017-04-20 15:18:45 +03:00
parent c492f339e6
commit f575e2710f
21 changed files with 250 additions and 11 deletions
@@ -18,7 +18,9 @@ package org.jetbrains.kotlin.idea.references
import com.intellij.psi.*
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.builtins.isExtensionFunctionType
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
@@ -232,8 +234,15 @@ fun KtReference.canBeResolvedViaImport(target: DeclarationDescriptor): Boolean {
if (target.isExtension) return true // assume that any type of reference can use imports when resolved to extension
val referenceExpression = this.element as? KtNameReferenceExpression ?: return false
if (CallTypeAndReceiver.detect(referenceExpression).receiver != null) return false
val callTypeAndReceiver = CallTypeAndReceiver.detect(referenceExpression)
if (callTypeAndReceiver.receiver != null) {
if (target !is PropertyDescriptor || !target.type.isExtensionFunctionType) return false
if (callTypeAndReceiver !is CallTypeAndReceiver.DOT && callTypeAndReceiver !is CallTypeAndReceiver.SAFE) return false
}
if (element.parent is KtThisExpression || element.parent is KtSuperExpression) return false // TODO: it's a bad design of PSI tree, we should change it
return true
}
@@ -29,9 +29,10 @@ import com.intellij.usageView.UsageInfo
import com.intellij.util.IncorrectOperationException
import com.intellij.util.SmartList
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
@@ -47,10 +48,9 @@ import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.resolve.descriptorUtil.isAncestorOf
import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -120,7 +120,7 @@ fun KtElement.processInternalReferencesToUpdateOnPackageNameChange(
if (descriptor is ClassDescriptor && descriptor.isInner && refExpr.parent is KtCallExpression) return null
val isCallable = descriptor is CallableDescriptor
val isExtension = isCallable && (descriptor as CallableDescriptor).extensionReceiverParameter != null
val isExtension = isCallable && refExpr.isExtensionRef(bindingContext)
val isCallableReference = isCallableReference(refExpr.mainReference)
if (isCallable) {
@@ -238,18 +238,26 @@ private enum class ReferenceKind {
IRRELEVANT
}
private fun KtSimpleNameExpression.isExtensionRef(bindingContext: BindingContext? = null): Boolean {
val resolvedCall = getResolvedCall(bindingContext ?: analyze(BodyResolveMode.PARTIAL)) ?: return false
if (resolvedCall is VariableAsFunctionResolvedCall) {
return resolvedCall.variableCall.candidateDescriptor.isExtension || resolvedCall.functionCall.candidateDescriptor.isExtension
}
return resolvedCall.candidateDescriptor.isExtension
}
private fun getReferenceKind(reference: PsiReference, referencedElement: PsiElement): ReferenceKind {
val target = referencedElement.unwrapped
val element = reference.element as? KtSimpleNameExpression ?: return ReferenceKind.QUALIFIABLE
if (element.getStrictParentOfType<KtSuperExpression>() != null) return ReferenceKind.IRRELEVANT
if ((referencedElement.namedUnwrappedElement as? KtDeclaration)?.isExtensionDeclaration() ?: false
&& reference.element.getNonStrictParentOfType<KtImportDirective>() == null) return ReferenceKind.UNQUALIFIABLE
if (element.isExtensionRef() && reference.element.getNonStrictParentOfType<KtImportDirective>() == null) return ReferenceKind.UNQUALIFIABLE
element.getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference }?.let {
if (it.receiverExpression != null) return ReferenceKind.IRRELEVANT
if (referencedElement is KtDeclaration && referencedElement.parent is KtFile) return ReferenceKind.UNQUALIFIABLE
if (referencedElement is PsiMember && referencedElement.getContainingClass() == null) return ReferenceKind.UNQUALIFIABLE
if (target is KtDeclaration && target.parent is KtFile) return ReferenceKind.UNQUALIFIABLE
if (target is PsiMember && target.containingClass == null) return ReferenceKind.UNQUALIFIABLE
}
return ReferenceKind.QUALIFIABLE
@@ -0,0 +1,7 @@
package foo
class CrExtended
val funExtension = fun CrExtended.(): Unit {}
// WITH_RUNTIME
@@ -0,0 +1,10 @@
package bar
import foo.CrExtended
import foo.funExtension
fun test() {
CrExtended().funExtension()
}
// WITH_RUNTIME
@@ -0,0 +1,4 @@
package bar
class CrExtended
@@ -0,0 +1,17 @@
package foo
import bar.CrExtended
fun test(ce: CrExtended) {
valWithFunType()
ce.valWithExtFunType()
with(1) {
extValWithFunType()
ce.extValWithExtFunType()
}
::valWithFunType
::valWithExtFunType
1::extValWithFunType
1::extValWithExtFunType
}
@@ -0,0 +1,9 @@
package foo
import bar.CrExtended
val valWithFunType = fun (): Unit {}
val valWithExtFunType = fun CrExtended.(): Unit {}
val Int.extValWithFunType get() = fun (): Unit {}
val Int.extValWithExtFunType get() = fun CrExtended.(): Unit {}
@@ -0,0 +1,19 @@
package bar
import foo.*
class CrExtended
fun <caret>test(ce: CrExtended) {
valWithFunType()
ce.valWithExtFunType()
with(1) {
extValWithFunType()
ce.extValWithExtFunType()
}
::valWithFunType
::valWithExtFunType
1::extValWithFunType
1::extValWithExtFunType
}
@@ -0,0 +1,9 @@
package foo
import bar.CrExtended
val valWithFunType = fun (): Unit {}
val valWithExtFunType = fun CrExtended.(): Unit {}
val Int.extValWithFunType get() = fun (): Unit {}
val Int.extValWithExtFunType get() = fun CrExtended.(): Unit {}
@@ -0,0 +1,5 @@
{
"mainFile": "bar/test.kt",
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
"targetPackage": "foo"
}
@@ -0,0 +1,21 @@
package baz
import bar.CrExtended
import foo.extValWithExtFunType
import foo.extValWithFunType
import foo.valWithExtFunType
import foo.valWithFunType
fun test(ce: CrExtended) {
valWithFunType()
ce.valWithExtFunType()
with(1) {
extValWithFunType()
ce.extValWithExtFunType()
}
::valWithFunType
::valWithExtFunType
1::extValWithFunType
1::extValWithExtFunType
}
@@ -0,0 +1,9 @@
package foo
import bar.CrExtended
val valWithFunType = fun (): Unit {}
val valWithExtFunType = fun CrExtended.(): Unit {}
val Int.extValWithFunType get() = fun (): Unit {}
val Int.extValWithExtFunType get() = fun CrExtended.(): Unit {}
@@ -0,0 +1,19 @@
package bar
import foo.*
class CrExtended
fun <caret>test(ce: CrExtended) {
valWithFunType()
ce.valWithExtFunType()
with(1) {
extValWithFunType()
ce.extValWithExtFunType()
}
::valWithFunType
::valWithExtFunType
1::extValWithFunType
1::extValWithExtFunType
}
@@ -0,0 +1,9 @@
package foo
import bar.CrExtended
val valWithFunType = fun (): Unit {}
val valWithExtFunType = fun CrExtended.(): Unit {}
val Int.extValWithFunType get() = fun (): Unit {}
val Int.extValWithExtFunType get() = fun CrExtended.(): Unit {}
@@ -0,0 +1,5 @@
{
"mainFile": "bar/test.kt",
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
"targetPackage": "baz"
}
@@ -0,0 +1,21 @@
package bar
import foo.CrExtended
import foo.extValWithExtFunType
import foo.extValWithFunType
import foo.valWithExtFunType
import foo.valWithFunType
fun test(ce: CrExtended) {
valWithFunType()
ce.valWithExtFunType()
with(1) {
extValWithFunType()
ce.extValWithExtFunType()
}
::valWithFunType
::valWithExtFunType
1::extValWithFunType
1::extValWithExtFunType
}
@@ -0,0 +1,9 @@
package foo
val valWithFunType = fun (): Unit {}
val valWithExtFunType = fun CrExtended.(): Unit {}
val Int.extValWithFunType get() = fun (): Unit {}
val Int.extValWithExtFunType get() = fun CrExtended.(): Unit {}
class CrExtended
@@ -0,0 +1,22 @@
package foo
val valWithFunType = fun (): Unit {}
val valWithExtFunType = fun CrExtended.(): Unit {}
val Int.extValWithFunType get() = fun (): Unit {}
val Int.extValWithExtFunType get() = fun CrExtended.(): Unit {}
class CrExtended
fun <caret>test(ce: CrExtended) {
valWithFunType()
ce.valWithExtFunType()
with(1) {
extValWithFunType()
ce.extValWithExtFunType()
}
::valWithFunType
::valWithExtFunType
1::extValWithFunType
1::extValWithExtFunType
}
@@ -0,0 +1,5 @@
{
"mainFile": "foo/test.kt",
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
"targetPackage": "bar"
}
@@ -468,6 +468,24 @@ public class MoveTestGenerated extends AbstractMoveTest {
doTest(fileName);
}
@TestMetadata("kotlin/moveTopLevelDeclarations/implicitInvokeCalls/differentSource/differentSource.test")
public void testKotlin_moveTopLevelDeclarations_implicitInvokeCalls_differentSource_DifferentSource() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/implicitInvokeCalls/differentSource/differentSource.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveTopLevelDeclarations/implicitInvokeCalls/differentSourceAndTarget/differentSourceAndTarget.test")
public void testKotlin_moveTopLevelDeclarations_implicitInvokeCalls_differentSourceAndTarget_DifferentSourceAndTarget() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/implicitInvokeCalls/differentSourceAndTarget/differentSourceAndTarget.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveTopLevelDeclarations/implicitInvokeCalls/differentTarget/differentTarget.test")
public void testKotlin_moveTopLevelDeclarations_implicitInvokeCalls_differentTarget_DifferentTarget() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/implicitInvokeCalls/differentTarget/differentTarget.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/companionExtensionMemberRef/companionExtensionMemberRef.test")
public void testKotlin_moveTopLevelDeclarations_misc_companionExtensionMemberRef_CompanionExtensionMemberRef() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/companionExtensionMemberRef/companionExtensionMemberRef.test");