Inline function: handle callable references through lambdas

This commit is contained in:
Mikhail Glukhikh
2017-03-28 20:25:24 +03:00
parent 75bb599991
commit 93b624fdbe
6 changed files with 58 additions and 18 deletions
@@ -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)
@@ -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")