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:
@@ -18,7 +18,9 @@ package org.jetbrains.kotlin.idea.references
|
|||||||
|
|
||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
import org.jetbrains.kotlin.asJava.unwrapped
|
import org.jetbrains.kotlin.asJava.unwrapped
|
||||||
|
import org.jetbrains.kotlin.builtins.isExtensionFunctionType
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
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.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
|
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
|
||||||
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
|
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
|
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
|
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
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ import com.intellij.usageView.UsageInfo
|
|||||||
import com.intellij.util.IncorrectOperationException
|
import com.intellij.util.IncorrectOperationException
|
||||||
import com.intellij.util.SmartList
|
import com.intellij.util.SmartList
|
||||||
import com.intellij.util.containers.MultiMap
|
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.descriptors.*
|
||||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
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.analyzeFully
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
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.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
|
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isAncestorOf
|
import org.jetbrains.kotlin.resolve.descriptorUtil.*
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
|
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
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
|
if (descriptor is ClassDescriptor && descriptor.isInner && refExpr.parent is KtCallExpression) return null
|
||||||
|
|
||||||
val isCallable = descriptor is CallableDescriptor
|
val isCallable = descriptor is CallableDescriptor
|
||||||
val isExtension = isCallable && (descriptor as CallableDescriptor).extensionReceiverParameter != null
|
val isExtension = isCallable && refExpr.isExtensionRef(bindingContext)
|
||||||
val isCallableReference = isCallableReference(refExpr.mainReference)
|
val isCallableReference = isCallableReference(refExpr.mainReference)
|
||||||
|
|
||||||
if (isCallable) {
|
if (isCallable) {
|
||||||
@@ -238,18 +238,26 @@ private enum class ReferenceKind {
|
|||||||
IRRELEVANT
|
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 {
|
private fun getReferenceKind(reference: PsiReference, referencedElement: PsiElement): ReferenceKind {
|
||||||
|
val target = referencedElement.unwrapped
|
||||||
val element = reference.element as? KtSimpleNameExpression ?: return ReferenceKind.QUALIFIABLE
|
val element = reference.element as? KtSimpleNameExpression ?: return ReferenceKind.QUALIFIABLE
|
||||||
|
|
||||||
if (element.getStrictParentOfType<KtSuperExpression>() != null) return ReferenceKind.IRRELEVANT
|
if (element.getStrictParentOfType<KtSuperExpression>() != null) return ReferenceKind.IRRELEVANT
|
||||||
|
|
||||||
if ((referencedElement.namedUnwrappedElement as? KtDeclaration)?.isExtensionDeclaration() ?: false
|
if (element.isExtensionRef() && reference.element.getNonStrictParentOfType<KtImportDirective>() == null) return ReferenceKind.UNQUALIFIABLE
|
||||||
&& reference.element.getNonStrictParentOfType<KtImportDirective>() == null) return ReferenceKind.UNQUALIFIABLE
|
|
||||||
|
|
||||||
element.getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference }?.let {
|
element.getParentOfTypeAndBranch<KtCallableReferenceExpression> { callableReference }?.let {
|
||||||
if (it.receiverExpression != null) return ReferenceKind.IRRELEVANT
|
if (it.receiverExpression != null) return ReferenceKind.IRRELEVANT
|
||||||
if (referencedElement is KtDeclaration && referencedElement.parent is KtFile) return ReferenceKind.UNQUALIFIABLE
|
if (target is KtDeclaration && target.parent is KtFile) return ReferenceKind.UNQUALIFIABLE
|
||||||
if (referencedElement is PsiMember && referencedElement.getContainingClass() == null) return ReferenceKind.UNQUALIFIABLE
|
if (target is PsiMember && target.containingClass == null) return ReferenceKind.UNQUALIFIABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
return ReferenceKind.QUALIFIABLE
|
return ReferenceKind.QUALIFIABLE
|
||||||
|
|||||||
+7
@@ -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
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
class CrExtended
|
||||||
|
|
||||||
+17
@@ -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
|
||||||
|
}
|
||||||
+9
@@ -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 {}
|
||||||
|
|
||||||
+19
@@ -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
|
||||||
|
}
|
||||||
+9
@@ -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 {}
|
||||||
|
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"mainFile": "bar/test.kt",
|
||||||
|
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
|
||||||
|
"targetPackage": "foo"
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
package bar
|
||||||
|
|
||||||
|
class CrExtended
|
||||||
|
|
||||||
+21
@@ -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
|
||||||
|
}
|
||||||
+9
@@ -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 {}
|
||||||
|
|
||||||
+19
@@ -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
|
||||||
|
}
|
||||||
+9
@@ -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 {}
|
||||||
|
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"mainFile": "bar/test.kt",
|
||||||
|
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
|
||||||
|
"targetPackage": "baz"
|
||||||
|
}
|
||||||
+21
@@ -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
|
||||||
|
}
|
||||||
+9
@@ -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
|
||||||
|
|
||||||
+22
@@ -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
|
||||||
|
}
|
||||||
+5
@@ -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);
|
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")
|
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/companionExtensionMemberRef/companionExtensionMemberRef.test")
|
||||||
public void testKotlin_moveTopLevelDeclarations_misc_companionExtensionMemberRef_CompanionExtensionMemberRef() throws Exception {
|
public void testKotlin_moveTopLevelDeclarations_misc_companionExtensionMemberRef_CompanionExtensionMemberRef() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/companionExtensionMemberRef/companionExtensionMemberRef.test");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/companionExtensionMemberRef/companionExtensionMemberRef.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user