diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionDialog.kt index dd194e1614e..ce575b4724f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionDialog.kt @@ -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() diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionHandler.kt index c2ba916ad59..1267346edae 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/inline/KotlinInlineFunctionHandler.kt @@ -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() 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 { it !== this && it.includesCallOf(descriptor, context) } + } + } \ No newline at end of file diff --git a/idea/testData/refactoring/inline/function/expressionBody/Recursive.kt b/idea/testData/refactoring/inline/function/expressionBody/Recursive.kt new file mode 100644 index 00000000000..b699fcb6c47 --- /dev/null +++ b/idea/testData/refactoring/inline/function/expressionBody/Recursive.kt @@ -0,0 +1 @@ +fun fact(x: Int): Int = if (x < 2) 1 else x * fact(x - 1) \ No newline at end of file diff --git a/idea/testData/refactoring/inline/function/expressionBody/Recursive.kt.after b/idea/testData/refactoring/inline/function/expressionBody/Recursive.kt.after new file mode 100644 index 00000000000..20a3221e56a --- /dev/null +++ b/idea/testData/refactoring/inline/function/expressionBody/Recursive.kt.after @@ -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) +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/function/returnAtEnd/Recursive.kt b/idea/testData/refactoring/inline/function/returnAtEnd/Recursive.kt new file mode 100644 index 00000000000..dd18502a332 --- /dev/null +++ b/idea/testData/refactoring/inline/function/returnAtEnd/Recursive.kt @@ -0,0 +1,3 @@ +fun fact(x: Int): Int { + return if (x < 2) 1 else fact(x - 1) +} \ No newline at end of file diff --git a/idea/testData/refactoring/inline/function/returnAtEnd/Recursive.kt.after b/idea/testData/refactoring/inline/function/returnAtEnd/Recursive.kt.after new file mode 100644 index 00000000000..cd02cca3c19 --- /dev/null +++ b/idea/testData/refactoring/inline/function/returnAtEnd/Recursive.kt.after @@ -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) + } +} \ 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 17e4cc8236d..4dc14d91ec1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/inline/InlineTestGenerated.java @@ -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");