From 93b624fdbe12ddf902416356498574526831be64 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Tue, 28 Mar 2017 20:25:24 +0300 Subject: [PATCH] Inline function: handle callable references through lambdas --- .../codeInliner/UsageReplacementStrategy.kt | 50 ++++++++++++------- .../refactoring/inline/function/Reference.kt | 4 ++ .../inline/function/Reference.kt.after | 1 + .../function/expressionBody/WithReference.kt | 6 +++ .../expressionBody/WithReference.kt.after | 3 ++ .../inline/InlineTestGenerated.java | 12 +++++ 6 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 idea/testData/refactoring/inline/function/Reference.kt create mode 100644 idea/testData/refactoring/inline/function/Reference.kt.after create mode 100644 idea/testData/refactoring/inline/function/expressionBody/WithReference.kt create mode 100644 idea/testData/refactoring/inline/function/expressionBody/WithReference.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInliner/UsageReplacementStrategy.kt b/idea/src/org/jetbrains/kotlin/idea/codeInliner/UsageReplacementStrategy.kt index 5095863ccd3..8f8a10fc7ef 100644 --- a/idea/src/org/jetbrains/kotlin/idea/codeInliner/UsageReplacementStrategy.kt +++ b/idea/src/org/jetbrains/kotlin/idea/codeInliner/UsageReplacementStrategy.kt @@ -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 { 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, targetPsiElement: PsiElement, @@ -83,6 +95,9 @@ fun UsageReplacementStrategy.replaceUsages( val replacements = mutableListOf() 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() 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 { 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) } } diff --git a/idea/testData/refactoring/inline/function/Reference.kt b/idea/testData/refactoring/inline/function/Reference.kt new file mode 100644 index 00000000000..8f08578d231 --- /dev/null +++ b/idea/testData/refactoring/inline/function/Reference.kt @@ -0,0 +1,4 @@ + +fun foo(x: Int) = x + +val y = ::foo \ No newline at end of file diff --git a/idea/testData/refactoring/inline/function/Reference.kt.after b/idea/testData/refactoring/inline/function/Reference.kt.after new file mode 100644 index 00000000000..dd6750002ee --- /dev/null +++ b/idea/testData/refactoring/inline/function/Reference.kt.after @@ -0,0 +1 @@ +val y = { x: Int -> x } \ No newline at end of file diff --git a/idea/testData/refactoring/inline/function/expressionBody/WithReference.kt b/idea/testData/refactoring/inline/function/expressionBody/WithReference.kt new file mode 100644 index 00000000000..62767067627 --- /dev/null +++ b/idea/testData/refactoring/inline/function/expressionBody/WithReference.kt @@ -0,0 +1,6 @@ + +fun foo(x: Int) = x + +val y = ::foo + +fun bar(x: Int) = foo(x * x) \ No newline at end of file diff --git a/idea/testData/refactoring/inline/function/expressionBody/WithReference.kt.after b/idea/testData/refactoring/inline/function/expressionBody/WithReference.kt.after new file mode 100644 index 00000000000..82051296c05 --- /dev/null +++ b/idea/testData/refactoring/inline/function/expressionBody/WithReference.kt.after @@ -0,0 +1,3 @@ +val y = { x: Int -> x } + +fun bar(x: Int) = x * x \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java index 4dc14d91ec1..f22b8a1edf6 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java @@ -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")