Inline function: handle recursive calls
This commit is contained in:
@@ -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()
|
||||
|
||||
+25
-2
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user