diff options
| author | 2026-10-14 10:39:47 +0000 | |
|---|---|---|
| committer | 2026-10-14 10:39:47 +0000 | |
| commit | 54b8a8918bb63c98a8f58e823033232243647562 (patch) | |
| tree | e0608f7dab1f568c22390262e5a6d39966519760 /tests | |
| parent | todo: handoff section with state, conventions, in-flight work and architectur... (diff) | |
zc: add the defer statement
defer expr; and defer { ... } register code that runs when the enclosing
block ends: at the end of the block, and at return, break and continue
that leave it. the tokens are kept at the defer and compiled again at each
exit (block_close, body, kw_return, kw_break, kw_continue), newest first,
before the block's variables are released, so the analyzer sees a deferred
rt.free(p) as the transfer of p where it runs. names mean what they meant
at the defer; values are read when it runs. return, break and continue
inside a deferred statement are errors, reported once. works in unchecked
code and closures (own base per function context).
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/errors/defer_freed_twice.zc | 10 | ||||
| -rw-r--r-- | tests/errors/defer_moved.zc | 11 | ||||
| -rw-r--r-- | tests/errors/defer_moved_early.zc | 14 | ||||
| -rw-r--r-- | tests/langerrors/defer_break.zc | 11 | ||||
| -rw-r--r-- | tests/langerrors/defer_continue.zc | 11 | ||||
| -rw-r--r-- | tests/langerrors/defer_later_name.zc | 9 | ||||
| -rw-r--r-- | tests/langerrors/defer_return.zc | 8 | ||||
| -rw-r--r-- | tests/programs/defer.zc | 235 | ||||
| -rwxr-xr-x | tests/test.sh | 1 |
9 files changed, 310 insertions, 0 deletions
diff --git a/tests/errors/defer_freed_twice.zc b/tests/errors/defer_freed_twice.zc new file mode 100644 index 0000000..d4fe08d --- /dev/null +++ b/tests/errors/defer_freed_twice.zc @@ -0,0 +1,10 @@ +namespace; +include rt; + +// expect: p: used after it was given away +fn main() { + int* p = rt.new(int); + defer rt.free(p); + rt.free(p); + return 0; +} diff --git a/tests/errors/defer_moved.zc b/tests/errors/defer_moved.zc new file mode 100644 index 0000000..d6086cd --- /dev/null +++ b/tests/errors/defer_moved.zc @@ -0,0 +1,11 @@ +namespace; +include rt; + +// expect: p: used after it was given away +fn take(int* .d p) { rt.free(p); return 0; } +fn main() { + int* p = rt.new(int); + defer rt.free(p); + take(p); + return 0; +} diff --git a/tests/errors/defer_moved_early.zc b/tests/errors/defer_moved_early.zc new file mode 100644 index 0000000..2c6feb9 --- /dev/null +++ b/tests/errors/defer_moved_early.zc @@ -0,0 +1,14 @@ +namespace; +include rt; + +// expect: p: used after it was given away +fn take(int* .d p) { rt.free(p); return 0; } +fn main(a) { + int* p = rt.new(int); + defer rt.free(p); + if (a) { + take(p); + return 1; + } + return 0; +} diff --git a/tests/langerrors/defer_break.zc b/tests/langerrors/defer_break.zc new file mode 100644 index 0000000..193f3f0 --- /dev/null +++ b/tests/langerrors/defer_break.zc @@ -0,0 +1,11 @@ +namespace; +include rt; + +// expect: break can't leave a deferred statement +fn main() { + while (1) { + defer { break; } + rt.put_n(1); + } + return 0; +} diff --git a/tests/langerrors/defer_continue.zc b/tests/langerrors/defer_continue.zc new file mode 100644 index 0000000..079b02b --- /dev/null +++ b/tests/langerrors/defer_continue.zc @@ -0,0 +1,11 @@ +namespace; +include rt; + +// expect: continue can't leave a deferred statement +fn main() { + for (let i = 0; i < 2; i++) { + defer { continue; } + rt.put_n(i); + } + return 0; +} diff --git a/tests/langerrors/defer_later_name.zc b/tests/langerrors/defer_later_name.zc new file mode 100644 index 0000000..17f175d --- /dev/null +++ b/tests/langerrors/defer_later_name.zc @@ -0,0 +1,9 @@ +namespace; +include rt; + +// expect: y is used but never defined +fn main() { + defer rt.put_n(y); + int y = 3; + return 0; +} diff --git a/tests/langerrors/defer_return.zc b/tests/langerrors/defer_return.zc new file mode 100644 index 0000000..bceefe0 --- /dev/null +++ b/tests/langerrors/defer_return.zc @@ -0,0 +1,8 @@ +namespace; +include rt; + +// expect: return can't be used in a deferred statement +fn main() { + defer { rt.put_n(1); return 1; } + return 0; +} diff --git a/tests/programs/defer.zc b/tests/programs/defer.zc new file mode 100644 index 0000000..bdf85e1 --- /dev/null +++ b/tests/programs/defer.zc @@ -0,0 +1,235 @@ +namespace; +include rt; + +// defer: code that runs when the enclosing block ends, on every way out of it + +fn pn(x) { rt.put_n(x); rt.put_c(' '); return 0; } +fn nl() { rt.put_c(10); return 0; } + +// several defers run in reverse order of registration +fn order() { + pn(1); + defer pn(30); + defer pn(20); + defer { pn(10); pn(11); } + pn(2); + return 0; +} + +// the deferred code sees the values the variables have when it runs +fn late() { + let x = 1; + defer pn(x); + x = 2; + x = 7; + return 0; +} + +// the value of return is computed before the defers run +fn early(a) { + let x = a; + defer x = x + 100; + defer pn(x); + if (a > 5) return x * 2; + x = x + 1; + return x; +} + +// a defer that is never reached registers nothing +fn skipped(a) { + if (a == 0) return 0 - 1; + defer pn(500); + if (a == 1) return 1; + defer pn(501); + return 2; +} + +// a defer inside an if only applies when that branch ran +fn branch(a) { + defer pn(9); + if (a) { defer pn(1); pn(2); } + else { defer pn(3); pn(4); } + pn(5); + return 0; +} + +// break and continue run the defers of the blocks they leave +fn loops() { + let i = 0; + defer pn(900); + while (i < 5) { + defer pn(100 + i); + i++; + if (i == 2) continue; + if (i == 4) break; + pn(i); + } + pn(777); + for (let j = 0; j < 3; j++) { + defer { pn(200 + j); } + if (j == 1) { defer pn(300); continue; } + pn(j); + } + let k = 0; + do { + k++; + defer pn(400 + k); + if (k == 2) continue; + if (k == 3) break; + } while (k < 10); + return 0; +} + +// the deferred code means what it meant where it was written: a variable an +// inner block declares again does not capture its names +fn lexical(a) { + let x = 1; + defer pn(x); + { + let x = 50; + defer pn(x + 1); + if (a) return x; + pn(x); + } + x = 2; + let y = 3; + return y; +} + +// a defer outside the loop is not run by break or continue +fn outer() { + defer pn(1000); + for (let i = 0; i < 3; i++) { + defer pn(i); + { + defer pn(50 + i); + if (i == 1) continue; + pn(60 + i); + } + if (i == 2) break; + } + pn(2000); + return 0; +} + +// nested blocks, each with its own +fn nest() { + defer pn(1); + { + defer pn(2); + { + defer pn(3); + pn(4); + } + pn(5); + } + pn(6); + return 7; +} + +// switch arms are blocks; break leaves the arm and the arm's defers run +fn arms(v) { + defer pn(0); + switch (v) { + case 1: defer pn(11); pn(10); + case 2: defer pn(21); if (v == 2) break; pn(20); + default: defer pn(31); return 30; + } + pn(99); + return 0; +} + +// a block expression is a block too +fn bexpr() { + let v = { defer pn(1); pn(2); 40 + 2 }; + pn(v); + return 0; +} + +// the owner stays usable until the block ends; the defer is the transfer +fn owner(n) { + let p = rt.new(4); + defer rt.free(p); + p[0] = n; + p[1] = n * 2; + if (n == 3) return p[0] + p[1]; + pn(p[0] + p[1]); + return 0; +} + +fn owner_loop() { + for (let i = 0; i < 3; i++) { + let p = rt.new(2); + defer rt.free(p); + p[0] = i; + if (i == 1) continue; + pn(p[0]); + } + return 0; +} + +// defer in a closure belongs to the closure +fn closure_defer() { + let f = fn(a) { defer pn(a + 1); pn(a); return a * 2; }; + defer pn(0); + pn(f(10)); + return 0; +} + +// unchecked code: no analysis, the deferred code still runs +unchecked fn fn_in() { + defer pn(5); + pn(4); + if (1) return 0; + return 1; +} +fn unchecked_defer() { + unchecked { + defer pn(3); + pn(2); + } + fn_in(); + return 0; +} + +// a deferred block can have its own loops, with break and continue +fn deferred_loop() { + defer { + for (let i = 0; i < 5; i++) { + if (i == 1) continue; + if (i == 4) break; + pn(i); + } + } + pn(9); + return 0; +} + +fn main() { + order(); nl(); + late(); nl(); + pn(early(1)); nl(); + pn(early(9)); nl(); + pn(skipped(0)); nl(); + pn(skipped(1)); nl(); + pn(skipped(2)); nl(); + branch(1); nl(); + branch(0); nl(); + loops(); nl(); + outer(); nl(); + pn(lexical(1)); nl(); + pn(lexical(0)); nl(); + pn(nest()); nl(); + arms(1); nl(); + arms(2); nl(); + arms(3); nl(); + bexpr(); nl(); + let before = rt.live_objects(); + pn(owner(3)); owner(4); nl(); + owner_loop(); nl(); + pn(rt.live_objects() - before); nl(); + closure_defer(); nl(); + unchecked_defer(); nl(); + deferred_loop(); nl(); + return 0; +} diff --git a/tests/test.sh b/tests/test.sh index 298c356..ae404d9 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -108,6 +108,7 @@ run(){ # $1 = vm command, $2 = label chk "$($vm < tests/programs/gc.zc)" "1000 1 900 1 199900" "$l mark-sweep collector" chk "$($vm < tests/programs/meta.zc | tr '\n' '/')" "1024 19/unless ok/rrr/15 42/7/42/7/144/" "$l parse words, quote, eval, load, operator tables" chk "$($vm < tests/programs/misc.zc | tr '\n' '/')" "45/54321/ok/" "$l compile-time eval, patched mnemonics, unchecked" + chk "$($vm < tests/programs/defer.zc | tr '\n' '/')" "1 2 10 11 20 30 /7 /2 2 /9 18 /-1 /500 1 /501 500 2 /2 1 5 9 /4 3 5 9 /1 101 102 3 103 104 777 0 200 300 201 2 202 401 402 403 900 /60 50 0 51 1 62 52 2 2000 1000 /51 1 50 /50 51 2 3 /4 3 5 2 6 1 7 /10 11 99 0 /21 99 0 /31 0 /2 1 42 /9 12 /0 2 /0 /10 11 20 0 /2 3 4 5 /9 0 2 3 /" "$l defer: block end, return, break, continue, ownership" chk "$($vm < tests/programs/types.zc | tr '\n' '/')" "12 5 2 15 56 42 7 14 3/rect rect* list<int>* list<T>*.u(int) T(list<T>*) fn(word.d)/a:point@0 b:point@2 tag:int@4 /point 1 1/" "$l types, generics, reflection" chk "$($vm < tests/programs/typedef.zc | tr '\n' '/')" "7 1 42 5 30/13 4 19 4/4 3 3 12/5 11/hihihihellook/8/" "$l typedef, global and local, in every place a type goes, and string" chk "$($vm < tests/programs/typeok.zc)" "14" "$l a program the typechecker accepts" |
