aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO.md2
-rw-r--r--docs/LEARN.md2
-rw-r--r--docs/SPEC.md2
-rw-r--r--src/compiler/analyzer.zc51
-rw-r--r--src/compiler/base.zc13
-rw-r--r--src/compiler/decl.zc43
-rw-r--r--src/compiler/expr.zc33
-rw-r--r--src/lib/std.zc13
-rw-r--r--src/lsp/server.zc1
-rw-r--r--tests/conformance/3-expressions/print_varargs.zc14
-rw-r--r--tests/conformance/3-expressions/print_varargs_fresh_object.zc6
-rw-r--r--tests/conformance/6-ownership/back_link_alias.zc6
-rw-r--r--tests/conformance/6-ownership/back_link_block_ok.zc6
-rw-r--r--tests/conformance/6-ownership/back_link_cont.zc6
-rw-r--r--tests/conformance/6-ownership/back_link_cycle.zc17
-rw-r--r--tests/conformance/6-ownership/back_link_free.zc6
-rw-r--r--tests/conformance/6-ownership/back_link_glob.zc7
-rw-r--r--tests/conformance/6-ownership/back_link_move.zc7
-rw-r--r--tests/conformance/6-ownership/back_link_reasg.zc6
-rw-r--r--tests/conformance/6-ownership/back_link_ret.zc7
-rw-r--r--tests/conformance/6-ownership/borrow_outlives_block.zc21
-rw-r--r--tests/conformance/6-ownership/infer_owned_return_before_body.zc8
-rw-r--r--tests/conformance/6-ownership/infer_owned_return_borrow_first.zc10
-rw-r--r--tests/conformance/6-ownership/infer_owned_return_discarded.zc7
-rw-r--r--tests/conformance/6-ownership/infer_owned_return_global_not_owned.zc7
-rw-r--r--tests/conformance/6-ownership/infer_owned_return_mixed.zc11
-rw-r--r--tests/conformance/6-ownership/infer_owned_return_recursive.zc21
-rw-r--r--tests/conformance/6-ownership/loop_build_list_item_after_move.zc13
-rw-r--r--tests/conformance/6-ownership/loop_build_list_not_taken_back.zc13
-rw-r--r--tests/conformance/6-ownership/loop_build_list_ok.zc21
-rw-r--r--tests/conformance/6-ownership/loop_build_list_other_owner.zc13
-rw-r--r--tests/conformance/6-ownership/outer_owner_in_inner_container.zc13
-rw-r--r--tests/conformance/6-ownership/u_return_param_field.zc7
-rw-r--r--tests/conformance/6-ownership/u_return_plain_param.zc8
-rw-r--r--tests/drivers/borrow_diag_test.js9
-rw-r--r--tests/errors/alias_scope.zc2
-rw-r--r--tests/errors/plain_return.zc5
-rw-r--r--tests/xref.baseline29
38 files changed, 435 insertions, 31 deletions
diff --git a/TODO.md b/TODO.md
index f2863b7..fe6baf9 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,3 +1,5 @@
+- HIGH PRIORITY: files are implicitly namespaced; `namespace;` should not be required. (zy already runs a program without it; zc0, the docs and every example still use it.)
+
- [x] read the compiler, runtime, vm cores, language server, and build tools.
- [x] verify formatted markdown and code in vs code hovers, including bundled server builds.
- [x] add mandatory documentation for every built-in type.
diff --git a/docs/LEARN.md b/docs/LEARN.md
index eae9ad4..081498d 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 7dd1247..1c65f42 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/analyzer.zc b/src/compiler/analyzer.zc
index ff6a6c1..55a96bb 100644
--- a/src/compiler/analyzer.zc
+++ b/src/compiler/analyzer.zc
@@ -122,7 +122,7 @@ int shadow_finish(int from) {
while (k < 24) { if ((e[2] >> k) & 1) flags = flags | effect_get(e[1], k); k++; }
if ((flags & (EF_ALIAS | EF_IDENTITY)) && !(flags & EF_UNKNOWN) && !dry) {
tunpack(e[3]); curinc = e[4]; panic = 0;
- errat(); ended_message(e[6], e[7], e[8], e[9]); rt.put_s("; "); rt.put_s(sname[e[1]]); rt.put_s(", defined after this call, returns a borrow of its argument"); errend();
+ errat(); ended_message(e[6], e[7], e[8], e[9], 0); rt.put_s("; "); rt.put_s(sname[e[1]]); rt.put_s(", defined after this call, returns a borrow of its argument"); errend();
}
e = next;
}
@@ -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;
@@ -1147,6 +1147,7 @@ int block_close(int save) {
unused_check(save);
exit_vars(fdepth, 1);
lend_drop(fdepth, save);
+ block_end_owners(save);
alias_end(fdepth, save);
while (i < save) { if (lhome[i] >= fdepth) { lstate[i] = S_DEAD; lhome[i] = fdepth - 1; } i++; }
local_drop(save);
@@ -1155,6 +1156,26 @@ int block_close(int save) {
return 0;
}
+// owners freed at this block's end: an outer alias of one, or an outer owner
+// stored into one, dies with it and remembers where (why 9).
+int block_end_owners(int save) {
+ int i = save, j; word info = 0;
+ if (!fchk || !live) return 0;
+ while (i < nloc) {
+ if (lhome[i] >= fdepth && lstate[i] == S_M && lown[i]) {
+ info = 0; j = lbase;
+ while (j < save) {
+ if (lstate[j] == S_CHILD && lroot(j) == i) {
+ if (!info) { info = talloc(6); info[0] = lname[i]; info[1] = lpos[i]; info[2] = tpack(); info[3] = 9; info[4] = 0; info[5] = info[2]; }
+ local_ended[j] = info[2]; local_end_info[j] = info; lstate[j] = S_DEAD;
+ }
+ j++;
+ }
+ }
+ i++;
+ }
+ return 0;
+}
// an alias in an outer block whose owner is about to disappear dies with it
int alias_end(int dep, int save) {
int i, r;
@@ -1532,6 +1553,19 @@ int assigncheck(int k, int i, int rc) {
else lstate[i] = S_PLAIN;
return C_PLAIN;
}
+ // an outer owner moved into a deeper container takes the container back:
+ // head = item after item.next = head. the container leaves its block.
+ if (st == S_CHILD && lown[i] && j >= 0 && lroot(i) == j && lstate[j] == S_M && lown[j]
+ && lhome[j] >= lhome[i] && !local_captured[i] && !local_captured[j]) {
+ int k = lbase;
+ while (k < nloc) { if (k != i && lstate[k] == S_CHILD && lowner[k] == j) lowner[k] = i; k++; }
+ lstate[i] = S_M; lused[i] = 0;
+ lnsz[i] = lnsz[j]; lleaf[i] = lleaf[j];
+ local_ended[i] = 0; local_end_info[i] = 0;
+ end_why = 3; end_whom = lname[i]; end_owner(j); luse(j); ev_put(j, EV_MOVE, i);
+ if (ltag[i] & 15) lstate[i] = ltag[i] & 15;
+ return C_PLAIN;
+ }
if (!empty(st)) {
cerrn(i, "still owns an object; overwriting it would leak it");
return C_PLAIN;
@@ -1578,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");
@@ -1587,7 +1624,7 @@ int assigncheck(int k, int i, int rc) {
}
else { end_why = 4; end_whom = lname[i]; end_owner(j); luse(j); ev_put(j, EV_STORE, i); emitown(i); } // storing it gives it away
} else if (js != st) cerrn(j, "its tag doesn't match its container's (both have to be m, u or d)");
- else if (lhome[j] < lhome[r]) cerrn(j, "lives in an outer block than the container it's stored into");
+ else if (lhome[j] < lhome[r] && (r != i || !lown[j] || (local_captured && (local_captured[i] || local_captured[j])))) cerrn(j, "lives in an outer block than the container it's stored into");
else { lstate[j] = S_CHILD; lowner[j] = i; luse(j); ev_put(j, EV_STORE, i); emitown(i); }
return C_PLAIN;
}
@@ -1607,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) {
@@ -1633,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/base.zc b/src/compiler/base.zc
index bd27e28..a209f2b 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)
@@ -986,9 +986,16 @@ int cerr(char* msg) {
}
// how an owner ended, from end_owner's record: the use, the owner, what ended it and where.
// "r: used after it was given away; r borrows t, and t was freed on line 5 (t declared on line 4)"
-int ended_message(char* use, word info, char* root, int cause) {
+int ended_message(char* use, word info, char* root, int cause, int own) {
char* owner = root; int why = 0; char* whom = 0;
if (info) { owner = info[0]; why = info[3]; whom = info[4]; dg_related(info[5], info[1], why, owner); }
+ if (why == 9) {
+ rt.put_s(use); rt.put_s(": ");
+ if (rt.eq_w(use, owner)) rt.put_s("was freed");
+ else { if (own) rt.put_s("belongs to "); else rt.put_s("borrows from "); rt.put_s(owner); rt.put_s(", which was freed"); }
+ rt.put_s(" at the end of its block (line "); rt.put_n(cause & 0xFFFFFF); rt.put_c(')');
+ return 0;
+ }
rt.put_s(use); rt.put_s(": used after it was given away; ");
if (!rt.eq_w(use, owner)) { rt.put_s(use); rt.put_s(" borrows "); rt.put_s(owner); rt.put_s(", and "); }
rt.put_s(owner);
@@ -1014,7 +1021,7 @@ int cerrn(int i, char* msg) {
if (!info && local_end_info) info = local_end_info[root];
if (info) cause = info[2]; else if (local_ended) cause = local_ended[root];
}
- if (cause && !panic && !dry) { errat(); dg_place(lreadpos[i]); ended_message(lname[i], info, lname[root], cause); errend(); }
+ if (cause && !panic && !dry) { errat(); dg_place(lreadpos[i]); ended_message(lname[i], info, lname[root], cause, lown[i]); errend(); }
else serrn(lname[i], msg);
}
return 0;
diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc
index 55e23a7..313d1ab 100644
--- a/src/compiler/decl.zc
+++ b/src/compiler/decl.zc
@@ -81,8 +81,48 @@ 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, 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")) {
+ 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 && was) {
+ 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;
@@ -409,6 +449,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
@@ -448,7 +489,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/src/compiler/expr.zc b/src/compiler/expr.zc
index 141ca16..2935a9b 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/src/lsp/server.zc b/src/lsp/server.zc
index 85f3ea5..536d71c 100644
--- a/src/lsp/server.zc
+++ b/src/lsp/server.zc
@@ -278,6 +278,7 @@ int diag_related(int at) {
else if (why == 3) what = " was moved here"; else if (why == 4) what = " was stored into a container here";
else if (why == 5) what = " was returned here"; else if (why == 6) what = " was stored in a global here";
else if (why == 7) what = " was freed by this reassignment"; else if (why == 8) what = " was pushed into an array here";
+ else if (why == 9) what = " was freed at the end of this block";
ws(",\"relatedInformation\":[{\"location\":"); wplace(e[1]); ws(",\"message\":\"owner "); ws(e[4]); ws(what); ws("\"}");
if (e[2]) { ws(",{\"location\":"); wplace(e[2]); ws(",\"message\":\"owner "); ws(e[4]); ws(" is declared here\"}"); }
wc(']');
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
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
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/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/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_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_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/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/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
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
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
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`);
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(); }
diff --git a/tests/xref.baseline b/tests/xref.baseline
index 79e948b..2f000af 100644
--- a/tests/xref.baseline
+++ b/tests/xref.baseline
@@ -316,8 +316,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
@@ -346,7 +346,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
@@ -374,10 +374,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
@@ -503,6 +503,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
@@ -711,27 +712,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