diff --git a/.gitignore b/.gitignore index f66d427a..67816b6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ nim.cfg bin deps +.tool-versions diff --git a/cps.nim b/cps.nim index 01e5525b..657a6e62 100644 --- a/cps.nim +++ b/cps.nim @@ -26,7 +26,7 @@ when (NimMajor, NimMinor, NimPatch) < (1, 7, 3): # we recommend against threads:on without define:useMalloc when not defined(useMalloc) and compileOption"threads": {.warning: - "cps recommends against --threads:on without --define:useMalloc".} + "cps recommends --define:useMalloc when used with threads".} proc state*(c: Continuation): State {.inline.} = ## Get the current state of a continuation diff --git a/tests/killer.nim b/tests/killer.nim index b853109c..b4b8b787 100644 --- a/tests/killer.nim +++ b/tests/killer.nim @@ -10,7 +10,11 @@ proc `=destroy`*(k: var Killer) {.raises: [FailError].} = if k.final != k.n: let e = getCurrentException() # don't obliterate current exception - if e.isNil or e isnot FailError or e isnot ExpectedError: + when defined(ExpectedError): + let shouldFail = e.isNil or e isnot FailError or e isnot ExpectedError + else: + let shouldFail = e.isNil or e isnot FailError + if shouldFail: fail: case k.n of 0: "uninitialized" diff --git a/tests/preamble.nim b/tests/preamble.nim index 1f612ebe..da2b1a2f 100644 --- a/tests/preamble.nim +++ b/tests/preamble.nim @@ -1,51 +1,58 @@ -import pkg/balls - -import pkg/cps except trampoline - -type - EmptyLoop = CatchableError - InfiniteLoop = CatchableError - Cont* = ref object of Continuation - -var jumps: int - -proc trampoline[T: Continuation](c: sink T) {.used.} = - jumps = 0 - var c: Continuation = move c - while c.running: - # capture the exception in the environment - let exception = getCurrentException() +# +# modules and sanity checking that we want to include +# in all test files +# +when not declaredInScope(InfiniteLoop): + import pkg/balls + + import cps except trampoline + + include killer + + type + EmptyLoop = CatchableError + InfiniteLoop = CatchableError + Cont* = ref object of Continuation + + var jumps: int + + proc trampoline[T: Continuation](c: sink T) {.used.} = + jumps = 0 + var c: Continuation = move c + while c.running: + # capture the exception in the environment + let exception = getCurrentException() + try: + var y = c.fn + var x = y(c) + c = x + except CatchableError: + if not c.dismissed: + writeStackFrames c + raise + # the current exception should not change + check getCurrentException() == exception + inc jumps + if jumps > 1000: + raise InfiniteLoop.newException: $jumps & " iterations" + if jumps == 0: + raise EmptyLoop.newException: + "continuations test best when they, uh, bounce" + + proc noop*(c: Cont): Cont {.cpsMagic.} = c + + # We have a lot of these for the purpose of control-flow validation + {.warning[UnreachableCode]: off.} + + template shouldRun(wanted: int; body: untyped) {.used.} = + var measured {.inject.} = 0 try: - var y = c.fn - var x = y(c) - c = x - except CatchableError: - if not c.dismissed: - writeStackFrames c - raise - # the current exception should not change - check getCurrentException() == exception - inc jumps - if jumps > 1000: - raise InfiniteLoop.newException: $jumps & " iterations" - if jumps == 0: - raise EmptyLoop.newException: - "continuations test best when they, uh, bounce" - -proc noop*(c: Cont): Cont {.cpsMagic.} = c - -# We have a lot of these for the purpose of control-flow validation -{.warning[UnreachableCode]: off.} - -template shouldRun(wanted: int; body: untyped) {.used.} = - var measured {.inject.} = 0 - try: - body - finally: - check measured == wanted: - if wanted == 0: "oops; continuation ran" - elif measured == 0: "continuation never ran" - elif measured > wanted: "continuation ran too often" - else: "continuation ran too rarely" - -template ran {.used, dirty.} = inc measured + body + finally: + check measured == wanted: + if wanted == 0: "oops; continuation ran" + elif measured == 0: "continuation never ran" + elif measured > wanted: "continuation ran too often" + else: "continuation ran too rarely" + + template ran {.used, dirty.} = inc measured diff --git a/tests/t0.nim b/tests/t00_smoke.nim similarity index 100% rename from tests/t0.nim rename to tests/t00_smoke.nim diff --git a/tests/tloops.nim b/tests/t10_loops.nim similarity index 98% rename from tests/tloops.nim rename to tests/t10_loops.nim index f0086bb7..9967dbe5 100644 --- a/tests/tloops.nim +++ b/tests/t10_loops.nim @@ -1,8 +1,4 @@ -import balls -import cps - include preamble -import killer suite "loops": diff --git a/tests/tapi.nim b/tests/t20_api.nim similarity index 97% rename from tests/tapi.nim rename to tests/t20_api.nim index d6a9a9dc..e8d0701d 100644 --- a/tests/tapi.nim +++ b/tests/t20_api.nim @@ -1,5 +1,4 @@ include preamble -import killer import tests/exports @@ -230,16 +229,19 @@ suite "cps api": step 2 send(42) step 3 - echo recv() + echo "level_two: ", recv() + let x = recv() + echo "level_two: ", x step 4 proc level_one() {.cps:C.} = step 1 level_two() step 5 + echo "level_one: ", recv() let v = recv() - echo recv() step 6 + check v == 0 var a = whelp level_one() trampoline a diff --git a/tests/tcc.nim b/tests/t30_cc.nim similarity index 98% rename from tests/tcc.nim rename to tests/t30_cc.nim index f88e5106..bb0ee96a 100644 --- a/tests/tcc.nim +++ b/tests/t30_cc.nim @@ -1,7 +1,4 @@ -import std/macros - include preamble -include killer suite "calling convention": diff --git a/tests/taste.nim b/tests/t40_ast.nim similarity index 98% rename from tests/taste.nim rename to tests/t40_ast.nim index 3a0a65b7..4f13ad91 100644 --- a/tests/taste.nim +++ b/tests/t40_ast.nim @@ -1,8 +1,7 @@ -import std/macros -import foreign - include preamble +import foreign + suite "tasteful tests": var r = 0 @@ -403,6 +402,10 @@ suite "tasteful tests": proc foo() {.cps: Continuation.} = inc r let o: RootRef = new O + #{.push warning[CondTrue]: off.} + check r == 1 and o is RootRef + #{.pop.} + check o of RootObj foo() @@ -420,6 +423,7 @@ suite "tasteful tests": # implicit conversion in assignment var j: int j = n + check i == n foo() diff --git a/tests/thooks.nim b/tests/t50_hooks.nim similarity index 99% rename from tests/thooks.nim rename to tests/t50_hooks.nim index 348b5fe0..4fc451c0 100644 --- a/tests/thooks.nim +++ b/tests/t50_hooks.nim @@ -1,3 +1,5 @@ +include preamble + import std/genasts import std/macros import std/os @@ -7,9 +9,6 @@ import std/strutils from cps/spec import Hook, cpsStackFrames from cps/hooks import findColonLit -include preamble -import killer - suite "hooks": block: @@ -53,7 +52,7 @@ suite "hooks": of Stack: sub else: astToStr c var path = info.filename.lastPathPart - path = if path == "thooks.nim": "👍" else: path + path = if path == "t50_hooks.nim": "👍" else: path found.add "$#: $# $# $#" % [ $hook, $sub, last, path ] body diff --git a/tests/treturns.nim b/tests/t60_returns.nim similarity index 99% rename from tests/treturns.nim rename to tests/t60_returns.nim index 4b5f7edf..2bd2cb17 100644 --- a/tests/treturns.nim +++ b/tests/t60_returns.nim @@ -1,9 +1,6 @@ import std/macros -import balls -import cps include preamble -import killer suite "returns and results": diff --git a/tests/tlocals.nim b/tests/t70_locals.nim similarity index 99% rename from tests/tlocals.nim rename to tests/t70_locals.nim index 994ee794..9fb7e4cd 100644 --- a/tests/tlocals.nim +++ b/tests/t70_locals.nim @@ -1,7 +1,4 @@ -import std/sugar - include preamble -include killer suite "locals": @@ -405,21 +402,6 @@ suite "tuples": foo() check r == 2 - block: - ## sugary procedure arguments can be used in expressions - r = 0 - proc bar(x: int): int {.cps: Cont.} = - inc r - result = x * 2 - - proc foo(fn: (int) -> int): int {.cps: Cont.} = - inc r - result = fn: bar(2) - inc r - - check 12 == foo(x => x * 3) - check r == 3 - type K = distinct object @@ -519,3 +501,22 @@ suite "lifetimes": # destroy bar.m; eg. step == 9 foo() + +import std/sugar + +suite "high-cal": + var r = 0 + block: + ## sugary procedure arguments can be used in expressions + r = 0 + proc bar(x: int): int {.cps: Cont.} = + inc r + result = x * 2 + + proc foo(fn: (int) -> int): int {.cps: Cont.} = + inc r + result = fn: bar(2) + inc r + + check 12 == foo(x => x * 3) + check r == 3 diff --git a/tests/ttry.nim b/tests/t80_try1.nim similarity index 81% rename from tests/ttry.nim rename to tests/t80_try1.nim index 50ce4e0b..ada41cc3 100644 --- a/tests/ttry.nim +++ b/tests/t80_try1.nim @@ -1,7 +1,6 @@ import std/strutils include preamble -import killer from cps/spec import cpsStackFrames @@ -540,7 +539,7 @@ suite "try statements": except CatchableError: let frames = renderStackFrames() check frames.len > 0, "expected at least one stack trace record" - check "ttry.nim" in frames[0], "couldn't find ttry.nim in the trace" + check "t80_try1.nim" in frames[0], "couldn't find t80_try1.nim in the trace" raise try: @@ -549,152 +548,3 @@ suite "try statements": except CatchableError as e: check e.msg == "test", "unable to pass exception message from cps" check r == 1 - -suite "defer statements": - - var r = 0 - - block: - ## a defer statement works across a continuation - r = 0 - proc foo() {.cps: Cont.} = - defer: - check r == 2, "defer run before end of scope" - inc r - - inc r - noop() - inc r - - foo() - check r == 3 - - block: - ## a basic defer statement is supported - r = 0 - proc foo() {.cps: Cont.} = - inc r - noop() - defer: - check r == 4 - inc r - inc r - defer: - check r == 3 - inc r - inc r - - foo() - check r == 5 - - block: - ## a defer in a nested template is supported - r = 0 - - template deferChk(i: int) = - inc r - defer: - check r == i - inc r - - proc foo() {.cps: Cont.} = - deferChk(5) - inc r - deferChk(4) - inc r - - foo() - check r == 6 - - block: - ## a defer inside a block statement works - r = 0 - proc foo() {.cps: Cont.} = - inc r - block: - defer: - check r == 2 - inc r - inc r - defer: - check r == 4 - inc r - inc r - - foo() - check r == 5 - - block: - ## a naked defer is not a problem - r = 0 - proc foo() {.cps: Cont.} = - defer: - inc r - - foo() - check r == 1 - - -when defined(gcArc) or defined(gcOrc): - suite "breaking deterministic memory managers": - block: - ## try-except-statement splits - proc foo() {.cps: Cont.} = - var k = newKiller(3) - step 1 - try: - noop() - step 2 - except CatchableError: - fail "this branch should not run" - step 3 - - foo() - - block: - ## try-except splits with raise - proc foo() {.cps: Cont.} = - var k = newKiller(4) - step 1 - try: - noop() - step 2 - raise newException(CatchableError, "") - fail "statement run after raise" - except CatchableError: - step 3 - step 4 - - foo() - - block: - ## try-finally-statement splits - proc foo() {.cps: Cont.} = - var k = newKiller(4) - step 1 - try: - noop() - step 2 - finally: - step 3 - step 4 - - foo() - - block: - ## try-except-finally splits with raise - proc foo() {.cps: Cont.} = - var k = newKiller(5) - step 1 - try: - noop() - step 2 - raise newException(CatchableError, "") - fail "statement run after raise" - except CatchableError: - step 3 - finally: - step 4 - step 5 - - foo() diff --git a/tests/t80_try2.nim b/tests/t80_try2.nim new file mode 100644 index 00000000..faf7dbf1 --- /dev/null +++ b/tests/t80_try2.nim @@ -0,0 +1,154 @@ +import std/strutils + +include preamble + +from cps/spec import cpsStackFrames + +suite "defer statements": + + var r = 0 + + block: + ## a defer statement works across a continuation + r = 0 + proc foo() {.cps: Cont.} = + defer: + check r == 2, "defer run before end of scope" + inc r + + inc r + noop() + inc r + + foo() + check r == 3 + + block: + ## a basic defer statement is supported + r = 0 + proc foo() {.cps: Cont.} = + inc r + noop() + defer: + check r == 4 + inc r + inc r + defer: + check r == 3 + inc r + inc r + + foo() + check r == 5 + + block: + ## a defer in a nested template is supported + r = 0 + + template deferChk(i: int) = + inc r + defer: + check r == i + inc r + + proc foo() {.cps: Cont.} = + deferChk(5) + inc r + deferChk(4) + inc r + + foo() + check r == 6 + + block: + ## a defer inside a block statement works + r = 0 + proc foo() {.cps: Cont.} = + inc r + block: + defer: + check r == 2 + inc r + inc r + defer: + check r == 4 + inc r + inc r + + foo() + check r == 5 + + block: + ## a naked defer is not a problem + r = 0 + proc foo() {.cps: Cont.} = + defer: + inc r + + foo() + check r == 1 + + +when defined(gcArc) or defined(gcOrc): + suite "breaking deterministic memory managers": + block: + ## try-except-statement splits + proc foo() {.cps: Cont.} = + var k = newKiller(3) + step 1 + try: + noop() + step 2 + except CatchableError: + fail "this branch should not run" + step 3 + + foo() + + block: + ## try-except splits with raise + proc foo() {.cps: Cont.} = + var k = newKiller(4) + step 1 + try: + noop() + step 2 + raise newException(CatchableError, "") + fail "statement run after raise" + except CatchableError: + step 3 + step 4 + + foo() + + block: + ## try-finally-statement splits + proc foo() {.cps: Cont.} = + var k = newKiller(4) + step 1 + try: + noop() + step 2 + finally: + step 3 + step 4 + + foo() + + block: + ## try-except-finally splits with raise + proc foo() {.cps: Cont.} = + var k = newKiller(5) + step 1 + try: + noop() + step 2 + raise newException(CatchableError, "") + fail "statement run after raise" + except CatchableError: + step 3 + finally: + step 4 + step 5 + + foo() diff --git a/tests/t90_exprs1.nim b/tests/t90_exprs1.nim new file mode 100644 index 00000000..d8f48ac9 --- /dev/null +++ b/tests/t90_exprs1.nim @@ -0,0 +1,143 @@ +include preamble + +suite "expression flattening": + test "flatten expression list in var/let": + var k = newKiller(3) + proc foo() {.cps: Cont.} = + let + x = (noop(); step 1; 42) + y = (noop(); step 2; x) + + check x == y + + var (a, b) = (noop(); step 3; (10, y)) + check a == 10 + check b == y + + foo() + + test "flatten block expression": + var k = newKiller(3) + proc foo() {.cps: Cont.} = + step 1 + + let x = block: + noop() + step 2 + 42 + + step 3 + check x == 42 + + foo() + + test "flatten if expression": + var k = newKiller(5) + proc foo() {.cps: Cont.} = + step 1 + + let x = + if true: + noop() + step 2 + 42 + elif true: + fail "this branch should not run" + 0 # needed because the compiler doesn't recognize fail as noreturn + else: + fail "this branch should not run" + -1 # needed because the compiler doesn't recognize fail as noreturn + + step 3 + check x == 42 + + let y = + if false: + fail "this branch should not run" + -1 + elif false: + fail "this branch should not run" + 0 + else: + noop() + step 4 + 30 + + step 5 + check y == 30 + + foo() + + test "flatten case expression": + var k = newKiller(3) + proc foo() {.cps: Cont.} = + step 1 + + let x = + case "true" + of "truer", "truest", "very true": + fail "this branch should not run" + 0 + of "false": + fail "this branch should not run" + -1 + of "true": + noop() + step 2 + 42 + elif true: + fail "this branch should not run" + -3 + else: + fail "this branch should not run" + -2 + + step 3 + check x == 42 + + foo() + + test "flatten try statement": + var k = newKiller(4) + proc foo() {.cps: Cont.} = + step 1 + + let x = + try: + raise newException(ValueError, "something") + 0 + except ValueError, IOError: + noop() + let e = getCurrentException() + check e of ValueError + check e.msg == "something" + step 2 + 42 + except CatchableError: + fail "this branch should not run" + -1 + finally: + step 3 + + step 4 + check x == 42 + + foo() + + test "flatten if condition": + var k = newKiller(5) + proc foo() {.cps: Cont.} = + step 1 + + if (noop(); step 2; false): + fail "This branch should not be run" + elif (noop(); step 3; true): + step 4 + elif (noop(); fail"This expression should not be evaluated"; false): + fail "This branch should not be run" + else: + fail "This branch should not be run" + + step 5 + + foo() diff --git a/tests/t90_exprs2.nim b/tests/t90_exprs2.nim new file mode 100644 index 00000000..bfb65725 --- /dev/null +++ b/tests/t90_exprs2.nim @@ -0,0 +1,123 @@ +include preamble + +suite "expression flattening": + test "flatten case matching expression": + var k = newKiller(4) + proc foo() {.cps: Cont.} = + step 1 + + case (noop(); step 2; "string") + of "str": + fail "This branch should not be run" + of "string": + step 3 + else: + fail "This branch should not be run" + + step 4 + + foo() + + test "flatten case elif branches": + var k = newKiller(4) + proc foo() {.cps: Cont.} = + step 1 + + case "string" + of "str": + fail "This branch should not be run" + of "String": + fail "This branch should not be run" + elif (noop(); step 2; true): + step 3 + else: + fail "This branch should not be run" + + step 4 + + foo() + + test "flatten while condition": + var k = newKiller(4) + proc foo() {.cps: Cont.} = + step 1 + + var x = 2 + while (noop(); step x; inc x; x < 4): + discard + + step 4 + + foo() + + test "flatten assignments with LHS being a symbol": + var k = newKiller(3) + proc foo() {.cps: Cont.} = + step 1 + var x: int + x = + if true: + noop() + step 2 + 42 + else: + fail "this branch should not be run" + -1 + + step 3 + + check x == 42 + + foo() + + test "flatten assignments with LHS being an object access": + type + A = object + i: int + O = object + a: A + + var k = newKiller(3) + proc foo() {.cps: Cont.} = + step 1 + var o: O + o.a.i = + if true: + noop() + step 2 + 42 + else: + fail "this branch should not be run" + -1 + + step 3 + + check o.a.i == 42 + + foo() + + test "flatten assignments with LHS being a ref access from immutable location": + type + A = object + i: int + O = ref object + a: A + + var k = newKiller(3) + proc foo() {.cps: Cont.} = + step 1 + let o = O() + o.a.i = + if true: + noop() + step 2 + 42 + else: + fail "this branch should not be run" + return + + step 3 + + check o.a.i == 42 + + foo() diff --git a/tests/t90_exprs3.nim b/tests/t90_exprs3.nim new file mode 100644 index 00000000..8949e52c --- /dev/null +++ b/tests/t90_exprs3.nim @@ -0,0 +1,108 @@ +include preamble + +suite "expression flattening": + test "flatten unpacking assignments": + type + O = object + x: int + y: int + + var k = newKiller(3) + proc foo() {.cps: Cont.} = + step 1 + var o = O() + (o.x, o.y) = + if true: + noop() + step 2 + (42, 10) + else: + fail "this branch should not be run" + return + + step 3 + + check o.x == 42 + check o.y == 10 + + foo() + + test "flatten upcasting assignments": + when not defined(release) and not defined(isNimSkull): + skip"compiler crashes on debug" + else: + type + O = ref object of RootObj + x: int + y: int + I = ref object of O + + var k = newKiller(3) + proc foo() {.cps: Cont.} = + step 1 + var o = O() + o = + if true: + noop() + step 2 + I(x: 42, y: 10) + else: + fail "this branch should not be run" + I(x: 42, y: 20) + + step 3 + + check o of I + check o.x == 42 + check o.y == 10 + + foo() + + test "flatten implicitly converted assignments": + var k = newKiller(3) + proc foo() {.cps: Cont.} = + step 1 + let o: int = + if true: + noop() + step 2 + Natural(42) + else: + fail "this branch should not be run" + return + + step 3 + + check o == 42 + + foo() + + test "flatten explicitly converted assignments": + var k = newKiller(3) + proc foo() {.cps: Cont.} = + step 1 + let i = int(block: (noop(); step 2; 42.Natural)) + + step 3 + + check i == 42 + + foo() + + test "flatten discard statements": + var k = newKiller(3) + proc foo() {.cps: Cont.} = + step 1 + discard (block: (noop(); step 2; 42.Natural)) + + step 3 + + foo() + + test "flatten return statements": + var k = newKiller(2) + proc foo(): int {.cps: Cont.} = + step 1 + return (block: (noop(); step 2; 42.Natural)) + + check foo() == 42 diff --git a/tests/t90_exprs4.nim b/tests/t90_exprs4.nim new file mode 100644 index 00000000..80deb1a8 --- /dev/null +++ b/tests/t90_exprs4.nim @@ -0,0 +1,96 @@ +include preamble + +suite "expression flattening": + test "flatten array construction": + var k = newKiller(5) + proc foo() {.cps: Cont.} = + step 1 + + let x = [1, 2, (step 2; 42), (noop(); step 3; 10), (step 4; 20)] + + step 5 + check x == [1, 2, 42, 10, 20] + + foo() + + test "flatten tuple construction": + var k = newKiller(5) + proc foo() {.cps: Cont.} = + step 1 + + let x = (a: 1, b: 2, c: (step 2; 42), d: (noop(); step 3; 10), e: (step 4; 20)) + + step 5 + # A few checks to verify that the names stay + check x.a == 1 + check x.b == 2 + check x == (1, 2, 42, 10, 20) + + foo() + + test "flatten object construction": + type + O = object of RootObj + x: int + y: int + z: float + + var k = newKiller(5) + proc foo() {.cps: Cont.} = + step 1 + + let x = O(x: (step 2; 42), y: (noop(); step 3; 10), z: (step 4; 20)) + + step 5 + # A few checks to verify that the names stay + check x == O(x: 42, y: 10, z: 20) + + foo() + + test "flatten calls": + var k = newKiller(5) + + proc bar(a, b: int) = + step 3 + check a == 42 + check b == 10 + + proc barvar(a: var int, b: int) = + step 5 + check a == 20 + check b == 20 + + proc foo() {.cps: Cont.} = + step 1 + var x = 42 + bar(x, (noop(); step 2; x = 10; x)) + + x = 42 + barvar(x, (noop(); step 4; x = 20; x)) + + foo() + + test "flatten and/or with short circuiting": + var k = newKiller(7) + + proc foo() {.cps: Cont.} = + step 1 + check (noop(); step 2; true) and (noop(); step 3; true) + check not((noop(); step 4; false) and (noop(); fail "this should not run"; true)) + check (noop(); step 5; false) or (noop(); step 6; true) + check (noop(); step 7; true) or (noop(); fail "this should not run"; false) + + foo() + + test "flatten raise statement": + var k = newKiller(3) + + proc foo() {.cps: Cont.} = + step 1 + try: + raise (noop(); step 2; newException(CatchableError, "test")) + except CatchableError as e: + step 3 + check e.msg == "test" + + foo() diff --git a/tests/t90_exprs5.nim b/tests/t90_exprs5.nim new file mode 100644 index 00000000..257024cb --- /dev/null +++ b/tests/t90_exprs5.nim @@ -0,0 +1,89 @@ +include preamble + +suite "expression flattening": + test "flatten pragma block expression": + var k = newKiller(3) + + proc foo() {.cps: Cont.} = + step 1 + let x = + block: + {.cast(gcsafe).}: + noop() + step 2 + 10 + step 3 + check x == 10 + + foo() + + test "flatten result expressions": + var k = newKiller(1) + proc foo(): int {.cps: Cont.} = + noop() + step 1 + 42.Natural + + check foo() == 42 + + test "flatten bracket expressions (array access)": + var k = newKiller(2) + + proc foo() {.cps: Cont.} = + check (noop(); step 1; [42])[(noop(); step 2; 0)] == 42 + + foo() + + test "flatten dot expressions": + type + P = object + val: int + + var k = newKiller(1) + + proc foo() {.cps: Cont.} = + check (noop(); step 1; P(val: 42)).val == 42 + + foo() + + test "flatten dereference expressions": + type + P = ref object + val: int + + var k = newKiller(1) + + proc foo() {.cps: Cont.} = + check (noop(); step 1; P(val: 42))[].val == 42 + + foo() + + test "flatten hidden dereference expressions": + type + P = ref object + val: int + + var k = newKiller(1) + + proc foo() {.cps: Cont.} = + check (noop(); step 1; P(val: 42)).val == 42 + + foo() + + test "flatten magic calls with mutable variables": + var k = newKiller(3) + + proc foo() {.cps: Cont.} = + var x: string + # add(var string, string) is a magic + x.add (noop(); step 1; "test") + check x == "test" + + var y: seq[string] + # add(var seq[T], T) is a magic with generics + y.add (noop(); step 2; "test") + check y == @["test"] + + step 3 + + foo() diff --git a/tests/texprs.nim b/tests/texprs.nim deleted file mode 100644 index e569d931..00000000 --- a/tests/texprs.nim +++ /dev/null @@ -1,553 +0,0 @@ -include preamble - -import killer - -suite "expression flattening": - test "flatten expression list in var/let": - var k = newKiller(3) - proc foo() {.cps: Cont.} = - let - x = (noop(); step 1; 42) - y = (noop(); step 2; x) - - check x == y - - var (a, b) = (noop(); step 3; (10, y)) - check a == 10 - check b == y - - foo() - - test "flatten block expression": - var k = newKiller(3) - proc foo() {.cps: Cont.} = - step 1 - - let x = block: - noop() - step 2 - 42 - - step 3 - check x == 42 - - foo() - - test "flatten if expression": - var k = newKiller(5) - proc foo() {.cps: Cont.} = - step 1 - - let x = - if true: - noop() - step 2 - 42 - elif true: - fail "this branch should not run" - 0 # needed because the compiler doesn't recognize fail as noreturn - else: - fail "this branch should not run" - -1 # needed because the compiler doesn't recognize fail as noreturn - - step 3 - check x == 42 - - let y = - if false: - fail "this branch should not run" - -1 - elif false: - fail "this branch should not run" - 0 - else: - noop() - step 4 - 30 - - step 5 - check y == 30 - - foo() - - test "flatten case expression": - var k = newKiller(3) - proc foo() {.cps: Cont.} = - step 1 - - let x = - case "true" - of "truer", "truest", "very true": - fail "this branch should not run" - 0 - of "false": - fail "this branch should not run" - -1 - of "true": - noop() - step 2 - 42 - elif true: - fail "this branch should not run" - -3 - else: - fail "this branch should not run" - -2 - - step 3 - check x == 42 - - foo() - - test "flatten try statement": - var k = newKiller(4) - proc foo() {.cps: Cont.} = - step 1 - - let x = - try: - raise newException(ValueError, "something") - 0 - except ValueError, IOError: - noop() - let e = getCurrentException() - check e of ValueError - check e.msg == "something" - step 2 - 42 - except CatchableError: - fail "this branch should not run" - -1 - finally: - step 3 - - step 4 - check x == 42 - - foo() - - test "flatten if condition": - var k = newKiller(5) - proc foo() {.cps: Cont.} = - step 1 - - if (noop(); step 2; false): - fail "This branch should not be run" - elif (noop(); step 3; true): - step 4 - elif (noop(); fail"This expression should not be evaluated"; false): - fail "This branch should not be run" - else: - fail "This branch should not be run" - - step 5 - - foo() - - test "flatten case matching expression": - var k = newKiller(4) - proc foo() {.cps: Cont.} = - step 1 - - case (noop(); step 2; "string") - of "str": - fail "This branch should not be run" - of "string": - step 3 - else: - fail "This branch should not be run" - - step 4 - - foo() - - test "flatten case elif branches": - var k = newKiller(4) - proc foo() {.cps: Cont.} = - step 1 - - case "string" - of "str": - fail "This branch should not be run" - of "String": - fail "This branch should not be run" - elif (noop(); step 2; true): - step 3 - else: - fail "This branch should not be run" - - step 4 - - foo() - - test "flatten while condition": - var k = newKiller(4) - proc foo() {.cps: Cont.} = - step 1 - - var x = 2 - while (noop(); step x; inc x; x < 4): - discard - - step 4 - - foo() - - test "flatten assignments with LHS being a symbol": - var k = newKiller(3) - proc foo() {.cps: Cont.} = - step 1 - var x: int - x = - if true: - noop() - step 2 - 42 - else: - fail "this branch should not be run" - -1 - - step 3 - - check x == 42 - - foo() - - test "flatten assignments with LHS being an object access": - type - A = object - i: int - O = object - a: A - - var k = newKiller(3) - proc foo() {.cps: Cont.} = - step 1 - var o: O - o.a.i = - if true: - noop() - step 2 - 42 - else: - fail "this branch should not be run" - -1 - - step 3 - - check o.a.i == 42 - - foo() - - test "flatten assignments with LHS being a ref access from immutable location": - type - A = object - i: int - O = ref object - a: A - - var k = newKiller(3) - proc foo() {.cps: Cont.} = - step 1 - let o = O() - o.a.i = - if true: - noop() - step 2 - 42 - else: - fail "this branch should not be run" - return - - step 3 - - check o.a.i == 42 - - foo() - - test "flatten unpacking assignments": - type - O = object - x: int - y: int - - var k = newKiller(3) - proc foo() {.cps: Cont.} = - step 1 - var o = O() - (o.x, o.y) = - if true: - noop() - step 2 - (42, 10) - else: - fail "this branch should not be run" - return - - step 3 - - check o.x == 42 - check o.y == 10 - - foo() - - test "flatten upcasting assignments": - when not defined(release) and not defined(isNimSkull): - skip"compiler crashes on debug" - else: - type - O = ref object of RootObj - x: int - y: int - I = ref object of O - - var k = newKiller(3) - proc foo() {.cps: Cont.} = - step 1 - var o = O() - o = - if true: - noop() - step 2 - I(x: 42, y: 10) - else: - fail "this branch should not be run" - I(x: 42, y: 20) - - step 3 - - check o of I - check o.x == 42 - check o.y == 10 - - foo() - - test "flatten implicitly converted assignments": - var k = newKiller(3) - proc foo() {.cps: Cont.} = - step 1 - let o: int = - if true: - noop() - step 2 - Natural(42) - else: - fail "this branch should not be run" - return - - step 3 - - check o == 42 - - foo() - - test "flatten explicitly converted assignments": - var k = newKiller(3) - proc foo() {.cps: Cont.} = - step 1 - let i = int(block: (noop(); step 2; 42.Natural)) - - step 3 - - check i == 42 - - foo() - - test "flatten discard statements": - var k = newKiller(3) - proc foo() {.cps: Cont.} = - step 1 - discard (block: (noop(); step 2; 42.Natural)) - - step 3 - - foo() - - test "flatten return statements": - var k = newKiller(2) - proc foo(): int {.cps: Cont.} = - step 1 - return (block: (noop(); step 2; 42.Natural)) - - check foo() == 42 - - test "flatten array construction": - var k = newKiller(5) - proc foo() {.cps: Cont.} = - step 1 - - let x = [1, 2, (step 2; 42), (noop(); step 3; 10), (step 4; 20)] - - step 5 - check x == [1, 2, 42, 10, 20] - - foo() - - test "flatten tuple construction": - var k = newKiller(5) - proc foo() {.cps: Cont.} = - step 1 - - let x = (a: 1, b: 2, c: (step 2; 42), d: (noop(); step 3; 10), e: (step 4; 20)) - - step 5 - # A few checks to verify that the names stay - check x.a == 1 - check x.b == 2 - check x == (1, 2, 42, 10, 20) - - foo() - - test "flatten object construction": - type - O = object of RootObj - x: int - y: int - z: float - - var k = newKiller(5) - proc foo() {.cps: Cont.} = - step 1 - - let x = O(x: (step 2; 42), y: (noop(); step 3; 10), z: (step 4; 20)) - - step 5 - # A few checks to verify that the names stay - check x == O(x: 42, y: 10, z: 20) - - foo() - - test "flatten calls": - var k = newKiller(5) - - proc bar(a, b: int) = - step 3 - check a == 42 - check b == 10 - - proc barvar(a: var int, b: int) = - step 5 - check a == 20 - check b == 20 - - proc foo() {.cps: Cont.} = - step 1 - var x = 42 - bar(x, (noop(); step 2; x = 10; x)) - - x = 42 - barvar(x, (noop(); step 4; x = 20; x)) - - foo() - - test "flatten and/or with short circuiting": - var k = newKiller(7) - - proc foo() {.cps: Cont.} = - step 1 - check (noop(); step 2; true) and (noop(); step 3; true) - check not((noop(); step 4; false) and (noop(); fail "this should not run"; true)) - check (noop(); step 5; false) or (noop(); step 6; true) - check (noop(); step 7; true) or (noop(); fail "this should not run"; false) - - foo() - - test "flatten raise statement": - var k = newKiller(3) - - proc foo() {.cps: Cont.} = - step 1 - try: - raise (noop(); step 2; newException(CatchableError, "test")) - except CatchableError as e: - step 3 - check e.msg == "test" - - foo() - - test "flatten pragma block expression": - var k = newKiller(3) - - proc foo() {.cps: Cont.} = - step 1 - let x = - block: - {.cast(gcsafe).}: - noop() - step 2 - 10 - step 3 - check x == 10 - - foo() - - test "flatten result expressions": - var k = newKiller(1) - proc foo(): int {.cps: Cont.} = - noop() - step 1 - 42.Natural - - check foo() == 42 - - test "flatten bracket expressions (array access)": - var k = newKiller(2) - - proc foo() {.cps: Cont.} = - check (noop(); step 1; [42])[(noop(); step 2; 0)] == 42 - - foo() - - test "flatten dot expressions": - type - P = object - val: int - - var k = newKiller(1) - - proc foo() {.cps: Cont.} = - check (noop(); step 1; P(val: 42)).val == 42 - - foo() - - test "flatten dereference expressions": - type - P = ref object - val: int - - var k = newKiller(1) - - proc foo() {.cps: Cont.} = - check (noop(); step 1; P(val: 42))[].val == 42 - - foo() - - test "flatten hidden dereference expressions": - type - P = ref object - val: int - - var k = newKiller(1) - - proc foo() {.cps: Cont.} = - check (noop(); step 1; P(val: 42)).val == 42 - - foo() - - test "flatten magic calls with mutable variables": - var k = newKiller(3) - - proc foo() {.cps: Cont.} = - var x: string - # add(var string, string) is a magic - x.add (noop(); step 1; "test") - check x == "test" - - var y: seq[string] - # add(var seq[T], T) is a magic with generics - y.add (noop(); step 2; "test") - check y == @["test"] - - step 3 - - foo()