From 31ae48845492c12f2745c7f4c0cd2ea448e0942a Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 02:35:04 +0330 Subject: destructors: reassigning an m owner that still holds a value frees the old value first, running its drop (right side first, then the free, then the store), with hint: x: reassigning frees the old value; a new value that is part of the old one is an error; u, d and captured owners keep the leak error; the new value's size, leaf type and drop are recorded like a declaration's; spec 6.8.5, 6.15.2 --- docs/SPEC.md | 4 +-- src/compiler/analyzer.zc | 35 ++++++++++++++++++++-- src/compiler/base.zc | 1 + src/compiler/expr.zc | 11 +++++-- tests/conformance/6-ownership/drop_reassign.zc | 25 ++++++++++++++++ tests/conformance/6-ownership/overwrite_frees.zc | 6 ++++ tests/conformance/6-ownership/overwrite_leaks.zc | 6 ---- .../6-ownership/reassign_moves_inner_owner.zc | 5 ++++ tests/data/reassign_hint.zc | 2 ++ tests/errors/leak_overwrite.zc | 4 ++- tests/errors/move_live_owner.zc | 5 ---- tests/errors/reassign_from_own_child.zc | 6 ++++ tests/test.sh | 1 + tests/xref.baseline | 21 ++++++------- 14 files changed, 104 insertions(+), 28 deletions(-) create mode 100644 tests/conformance/6-ownership/drop_reassign.zc create mode 100644 tests/conformance/6-ownership/overwrite_frees.zc delete mode 100644 tests/conformance/6-ownership/overwrite_leaks.zc create mode 100644 tests/conformance/6-ownership/reassign_moves_inner_owner.zc create mode 100644 tests/data/reassign_hint.zc delete mode 100644 tests/errors/move_live_owner.zc create mode 100644 tests/errors/reassign_from_own_child.zc diff --git a/docs/SPEC.md b/docs/SPEC.md index ed7822b..8c2f5dc 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -908,7 +908,7 @@ placing `rt.free(b)` after the use is accepted (the alias's last use precedes th 6.8.4 a block expression's value (3.16): the tail may be a `u` local of that block (it is handed up and its state becomes dead, the expression is fresh). an `m` tail is error: x: has to be u to be handed up as the block's value; a `u` of an outer block: x: only a u object of this block can be its value. -6.8.5 assignment of an owner into another local. `a = b` where `b` is an owner: `a` must be empty (plain or dead). three cases are accepted: (1) `b` is `u` and `a` belongs to the block directly enclosing `b`'s block (hand up one level: `b` is dead, `a` is `m`); (2) `a` is dead with the own flag (it was freed or given away) and `b` is `m` with the own flag in the same block or a deeper one: `rt.free(a); a = b;` moves the object, `b` and its aliases expire; (3) `b` is a borrow or alias: `a` becomes an alias. otherwise: x: is an owned object and can't be copied into another variable; or, for a `u` to anything but the enclosing block: x: is u; it can only be handed to a variable of the enclosing block, or returned. overwriting a variable that still owns: x: still owns an object; overwriting it would leak it. +6.8.5 assignment of an owner into another local. `a = b` where `b` is an owner: `a` must be empty (plain or dead). three cases are accepted: (1) `b` is `u` and `a` belongs to the block directly enclosing `b`'s block (hand up one level: `b` is dead, `a` is `m`); (2) `a` is dead with the own flag (it was freed or given away) and `b` is `m` with the own flag in the same block or a deeper one: `rt.free(a); a = b;` moves the object, `b` and its aliases expire; (3) `b` is a borrow or alias: `a` becomes an alias. otherwise: x: is an owned object and can't be copied into another variable; or, for a `u` to anything but the enclosing block: x: is u; it can only be handed to a variable of the enclosing block, or returned. overwriting an `m` variable that still owns frees the old object first, running its drop (6.15), and then stores; the right side is computed before the free (hint: x: reassigning frees the old value). a new value that is part of the old one (a child, a field or a borrow of it, as in `a = a.next`) is error: a: reassigning frees the old value, and the new value is part of it. overwriting a `u` or `d` object, which still has to leave, or an owner a closure captures: x: still owns an object; overwriting it would leak it. 6.8.6 an `m` local stored in a `T[?]` or container is a child (6.3.5): `push(a, x)` is a store with tag-match rules: the tag of the stored local must equal the container's (both `m`, `u` or `d`), the stored local must not live in an outer block than the container (error: ... lives in an outer block than the container it's stored into), and a borrowed container takes the child by giving it away. messages: c: its tag doesn't match its container's (both have to be m, u or d); c: already belongs to a container; c: is borrowed and can't be stored; c: is u; it has to leave through the block above, not into a borrowed object. @@ -994,7 +994,7 @@ the compiler also prints non-error findings as `zc: line N: warning: ...` (a swi rt.put_n(*heap(15).drop(fn() { rt.put_s(" bye "); rt.put_n(*this); })); // 15 bye 15 let guard = drop(heap(0), fn() { unlock(); }); // unlocks at block end ``` -6.15.2 a value dies, and its drop runs, where 6.5 frees it: at the end of its owner's block or an early exit, at `rt.free`, or with its container, whose drop runs before its children's. a value with a drop that nothing holds (a temporary) dies at the end of its full statement, with hint: value with drop is never bound; it drops at the end of this statement. an owned local with a drop is not "never used" (6.5.2): holding it is its use. +6.15.2 a value dies, and its drop runs, where 6.5 frees it: at the end of its owner's block or an early exit, at `rt.free`, when its owner is reassigned (6.8.5), or with its container, whose drop runs before its children's. a new value assigned to the owner can have its own drop set on the next statement, like a declaration's. a value with a drop that nothing holds (a temporary) dies at the end of its full statement, with hint: value with drop is never bound; it drops at the end of this statement. an owned local with a drop is not "never used" (6.5.2): holding it is its use. 6.15.3 the collector runs the drop of an unreachable value after its sweep, never during it, and at most once: the value is kept until a later collection finds it unreachable again. a drop that stores `this` somewhere reachable keeps the value alive, and its drop does not run again. there is no collection at exit, so collector-owned values are not dropped then. a freed block forgets its drop, so a block reused at the same address has none. 6.15.4 copying a value with a drop into an actor message is error: x: has drop and can't be copied into a message. diff --git a/src/compiler/analyzer.zc b/src/compiler/analyzer.zc index 34c0a8b..666a89d 100644 --- a/src/compiler/analyzer.zc +++ b/src/compiler/analyzer.zc @@ -987,12 +987,12 @@ int local_hints(int from) { if (!cmode || dry || panic || chkabort || stubgen) return 0; while (i < nloc) { f = ltag[i]; - if (lrskind[i] == RS_PARAM && lname[i] != s_this && !rt.eq_w(lname[i], "_") && !(f & LREAD)) { + if (lrskind[i] == RS_PARAM && lname[i] != s_this && !rt.eq_w(lname[i], "_") && !(f & (LREAD | LWASREAD))) { if (hint_begin(lpos[i], HINT_SMALL)) { rt.put_s("unused parameter `"); rt.put_s(lname[i]); rt.put_s("`"); dg_close(); } } else if ((f & LDECL) && !larr[i] && !rt.eq_w(lname[i], "_")) { // _ discards its value - if (!(f & LREAD)) hint_name(i, " is never read", HINT_SMALL); + if (!(f & (LREAD | LWASREAD))) hint_name(i, " is never read", HINT_SMALL); else if ((f & LCONST) && !(f & LASG) && !(ltype[i] && treadonly(ltype[i]))) hint_name(i, " is never reassigned; make it a const", HINT_ALL); else if (fchk && lstate[i] == S_M && lown[i] && lnsz[i] > 0 && lnsz[i] <= 64) hint_name(i, " never leaves this function; a stack buffer (let name[n]) avoids the heap", HINT_SMALL); else if (fchk && lstate[i] == S_M && lown[i] && lused[i] && tline - lused[i] >= 4) hint_name(i, " is last used several lines above the end of its block; close the block sooner so it is freed sooner", HINT_ALL); @@ -1347,6 +1347,36 @@ int emitown(int p) { if (o3on && hasptr(xty) && !xstamp) { lit(xty); callname(s_fatten); } return 0; } +// reassigning local i while it still owns a value (6.8.5): the right side is +// already on the stack, so free the old value here, running its drop, before the +// store; then assigncheck sees an empty owner. only an m owner that a closure +// doesn't capture is freed: a u or d object still has to leave. a new value that +// comes from the old one (a child, a field, a borrow of it) would be freed with it. +int asg_quiet; +int reassign_free(int i, int rc, int bor) { + word t = xty, af = xaf; int st = xstr, fl = xfloat, j = 0 - 1; + if (!fchk || lstate[i] != S_M || isalias(i) || (local_captured && local_captured[i])) return 0; + if (rc >= C_VAR && tracked(rc - C_VAR)) j = rc - C_VAR; + if ((j >= 0 && lroot(j) == i) || (bor >= 0 && tracked(bor) && lroot(bor) == i)) { + cerrn(i, "reassigning frees the old value, and the new value is part of it"); + asg_quiet = 1; // assigncheck says nothing more about this store + return 0; + } + if (hint_begin(tpack(), HINT_SMALL)) { rt.put_s(lname[i]); rt.put_s(": reassigning frees the old value"); dg_close(); } + ev_put(i, EV_FREE, -1); + if (o3on && ldroppable(i)) cerrn(i, "has drop and can't end inside an o3 region"); + // freevar without its clearing store: the new value is stored next, through + // the address taken before the right side (a captured local's slot holds its cell) + vaddr(i); op(O_LD); + if (lleaf[i] && !o3on && !dry && lookup(s_recycle_leaf) >= 0) { lit(lleaf[i]); lit(szclass(lnsz[i])); callname(s_recycle_leaf); } + else callname(s_recycle); + op(O_DROP); + end_owner(i); + if (local_dropset) local_dropset[i] = 0; + lnsz[i] = 0; lleaf[i] = 0; + xty = t; xaf = af; xstr = st; xfloat = fl; xbor = bor; + return 1; +} // a store of class rc into an lvalue of kind k (local or container index i) int assigncheck(int k, int i, int rc) { int st, j, r, js, bor, origin = effect_origin(rc); @@ -1367,6 +1397,7 @@ int assigncheck(int k, int i, int rc) { if (k == LV_LOCAL) { st = lstate[i]; if (isalias(i)) st = S_PLAIN; // a borrow isn't an owner; overwriting it drops the alias + if (asg_quiet) { asg_quiet = 0; return C_PLAIN; } // reassign_free reported it if (j < 0 && rc != C_FRESH) { if (!empty(st)) cerrn(i, "still owns an object; overwriting it would leak it"); else if (bor >= 0 && bor != i && (tracked(bor) || larr[bor])) { lstate[i] = S_CHILD; lowner[i] = bor; lown[i] = 0; } diff --git a/src/compiler/base.zc b/src/compiler/base.zc index 99ee10f..a8dc19f 100644 --- a/src/compiler/base.zc +++ b/src/compiler/base.zc @@ -391,6 +391,7 @@ int nerr, panic; // read, declared with let, initialized with a literal; the size of the object // a new gave it (bits 16 to 39); and how many times it was used (from bit 56) const int LASG = 0x1000, LREAD = 0x2000, LDECL = 0x4000, LCONST = 0x8000, LCNT1 = 0x100000000000000; +const int LWASREAD = 0x10000; // an earlier value was read: a fresh value assigned over it clears LREAD, so its drop can still be set (6.15.1) const int NEWTYPED = 0x800000; word[NL] lpos_initial; word lpos = lpos_initial; word[NL] lreadpos_initial; word lreadpos = lreadpos_initial; word[NL] lnsz_initial; word lnsz = lnsz_initial; word[NL] lleaf_initial; word lleaf = lleaf_initial; word[NL] lrsoff_initial; word lrsoff = lrsoff_initial; word[NL] lrskind_initial; word lrskind = lrskind_initial; int newsz, newleaf, nomagic, callpos; word[NL] lptr_initial; word lptr = lptr_initial; // annotate mode: the local was indexed or dereferenced, so it holds an address diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc index e6acfc8..7687394 100644 --- a/src/compiler/expr.zc +++ b/src/compiler/expr.zc @@ -1868,8 +1868,8 @@ int expr() { next(); a = o3on && k == LV_LOCAL && (ltag[i] & 16) && !larr[i]; if (a) op(O_DROP); // an o3 variable: its address comes after the value, so it never waits on the stack across a branch - xlend = -1; - rc = expr(); rval(); + xlend = -1; newsz = -1; newleaf = 0; + rc = expr(); rval(); fk = drop_tail == here; if (k != LV_LOCAL) af_escape(xaf); fcoerce(lt); if (k == LV_LOCAL) { @@ -1881,6 +1881,13 @@ int expr() { if (cmode && !dry && k == LV_GLOBAL) { vf_symbols(); svkind[i] = xvk; svvalue[i] = xvv; } else if (k != LV_LOCAL) vf_effect(); if (!compat(lt, xty)) tcerr("assignment", lt, xty); + if (k == LV_LOCAL) { + reassign_free(i, rc, xbor); + if (rc == C_FRESH && fchk) { // the new value's size and drop, as a declaration records them + lnsz[i] = newsz; lleaf[i] = newleaf; if (local_dropset) local_dropset[i] = fk; + if (ltag[i] & LREAD) ltag[i] = (ltag[i] - LREAD) | LWASREAD; + } + } if (a) { lit(0); op(O_PICK); vaddr(i); op(O_ST); o3refresh(i); } else { if (o3on && k != LV_ELEM && hasptr(lt)) { lit(lt); callname(s_fatten); } // o3 data going somewhere normal code can see diff --git a/tests/conformance/6-ownership/drop_reassign.zc b/tests/conformance/6-ownership/drop_reassign.zc new file mode 100644 index 0000000..89f233e --- /dev/null +++ b/tests/conformance/6-ownership/drop_reassign.zc @@ -0,0 +1,25 @@ +// expect: a drop1 b drop2 3 drop3 drop10 drop11 12 t:7 t:8 9 t:9 drop12 +// destructors 6.8.5, 6.15.2: reassigning an owner that still holds a value frees the old value first, running its drop; the right side is computed before the free +include rt; +struct t { int v; void drop() { rt.put_s("t:"); rt.put_n(this.v); rt.put_s(" "); } } +int* mk(int v) { return drop(heap(v), fn() { rt.put_s("drop"); rt.put_n(*this); rt.put_s(" "); }); } +fn main() { + int* p = heap(1); + p.drop = fn() { rt.put_s("drop"); rt.put_n(*this); rt.put_s(" "); }; + rt.put_s("a "); + p = heap(2); + p.drop = fn() { rt.put_s("drop"); rt.put_n(*this); rt.put_s(" "); }; + rt.put_s("b "); + p = mk(*p + 1); + rt.put_n(*p); rt.put_s(" "); + int i = 10; + while (i < 13) { p = mk(i); i++; } + rt.put_n(*p); rt.put_s(" "); + t* s = rt.new(t); s.v = 7; + s = rt.new(t); s.v = 8; + t* u = rt.new(t); u.v = 9; + s = u; + rt.put_n(s.v); rt.put_s(" "); + return 0; +} +// zc0: skip diff --git a/tests/conformance/6-ownership/overwrite_frees.zc b/tests/conformance/6-ownership/overwrite_frees.zc new file mode 100644 index 0000000..9ae60d0 --- /dev/null +++ b/tests/conformance/6-ownership/overwrite_frees.zc @@ -0,0 +1,6 @@ +// expect: 0/ +// spec 6.8.5: overwriting a variable that still owns frees the old object first +include rt; +void pn(int x) { rt.put_n(x); rt.put_c(10); } +fn main() { int* a = rt.new(1); a[0] = 1; a = rt.new(1); pn(a[0]); return 0; } +// zc0: skip diff --git a/tests/conformance/6-ownership/overwrite_leaks.zc b/tests/conformance/6-ownership/overwrite_leaks.zc deleted file mode 100644 index a9b0138..0000000 --- a/tests/conformance/6-ownership/overwrite_leaks.zc +++ /dev/null @@ -1,6 +0,0 @@ -// expect error: a: still owns an object; overwriting it would leak it -// spec 6.8.5 -include rt; -void pn(int x) { rt.put_n(x); rt.put_c(10); } -fn main() { int* a = rt.new(1); a[0] = 1; a = rt.new(1); pn(a[0]); return 0; } -// zc0: skip diff --git a/tests/conformance/6-ownership/reassign_moves_inner_owner.zc b/tests/conformance/6-ownership/reassign_moves_inner_owner.zc new file mode 100644 index 0000000..d66d886 --- /dev/null +++ b/tests/conformance/6-ownership/reassign_moves_inner_owner.zc @@ -0,0 +1,5 @@ +// expect: 1 2/ +// spec 6.8.5: r still owns; assigning n frees r's object and moves n's into r, so n's block end frees nothing +include rt; +fn main() { word r = rt.new(4); { word n = rt.new(8); n[0] = 1; r = n; } rt.put_n(r[0]); r[0] = 2; rt.put_c(32); rt.put_n(r[0]); rt.put_c(10); return 0; } +// zc0: skip diff --git a/tests/data/reassign_hint.zc b/tests/data/reassign_hint.zc new file mode 100644 index 0000000..6fd9a0c --- /dev/null +++ b/tests/data/reassign_hint.zc @@ -0,0 +1,2 @@ +include rt; +fn main() { int* p = rt.new(int); *p = 1; p = rt.new(int); return *p; } diff --git a/tests/errors/leak_overwrite.zc b/tests/errors/leak_overwrite.zc index 50fecfa..45febed 100644 --- a/tests/errors/leak_overwrite.zc +++ b/tests/errors/leak_overwrite.zc @@ -1,5 +1,7 @@ namespace; include rt; +// a u object still has to leave its block, so overwriting it is not a free (spec 6.8.5) // expect: x: still owns an object -fn main() { let x = rt.new(1); x = rt.new(2); return x[0]; } +int*.u mk() { int*.u x = rt.new(1); x = rt.new(2); return x; } +fn main() { let p = mk(); return p[0]; } diff --git a/tests/errors/move_live_owner.zc b/tests/errors/move_live_owner.zc deleted file mode 100644 index e36909c..0000000 --- a/tests/errors/move_live_owner.zc +++ /dev/null @@ -1,5 +0,0 @@ -namespace; -include rt; - -// expect: still owns an object; overwriting it would leak it -fn main() { word r = rt.new(4); { word n = rt.new(8); n[0] = 1; r = n; } r[0] = 2; return 0; } diff --git a/tests/errors/reassign_from_own_child.zc b/tests/errors/reassign_from_own_child.zc new file mode 100644 index 0000000..4a23e51 --- /dev/null +++ b/tests/errors/reassign_from_own_child.zc @@ -0,0 +1,6 @@ +namespace; +include rt; + +struct node { node* next; int v; } +// expect: a: reassigning frees the old value, and the new value is part of it +fn main() { node* a = rt.new(node); a.next = rt.new(node); a = a.next; return 0; } diff --git a/tests/test.sh b/tests/test.sh index e93472c..9aee6d0 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -579,6 +579,7 @@ s_lsp() { done chk "$($lsp < examples/struct_closure_opt.zc | grep -c error)" "0" "lsp: methods and closures check clean" case "$($lsp < tests/data/drop_hint.zc)" in *"value with drop is never bound; it drops at the end of this statement"*) chk 1 1 "lsp hint: a value with drop that is never bound";; *) chk 0 1 "lsp hint: a value with drop that is never bound";; esac + case "$($lsp < tests/data/reassign_hint.zc)" in *"p: reassigning frees the old value"*) chk 1 1 "lsp hint: reassigning an owner frees the old value";; *) chk 0 1 "lsp hint: reassigning an owner frees the old value";; esac # a file of a larger build checks on its own: its bodyless declarations are not errors in check mode for f in src/runtime/rt.zc src/runtime/run.zc src/lsp/server.zc; do chk "$($lsp < $f | grep -c ': error:')" "0" "lsp: $f checks alone"; done # the compiler is several files that only make sense together: check them as one document diff --git a/tests/xref.baseline b/tests/xref.baseline index b8b1e1f..1660302 100644 --- a/tests/xref.baseline +++ b/tests/xref.baseline @@ -25,6 +25,7 @@ argowner args argowner_fact args argtemporary args arrloc arrvalue chk_reset +asg_quiet assigncheck reassign_free asgcap reserve_assignment asm_fixes reserve_asm_references asm_labelcap reserve_asm_labels @@ -291,14 +292,14 @@ lkeycap local_bind lkeyn local_bind zc_init lkeys local_bind lkeys_initial -lleaf assigncheck embed_bind localdecl reserve_locals +lleaf assigncheck embed_bind expr localdecl reassign_free reserve_locals lleaf_initial lname newloc reserve_locals -lnsz assigncheck embed_bind localdecl reserve_locals +lnsz assigncheck embed_bind expr localdecl reassign_free reserve_locals lnsz_initial local_captured clocap_at newloc reserve_locals local_cell capture_cell newloc reserve_locals -local_dropset drop_attach localdecl 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_origin assigncheck newloc reserve_locals @@ -379,8 +380,8 @@ nclo chk_reset closure ndfr defer_drop kw_defer nenh blob_load build_image_grow chk_reset enhdef nerr chk_begin chk_reset errat prescan replace_function -newleaf array_dynamic_literal array_pop call heap_box localdecl newtyped -newsz array_dynamic_literal array_pop call chk_reset heap_box localdecl newtyped +newleaf array_dynamic_literal array_pop call expr heap_box localdecl newtyped +newsz array_dynamic_literal array_pop call chk_reset expr heap_box localdecl newtyped nfact chk_reset fdrop putfact nfixu fixnew zc_init ngbind call chk_reset primary @@ -691,8 +692,8 @@ 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 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 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 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 xc_file xc_init xc_fn closure fndef xc_hook xc_init @@ -701,14 +702,14 @@ 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 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 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 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 xprojection ctx_pop field postfix_tail primary 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 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 ternary 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 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 -- cgit v1.2.3