diff --git a/compiler/testData/codegen/box/coroutines/controlFlow/breakFinally.kt b/compiler/testData/codegen/box/coroutines/controlFlow/breakFinally.kt new file mode 100644 index 00000000000..32996e58a76 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/controlFlow/breakFinally.kt @@ -0,0 +1,50 @@ +// WITH_RUNTIME + +class Controller { + var result = "" + + suspend fun suspendWithResult(value: T, c: Continuation) { + c.resume(value) + } +} + +fun builder(coroutine c: Controller.() -> Continuation): String { + val controller = Controller() + c(controller).resume(Unit) + return controller.result +} + +fun box(): String { + val value = builder { + try { + outer@for (x in listOf("A", "B")) { + try { + result += suspendWithResult(x) + for (y in listOf("C", "D")) { + try { + result += suspendWithResult(y) + if (y == "D") { + break@outer + } + } + finally { + result += "!" + } + result += "E" + } + } + finally { + result += "@" + } + result += "ignore" + } + } + finally { + result += "finally" + } + result += "." + } + if (value != "AC!ED!@finally.") return "fail: $value" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/coroutines/controlFlow/breakStatement.kt b/compiler/testData/codegen/box/coroutines/controlFlow/breakStatement.kt new file mode 100644 index 00000000000..869770d4b31 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/controlFlow/breakStatement.kt @@ -0,0 +1,47 @@ +// WITH_RUNTIME + +class Controller { + var result = "" + + suspend fun suspendWithResult(value: T, c: Continuation) { + c.resume(value) + } +} + +fun builder(coroutine c: Controller.() -> Continuation): String { + val controller = Controller() + c(controller).resume(Unit) + return controller.result +} + +fun box(): String { + var value = builder { + outer@for (x in listOf("O", "K")) { + result += suspendWithResult(x) + for (y in listOf("Q", "W")) { + result += suspendWithResult(y) + if (y == "W") { + break@outer + } + } + } + result += "." + } + if (value != "OQW.") return "fail: break outer loop: $value" + + value = builder { + for (x in listOf("O", "K")) { + result += suspendWithResult(x) + for (y in listOf("Q", "W")) { + if (y == "W") { + break + } + result += suspendWithResult(y) + } + } + result += "." + } + if (value != "OQKQ.") return "fail: break inner loop: $value" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/coroutines/controlFlow/doWhileStatement.kt b/compiler/testData/codegen/box/coroutines/controlFlow/doWhileStatement.kt new file mode 100644 index 00000000000..63dfc90a06a --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/controlFlow/doWhileStatement.kt @@ -0,0 +1,38 @@ +// WITH_RUNTIME + +class Controller { + var result = "" + + suspend fun suspendWithResult(value: T, c: Continuation) { + c.resume(value) + } +} + +fun builder(coroutine c: Controller.() -> Continuation): String { + val controller = Controller() + c(controller).resume(Unit) + return controller.result +} + +fun box(): String { + var value = builder { + var x = 1 + do { + result += x + } while (suspendWithResult(x++) < 3) + result += "." + } + if (value != "123.") return "fail: suspend as do..while condition: $value" + + value = builder { + var x = 1 + do { + result += suspendWithResult(x) + result += ";" + } while (x++ < 3) + result += "." + } + if (value != "1;2;3;.") return "fail: suspend in do..while body: $value" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/coroutines/controlFlow/forStatement.kt b/compiler/testData/codegen/box/coroutines/controlFlow/forStatement.kt new file mode 100644 index 00000000000..6dc2c2ddcd8 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/controlFlow/forStatement.kt @@ -0,0 +1,27 @@ +// WITH_RUNTIME + +class Controller { + var result = "" + + suspend fun suspendWithResult(value: T, c: Continuation) { + c.resume(value) + } +} + +fun builder(coroutine c: Controller.() -> Continuation): String { + val controller = Controller() + c(controller).resume(Unit) + return controller.result +} + +fun box(): String { + val value = builder { + for (x in listOf("O", "K")) { + result += suspendWithResult(x) + } + result += "." + } + if (value != "OK.") return "fail: suspend in for body: $value" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/coroutines/controlFlow/ifStatement.kt b/compiler/testData/codegen/box/coroutines/controlFlow/ifStatement.kt new file mode 100644 index 00000000000..e1474b4cdea --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/controlFlow/ifStatement.kt @@ -0,0 +1,72 @@ +// WITH_RUNTIME + +class Controller { + var result = "" + + suspend fun suspendWithResult(value: T, c: Continuation) { + c.resume(value) + } +} + +fun builder(coroutine c: Controller.() -> Continuation): String { + val controller = Controller() + c(controller).resume(Unit) + return controller.result +} + +fun box(): String { + var value = builder { + if (suspendWithResult(true)) { + result = "OK" + } + } + if (value != "OK") return "fail: suspend as if condition: $value" + + value = builder { + for (x in listOf(true, false)) { + if (x) { + result += suspendWithResult("O") + } + else { + result += "K" + } + } + } + if (value != "OK") return "fail: suspend in then branch: $value" + + value = builder { + for (x in listOf(true, false)) { + if (x) { + result += "O" + } + else { + result += suspendWithResult("K") + } + } + } + if (value != "OK") return "fail: suspend in else branch: $value" + + value = builder { + for (x in listOf(true, false)) { + if (x) { + result += suspendWithResult("O") + } + else { + result += suspendWithResult("K") + } + } + } + if (value != "OK") return "fail: suspend in both branches: $value" + + value = builder { + for (x in listOf(true, false)) { + if (x) { + result += suspendWithResult("O") + } + result += ";" + } + } + if (value != "O;;") return "fail: suspend in then branch without else: $value" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/coroutines/controlFlow/returnFromFinally.kt b/compiler/testData/codegen/box/coroutines/controlFlow/returnFromFinally.kt new file mode 100644 index 00000000000..d94f822c341 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/controlFlow/returnFromFinally.kt @@ -0,0 +1,40 @@ +// WITH_RUNTIME + +class Controller { + var result = "" + + suspend fun suspendAndLog(value: T, c: Continuation) { + result += "suspend($value);" + c.resume(value) + } + + operator fun handleResult(value: String, c: Continuation) { + result += "return($value);" + } +} + +fun builder(coroutine c: Controller.() -> Continuation): String { + val controller = Controller() + c(controller).resume(Unit) + return controller.result +} + +fun id(value: T) = value + +fun box(): String { + val value = builder { + try { + if (id(23) == 23) { + return@builder suspendAndLog("OK") + } + } + finally { + result += "finally;" + } + result += "afterFinally;" + "shouldNotReach" + } + if (value != "suspend(OK);finally;return(OK);") return "fail: $value" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/coroutines/controlFlow/whileStatement.kt b/compiler/testData/codegen/box/coroutines/controlFlow/whileStatement.kt new file mode 100644 index 00000000000..ad2faf23896 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/controlFlow/whileStatement.kt @@ -0,0 +1,38 @@ +// WITH_RUNTIME + +class Controller { + var result = "" + + suspend fun suspendWithResult(value: T, c: Continuation) { + c.resume(value) + } +} + +fun builder(coroutine c: Controller.() -> Continuation): String { + val controller = Controller() + c(controller).resume(Unit) + return controller.result +} + +fun box(): String { + var value = builder { + var x = 1 + while (suspendWithResult(x) <= 3) { + result += x++ + } + result += "." + } + if (value != "123.") return "fail: suspend as while condition: $value" + + value = builder { + var x = 1 + while (x <= 3) { + result += suspendWithResult(x++) + result += ";" + } + result += "." + } + if (value != "1;2;3;.") return "fail: suspend in while body: $value" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/coroutines/handleResultSuspended.kt b/compiler/testData/codegen/box/coroutines/handleResultSuspended.kt new file mode 100644 index 00000000000..d17302d15c4 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/handleResultSuspended.kt @@ -0,0 +1,26 @@ +class Controller { + var log = "" + + suspend fun suspendAndLog(value: T, x: Continuation) { + log += "suspend($value);" + x.resume(value) + } + + operator fun handleResult(value: String, y: Continuation) { + log += "return($value);" + } +} + +fun builder(coroutine c: Controller.() -> Continuation): String { + val controller = Controller() + c(controller).resume(Unit) + return controller.log +} + +fun box(): String { + val result = builder { suspendAndLog("OK") } + + if (result != "suspend(OK);return(OK);") return "fail: $result" + + return "OK" +} diff --git a/compiler/testData/codegen/box/coroutines/multiModule/simple.kt b/compiler/testData/codegen/box/coroutines/multiModule/simple.kt new file mode 100644 index 00000000000..c479879f386 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/multiModule/simple.kt @@ -0,0 +1,27 @@ +// MODULE: controller +// FILE: controller.kt +package lib + +class Controller { + suspend fun suspendHere(x: Continuation) { + x.resume("OK") + } +} + +// MODULE: main(controller) +// FILE: main.kt +import lib.* + +fun builder(coroutine c: Controller.() -> Continuation) { + c(Controller()).resume(Unit) +} + +fun box(): String { + var result = "" + + builder { + result = suspendHere() + } + + return result +} diff --git a/compiler/testData/codegen/box/coroutines/multiModule/suspendExtension.kt b/compiler/testData/codegen/box/coroutines/multiModule/suspendExtension.kt new file mode 100644 index 00000000000..492805aac7d --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/multiModule/suspendExtension.kt @@ -0,0 +1,57 @@ +// MODULE: controller +// FILE: controller.kt +package lib + +@AllowSuspendExtensions +class Controller { + suspend fun String.suspendHere(x: Continuation) { + x.resume(this) + } + + inline suspend fun String.inlineSuspendHere(x: Continuation) { + suspendHere(x) + } +} + +suspend fun Controller.suspendExtension(v: String, x: Continuation) { + v.suspendHere(x) +} + +inline suspend fun Controller.inlineSuspendExtension(v: String, x: Continuation) { + v.inlineSuspendHere(x) +} + +// MODULE: main(controller) +// FILE: main.kt +import lib.* + +suspend fun Controller.localSuspendExtension(v: String, x: Continuation) { + v.suspendHere(x) +} + +inline suspend fun Controller.localInlineSuspendExtension(v: String, x: Continuation) { + v.inlineSuspendHere(x) +} + +fun builder(coroutine c: Controller.() -> Continuation) { + c(Controller()).resume(Unit) +} + +fun box(): String { + var result = "" + + builder { + if ("56".suspendHere() != "56") throw RuntimeException("fail 1") + if ("28".inlineSuspendHere() != "28") throw RuntimeException("fail 2") + + if (suspendExtension("123") != "123") throw RuntimeException("fail 3") + if (inlineSuspendExtension("234") != "234") throw RuntimeException("fail 4") + + if (localSuspendExtension("9123") != "9123") throw RuntimeException("fail 5") + if (localInlineSuspendExtension("9234") != "9234") throw RuntimeException("fail 6") + + result = "OK" + } + + return result +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 8e11331da6f..81d799616f4 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -4490,6 +4490,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("handleResultSuspended.kt") + public void testHandleResultSuspended() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/handleResultSuspended.kt"); + doTest(fileName); + } + @TestMetadata("illegalState.kt") public void testIllegalState() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/illegalState.kt"); @@ -4706,6 +4712,57 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("compiler/testData/codegen/box/coroutines/controlFlow") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ControlFlow extends AbstractIrBlackBoxCodegenTest { + public void testAllFilesPresentInControlFlow() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/controlFlow"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("breakFinally.kt") + public void testBreakFinally() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/breakFinally.kt"); + doTest(fileName); + } + + @TestMetadata("breakStatement.kt") + public void testBreakStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/breakStatement.kt"); + doTest(fileName); + } + + @TestMetadata("doWhileStatement.kt") + public void testDoWhileStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/doWhileStatement.kt"); + doTest(fileName); + } + + @TestMetadata("forStatement.kt") + public void testForStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/forStatement.kt"); + doTest(fileName); + } + + @TestMetadata("ifStatement.kt") + public void testIfStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/ifStatement.kt"); + doTest(fileName); + } + + @TestMetadata("returnFromFinally.kt") + public void testReturnFromFinally() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/returnFromFinally.kt"); + doTest(fileName); + } + + @TestMetadata("whileStatement.kt") + public void testWhileStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/whileStatement.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) @@ -4774,6 +4831,27 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } } + + @TestMetadata("compiler/testData/codegen/box/coroutines/multiModule") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class MultiModule extends AbstractIrBlackBoxCodegenTest { + public void testAllFilesPresentInMultiModule() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/multiModule"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/simple.kt"); + doTest(fileName); + } + + @TestMetadata("suspendExtension.kt") + public void testSuspendExtension() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/suspendExtension.kt"); + doTest(fileName); + } + } } @TestMetadata("compiler/testData/codegen/box/dataClasses") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index d09903c16a3..dc0eefc5513 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -4490,6 +4490,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("handleResultSuspended.kt") + public void testHandleResultSuspended() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/handleResultSuspended.kt"); + doTest(fileName); + } + @TestMetadata("illegalState.kt") public void testIllegalState() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/illegalState.kt"); @@ -4706,6 +4712,57 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("compiler/testData/codegen/box/coroutines/controlFlow") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ControlFlow extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInControlFlow() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/controlFlow"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("breakFinally.kt") + public void testBreakFinally() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/breakFinally.kt"); + doTest(fileName); + } + + @TestMetadata("breakStatement.kt") + public void testBreakStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/breakStatement.kt"); + doTest(fileName); + } + + @TestMetadata("doWhileStatement.kt") + public void testDoWhileStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/doWhileStatement.kt"); + doTest(fileName); + } + + @TestMetadata("forStatement.kt") + public void testForStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/forStatement.kt"); + doTest(fileName); + } + + @TestMetadata("ifStatement.kt") + public void testIfStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/ifStatement.kt"); + doTest(fileName); + } + + @TestMetadata("returnFromFinally.kt") + public void testReturnFromFinally() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/returnFromFinally.kt"); + doTest(fileName); + } + + @TestMetadata("whileStatement.kt") + public void testWhileStatement() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/controlFlow/whileStatement.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/coroutines/intLikeVarSpilling") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) @@ -4774,6 +4831,27 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } } + + @TestMetadata("compiler/testData/codegen/box/coroutines/multiModule") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class MultiModule extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInMultiModule() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/multiModule"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/simple.kt"); + doTest(fileName); + } + + @TestMetadata("suspendExtension.kt") + public void testSuspendExtension() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/suspendExtension.kt"); + doTest(fileName); + } + } } @TestMetadata("compiler/testData/codegen/box/dataClasses") diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt index dd8b7ca7c3c..b6803e66119 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineBodyTransformer.kt @@ -50,7 +50,8 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t get private set - private val currentTryDepth = tryStack.lastIndex + private val currentTryDepth: Int + get() = tryStack.lastIndex fun preProcess(node: JsNode) { val nodes = mutableSetOf() @@ -323,7 +324,7 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t val targetBlock = breakBlocks[targetStatement]!! referencedBlocks += targetBlock - jumpWithFinally(targetTryDepth, targetBlock) + jumpWithFinally(targetTryDepth + 1, targetBlock) currentStatements += jump() } @@ -339,15 +340,15 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t val targetBlock = continueBlocks[targetStatement]!! referencedBlocks += targetBlock - jumpWithFinally(targetTryDepth, targetBlock) + jumpWithFinally(targetTryDepth + 1, targetBlock) currentStatements += jump() } private fun jumpWithFinally(targetTryDepth: Int, successor: CoroutineBlock) { - if (targetTryDepth == tryStack.size) return - - val tryBlock = tryStack[targetTryDepth] - currentStatements += exceptionState(tryBlock.catchBlock) + if (targetTryDepth < tryStack.size) { + val tryBlock = tryStack[targetTryDepth] + currentStatements += exceptionState(tryBlock.catchBlock) + } val relativeFinallyPath = relativeFinallyPath(targetTryDepth) val fullPath = relativeFinallyPath + successor @@ -593,7 +594,10 @@ class CoroutineBodyTransformer(val program: JsProgram, val scope: JsScope, val t } } - private fun relativeFinallyPath(targetTryDepth: Int) = tryStack.subList(targetTryDepth, tryStack.size).mapNotNull { it.finallyBlock } + private fun relativeFinallyPath(targetTryDepth: Int) = tryStack + .subList(targetTryDepth, tryStack.size) + .mapNotNull { it.finallyBlock } + .reversed() private fun hasEnclosingFinallyBlock() = tryStack.any { it.finallyBlock != null } }