Inline function: handle recursive calls

This commit is contained in:
Mikhail Glukhikh
2017-03-28 18:27:29 +03:00
parent e79f006659
commit 75bb599991
7 changed files with 54 additions and 4 deletions
@@ -72,8 +72,9 @@ class KotlinInlineFunctionDialog(
public override fun doAction() {
invokeRefactoring(
KotlinInlineFunctionProcessor(project, replacementStrategy, function,
reference, isInlineThisOnly, !isInlineThisOnly && !isKeepTheDeclaration)
KotlinInlineFunctionProcessor(project, replacementStrategy, function, reference,
inlineThisOnly = isInlineThisOnly || allowInlineThisOnly,
deleteAfter = !isInlineThisOnly && !isKeepTheDeclaration && !allowInlineThisOnly)
)
val settings = JavaRefactoringSettings.getInstance()
@@ -26,9 +26,11 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.util.CommonRefactoringUtil
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
@@ -37,10 +39,13 @@ import org.jetbrains.kotlin.idea.core.copied
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
@@ -102,11 +107,17 @@ class KotlinInlineFunctionHandler: InlineActionHandler() {
is PsiMultiReference -> reference.references.firstIsInstanceOrNull<KtSimpleNameReference>()
else -> null
}
val recursive = element.isRecursive()
if (recursive && nameReference == null) {
val message = RefactoringBundle.getCannotRefactorMessage("Inline recursive function is supported only on references")
CommonRefactoringUtil.showErrorHint(project, editor, message, "Inline Function", null)
return
}
val replacementStrategy = CallableUsageReplacementStrategy(replacement)
// TODO: allowInlineThisOnly
val dialog = KotlinInlineFunctionDialog(project, element, nameReference, replacementStrategy, allowInlineThisOnly = false)
val dialog = KotlinInlineFunctionDialog(project, element, nameReference, replacementStrategy,
allowInlineThisOnly = recursive)
if (!ApplicationManager.getApplication().isUnitTestMode) {
dialog.show()
}
@@ -114,4 +125,16 @@ class KotlinInlineFunctionHandler: InlineActionHandler() {
dialog.doAction()
}
}
private fun KtNamedFunction.isRecursive(): Boolean {
val context = analyzeFullyAndGetResult().bindingContext
return bodyExpression?.includesCallOf(context[BindingContext.FUNCTION, this] ?: return false, context) ?: false
}
private fun KtExpression.includesCallOf(descriptor: FunctionDescriptor, context: BindingContext): Boolean {
val refDescriptor = getResolvedCall(context)?.resultingDescriptor
return descriptor == refDescriptor ||
anyDescendantOfType<KtExpression> { it !== this && it.includesCallOf(descriptor, context) }
}
}
@@ -0,0 +1 @@
fun fact(x: Int): Int = if (x < 2) 1 else x * <caret>fact(x - 1)
@@ -0,0 +1,4 @@
fun fact(x: Int): Int = if (x < 2) 1 else {
val x1 = x - 1
x * if (x1 < 2) 1 else x1 * fact(x1 - 1)
}
@@ -0,0 +1,3 @@
fun fact(x: Int): Int {
return if (x < 2) 1 else <caret>fact(x - 1)
}
@@ -0,0 +1,6 @@
fun fact(x: Int): Int {
return if (x < 2) 1 else {
val x1 = x - 1
if (x1 < 2) 1 else fact(x1 - 1)
}
}
@@ -142,6 +142,12 @@ public class InlineTestGenerated extends AbstractInlineTest {
doTest(fileName);
}
@TestMetadata("Recursive.kt")
public void testRecursive() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/Recursive.kt");
doTest(fileName);
}
@TestMetadata("SafeCall.kt")
public void testSafeCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/SafeCall.kt");
@@ -205,6 +211,12 @@ public class InlineTestGenerated extends AbstractInlineTest {
doTest(fileName);
}
@TestMetadata("Recursive.kt")
public void testRecursive() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/returnAtEnd/Recursive.kt");
doTest(fileName);
}
@TestMetadata("ReturnFromLambda.kt")
public void testReturnFromLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/returnAtEnd/ReturnFromLambda.kt");