Inline function: handle recursive calls
This commit is contained in:
@@ -72,8 +72,9 @@ class KotlinInlineFunctionDialog(
|
|||||||
|
|
||||||
public override fun doAction() {
|
public override fun doAction() {
|
||||||
invokeRefactoring(
|
invokeRefactoring(
|
||||||
KotlinInlineFunctionProcessor(project, replacementStrategy, function,
|
KotlinInlineFunctionProcessor(project, replacementStrategy, function, reference,
|
||||||
reference, isInlineThisOnly, !isInlineThisOnly && !isKeepTheDeclaration)
|
inlineThisOnly = isInlineThisOnly || allowInlineThisOnly,
|
||||||
|
deleteAfter = !isInlineThisOnly && !isKeepTheDeclaration && !allowInlineThisOnly)
|
||||||
)
|
)
|
||||||
|
|
||||||
val settings = JavaRefactoringSettings.getInstance()
|
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.psi.impl.source.resolve.reference.impl.PsiMultiReference
|
||||||
import com.intellij.refactoring.RefactoringBundle
|
import com.intellij.refactoring.RefactoringBundle
|
||||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||||
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
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.getResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
import org.jetbrains.kotlin.idea.codeInliner.CallableUsageReplacementStrategy
|
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.references.KtSimpleNameReference
|
||||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
import org.jetbrains.kotlin.psi.KtBlockExpression
|
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||||
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||||
import org.jetbrains.kotlin.psi.KtReturnExpression
|
import org.jetbrains.kotlin.psi.KtReturnExpression
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
|
||||||
@@ -102,11 +107,17 @@ class KotlinInlineFunctionHandler: InlineActionHandler() {
|
|||||||
is PsiMultiReference -> reference.references.firstIsInstanceOrNull<KtSimpleNameReference>()
|
is PsiMultiReference -> reference.references.firstIsInstanceOrNull<KtSimpleNameReference>()
|
||||||
else -> null
|
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)
|
val replacementStrategy = CallableUsageReplacementStrategy(replacement)
|
||||||
|
|
||||||
// TODO: allowInlineThisOnly
|
val dialog = KotlinInlineFunctionDialog(project, element, nameReference, replacementStrategy,
|
||||||
val dialog = KotlinInlineFunctionDialog(project, element, nameReference, replacementStrategy, allowInlineThisOnly = false)
|
allowInlineThisOnly = recursive)
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
@@ -114,4 +125,16 @@ class KotlinInlineFunctionHandler: InlineActionHandler() {
|
|||||||
dialog.doAction()
|
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);
|
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")
|
@TestMetadata("SafeCall.kt")
|
||||||
public void testSafeCall() throws Exception {
|
public void testSafeCall() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/SafeCall.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/expressionBody/SafeCall.kt");
|
||||||
@@ -205,6 +211,12 @@ public class InlineTestGenerated extends AbstractInlineTest {
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("ReturnFromLambda.kt")
|
||||||
public void testReturnFromLambda() throws Exception {
|
public void testReturnFromLambda() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/returnAtEnd/ReturnFromLambda.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/inline/function/returnAtEnd/ReturnFromLambda.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user