From a4d1358e57fa0a64169454f5a65d0184d38b98f5 Mon Sep 17 00:00:00 2001 From: pyos Date: Wed, 15 Sep 2021 14:39:46 +0200 Subject: [PATCH] FIR: permit tailrec calls in inline lambdas --- .../declaration/FirTailrecFunctionChecker.kt | 38 ++++--------------- .../recursiveCallInInlineLambda.kt | 1 - .../recursiveCallInInlineLambdaWithCapture.kt | 1 - .../tests/tailRecInNestedScopes.fir.kt | 37 ------------------ .../tests/tailRecInNestedScopes.kt | 1 + 5 files changed, 9 insertions(+), 69 deletions(-) delete mode 100644 compiler/testData/diagnostics/tests/tailRecInNestedScopes.fir.kt diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTailrecFunctionChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTailrecFunctionChecker.kt index bc408d74468..fc3a565e7af 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTailrecFunctionChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTailrecFunctionChecker.kt @@ -36,16 +36,8 @@ object FirTailrecFunctionChecker : FirSimpleFunctionChecker() { var catchScopeCount = 0 var finallyScopeCount = 0 var tailrecCount = 0 - var lambdaScopeCount = 0 graph.traverse(TraverseDirection.Forward, object : ControlFlowGraphVisitorVoid() { override fun visitNode(node: CFGNode<*>) {} - override fun visitPostponedLambdaEnterNode(node: PostponedLambdaEnterNode) { - lambdaScopeCount++ - } - - override fun visitPostponedLambdaExitNode(node: PostponedLambdaExitNode) { - lambdaScopeCount-- - } override fun visitTryMainBlockEnterNode(node: TryMainBlockEnterNode) { tryScopeCount++ @@ -81,30 +73,16 @@ object FirTailrecFunctionChecker : FirSimpleFunctionChecker() { return } val dispatchReceiver = functionCall.dispatchReceiver - // A tailrec call does not support changing dispatchers. Here we report changing dispatch receiver if the dispatch receiver - // is present and not a `this` or a singleton. For the `this` check, we don't need to actually compare if the dispatch - // receiver `this` references the same `this` made available from `declaration`. This is because - // 1. if `this` is not labeled, then it references the innermost `this` receiver. If the innermost scope is not the - // `declaration` body, then follow-up checks on following nodes would report there to be more instructions, which would - // then make this call non-tailrec. - // 2. If `this` is labeled, then one of the following is possible. - // a. the call is in some context that has additional implicit `this` declared. But this can only happen if the call is - // placed inside some extension lambda, which would be covered by the later check on exiting node. - // b. `declaration` is a member function in a local class and the receiver is a labeled `this` pointing to the outer - // non-local class. In this case, the resolved symbol cannot be the same as the symbol of `declaration`, and this case - // is already bailed out earlier. So there is no need to report anything. - // c. `declaration` is a member function of an inner class and the receiver is a labeled `this` pointing to the outer - // class. The reasoning is the same with b. - if (dispatchReceiver !is FirThisReceiverExpression && - dispatchReceiver !is FirNoReceiverExpression && - (declaration.dispatchReceiverType?.toSymbol(context.session) as? FirClassSymbol<*>)?.classKind?.isSingleton != true - ) { + val dispatchReceiverOwner = declaration.dispatchReceiverType?.toSymbol(context.session) as? FirClassSymbol<*> + val sameReceiver = dispatchReceiver is FirNoReceiverExpression || + (dispatchReceiver is FirThisReceiverExpression && dispatchReceiver.calleeReference.boundSymbol == dispatchReceiverOwner) || + dispatchReceiverOwner?.classKind?.isSingleton == true + if (!sameReceiver) { + // A call on a different receiver might get dispatched to a different method, so it can't be optimized. reporter.reportOn(functionCall.source, FirErrors.NON_TAIL_RECURSIVE_CALL, context) - return - } - if (tryScopeCount > 0 || catchScopeCount > 0 || finallyScopeCount > 0) { + } else if (tryScopeCount > 0 || catchScopeCount > 0 || finallyScopeCount > 0) { reporter.reportOn(functionCall.source, FirErrors.TAIL_RECURSION_IN_TRY_IS_NOT_SUPPORTED, context) - } else if (lambdaScopeCount > 0 || node.hasMoreFollowingInstructions(declaration)) { + } else if (node.hasMoreFollowingInstructions(declaration)) { reporter.reportOn(functionCall.source, FirErrors.NON_TAIL_RECURSIVE_CALL, context) } else if (!node.isDead) { tailrecCount++ diff --git a/compiler/testData/codegen/box/diagnostics/functions/tailRecursion/recursiveCallInInlineLambda.kt b/compiler/testData/codegen/box/diagnostics/functions/tailRecursion/recursiveCallInInlineLambda.kt index 21c0423839c..657425ab046 100644 --- a/compiler/testData/codegen/box/diagnostics/functions/tailRecursion/recursiveCallInInlineLambda.kt +++ b/compiler/testData/codegen/box/diagnostics/functions/tailRecursion/recursiveCallInInlineLambda.kt @@ -1,6 +1,5 @@ // KT-16549 // IGNORE_BACKEND: JVM, JS -// IGNORE_FIR_DIAGNOSTICS_DIFF class TailInline { private inline fun act(action: () -> Unit) { diff --git a/compiler/testData/codegen/box/diagnostics/functions/tailRecursion/recursiveCallInInlineLambdaWithCapture.kt b/compiler/testData/codegen/box/diagnostics/functions/tailRecursion/recursiveCallInInlineLambdaWithCapture.kt index 997d58f5ff4..6b3da285c1c 100644 --- a/compiler/testData/codegen/box/diagnostics/functions/tailRecursion/recursiveCallInInlineLambdaWithCapture.kt +++ b/compiler/testData/codegen/box/diagnostics/functions/tailRecursion/recursiveCallInInlineLambdaWithCapture.kt @@ -1,7 +1,6 @@ // KT-14961 // IGNORE_BACKEND: JVM, JS_IR, WASM // WITH_RUNTIME -// IGNORE_FIR_DIAGNOSTICS_DIFF fun listOfFactor(number: Int): List { tailrec fun listOfFactor(number: Int, acc: List): List { diff --git a/compiler/testData/diagnostics/tests/tailRecInNestedScopes.fir.kt b/compiler/testData/diagnostics/tests/tailRecInNestedScopes.fir.kt deleted file mode 100644 index 4993f8237ad..00000000000 --- a/compiler/testData/diagnostics/tests/tailRecInNestedScopes.fir.kt +++ /dev/null @@ -1,37 +0,0 @@ -// WITH_STDLIB - -tailrec fun foo1() { - run { - foo1() - } -} - -fun myRun(f: () -> Unit) = f() - - -tailrec fun foo2() { - myRun { - foo2() - } -} - -tailrec fun foo3() { - fun bar() { - foo3() - } - bar() -} - -class A { - tailrec fun foo4() { - with(this) { - foo4() - } - } -} - -tailrec fun foo5() { - run { - return foo5() - } -} diff --git a/compiler/testData/diagnostics/tests/tailRecInNestedScopes.kt b/compiler/testData/diagnostics/tests/tailRecInNestedScopes.kt index ac8852d3397..f687a885c2e 100644 --- a/compiler/testData/diagnostics/tests/tailRecInNestedScopes.kt +++ b/compiler/testData/diagnostics/tests/tailRecInNestedScopes.kt @@ -1,3 +1,4 @@ +// FIR_IDENTICAL // WITH_STDLIB tailrec fun foo1() {