Inline function: handle callable references through lambdas
This commit is contained in:
@@ -26,17 +26,16 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.ui.GuiUtils
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.targetDescriptors
|
||||
import org.jetbrains.kotlin.idea.intentions.ConvertReferenceToLambdaIntention
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtImportDirective
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
@@ -69,6 +68,19 @@ fun UsageReplacementStrategy.replaceUsagesInWholeProject(
|
||||
})
|
||||
}
|
||||
|
||||
private fun UsageReplacementStrategy.doRefactoringInside(
|
||||
element: KtElement, targetName: String?, targetDescriptor: DeclarationDescriptor?
|
||||
) {
|
||||
element.forEachDescendantOfType<KtSimpleNameExpression> { usage ->
|
||||
if (usage.isValid && usage.getReferencedName() == targetName) {
|
||||
val context = usage.analyze(BodyResolveMode.PARTIAL)
|
||||
if (targetDescriptor == context[BindingContext.REFERENCE_TARGET, usage]) {
|
||||
createReplacer(usage)?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun UsageReplacementStrategy.replaceUsages(
|
||||
usages: Collection<KtSimpleNameExpression>,
|
||||
targetPsiElement: PsiElement,
|
||||
@@ -83,6 +95,9 @@ fun UsageReplacementStrategy.replaceUsages(
|
||||
val replacements = mutableListOf<KtElement>()
|
||||
|
||||
var invalidUsagesFound = false
|
||||
|
||||
val targetDeclaration = targetPsiElement as? KtNamedDeclaration
|
||||
|
||||
// NB: reversed order is better in case of composition like sqr(sqr(x))
|
||||
for (usage in usages.reversed()) {
|
||||
try {
|
||||
@@ -91,6 +106,14 @@ fun UsageReplacementStrategy.replaceUsages(
|
||||
continue
|
||||
}
|
||||
|
||||
val usageParent = usage.parent
|
||||
if (usageParent is KtCallableReferenceExpression) {
|
||||
val grandParent = usageParent.parent
|
||||
ConvertReferenceToLambdaIntention().applyTo(usageParent, null)
|
||||
(grandParent as? KtElement)?.let { doRefactoringInside(it, targetDeclaration?.name, targetDeclaration?.descriptor) }
|
||||
continue
|
||||
}
|
||||
|
||||
//TODO: keep the import if we don't know how to replace some of the usages
|
||||
val importDirective = usage.getStrictParentOfType<KtImportDirective>()
|
||||
if (importDirective != null) {
|
||||
@@ -110,20 +133,11 @@ fun UsageReplacementStrategy.replaceUsages(
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidUsagesFound && targetPsiElement is KtNamedDeclaration) {
|
||||
val name = targetPsiElement.name
|
||||
val targetDescriptor = targetPsiElement.descriptor
|
||||
if (name != null && targetDescriptor != null) {
|
||||
for (replacement in replacements) {
|
||||
replacement.forEachDescendantOfType<KtSimpleNameExpression> { usage ->
|
||||
if (usage.isValid && usage.getReferencedName() == name) {
|
||||
val context = usage.analyze(BodyResolveMode.PARTIAL)
|
||||
if (targetDescriptor == context[BindingContext.REFERENCE_TARGET, usage]) {
|
||||
createReplacer(usage)?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invalidUsagesFound && targetDeclaration != null) {
|
||||
val name = targetDeclaration.name
|
||||
val descriptor = targetDeclaration.descriptor
|
||||
for (replacement in replacements) {
|
||||
doRefactoringInside(replacement, name, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
fun foo(x: Int) = x
|
||||
|
||||
val y = ::<caret>foo
|
||||
@@ -0,0 +1 @@
|
||||
val y = { x: Int -> x }
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
fun <caret>foo(x: Int) = x
|
||||
|
||||
val y = ::foo
|
||||
|
||||
fun bar(x: Int) = foo(x * x)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
val y = { x: Int -> x }
|
||||
|
||||
fun bar(x: Int) = x * x
|
||||
@@ -68,6 +68,12 @@ public class InlineTestGenerated extends AbstractInlineTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("Reference.kt")
|
||||
public void testReference() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/Reference.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ReturnNotInTheEnd.kt")
|
||||
public void testReturnNotInTheEnd() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/ReturnNotInTheEnd.kt");
|
||||
@@ -159,6 +165,12 @@ public class InlineTestGenerated extends AbstractInlineTest {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/Simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("WithReference.kt")
|
||||
public void testWithReference() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/WithReference.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/refactoring/inline/function/returnAtEnd")
|
||||
|
||||
Reference in New Issue
Block a user