diff --git a/tests/lang/s01_basics/s08_control_flow/t06_procedure_calls.nim b/tests/lang/s01_basics/s08_control_flow/t06_procedure_calls.nim index 154c9678a67..a65cdc6ecfe 100644 --- a/tests/lang/s01_basics/s08_control_flow/t06_procedure_calls.nim +++ b/tests/lang/s01_basics/s08_control_flow/t06_procedure_calls.nim @@ -1,3 +1,7 @@ +discard """ + targets: "c cpp" +""" + ## This specifices how control flow is transferred in the procedure body. ## It also shows how exceptions, `defer` and `return` affect the control ## flow. Note - even though exceptions are used in this section. it is @@ -160,3 +164,380 @@ block self_recursive_raise: "pre recurse 1", "pre recurse 2" ] + +## A `finally` clause is executed when control-flow leaves the corresponding try clause and, if present, any executed catch clause. + +block finally_simple: + var values: seq[string] + + values.add("before") + + try: + values.add("try") + finally: + ## When a `finally` clause is present, the `try` statement isn't required to have any `except` clauses. + values.add("finally") + + values.add("end") + + doAssert values == @[ + "before", + "try", + "finally", + "end" + ] + + +block finally_return: + var values: seq[string] + + proc impl1() = + values.add("before") + try: + values.add("try") + return + finally: + ## Even though the `try`-clause is left via a return + ## statement, the finally clause is still executed + values.add("finally") + + values.add("end") + + try: impl1() except: discard + + doAssert values == @[ + "before", + "try", + "finally" + ] + +block finally_raise: + var values: seq[string] + + proc impl1() = + values.add("before") + + try: + values.add("try") + raise (ref CatchableError)() + values.add("end try") + finally: + values.add("finally") + + values.add("end") + + try: impl1() except: discard + + doAssert values == @[ + "before", + "try", + "finally" + ] + +block simple_except_finally: + var values: seq[string] + + proc impl1() = + values.add("before") + + try: + values.add("try") + raise (ref CatchableError)() + values.add("end try") + except: + values.add("except") + finally: + ## The `finally` clause is executed after any present `except` clause + values.add("finally") + + values.add("end") + + impl1() + + doAssert values == @[ + "before", + "try", + "except", + "finally", + "end" + ] + + +block nested_finally: + var values: seq[string] + + proc impl2() = + values.add("enter impl2") + try: + raise (ref CatchableError)() + finally: + values.add("finally impl2") + + values.add("leave impl2") + + proc impl1() = + values.add("enter impl1") + try: + impl2() + except: + values.add("except") + finally: + values.add("finally impl1") + + values.add("leave impl2") + + impl1() + + doAssert values == @[ + "enter impl1", + "enter impl2", + "finally impl2", + "except", + "finally impl1", + "leave impl2" + ] + +block nested_finally2: + var values: seq[string] + + proc impl2() = + values.add("enter impl2") + try: + raise (ref CatchableError)() + finally: + values.add("finally impl2") + + values.add("leave impl2") + + proc impl1() = + values.add("enter impl1") + try: + impl2() + finally: + values.add("finally impl1") + + values.add("leave impl2") + + try: impl1() except: discard + + doAssert values == @[ + "enter impl1", + "enter impl2", + "finally impl2", + "finally impl1" + ] + + +block reraise: + var values: seq[string] + + proc impl1() = + values.add("begin") + try: + values.add("try") + raise (ref CatchableError)() + except: + values.add("except") + raise + values.add("except end") + + values.add("end") + + try: impl1() except: discard + + doAssert values == @[ + "begin", + "try", + "except" + ] + +block reraise_finally: + var values: seq[string] + + proc impl1() = + values.add("begin") + try: + values.add("try") + raise (ref CatchableError)() + except: + values.add("except") + raise + finally: + values.add("finally") + + values.add("end") + + try: impl1() except: discard + + doAssert values == @[ + "begin", + "try", + "except", + "finally" + ] + + +block nested_finally_return: + var values: seq[string] + + proc impl1() = + values.add("begin") + try: + try: + raise (ref CatchableError)() + finally: + values.add("inner finally") + return + finally: + values.add("finally") + + try: impl1() except: discard + + doAssert values == @[ + "begin", + "inner finally", + "finally" + ] + +block finally_return: + var values: seq[string] + + proc impl1() = + values.add("begin") + try: + values.add("try") + raise (ref CatchableError)() + finally: + values.add("finally") + return + values.add("end") + + try: impl1() except: values.add("except") + + doAssert values == @[ + "begin", + "try", + "finally", + # "except" + ] + + +block handled_exception_in_finally: + type ExA = object of CatchableError + type ExB = object of CatchableError + + var values: seq[string] + + proc impl1() = + values.add("begin") + try: + values.add("try") + raise (ref ExA)() + finally: + values.add("finally") + try: + raise (ref ExB)() + except ExB: + values.add("except") + + values.add("finally end") + + values.add("end") + + try: impl1() except: values.add("outer except") + + doAssert values == @[ + "begin", + "try", + "finally", + "except", + "finally end", + "outer except" + ] + +block finally_reraise: + var values: seq[string] + + proc impl1() = + values.add("begin") + try: + values.add("try") + raise (ref CatchableError)() + finally: + values.add("finally") + raise + values.add("finally end") + + values.add("end") + + try: impl1() except: values.add("except") + + doAssert values == @[ + "begin", + "try", + "finally", + "except" + ] + +block finally_raise: + var values: seq[string] + + proc impl1() = + values.add("begin") + try: + try: + values.add("try") + raise (ref CatchableError)() + except: + raise + finally: + values.add("finally") + try: + values.add("try inner") + raise + finally: + values.add("finally inner end") + + values.add("finally end") + finally: + values.add("last finally") + + values.add("end") + + + try: impl1() except: discard + + doAssert values == @[ + "begin", + "try", + "finally", + "try inner", + "finally inner end", + "last finally" + ] + + +block finally_defer: + + var values: seq[string] + + proc impl1() = + values.add("begin") + try: + values.add("try") + raise (ref CatchableError)() + finally: + values.add("finally") + defer: values.add("defer") + values.add("finally 2") + values.add("end") + + try: impl1() except: discard + + doAssert values == @[ + "begin", + "try", + "finally", + "finally 2", + "defer" + ] \ No newline at end of file diff --git a/tests/lang/s01_basics/s08_control_flow/t07_finally.nim b/tests/lang/s01_basics/s08_control_flow/t07_finally.nim new file mode 100644 index 00000000000..86e3856465e --- /dev/null +++ b/tests/lang/s01_basics/s08_control_flow/t07_finally.nim @@ -0,0 +1,786 @@ +discard """ + targets: "c cpp" +""" + +## A finally clause is executed once control-flow leaves the `finally`'s corresponding `try` and (if present) `except clause. This +## includes normal, interrupted (`break`, `continue`, `yield`, `return`) and exceptional (`raise`) control flow. + +block: + var values: seq[string] + + proc p() = + block a: + block b: + try: + values.add("1") + try: + values.add("2") + return + finally: + values.add("3") + break a + finally: + values.add("4") + break b + values.add("5") + values.add("6") + values.add("7") + + p() + + doAssert values == @[ + "1", + "2", + "3", + "4", + "6", + "7", + ] + + +block: + var values: seq[string] + block a: + try: + try: + values.add("1") + raise (ref CatchableError)() + finally: + values.add("2") + break a + except: + values.add("3") + finally: + values.add("4") + values.add("5") + + doAssert values == @[ + "1", + "2", + "3", + "4" + ] + +block: + var values: seq[string] + + block a: + block b: + try: + values.add("1") + break a + finally: + values.add("2") + block c: + try: + values.add("3") + break b + finally: + values.add("4") + break c + values.add("5") + values.add("6") + values.add("7") + + doAssert values == @[ + "1", + "2", + "3", + "4", + "6", + "7" + ] + + +block: + var values: seq[string] + try: + try: + values.add("1") + raise (ref CatchableError)() + finally: + values.add("2") + try: + values.add("3") + raise (ref CatchableError)() + except: + values.add("4") + except: + values.add("5") + + doAssert values == @[ + "1", + "2", + "3", + "4", + "5" + ] + + +block: + iterator iter(): int {.closure.} = + try: + raise (ref CatchableError)() + finally: + yield 1 + + var it = iter + # TODO: what should happen here? + doAssert it() == 1 + +block: + iterator iter(): int {.closure.} = + # This closure iterator currently kills the compiler with a stack-overflow + block: + try: + try: + raise (ref CatchableError)() + finally: + yield 1 + finally: + break + + var it = iter + doAssert it() == 1 + +block: + var values: seq[string] + proc p() = + values.add("1") + try: + values.add("2") + return + finally: + values.add("3") + values.add("4") + + p() + + doAssert values == @[ + "1", + "2", + "3", + "4" + ] + + +block return_raise_rvo: + + type Large = object + x: int + a: array[256, int] + + proc p(): Large = + result.x = 0 + try: + raise (ref Exception)() + finally: + return Large(x: 1) + + block: + var a: Large + try: + a = p() + except: + discard + doAssert a.x == 0 + + + +block return_break_rvo: + + proc p(): int = + block: + try: + return 1 + finally: + break + + block: + var a: int + a = p() + doAssert a == 1 + + +block break_and_finally: + var values: seq[string] + + proc p() = + try: + block b: + try: + return + finally: + values.add "inner" + break b + + values.add "end" + finally: + values.add "finally" + + p() + + doAssert values == @[ + "inner", + "end", + "finally" + ] + + +block raise_break_and_finally: + var values: seq[string] + + proc p() = + try: + block b: + try: + raise (ref CatchableError)() + finally: + values.add "inner" + break b + + values.add "block end" + except: + values.add "except" + finally: + values.add "finally" + values.add("end") + + p() + + doAssert values == @[ + "inner", + "except", + "finally", + "end" + ] + + + + + +block break_and_none_finally: + var values: seq[string] + proc p() = + for i in 0..2: + values.add "loop" + try: + try: + raise (ref CatchableError)() + finally: + values.add "finally" + break + except: + values.add "except" + finally: + values.add "finally outer" + values.add $i + values.add "end" + + p() + + doAssert values == @[ + "loop", + "finally", + "except", + "finally outer", + "end" + ] + + +block continue_and_none_finally: + var values: seq[string] + proc p() = + for i in 0..2: + values.add "loop" + try: + try: + raise (ref CatchableError)() + finally: + values.add "finally" + continue + except: + values.add "except" + finally: + values.add "finally outer" + values.add $i + values.add "end" + + p() + + doAssert values == @[ + "loop", + "finally", + "except", + "finally outer", + "loop", + "finally", + "except", + "finally outer", + "loop", + "finally", + "except", + "finally outer", + "end" + ] + + + +block continue_and_none: + var values: seq[string] + proc p() = + for i in 0..2: + values.add "loop" + try: + try: + raise (ref CatchableError)() + finally: + values.add "finally" + continue + except: + values.add "except" + values.add $i + values.add "end" + + p() + + doAssert values == @[ + "loop", + "finally", + "except", + "loop", + "finally", + "except", + "loop", + "finally", + "except", + "end" + ] + +block continue_and_return: + var values: seq[string] + proc p() = + for i in 0..2: + values.add "loop" + try: + try: + raise (ref CatchableError)() + finally: + values.add "finally" + continue + except: + values.add "except" + return + values.add $i + values.add "end" + + p() + + doAssert values == @[ + "loop", + "finally", + "except" + ] + +block continue_and_continue: + var values: seq[string] + proc p() = + for i in 0..2: + values.add "loop" + try: + try: + raise (ref CatchableError)() + finally: + values.add "finally" + continue + except: + values.add "except" + continue + values.add $i + values.add "end" + + p() + + doAssert values == @[ + "loop", + "finally", + "except", + "loop", + "finally", + "except", + "loop", + "finally", + "except", + "end" + ] + +block continue_and_break: + var values: seq[string] + proc p() = + for i in 0..2: + values.add "loop" + try: + try: + raise (ref CatchableError)() + finally: + values.add "finally" + continue + except: + values.add "except" + break + values.add $i + values.add "end" + + p() + + doAssert values == @[ + "loop", + "finally", + "except", + "end" + ] + + +block break_and_continue: + var values: seq[string] + proc p() = + for i in 0..2: + values.add "loop" + try: + try: + raise (ref CatchableError)() + finally: + values.add "finally" + break + except: + values.add "except" + continue + values.add $i + values.add "end" + + p() + + doAssert values == @[ + "loop", + "finally", + "except", + "loop", + "finally", + "except", + "loop", + "finally", + "except", + "end" + ] + + + +## Control-flow statements in a `finally` clause are able to override control-flow statements +## in the corresponding try/except clauses or finally clauses nested inside the try/except clauses + +template defineBlockProc(values, inTryStmt, inFinallyStmt) {.dirty.} = + proc p() = + values.add("begin") + block b: + values.add("block") + try: + values.add("try") + inTryStmt + finally: + values.add("finally") + inFinallyStmt + values.add("block end") + values.add("end") + +template blockTestCase(inTryStmt, inFinallyStmt, expect) = + block: + var values {.inject.}: seq[string] + + defineBlockProc(values, inTryStmt, inFinallyStmt) + + p() + + doAssert values == expect + + +blockTestCase(): + break +do: + return +do: + @[ + "begin", + "block", + "try", + "finally" + ] + +blockTestCase(): + return +do: + break +do: + @[ + "begin", + "block", + "try", + "finally", + "end" + ] + +blockTestCase(): + break +do: + discard +do: + @[ + "begin", + "block", + "try", + "finally", + "end" + ] + +block finally_control_flow: + var values: seq[string] + + proc p() = + values.add("begin") + for i in 0..2: + values.add($i) + try: + break + finally: + values.add("finally") + continue + + values.add("end") + + p() + + doAssert values == @[ + "begin", + "0", + "finally", + "1", + "finally", + "2", + "finally", + "end" + ] + + +block finally_control_flow: + var values: seq[string] + + proc p() = + values.add("begin") + for i in 0..2: + values.add($i) + try: + return + finally: + values.add("finally") + continue + + values.add("end") + + p() + + doAssert values == @[ + "begin", + "0", + "finally", + "1", + "finally", + "2", + "finally", + "end" + ] + + + +block finally_control_flow: + var values: seq[string] + + proc p() = + values.add("begin") + for i in 0..2: + values.add($i) + try: + continue + finally: + values.add("finally") + break + + values.add("end") + + p() + + doAssert values == @[ + "begin", + "0", + "finally", + "end" + ] + + +block nested_finally_in_finally: + blockTestCase(): + return + do: + try: + values.add("inner try") + finally: + ## This will leave the function + return + + values.add("finally end") + break b + do: + @[ + "b", + "inner try" + ] + + + +block: + var values: seq[string] + proc p() = + block blo: + try: + try: + values.add("1") + return + finally: + values.add("2") + return + finally: + values.add("3") + break + values.add("4") + values.add("5") + + p() + + doAssert values == @[ + "1", + "2", + "3", + "5" + ] + +block: + var values: seq[string] + proc a() = + for i in 0..2: + try: + try: + try: + values.add("raise") + raise (ref CatchableError)() + finally: + values.add("inner finally") + return + except: + values.add("inner except") + raise + except: + values.add("except") + finally: + values.add("finally") + values.add($i) + + values.add("end") + + a() + + doAssert values == @[ + "raise", + "inner finally", + "inner except", + "except", + "finally", + "0", + + "raise", + "inner finally", + "inner except", + "except", + "finally", + "1", + + "raise", + "inner finally", + "inner except", + "except", + "finally", + "2", + + "end" + ] + +block: + var values: seq[string] + proc a() = + for i in 0..1: + try: + try: + for j in 0..1: + try: + raise (ref CatchableError)() + finally: + values.add("in finally") + break + values.add("in loop") + values.add("post inner loop") + except: + values.add("in except") + raise + except: + values.add("except") + finally: + values.add("finally") + + values.add("loop") + + values.add("end") + + a() + + doAssert values == @[ + "in finally", + "in except", + "except", + "finally", + "loop", + + "in finally", + "in except", + "except", + "finally", + "loop", + + "end" + ] + + + +block: + var values: seq[string] + block a: + for i in 0..1: + try: + break a + finally: + values.add("finally") + break + values.add($i) + + values.add("end") + + doAssert values == @[ + "finally", + "end" + ] \ No newline at end of file