From 0432ac6aefccbc5a9af25bd4ed2d4451611c4ff4 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 14:57:42 +0330 Subject: analyzer: an outer owner moved into a deeper container can take it back (loop-built lists); block-end frees name the owner (why 9) in messages and LSP related info --- .../6-ownership/borrow_outlives_block.zc | 21 +++++++++++++++++++++ .../6-ownership/loop_build_list_item_after_move.zc | 13 +++++++++++++ .../6-ownership/loop_build_list_not_taken_back.zc | 13 +++++++++++++ tests/conformance/6-ownership/loop_build_list_ok.zc | 21 +++++++++++++++++++++ .../6-ownership/loop_build_list_other_owner.zc | 13 +++++++++++++ .../6-ownership/outer_owner_in_inner_container.zc | 13 +++++++++++++ 6 files changed, 94 insertions(+) create mode 100644 tests/conformance/6-ownership/borrow_outlives_block.zc create mode 100644 tests/conformance/6-ownership/loop_build_list_item_after_move.zc create mode 100644 tests/conformance/6-ownership/loop_build_list_not_taken_back.zc create mode 100644 tests/conformance/6-ownership/loop_build_list_ok.zc create mode 100644 tests/conformance/6-ownership/loop_build_list_other_owner.zc create mode 100644 tests/conformance/6-ownership/outer_owner_in_inner_container.zc (limited to 'tests') diff --git a/tests/conformance/6-ownership/borrow_outlives_block.zc b/tests/conformance/6-ownership/borrow_outlives_block.zc new file mode 100644 index 0000000..4d9b44a --- /dev/null +++ b/tests/conformance/6-ownership/borrow_outlives_block.zc @@ -0,0 +1,21 @@ +// expect error: second: borrows from head, which was freed at the end of its block (line 17) +// spec 6.6.3 +include rt; + +struct node { + int value; + node* next; +}; + +fn main() { + node* second; + { + node* head = rt.new(node); + head.next = rt.new(node); + head.next.value = 1; + second = head.next; + } + rt.put_n(second.value); + rt.put_c(10); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/loop_build_list_item_after_move.zc b/tests/conformance/6-ownership/loop_build_list_item_after_move.zc new file mode 100644 index 0000000..5346569 --- /dev/null +++ b/tests/conformance/6-ownership/loop_build_list_item_after_move.zc @@ -0,0 +1,13 @@ +// expect error: item: used after it was given away; item was moved into head +// spec 6.8.5 +include rt; +struct node { int value; node* next; }; +fn main() { + node* head = rt.new(node); + int i = 0; + while (i < 3) { + node* item = rt.new(node); item.next = head; head = item; rt.put_n(item.value); i++; + } + rt.put_n(head.value); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/loop_build_list_not_taken_back.zc b/tests/conformance/6-ownership/loop_build_list_not_taken_back.zc new file mode 100644 index 0000000..a9fadcd --- /dev/null +++ b/tests/conformance/6-ownership/loop_build_list_not_taken_back.zc @@ -0,0 +1,13 @@ +// expect error: head: is given away inside a loop that comes back around to it +// spec 6.8.5 +include rt; +struct node { int value; node* next; }; +fn main() { + node* head = rt.new(node); + int i = 0; + while (i < 3) { + node* item = rt.new(node); item.next = head; i++; + } + rt.put_n(head.value); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/loop_build_list_ok.zc b/tests/conformance/6-ownership/loop_build_list_ok.zc new file mode 100644 index 0000000..5f3de5a --- /dev/null +++ b/tests/conformance/6-ownership/loop_build_list_ok.zc @@ -0,0 +1,21 @@ +// expect: 7/0/ +// spec 6.8.5 +include rt; +memdebug on; +struct node { int value; node* next; }; +fn main() { + { + node* head = rt.new(node); + int i = 0; + while (i < 5) { + node* item = rt.new(node); + item.value = i; + item.next = head; + head = item; + i++; + } + rt.put_n(head.value + head.next.value); rt.put_c(10); + } + rt.put_n(rt.memreport()); rt.put_c(10); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/loop_build_list_other_owner.zc b/tests/conformance/6-ownership/loop_build_list_other_owner.zc new file mode 100644 index 0000000..ff13262 --- /dev/null +++ b/tests/conformance/6-ownership/loop_build_list_other_owner.zc @@ -0,0 +1,13 @@ +// expect error: head: still owns an object; overwriting it would leak it +// spec 6.8.5 +include rt; +struct node { int value; node* next; }; +fn main() { + node* head = rt.new(node); + int i = 0; + while (i < 3) { + node* item = rt.new(node); node* other = rt.new(node); item.next = head; other.value = 1; head = other; i++; + } + rt.put_n(head.value); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/outer_owner_in_inner_container.zc b/tests/conformance/6-ownership/outer_owner_in_inner_container.zc new file mode 100644 index 0000000..d9043c0 --- /dev/null +++ b/tests/conformance/6-ownership/outer_owner_in_inner_container.zc @@ -0,0 +1,13 @@ +// expect error: head: belongs to item, which was freed at the end of its block (line 10) +// spec 6.8.5 +include rt; +struct node { int value; node* next; }; +fn main() { + node* head = rt.new(node); + { + node* item = rt.new(node); + item.next = head; + } + rt.put_n(head.value); +} +// zc0: skip -- cgit v1.2.3 From 2b9e79db9e1f4d1f74c6ca99c794ba4886e9f003 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 14:58:36 +0330 Subject: analyzer: infer .u from returns of fresh objects in the body (prescan guess, then checked as .u) --- src/compiler/base.zc | 2 +- src/compiler/decl.zc | 37 ++++++++++++++++++++++ .../6-ownership/infer_owned_return_discarded.zc | 7 ++++ .../6-ownership/infer_owned_return_recursive.zc | 21 ++++++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 tests/conformance/6-ownership/infer_owned_return_discarded.zc create mode 100644 tests/conformance/6-ownership/infer_owned_return_recursive.zc (limited to 'tests') diff --git a/src/compiler/base.zc b/src/compiler/base.zc index de699aa..7d06f8e 100644 --- a/src/compiler/base.zc +++ b/src/compiler/base.zc @@ -192,7 +192,7 @@ int bincap = bprec_BASE - bname_BASE, uncap = useq_BASE - uname_BASE, asgcap = a const int T_EOF = 0, T_NUM = 1, T_ID = 2, T_STR = 3, T_OP = 4, T_FLT = 5, T_BSTR = 6; const int K_FN = 1, K_GVAR = 2, K_GARR = 3, K_CONST = 4, K_KW = 5, K_ENH = 6, K_TYPE = 7, K_DOC = 8; -const int F_DEF = 1, F_SIG = 2, F_UNTYPED = 4, F_STMT = 8, F_EXPR = 16, F_TOP = 32, F_FALLBACK = 64, F_GEN = 128, F_PRE = 256, F_O3 = 512, F_EXT = 1024, F_TPL = 2048, F_STATIC = 4096, F_DECL = 8192, F_RETOWN = 16384, F_EARLY = 32768; +const int F_DEF = 1, F_SIG = 2, F_UNTYPED = 4, F_STMT = 8, F_EXPR = 16, F_TOP = 32, F_FALLBACK = 64, F_GEN = 128, F_PRE = 256, F_O3 = 512, F_EXT = 1024, F_TPL = 2048, F_STATIC = 4096, F_DECL = 8192, F_RETOWN = 16384, F_EARLY = 32768, F_GUESS = 65536; // variable states for the lifetime analyzer const int S_PLAIN = 0, S_M = 1, S_U = 2, S_D = 3, S_DEAD = 4, S_CHILD = 5, S_BORROW = 6; // expression classes: plain word, a new object, or a bare variable (C_VAR + index) diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc index 52d08c7..6bc8245 100644 --- a/src/compiler/decl.zc +++ b/src/compiler/decl.zc @@ -81,8 +81,44 @@ int prescan_fn(int ret, int n, int o3) { sflag[s] = sflag[s] | F_SIG | F_PRE; } if (o3) sflag[s] = sflag[s] | F_O3; + if (!ret && is_op(s_lb) && prescan_owned(n)) { sflag[s] = sflag[s] | F_GUESS; if (sflag[s] & F_PRE) ssig[s] = ssig[s] | 1; } return 0; } +// true for a token that makes a fresh owned object when called: new or heap. +// nothing else: a call to the function itself is no evidence. +int prescan_maker() { + return tk == T_ID && (rt.eq_w(tv, "rt.new") || rt.eq_w(tv, "new") || rt.eq_w(tv, "heap")); +} +// prescan: at the { of an untagged function's body, guess whether it returns an +// owned object: a return of a fresh object, or of a local last set from one. +// the guess only makes the function compile as if declared .u; its body is then +// checked against that, so a wrong guess is an error, never a silent leak. +int prescan_owned(int self) { + int depth = 0, n, k, found = 0, nown = 0; let owned[16]; + while (tk != T_EOF) { + if (is_op(s_lb)) depth++; + else if (is_op(s_rb)) { depth--; if (!depth) { next(); return found; } } + else if (tk == T_ID && rt.eq_w(tv, "return")) { + next(); + if (tk == T_ID) { + n = tv; peek(); + if (pk_tk == T_OP && pk_tv == s_lp && prescan_maker()) found = 1; + else if (pk_tk == T_OP && pk_tv == s_semi) { k = 0; while (k < nown) { if (owned[k] == n) found = 1; k++; } } + } + continue; + } + else if (tk == T_ID) { + n = tv; peek(); + if (pk_tk == T_OP && pk_tv == s_eq) { + next(); next(); + if (prescan_maker()) { peek(); if (pk_tk == T_OP && pk_tv == s_lp && nown < 16) { owned[nown] = n; nown++; } } + continue; + } + } + next(); + } + return found; +} // scan ahead, without emitting code, to learn what a block of tokens contains. int prescan(int kind, int obj) { int depth = 0, sp = panic, sn = nerr, pend = 0, po3 = 0, n, dep; char* oldmodule = module_name; word oldimports = module_imports; @@ -359,6 +395,7 @@ int fndef(int chk, int rty, int ret, int n) { if (amb & 1) { t = lookup(pn[i]); if (t >= 0 && skind[t] == K_TYPE) { pty[i] = sval[t]; typed = 1; } } amb = amb >> 1; i++; } + if (!ret && (sflag[s] & F_GUESS)) ret = 1; // prescan saw it return an owned object: compile it as .u sig = ret + np + np + tags * 64; lo = borrows_clause(pn, np, 1); // a definition after a prototype keeps the prototype's borrows clause diff --git a/tests/conformance/6-ownership/infer_owned_return_discarded.zc b/tests/conformance/6-ownership/infer_owned_return_discarded.zc new file mode 100644 index 0000000..33d51a7 --- /dev/null +++ b/tests/conformance/6-ownership/infer_owned_return_discarded.zc @@ -0,0 +1,7 @@ +// expect error: a new object (or a u result) can't be thrown away +// spec 6.8 +include rt; +struct node { int value; node* next; }; +node* make() { node* n = rt.new(node); return n; } +fn main() { make(); } +// zc0: skip diff --git a/tests/conformance/6-ownership/infer_owned_return_recursive.zc b/tests/conformance/6-ownership/infer_owned_return_recursive.zc new file mode 100644 index 0000000..1299f64 --- /dev/null +++ b/tests/conformance/6-ownership/infer_owned_return_recursive.zc @@ -0,0 +1,21 @@ +// expect: 1/0/ +// spec 6.8 +include rt; +memdebug on; +struct node { int value; node* next; }; +node* make_list(int count) { + node* head = rt.new(node); + head.value = count; + if (count > 1) head.next = make_list(count - 1); + return head; +} +node* last(node* list) { + node* cursor = list; + while (cursor.next) cursor = cursor.next; + return cursor; +} +fn main() { + { node* list = make_list(4); rt.put_n(last(list).value); rt.put_c(10); } + rt.put_n(rt.memreport()); rt.put_c(10); +} +// zc0: skip -- cgit v1.2.3 From 8703e2c85a8ecdea9e869e876e347ebd4cb6ef88 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 14:59:31 +0330 Subject: analyzer: a back link (container's owner stored into it) stays a plain reference, fixing a compile hang on a<->b and a leak; ending an owner kills its owned children too (fixes use after reassigning a container); escape fixtures --- src/compiler/analyzer.zc | 5 ++++- tests/conformance/6-ownership/back_link_alias.zc | 6 ++++++ tests/conformance/6-ownership/back_link_block_ok.zc | 6 ++++++ tests/conformance/6-ownership/back_link_cont.zc | 6 ++++++ tests/conformance/6-ownership/back_link_cycle.zc | 17 +++++++++++++++++ tests/conformance/6-ownership/back_link_free.zc | 6 ++++++ tests/conformance/6-ownership/back_link_glob.zc | 7 +++++++ tests/conformance/6-ownership/back_link_move.zc | 7 +++++++ tests/conformance/6-ownership/back_link_reasg.zc | 6 ++++++ tests/conformance/6-ownership/back_link_ret.zc | 7 +++++++ 10 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/conformance/6-ownership/back_link_alias.zc create mode 100644 tests/conformance/6-ownership/back_link_block_ok.zc create mode 100644 tests/conformance/6-ownership/back_link_cont.zc create mode 100644 tests/conformance/6-ownership/back_link_cycle.zc create mode 100644 tests/conformance/6-ownership/back_link_free.zc create mode 100644 tests/conformance/6-ownership/back_link_glob.zc create mode 100644 tests/conformance/6-ownership/back_link_move.zc create mode 100644 tests/conformance/6-ownership/back_link_reasg.zc create mode 100644 tests/conformance/6-ownership/back_link_ret.zc (limited to 'tests') diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc index a1588e8..839c1ef 100644 --- a/src/compiler/analyzer.zc +++ b/src/compiler/analyzer.zc @@ -1009,7 +1009,7 @@ int end_owner(int i) { end_why = 0; end_whom = 0; local_ended[i] = info[2]; local_end_info[i] = info; lstate[i] = S_DEAD; while (j < nloc) { - if (j != i && isalias(j) && lroot(j) == i) { local_ended[j] = info[2]; local_end_info[j] = info; lstate[j] = S_DEAD; } + if (j != i && lstate[j] == S_CHILD && lroot(j) == i) { local_ended[j] = info[2]; local_end_info[j] = info; lstate[j] = S_DEAD; } // aliases and owned children go with it j++; } return 0; @@ -1612,6 +1612,9 @@ int assigncheck(int k, int i, int rc) { touch(i); if (rc == C_FRESH) { emitown(i); return C_PLAIN; } if (j < 0) return C_PLAIN; + // a back link: storing the container's own owner (or the container itself) + // into it. it stays a plain reference; ownership would form a cycle. + if (r == j) { touch(j); return C_PLAIN; } js = lstate[j]; if (js == S_CHILD) cerrn(j, "already belongs to a container"); else if (js == S_BORROW) cerrn(j, "is borrowed and can't be stored"); diff --git a/tests/conformance/6-ownership/back_link_alias.zc b/tests/conformance/6-ownership/back_link_alias.zc new file mode 100644 index 0000000..58db710 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_alias.zc @@ -0,0 +1,6 @@ +// expect error: b: is an owned object and can't be copied into another variable +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { node* p; { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; p = b; } rt.put_n(p.prev.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_block_ok.zc b/tests/conformance/6-ownership/back_link_block_ok.zc new file mode 100644 index 0000000..7955081 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_block_ok.zc @@ -0,0 +1,6 @@ +// expect: 40 +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { memdebug on; { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; a.value = 4; rt.put_n(b.prev.value); } rt.put_n(rt.memreport()); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_cont.zc b/tests/conformance/6-ownership/back_link_cont.zc new file mode 100644 index 0000000..d8083fd --- /dev/null +++ b/tests/conformance/6-ownership/back_link_cont.zc @@ -0,0 +1,6 @@ +// expect error: b: already belongs to a container +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { node* c = rt.new(node); node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; c.next = b; rt.put_n(c.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_cycle.zc b/tests/conformance/6-ownership/back_link_cycle.zc new file mode 100644 index 0000000..fc47100 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_cycle.zc @@ -0,0 +1,17 @@ +// expect: 0/ +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { + memdebug on; + for (int round = 0; round < 50; round++) { + node* a = rt.new(node); + node* b = rt.new(node); + a.next = b; + b.prev = a; + a.value = round; + b.value = b.prev.value; + } + rt.put_n(rt.memreport()); rt.put_c(10); +} +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_free.zc b/tests/conformance/6-ownership/back_link_free.zc new file mode 100644 index 0000000..a8fb5e8 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_free.zc @@ -0,0 +1,6 @@ +// expect error: b: used after it was given away; b borrows a, and a was freed +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; rt.free(a); rt.put_n(b.prev.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_glob.zc b/tests/conformance/6-ownership/back_link_glob.zc new file mode 100644 index 0000000..ecba2e5 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_glob.zc @@ -0,0 +1,7 @@ +// expect error: b: is not an owner, so it can't be stored in a global +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +node* keep; +fn main() { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; keep = b; } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_move.zc b/tests/conformance/6-ownership/back_link_move.zc new file mode 100644 index 0000000..47490d6 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_move.zc @@ -0,0 +1,7 @@ +// expect error: a was stored in a global or untracked memory +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +node* keep2; +fn main() { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; keep2 = a; rt.put_n(b.prev.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_reasg.zc b/tests/conformance/6-ownership/back_link_reasg.zc new file mode 100644 index 0000000..02709a8 --- /dev/null +++ b/tests/conformance/6-ownership/back_link_reasg.zc @@ -0,0 +1,6 @@ +// expect error: b: used after it was given away; b borrows a, and a was freed by reassigning it +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +fn main() { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; a = rt.new(node); a.value = 1; rt.put_n(b.prev.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/back_link_ret.zc b/tests/conformance/6-ownership/back_link_ret.zc new file mode 100644 index 0000000..ee10fce --- /dev/null +++ b/tests/conformance/6-ownership/back_link_ret.zc @@ -0,0 +1,7 @@ +// expect error: b: has to be u to be returned +// spec 6.6 +include rt; +struct node { int value; node* prev; node* next; }; +node* g() { node* a = rt.new(node); node* b = rt.new(node); a.next = b; b.prev = a; return b; } +fn main() { node* x = g(); rt.put_n(x.value); } +// zc0: skip -- cgit v1.2.3 From a5a5029a80a14be46c244b5bcca63e5e2cd9026f Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 15:00:36 +0330 Subject: std.Io.print/println take any mix of printable arguments (compiler expands per type); docs, conformance --- docs/LEARN.md | 2 +- docs/SPEC.md | 2 ++ src/compiler/expr.zc | 33 ++++++++++++++++++++++ src/lib/std.zc | 13 +++++---- tests/conformance/3-expressions/print_varargs.zc | 14 +++++++++ .../3-expressions/print_varargs_fresh_object.zc | 6 ++++ 6 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 tests/conformance/3-expressions/print_varargs.zc create mode 100644 tests/conformance/3-expressions/print_varargs_fresh_object.zc (limited to 'tests') diff --git a/docs/LEARN.md b/docs/LEARN.md index 35ee914..8e0d369 100644 --- a/docs/LEARN.md +++ b/docs/LEARN.md @@ -533,7 +533,7 @@ output: ### terminal input and output -`std.Io` groups the terminal: `print(s)` and `println(s)` write a string, `nl()` a line break, and `put_c`, `put_s`, `put_n`, `put_hex`, `put_bs` and `put_f` write a character, a string, a decimal or hexadecimal integer, a packed byte string and a float. `get_c()`, `eof()`, `read(buf, n)` and `getline(buf, n)` read the program input; `getline` drops the line break and returns -1 at the end of the input. zc has no varargs or overloading, so `print` takes a string only and numbers use their own writers. the writers go through `rt.put_c`, so `std.capture` sees them, and the `rt` names (`rt.put_n` and the rest) keep working. +`std.Io` groups the terminal: `print(s)` and `println(s)` write a string, `nl()` a line break, and `put_c`, `put_s`, `put_n`, `put_hex`, `put_bs` and `put_f` write a character, a string, a decimal or hexadecimal integer, a packed byte string and a float. `get_c()`, `eof()`, `read(buf, n)` and `getline(buf, n)` read the program input; `getline` drops the line break and returns -1 at the end of the input. `print` and `println` take any number of arguments of any printable type, such as `std.Io.println("total: ", n, " avg: ", x);`; the compiler picks the writer for each argument's type (string, byte string, integer or float), and a fresh owned string argument is freed after it is written. `println()` alone writes a line break. other functions have no varargs or overloading. the writers go through `rt.put_c`, so `std.capture` sees them, and the `rt` names (`rt.put_n` and the rest) keep working. [example source](../examples/learn/35-io.zc): diff --git a/docs/SPEC.md b/docs/SPEC.md index b2aa5ea..266a1af 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -469,6 +469,8 @@ pn((int)(p + 1) - (int)p); // 1: p + 1 moves one word 3.11.6 `heap(e)` copies the value of `e` into a fresh owned heap block sized to it and returns a pointer to the block (a fresh object, 6.1): one word for a scalar or pointer (`heap(10)` is an `int*`), the characters and the terminating 0 for a `char*` word string (`heap("hi")` is a `char*`), and `sizeof(T)` words for a struct or array value (`heap(v)` with `v` a `foo` is a `foo*`). pointer fields of a copied struct are copied as plain words. a struct whose body declares `drop` gives the block its type drop (6.15). `heap` is found only when no variable or function of that name is in scope. +3.11.7 `std.Io.print(args)` and `std.Io.println(args)` take any number of arguments, including none. each argument is evaluated left to right and written by the writer for its type: a byte string (`bstr`) by `put_bs`, a float by `put_f`, a `char*` or `char[]` by `put_s`, anything else by `put_n`. a fresh owned string argument is written and then freed; another fresh object is error: a new object can't be printed; bind it to a name first. `println` then writes a line break. the call's value is 0. these two are the only calls with a variable number of arguments. + ### 3.12 `sizeof` and `typeof` 3.12.1 `sizeof(T)` and `sizeof(e)` give the size in words. the expression form is compiled but never run: `int i = 0; int n = sizeof(i++);` leaves `i` 0 and `n` 1. a `u8[100]` is 13 words, a struct of four `u8` fields is 1. diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc index a6cc55c..2cb36cf 100644 --- a/src/compiler/expr.zc +++ b/src/compiler/expr.zc @@ -409,9 +409,42 @@ int shifthint(int p, int v) { // function isn't defined yet) // 6. the result's type and class are set: .u functions return C_FRESH, which // is how the analyzer knows the caller now owns a new object +// std.Io.print(a, b, ...) and println: any mix of strings, byte strings, +// numbers and floats. each argument goes to the writer for its type; a fresh +// owned string is written and then freed. println ends with a line break. +int printcall(int line) { + int c; char* w; + expect(s_lp); + if (!is_op(s_rp)) { + while (1) { + c = expr(); rval(); + if (xty == tbstr || xstr) w = "std.Io.put_bs"; + else if (xfloat) w = "std.Io.put_f"; + else if (xty[0] == TY_PTR && xty[4] == tchar) w = "std.Io.put_s"; + else if (xty[0] == TY_ARR && xty[4] == tchar) w = "std.Io.put_s"; + else w = "std.Io.put_n"; + if (c == C_FRESH && fchk) { + if (!rt.eq_w(w, "std.Io.put_s") && !rt.eq_w(w, "std.Io.put_bs")) cerr("a new object can't be printed; bind it to a name first"); + lit(0); op(O_PICK); callname(intern(w)); op(O_DROP); callname(s_free); op(O_DROP); + } else { + if (c >= C_VAR && tracked(c - C_VAR)) touch(c - C_VAR); + callname(intern(w)); op(O_DROP); + } + if (!accept(s_comma)) break; + } + } + expect(s_rp); + if (line) { lit(10); callname(intern("rt.put_c")); op(O_DROP); } + lit(0); + lv = 0; lvk = LV_NONE; xty = tint; xzero = 0; xbor = -1; xconst = 0; + xfloat = 0; xstr = 0; xlenloc = -1; xstamp = 0; xaf = 0; + return C_PLAIN; +} int call(int s) { int np, tags = 0, ret = 0; word ft = stype[s]; word[8] bs; int rs, rhs_type = 0, kop, cp = callpos, original = s; word storage; int origin, temporary, owner; word origins, owner_fact; word requirements = effect_requirements, edges = effect_edges; + if (rt.eq_w(sname[s], "std.Io.print")) return printcall(0); + if (rt.eq_w(sname[s], "std.Io.println")) return printcall(1); borrow_depth++; if (sname[s] == s_new && skind[s] == K_FN) { peek(); diff --git a/src/lib/std.zc b/src/lib/std.zc index b206888..5c86aa6 100644 --- a/src/lib/std.zc +++ b/src/lib/std.zc @@ -421,15 +421,16 @@ struct Host { // called as std.Io.name(args). the writers go through rt.put_c, so a capture // (std.capture) sees their text too. the rt names (rt.put_n, rt.put_s and the // rest) stay; these are the same operations, grouped with the library. -// zc has no varargs or overloading, so print takes a string, and numbers have -// their own writers: std.Io.print("n = "); std.Io.put_n(n); std.Io.nl(); +// print and println take any mix of strings, byte strings, numbers and floats, +// each written by its type: std.Io.println("n = ", n, ", x = ", x); struct Io { - // writes a string. - // s: the text + // writes its arguments in order: any mix of strings, byte strings, numbers + // and floats (the compiler picks the writer for each argument's type). + // s: the first value; more may follow // returns: 0 static int print(char* s) { return rt.put_s(s); } - // writes a string and a line break. - // s: the text + // writes its arguments like print, then a line break. no arguments: just the line break. + // s: the first value; more may follow // returns: 0 static int println(char* s) { rt.put_s(s); return rt.put_c(10); } // writes a line break. diff --git a/tests/conformance/3-expressions/print_varargs.zc b/tests/conformance/3-expressions/print_varargs.zc new file mode 100644 index 0000000..6918c8c --- /dev/null +++ b/tests/conformance/3-expressions/print_varargs.zc @@ -0,0 +1,14 @@ +// expect: a b 42 -7 1.500000//name: zc/6/ +// spec 3.11.7 +include rt, std; +char* make_name() { char* s = rt.new(3); s[0] = 'z'; s[1] = 'c'; s[2] = 0; return s; } +fn main() { + float f = 1.5; + std.Io.print("a"); + std.Io.println(" b ", 42, " ", -7, " ", f); + std.Io.println(); + std.Io.println("name: ", make_name()); + int x = 3; + std.Io.println(x * 2); +} +// zc0: skip diff --git a/tests/conformance/3-expressions/print_varargs_fresh_object.zc b/tests/conformance/3-expressions/print_varargs_fresh_object.zc new file mode 100644 index 0000000..5cba2a4 --- /dev/null +++ b/tests/conformance/3-expressions/print_varargs_fresh_object.zc @@ -0,0 +1,6 @@ +// expect error: a new object can't be printed +// spec 3.11.7 +include rt, std; +struct node { int v; }; +fn main() { std.Io.println(rt.new(node)); } +// zc0: skip -- cgit v1.2.3 From 5aaf70abdda0026a7b4d647746a109d94b25f7a0 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 15:01:29 +0330 Subject: analyzer: a .u (declared or inferred) function can't return a borrowed parameter or a value derived from one; fixtures --- src/compiler/analyzer.zc | 8 ++++++++ src/compiler/decl.zc | 2 +- .../6-ownership/infer_owned_return_borrow_first.zc | 10 ++++++++++ tests/conformance/6-ownership/infer_owned_return_mixed.zc | 11 +++++++++++ tests/conformance/6-ownership/u_return_param_field.zc | 7 +++++++ tests/conformance/6-ownership/u_return_plain_param.zc | 8 ++++++++ 6 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/conformance/6-ownership/infer_owned_return_borrow_first.zc create mode 100644 tests/conformance/6-ownership/infer_owned_return_mixed.zc create mode 100644 tests/conformance/6-ownership/u_return_param_field.zc create mode 100644 tests/conformance/6-ownership/u_return_plain_param.zc (limited to 'tests') diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc index 839c1ef..55a96bb 100644 --- a/src/compiler/analyzer.zc +++ b/src/compiler/analyzer.zc @@ -1644,6 +1644,7 @@ int assigncheck(int k, int i, int rc) { // the function has no .u (its callers compiled after this point see it). an m // object is lent, not given, so returning it is an error, and so is a plain word // from an owned function that was called before its body. +int ret_borrowed; int retcheck(int c) { int i; if (isptr(xty) || xty == tword) { @@ -1670,9 +1671,16 @@ int retcheck(int c) { if ((isptr(xty) || isagg(xty)) && ((xaf && xaf[2] == 4) || (xbor >= 0 && lafact[xbor] && lafact[xbor][2] == 4))) { cerr("an array literal cannot escape its function through return"); return 0; } i = -1; if (c >= C_VAR && tracked(c - C_VAR)) i = c - C_VAR; + // a value that comes from a parameter the caller still owns (not a .d one, + // which is tracked) is a borrow: it can't become the caller's owned result. + if (c != C_FRESH && i < 0 && isptr(xty) && effect_mask(c)) { + if (fret) { cerr("a borrowed parameter cannot be returned as an owned result"); return 0; } + ret_borrowed = 1; + } if (i >= 0 && lstate[lroot(i)] == S_DEAD) { cerrn(i, "used after it was given away"); return 0; } if (i >= 0 && (lstate[i] == S_D || (lstate[i] == S_M && lown[i])) && (fret || finfer)) lstate[i] = S_U; // returning an owned object (a d object, or a local that owns one) gives it up, as u*x does if (!fret && finfer && (c == C_FRESH || (i >= 0 && lstate[i] == S_U))) { + if (ret_borrowed) cerr("this function also returns a borrowed parameter, so its result can't be owned"); if (sflag[finsym] & F_EARLY) serrn(sname[finsym], "is called before its body, so its owned return can't be known there; declare the return type with .u"); ssig[finsym] = ssig[finsym] | 1; // the return is owned: callers read it from the signature sflag[finsym] = sflag[finsym] | F_RETOWN; diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc index 6bc8245..e2c3613 100644 --- a/src/compiler/decl.zc +++ b/src/compiler/decl.zc @@ -435,7 +435,7 @@ int fndef(int chk, int rty, int ret, int n) { fixall(sfix[s], here); sfix[s] = 0; fn_begin(chk, ret); lt = lt_begin(sval[s], sname[s], lfl); xc_fn = sname[s]; - finsym = s; finfer = !ret; + finsym = s; finfer = !ret; ret_borrowed = 0; effect_current = effect_record(s); rt.fill_w(effect_current, 0, 50); effect_current[0] = 1; if (!chk || stubgen) effect_current[0] = 2; effect_current[1] = np; i = 0; while (i < np) { if (pt[i] == 2) { effect_current[2 + i] = EF_CONSUME; effect_current[26 + i] = EF_CONSUME; } i++; } if (typed && explicit_rty) fty = rty; diff --git a/tests/conformance/6-ownership/infer_owned_return_borrow_first.zc b/tests/conformance/6-ownership/infer_owned_return_borrow_first.zc new file mode 100644 index 0000000..e75fb10 --- /dev/null +++ b/tests/conformance/6-ownership/infer_owned_return_borrow_first.zc @@ -0,0 +1,10 @@ +// expect error: a borrowed parameter cannot be returned as an owned result +// spec 6.8 +include rt; +struct node { int v; node* next; }; +node* pick(node* other, int which) { + if (which) return other; + return rt.new(node); +} +fn main() { } +// zc0: skip diff --git a/tests/conformance/6-ownership/infer_owned_return_mixed.zc b/tests/conformance/6-ownership/infer_owned_return_mixed.zc new file mode 100644 index 0000000..5216003 --- /dev/null +++ b/tests/conformance/6-ownership/infer_owned_return_mixed.zc @@ -0,0 +1,11 @@ +// expect error: a borrowed parameter cannot be returned as an owned result +// spec 6.8 +include rt; +struct node { int value; node* next; }; +node* pick(node* other, int which) { + node* fresh = rt.new(node); + if (which) return fresh; + return other; +} +fn main() { node* a = rt.new(node); node* b = pick(a, 1); rt.put_n(b.value); } +// zc0: skip diff --git a/tests/conformance/6-ownership/u_return_param_field.zc b/tests/conformance/6-ownership/u_return_param_field.zc new file mode 100644 index 0000000..af8284d --- /dev/null +++ b/tests/conformance/6-ownership/u_return_param_field.zc @@ -0,0 +1,7 @@ +// expect error: a borrowed parameter cannot be returned as an owned result +// spec 6.8 +include rt; +struct node { int v; node* next; }; +node*.u nxt(node* l) { node* c = l.next; return c; } +fn main() { } +// zc0: skip diff --git a/tests/conformance/6-ownership/u_return_plain_param.zc b/tests/conformance/6-ownership/u_return_plain_param.zc new file mode 100644 index 0000000..5aca40b --- /dev/null +++ b/tests/conformance/6-ownership/u_return_plain_param.zc @@ -0,0 +1,8 @@ +// expect error: a borrowed parameter cannot be returned as an owned result +// spec 6.8 +include rt; +memdebug on; +struct node { int value; node* next; }; +node*.u pick(node* other) { return other; } +fn main() { node* a = rt.new(node); { node* b = pick(a); rt.put_n(b.value); } rt.put_n(a.value); } +// zc0: skip -- cgit v1.2.3 From 0c047b14a6488c4bda405cead225b7a700647c1a Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 15:02:33 +0330 Subject: tests: alias_scope expects the block-end message; plain_return now covers a forwarding owned return called early; direct owned return before its body is accepted --- tests/conformance/6-ownership/infer_owned_return_before_body.zc | 8 ++++++++ tests/errors/alias_scope.zc | 2 +- tests/errors/plain_return.zc | 5 +++-- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 tests/conformance/6-ownership/infer_owned_return_before_body.zc (limited to 'tests') diff --git a/tests/conformance/6-ownership/infer_owned_return_before_body.zc b/tests/conformance/6-ownership/infer_owned_return_before_body.zc new file mode 100644 index 0000000..8db6b45 --- /dev/null +++ b/tests/conformance/6-ownership/infer_owned_return_before_body.zc @@ -0,0 +1,8 @@ +// expect: 5/0/ +// spec 6.8 +include rt; +memdebug on; +int use() { int* q = mk(); int v = *q; return v; } +int* mk() { int* p = rt.new(int); *p = 5; return p; } +fn main() { rt.put_n(use()); rt.put_c(10); rt.put_n(rt.memreport()); rt.put_c(10); } +// zc0: skip diff --git a/tests/errors/alias_scope.zc b/tests/errors/alias_scope.zc index b52bfa8..ee56075 100644 --- a/tests/errors/alias_scope.zc +++ b/tests/errors/alias_scope.zc @@ -1,7 +1,7 @@ namespace; include rt; -// expect: q: used after it was given away +// expect: q: borrows from p, which was freed at the end of its block fn main() { let q; { let p = rt.new(2); q = p + 0; } diff --git a/tests/errors/plain_return.zc b/tests/errors/plain_return.zc index c44c533..723ab98 100644 --- a/tests/errors/plain_return.zc +++ b/tests/errors/plain_return.zc @@ -2,5 +2,6 @@ namespace; include rt; // expect: is called before its body, so its owned return can't be known there -fn main() { return mk(); } -fn mk() { return rt.new(1); } +int* mk() { int* p = rt.new(int); return p; } +fn main() { return forward(); } +int* forward() { return mk(); } -- cgit v1.2.3 From 8535d74aa376d85a5a199f596ed0926eb991507a Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 15:02:51 +0330 Subject: lsp test: block-end borrow message and related info --- tests/drivers/borrow_diag_test.js | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'tests') diff --git a/tests/drivers/borrow_diag_test.js b/tests/drivers/borrow_diag_test.js index 93a7561..027b978 100644 --- a/tests/drivers/borrow_diag_test.js +++ b/tests/drivers/borrow_diag_test.js @@ -70,4 +70,13 @@ assert.strictEqual(d.relatedInformation[0].message, 'owner t was freed here'); text = 'include rt;\nchar* first(char* a, char* b) borrows a;\nchar* first(char* a, char* b) { return b; }\nfn main() { return 0; }\n'; d = same(text, 'borrows clause'); assert.match(d.message, /returns a borrow of b/); +// a borrow that outlives the block its owner was freed at the end of +text = 'include rt;\nstruct node { int v; node* next; };\nfn main() {\n\tnode* second;\n\t{\n\t\tnode* head = rt.new(node);\n\t\thead.next = rt.new(node);\n\t\tsecond = head.next;\n\t}\n\treturn second.v;\n}\n'; +d = same(text, 'block end'); +assert.match(d.message, /^second: borrows from head, which was freed at the end of its block \(line 9\)$/); +assert.deepStrictEqual(d.range.start, at(text, 'second.v'), 'the range is the conflicting use'); +assert.ok(d.relatedInformation && d.relatedInformation.length === 2, JSON.stringify(d)); +assert.strictEqual(d.relatedInformation[0].message, 'owner head was freed at the end of this block'); +assert.strictEqual(d.relatedInformation[1].message, 'owner head is declared here'); +assert.deepStrictEqual(d.relatedInformation[1].location.range.start, at(text, 'head =')); console.log(`ok borrow diagnostics: ${checks} cases match zy, with ranges and related owner places`); -- cgit v1.2.3 From d36190a5f3b8fa33fe3381771cd1efb9a1335e50 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 15:19:17 +0330 Subject: prescan: guess an owned return only from a local declared from new/heap, not a global assigned one (fixes lsp self-check); xref baseline for the new writers --- src/compiler/decl.zc | 8 ++++-- .../infer_owned_return_global_not_owned.zc | 7 ++++++ tests/xref.baseline | 29 +++++++++++----------- 3 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 tests/conformance/6-ownership/infer_owned_return_global_not_owned.zc (limited to 'tests') diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc index e2c3613..bbde5ba 100644 --- a/src/compiler/decl.zc +++ b/src/compiler/decl.zc @@ -94,8 +94,12 @@ int prescan_maker() { // the guess only makes the function compile as if declared .u; its body is then // checked against that, so a wrong guess is an error, never a silent leak. int prescan_owned(int self) { - int depth = 0, n, k, found = 0, nown = 0; let owned[16]; + int depth = 0, n, k, found = 0, nown = 0, decl = 0; let owned[16]; while (tk != T_EOF) { + // a declaration's name follows a type name, a *, let or a comma; a plain + // assignment (to a global, say) follows ; { or } + int was = decl; + decl = tk == T_ID || (tk == T_OP && (rt.eq_w(tv, "*") || rt.eq_w(tv, ","))); if (is_op(s_lb)) depth++; else if (is_op(s_rb)) { depth--; if (!depth) { next(); return found; } } else if (tk == T_ID && rt.eq_w(tv, "return")) { @@ -107,7 +111,7 @@ int prescan_owned(int self) { } continue; } - else if (tk == T_ID) { + else if (tk == T_ID && was) { n = tv; peek(); if (pk_tk == T_OP && pk_tv == s_eq) { next(); next(); diff --git a/tests/conformance/6-ownership/infer_owned_return_global_not_owned.zc b/tests/conformance/6-ownership/infer_owned_return_global_not_owned.zc new file mode 100644 index 0000000..5d7f793 --- /dev/null +++ b/tests/conformance/6-ownership/infer_owned_return_global_not_owned.zc @@ -0,0 +1,7 @@ +// expect: 3/ +// spec 6.8 +include rt; +int* cache; +int* refill() { cache = rt.new(int); *cache = 3; return cache; } +fn main() { refill(); rt.put_n(*cache); rt.put_c(10); } +// zc0: skip diff --git a/tests/xref.baseline b/tests/xref.baseline index 26b6931..8b7c725 100644 --- a/tests/xref.baseline +++ b/tests/xref.baseline @@ -310,8 +310,8 @@ lnsz_initial local_captured clocap_at newloc reserve_locals local_cell capture_cell newloc reserve_locals local_dropset drop_attach expr localdecl newloc reassign_free reserve_locals -local_end_info assigncheck end_owner newloc reserve_locals -local_ended assigncheck end_owner newloc reserve_locals +local_end_info assigncheck block_end_owners end_owner newloc reserve_locals +local_ended assigncheck block_end_owners end_owner newloc reserve_locals local_origin assigncheck newloc reserve_locals local_origin_mask assigncheck newloc reserve_locals local_shadow newloc reserve_locals shadow_take shadow_use @@ -340,7 +340,7 @@ lrsoff_initial lsets chk_unit lslot newloc reserve_locals lsp ctx_pop loop_end loop_push zc_init -lstate alias_end assigncheck backedge block_close closure end_owner fndef handup join newloc recast reserve_locals retcheck snap_cmp snap_restore touch +lstate alias_end assigncheck backedge block_close block_end_owners closure end_owner fndef handup join newloc recast reserve_locals retcheck snap_cmp snap_restore touch lt_at lt_note lt_base lt_begin lt_end lt_buf lt_begin lt_room lt_stmt @@ -368,10 +368,10 @@ ltxt lt_note ltxtcap lt_note ltype array_literal_tokens closure embed_bind fndef kw_switch localdecl newloc reserve_locals lused assigncheck luse newloc reserve_locals -lv array_dynamic_literal array_literal_tokens bin blockexpr call chk_reset closure ctx_pop drop_attach enhcall expr field fn_begin heap_box incdec kwcall methcall newtyped postfix_tail primary rval ternary unary +lv array_dynamic_literal array_literal_tokens bin blockexpr call chk_reset closure ctx_pop drop_attach enhcall expr field fn_begin heap_box incdec kwcall methcall newtyped postfix_tail primary printcall rval ternary unary lvescaped closure newloc reserve_locals unary lvi chk_reset ctx_pop field postfix_tail primary -lvk array_dynamic_literal array_literal_tokens bin blockexpr call chk_reset closure ctx_pop drop_attach enhcall expr field fn_begin heap_box incdec kwcall methcall newtyped postfix_tail primary ternary unary +lvk array_dynamic_literal array_literal_tokens bin blockexpr call chk_reset closure ctx_pop drop_attach enhcall expr field fn_begin heap_box incdec kwcall methcall newtyped postfix_tail primary printcall ternary unary lvkind closure expr incdec newloc reserve_locals unary vf_effect vf_store lvvalue newloc reserve_locals vf_store mdbg chk_reset kw_memdebug @@ -497,6 +497,7 @@ qp readsrc src_push srcend qst readsrc src_push srcend readonly_types tconst types_init receiver_methods add_method zc_init +ret_borrowed fndef retcheck rsbase chk_unit rscap chk_unit rs_put rsn chk_unit rs_put @@ -705,27 +706,27 @@ uname add_unop reserve_unary uncap reserve_unary useq add_unop reserve_unary vislo build_image_grow chk_unit start syntax_resident -xaf af_forget args array_cap array_dynamic_literal array_len array_literal_tokens array_pop bin call chk_reset drop_attach drop_member expr field heap_box incdec kw_share_value kwcall methcall newtyped postfix_tail primary reassign_free ternary unary -xbor array_cap array_dynamic_literal array_len array_literal_tokens array_pop assigncheck bin blockexpr call chk_reset closure discard drop_attach drop_member enhcall field fn_begin heap_box kw_share_value kwcall methcall postfix_tail primary reassign_free ternary typearg unary +xaf af_forget args array_cap array_dynamic_literal array_len array_literal_tokens array_pop bin call chk_reset drop_attach drop_member expr field heap_box incdec kw_share_value kwcall methcall newtyped postfix_tail primary printcall reassign_free ternary unary +xbor array_cap array_dynamic_literal array_len array_literal_tokens array_pop assigncheck bin blockexpr call chk_reset closure discard drop_attach drop_member enhcall field fn_begin heap_box kw_share_value kwcall methcall postfix_tail primary printcall reassign_free ternary typearg unary xc_file xc_init xc_fn closure fndef xc_hook xc_init xc_on xc_init xc_src unit_stdin xcls array_cap array_len array_pop array_push array_resize chk_reset ctx_pop kw_asm kwcall quote_tokens -xconst array_dynamic_literal array_literal_tokens bin call chk_reset closure drop_attach heap_box kw_share_value methcall postfix_tail primary unary +xconst array_dynamic_literal array_literal_tokens bin call chk_reset closure drop_attach heap_box kw_share_value methcall postfix_tail primary printcall unary xcval array_literal_tokens primary unary -xfloat array_dynamic_literal array_len array_literal_tokens array_pop array_push array_resize bin blockexpr call closure drop_attach expr fcoerce femit ftest heap_box incdec kw_cfunc kw_for methcall postfix_tail primary reassign_free ternary unary +xfloat array_dynamic_literal array_len array_literal_tokens array_pop array_push array_resize bin blockexpr call closure drop_attach expr fcoerce femit ftest heap_box incdec kw_cfunc kw_for methcall postfix_tail primary printcall reassign_free ternary unary xgive argcheck args chk_reset xlend assigncheck expr unary -xlenloc array_len bin call closure enhcall methcall postfix_tail primary unary +xlenloc array_len bin call closure enhcall methcall postfix_tail primary printcall unary xprojection ctx_pop field postfix_tail primary xshadow assigncheck call shadow_take -xstamp array_dynamic_literal array_len bin call closure enhcall methcall newtyped postfix_tail primary unary -xstr array_dynamic_literal array_len array_literal_tokens bin blockexpr call closure drop_attach heap_box incdec methcall postfix_tail primary reassign_free unary -xty args array_cap array_dynamic_literal array_len array_literal_tokens array_pop array_push array_resize bin blockexpr call closure ctx_pop drop_attach drop_member enhcall expr fcoerce field ftest heap_box kw_cfunc kw_share_value kw_sizeof kw_typeof kwcall methcall newtyped postfix_tail primary reassign_free ternary unary +xstamp array_dynamic_literal array_len bin call closure enhcall methcall newtyped postfix_tail primary printcall unary +xstr array_dynamic_literal array_len array_literal_tokens bin blockexpr call closure drop_attach heap_box incdec methcall postfix_tail primary printcall reassign_free unary +xty args array_cap array_dynamic_literal array_len array_literal_tokens array_pop array_push array_resize bin blockexpr call closure ctx_pop drop_attach drop_member enhcall expr fcoerce field ftest heap_box kw_cfunc kw_share_value kw_sizeof kw_typeof kwcall methcall newtyped postfix_tail primary printcall reassign_free ternary unary xub bin xubat bin xvk array_dynamic_literal array_literal_tokens bin blockexpr chk_reset ctx_pop expr field fn_begin incdec kw_share_value kwcall postfix_tail primary ternary unary vf_coerce vf_effect vf_forget xvv blockexpr primary unary vf_coerce -xzero array_dynamic_literal array_literal_tokens bin blockexpr call closure drop_attach enhcall expr field heap_box incdec kwcall methcall newtyped postfix_tail primary ternary unary +xzero array_dynamic_literal array_literal_tokens bin blockexpr call closure drop_attach enhcall expr field heap_box incdec kwcall methcall newtyped postfix_tail primary printcall ternary unary -- cgit v1.2.3