From 4759b63894879b7c17a745a6128e6d64557b05c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 21:42:56 +0700 Subject: [PATCH 1/5] t27o: a fn returning Vec built by push converts in the out-buffer form -- a last out: []T, the result the count, the oracle checks the written prefix (Closes #11073) Step 2 of #10546. A fn whose result is Vec (T an integer, float or bool type), made as one local by Vec::new() / Vec::with_capacity(n) / vec![], changed only by push(e) and extend_from_slice(s) and handed back, takes the caller's buffer as a last out: []T and returns how many elements the Rust fn returns. An element past out.len is counted, not written, as the String form counts bytes; with_capacity(n) is a hint, read and dropped. A read of the Vec, any other change, a second Vec, any other result and a call of a fn in this form refuse, each with a reason that names the use (R_VECOUT, R_VECCALL). The oracle records the count and every element of the written prefix in a 256-element buffer and the same count in a zero-length one. Tests first (nine failed on the merge base), a negative control per rule, a direct test for every new fn (coverage 100 %). gen/rust/tools/t27o/rust_convert.rs is t27c gen-rust of the spec. Co-Authored-By: Claude Sonnet 5.5 --- gen/rust/tools/t27o/rust_convert.rs | 479 +++++++++++++++- specs/tools/t27o/rust_convert.t27 | 809 +++++++++++++++++++++++++++- 2 files changed, 1259 insertions(+), 29 deletions(-) diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index 4f442bd3df..7b9daa357f 100644 --- a/gen/rust/tools/t27o/rust_convert.rs +++ b/gen/rust/tools/t27o/rust_convert.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 333 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 350 test(s), 0 invariant(s), 0 function(s) with no body. // This backend emits declarations only. The spec's checks live in // the Zig and Verilog outputs; do not read this file as verified. @@ -174,6 +174,8 @@ pub const V_SLICE: usize = 14; pub const V_MSLICE: usize = 15; +pub const V_OUTVEC: usize = 16; + pub const K_FN: usize = 1; pub const K_MEMBER: usize = 2; @@ -204,6 +206,10 @@ pub const K_NEEDPRE: usize = 14; pub const K_NOPRE: usize = 15; +pub const K_VECLOC: usize = 16; + +pub const K_VECFN: usize = 17; + pub const M_STMT: u8 = 0; pub const M_RET: u8 = 1; @@ -316,6 +322,10 @@ pub const R_OPERAND: usize = 41; pub const R_TYPENAME: usize = 42; +pub const R_VECOUT: usize = 44; + +pub const R_VECCALL: usize = 45; + pub const R_FLOATLIT: usize = 43; pub const R_NOPRELUDE: usize = 40; @@ -1007,10 +1017,14 @@ pub fn named(src: &[u8], st: &mut [usize], kind: usize, a: usize, b: usize) -> b } pub fn strloc_in(src: &[u8], st: &mut [usize], a: usize, b: usize) -> bool { + return loc_in(src, st, K_STRLOC, a, b); +} + +pub fn loc_in(src: &[u8], st: &mut [usize], kind: usize, a: usize, b: usize) -> bool { let mut k: usize = 0; while (k < st[(NNAMES) as usize]) { let s: usize = (NAMES + (k * NAME_SLOTS)); - if ((((st[((s + 4)) as usize] == K_STRLOC) && same_text(src, st[(s) as usize], st[((s + 1)) as usize], a, b)) && (a >= st[((s + 2)) as usize])) && (a < st[((s + 3)) as usize])) { + if ((((st[((s + 4)) as usize] == kind) && same_text(src, st[(s) as usize], st[((s + 1)) as usize], a, b)) && (a >= st[((s + 2)) as usize])) && (a < st[((s + 3)) as usize])) { st[(HIT) as usize] = s; return true; } @@ -1475,10 +1489,22 @@ pub fn deps(src: &[u8], st: &mut [usize], lo: usize, hi: usize) -> () { if ((kind == K_ITEM) || (kind == K_EXT)) { hit = (((!(after_dot) && !(after_path)) && !(after_fn)) && !(tok_is(src, nx, b":"))); } + if ((kind == K_VECFN) && (st[((s + 2)) as usize] == st[((s + 3)) as usize])) { + hit = (((call && !(after_dot)) && !(after_path)) && !(after_fn)); + } + if ((((kind == K_VECFN) && (st[((s + 2)) as usize] != st[((s + 3)) as usize])) && after_dot) && call) { + hit = true; + } + if ((((kind == K_VECFN) && (st[((s + 2)) as usize] != st[((s + 3)) as usize])) && after_path) && (p1 < src.len())) { + hit = (same_text(src, st[((s + 2)) as usize], st[((s + 3)) as usize], p1, tok_end(src, p1)) || tok_is(src, p1, b"Self")); + } if (hit && (kind == K_EXT)) { refuse(st, R_EXTERNAL, q, q, e); } - if (hit && (kind != K_EXT)) { + if (hit && (kind == K_VECFN)) { + refuse(st, R_VECCALL, q, q, e); + } + if ((hit && (kind != K_EXT)) && (kind != K_VECFN)) { refuse(st, R_DEPENDS, q, q, e); } } @@ -5572,6 +5598,9 @@ pub fn stmt(src: &[u8], st: &mut [usize], out: &mut [u8], mode: u8) -> () { if ((st[(OUTFORM) as usize] == 1) && out_str_stmt(src, st, out, m)) { return (); } + if ((st[(OUTFORM) as usize] == 2) && out_vec_stmt(src, st, out, m)) { + return (); + } if word_is(src, a, b, b"let") { let_stmt(src, st, out); } else { @@ -6958,6 +6987,9 @@ pub fn return_stmt(src: &[u8], st: &mut [usize], out: &mut [u8]) -> () { if ((st[(OUTFORM) as usize] == 1) && out_str_return(src, st, out)) { return (); } + if ((st[(OUTFORM) as usize] == 2) && out_vec_return(src, st, out)) { + return (); + } step(src, st); puts(st, out, b"return"); if ((!(at(src, st[(P) as usize], b";")) && !(at(src, st[(P) as usize], b"}"))) && !(at(src, st[(P) as usize], b","))) { @@ -7130,6 +7162,9 @@ pub fn arm_expr(src: &[u8], st: &mut [usize], out: &mut [u8], mode: u8) -> () { if ((st[(OUTFORM) as usize] == 1) && out_str_stmt(src, st, out, mode)) { return (); } + if ((st[(OUTFORM) as usize] == 2) && out_vec_stmt(src, st, out, mode)) { + return (); + } let w0: usize = st[(W) as usize]; expr_stmt(src, st, out); tail_of(src, st, out, w0, mode); @@ -7771,10 +7806,18 @@ pub fn put_reason(src: &[u8], st: &mut [usize], out: &mut [u8]) -> () { if (c == R_NOPRELUDE) { puts(st, out, b"an assert or a comparison of texts in a spec with no test block (t27c writes the std import and assert helper with the first one)"); } else { - if (c == R_SPLICE) { - puts(st, out, b"a used module t27c cannot splice beside type_text (its own use stays unresolved, or its same clashes with type_text's, the gap specs/compiler/int_type_names.t27 records)"); + if (c == R_VECOUT) { + puts(st, out, b"a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form)"); } else { - puts(st, out, b"syntax"); + if (c == R_VECCALL) { + puts(st, out, b"a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own)"); + } else { + if (c == R_SPLICE) { + puts(st, out, b"a used module t27c cannot splice beside type_text (its own use stays unresolved, or its same clashes with type_text's, the gap specs/compiler/int_type_names.t27 records)"); + } else { + puts(st, out, b"syntax"); + } + } } } } @@ -8160,10 +8203,17 @@ pub fn fn_head(src: &[u8], st: &mut [usize], out: &mut [u8], is_pub: bool, na: u } puts(st, out, b"out: []u8"); } + if (st[(OUTFORM) as usize] == 2) { + if !(tok_is(src, skip_ws(src, (pp + 1)), b")")) { + puts(st, out, b", "); + } + puts(st, out, b"out: []"); + put_elem_ty(src, st, out, vec_ret_elem(src, pp)); + } puts(st, out, b")"); let mut mode: u8 = M_STMT; st[(FAILK) as usize] = F_NONE; - if (st[(OUTFORM) as usize] == 1) { + if ((st[(OUTFORM) as usize] == 1) || (st[(OUTFORM) as usize] == 2)) { take(src, st, b"->"); while ((((st[(P) as usize] < src.len()) && !(tok_is(src, st[(P) as usize], b"{"))) && !(tok_is(src, st[(P) as usize], b"where"))) && !(tok_is(src, st[(P) as usize], b";"))) { step(src, st); @@ -9041,6 +9091,14 @@ pub fn put_dc(st: &mut [usize], out: &mut [u8], pre: bool) -> () { } pub fn put_call(src: &[u8], st: &mut [usize], out: &mut [u8], na: usize, nb: usize, n: usize, k0: usize, t0: usize, k1: usize, t1: usize, k2: usize, t2: usize, k3: usize, t3: usize, k4: usize, t4: usize, j: usize, selfp: bool, rust: bool, outp: bool) -> () { + let mut buf: &'static str = ""; + if outp { + buf = "[0 .. 256]"; + } + put_call_buf(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, rust, buf.as_bytes()); +} + +pub fn put_call_buf(src: &[u8], st: &mut [usize], out: &mut [u8], na: usize, nb: usize, n: usize, k0: usize, t0: usize, k1: usize, t1: usize, k2: usize, t2: usize, k3: usize, t3: usize, k4: usize, t4: usize, j: usize, selfp: bool, rust: bool, buf: &[u8]) -> () { if (rust && selfp) { put_value(src, st, out, k0, t0, j, rust, true); puts(st, out, b"."); @@ -9086,10 +9144,10 @@ pub fn put_call(src: &[u8], st: &mut [usize], out: &mut [u8], na: usize, nb: usi puts(st, out, b", "); put_arg(src, st, out, k4, t4, (j + 4), j, 4, rust); } - if outp { + if (buf.len() > 0) { puts(st, out, b", t27o_o"); put_num(st, out, j); - puts(st, out, b"[0 .. 256]"); + puts(st, out, buf); } puts(st, out, b")"); } @@ -9134,6 +9192,34 @@ pub fn put_tv(src: &[u8], st: &mut [usize], out: &mut [u8], opt: bool, fa: usize } } +pub fn put_vec_record(src: &[u8], st: &mut [usize], out: &mut [u8], j: usize, tp: usize, finite: bool, pre: bool) -> () { + puts(st, out, b"if r.len() <= 256"); + if finite { + puts(st, out, b" && r.iter().all(|x| x.is_finite())"); + } + puts(st, out, b" { let mut s = format!(\""); + if pre { + puts(st, out, b"{}"); + } + puts(st, out, b" var t27o_o"); + put_num(st, out, j); + puts(st, out, b" : [256]"); + put_src(src, st, out, tp, tok_end(src, tp)); + puts(st, out, b" = undefined;\\n const t27o_n"); + put_num(st, out, j); + puts(st, out, b" = {};\\n assert(t27o_n"); + put_num(st, out, j); + puts(st, out, b" == {});\", "); + put_dc(st, out, pre); + puts(st, out, b", r.len()); for (i, x) in r.iter().enumerate() { s.push_str(&format!(\"\\n assert(t27o_o"); + put_num(st, out, j); + puts(st, out, b"[{}] == {:?});\", i, x)); } s.push_str(&format!(\"\\n const t27o_m"); + put_num(st, out, j); + puts(st, out, b" = {};\\n assert(t27o_m"); + put_num(st, out, j); + puts(st, out, b" == {});\", z, r.len())); v.push(s); } "); +} + pub fn put_print(src: &[u8], st: &mut [usize], out: &mut [u8], rk: usize, en: usize, v: &[u8], opt: bool, pre: bool, fa: usize, fb: usize, tup: usize, ix: usize) -> () { if (rk == V_FLOAT) { puts(st, out, b"if "); @@ -9435,6 +9521,13 @@ pub fn vectors(src: &[u8], st: &mut [usize], out: &mut [u8], na: usize, nb: usiz if (wrapk == 3) { rk = V_BOOL; } + let mut ovec: usize = V_NONE; + if (((wrapk == 0) && (st[(OUTFORM) as usize] == 2)) && word_is(src, rq, re, b"Vec")) { + ovec = vkind(src, skip_ws(src, (skip_ws(src, re) + 1))); + if ((((ovec == V_UNSIGNED) || (ovec == V_SIGNED)) || (ovec == V_BOOL)) || (ovec == V_FLOAT)) { + rk = V_OUTVEC; + } + } let mut so: usize = src.len(); if (wrapk != 3) { let mut sn: usize = bq; @@ -9449,7 +9542,7 @@ pub fn vectors(src: &[u8], st: &mut [usize], out: &mut [u8], na: usize, nb: usiz rk = V_STRUCT; } } - if ((((rk == V_NONE) || (rk == V_BYTES)) || (rk == V_ARRAY)) || ((rk > V_STRING) && (rk != V_STRUCT))) { + if ((((rk == V_NONE) || (rk == V_BYTES)) || (rk == V_ARRAY)) || (((rk > V_STRING) && (rk != V_STRUCT)) && (rk != V_OUTVEC))) { return (); } let mut en: usize = 0; @@ -9494,8 +9587,13 @@ pub fn vectors(src: &[u8], st: &mut [usize], out: &mut [u8], na: usize, nb: usiz puts(st, out, b" if let Ok(r) = std::panic::catch_unwind(|| "); put_call(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, true, false); puts(st, out, b") { let c = \""); - put_call(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, false, ((wrapk == 0) && (rk == V_STRING))); + put_call(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, false, ((wrapk == 0) && ((rk == V_STRING) || (rk == V_OUTVEC)))); puts(st, out, b"\"; "); + if (rk == V_OUTVEC) { + puts(st, out, b"let z = \""); + put_call_buf(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, false, b"[0 .. 0]"); + puts(st, out, b"\"; "); + } let pre: bool = ((((local_arg(k0) || local_arg(k1)) || local_arg(k2)) || local_arg(k3)) || local_arg(k4)); if pre { puts(st, out, b"let d = \""); @@ -9532,10 +9630,14 @@ pub fn vectors(src: &[u8], st: &mut [usize], out: &mut [u8], na: usize, nb: usiz put_dc(st, out, pre); puts(st, out, b", r)); } "); } else { - if (rk == V_STRUCT) { - put_fields(src, st, out, so, b"r", false, pre); + if (rk == V_OUTVEC) { + put_vec_record(src, st, out, j, skip_ws(src, (skip_ws(src, re) + 1)), (ovec == V_FLOAT), pre); } else { - put_print(src, st, out, rk, en, b"r", false, pre, 0, 0, NO_IX, NO_IX); + if (rk == V_STRUCT) { + put_fields(src, st, out, so, b"r", false, pre); + } else { + put_print(src, st, out, rk, en, b"r", false, pre, 0, 0, NO_IX, NO_IX); + } } } } else { @@ -10650,6 +10752,346 @@ pub fn out_str_return(src: &[u8], st: &mut [usize], out: &mut [u8]) -> bool { return true; } +pub fn vec_ret_elem(src: &[u8], pn: usize) -> usize { + if !(tok_is(src, pn, b"(")) { + return src.len(); + } + let r: usize = skip_ws(src, block_end(src, pn)); + if !(tok_is(src, r, b"->")) { + return src.len(); + } + let v: usize = skip_ws(src, tok_end(src, r)); + let lt: usize = skip_ws(src, tok_end(src, v)); + if (!(tok_is(src, v, b"Vec")) || !(tok_is(src, lt, b"<"))) { + return src.len(); + } + let t: usize = skip_ws(src, tok_end(src, lt)); + let gt: usize = skip_ws(src, tok_end(src, t)); + if (((t >= src.len()) || !(tok_is(src, gt, b">"))) || !(tok_is(src, skip_ws(src, tok_end(src, gt)), b"{"))) { + return src.len(); + } + let k: usize = vkind(src, t); + if ((((k == V_UNSIGNED) || (k == V_SIGNED)) || (k == V_BOOL)) || (k == V_FLOAT)) { + return t; + } + return src.len(); +} + +pub fn vec_init_open(src: &[u8], q: usize) -> usize { + if (q >= src.len()) { + return src.len(); + } + let qe: usize = tok_end(src, q); + if (tok_is(src, q, b"Vec") && tok_is(src, skip_ws(src, qe), b"::")) { + let m: usize = skip_ws(src, (skip_ws(src, qe) + 2)); + if ((m >= src.len()) || !(is_alpha(src[(m) as usize]))) { + return src.len(); + } + return skip_ws(src, tok_end(src, m)); + } + if (tok_is(src, q, b"vec") && tok_is(src, skip_ws(src, qe), b"!")) { + return skip_ws(src, (skip_ws(src, qe) + 1)); + } + return src.len(); +} + +pub fn vec_let_init(src: &[u8], q: usize) -> usize { + let o: usize = vec_init_open(src, q); + if ((o >= src.len()) || !(ends_stmt(src, (block_end(src, o) - 1)))) { + return 0; + } + if tok_is(src, q, b"vec") { + if (tok_is(src, o, b"[") && peek_is(src, o, b"]")) { + return 3; + } + return 0; + } + let m: usize = skip_ws(src, (skip_ws(src, tok_end(src, q)) + 2)); + let me: usize = tok_end(src, m); + if ((word_is(src, m, me, b"new") && tok_is(src, o, b"(")) && peek_is(src, o, b")")) { + return 1; + } + if (word_is(src, m, me, b"with_capacity") && tok_is(src, o, b"(")) { + return 2; + } + return 0; +} + +pub fn vec_cap_arg(src: &[u8], o: usize) -> bool { + return (((o < src.len()) && tok_is(src, o, b"(")) && !(peek_is(src, o, b")"))); +} + +pub fn stmt_edge(src: &[u8], p0: usize) -> bool { + return (((tok_is(src, p0, b"{") || tok_is(src, p0, b";")) || tok_is(src, p0, b"}")) || tok_is(src, p0, b"=>")); +} + +pub fn vecout_mention(src: &[u8], st: &mut [usize], q: usize, e: usize, p0: usize) -> () { + let nx: usize = skip_ws(src, e); + if (tok_is(src, p0, b".") || tok_is(src, nx, b":")) { + return (); + } + if tok_is(src, nx, b".") { + let m: usize = skip_ws(src, tok_end(src, nx)); + let me: usize = tok_end(src, m); + let lp: usize = skip_ws(src, me); + let grows: bool = (word_is(src, m, me, b"push") || word_is(src, m, me, b"extend_from_slice")); + if (((grows && tok_is(src, lp, b"(")) && stmt_edge(src, p0)) && ends_stmt(src, (block_end(src, lp) - 1))) { + return (); + } + refuse(st, R_VECOUT, m, m, me); + return (); + } + if (tok_is(src, p0, b"return") && (tok_is(src, nx, b";") || tok_is(src, nx, b"}"))) { + return (); + } + if (stmt_edge(src, p0) && tok_is(src, nx, b"}")) { + return (); + } + if (tok_is(src, p0, b"=>") && tok_is(src, nx, b",")) { + return (); + } + refuse(st, R_VECOUT, q, q, e); +} + +pub fn vec_out_form(src: &[u8], st: &mut [usize], lo: usize, hi: usize) -> bool { + let mut na: usize = src.len(); + let mut nb: usize = 0; + let mut lets: usize = 0; + let mut q: usize = lo; + while ((q < hi) && (st[(ERR) as usize] == 0)) { + let e: usize = tok_end(src, q); + if word_is(src, q, e, b"let") { + let mut n: usize = skip_ws(src, e); + if tok_is(src, n, b"mut") { + n = skip_ws(src, tok_end(src, n)); + } + let ne: usize = tok_end(src, n); + if (((n < src.len()) && is_alpha(src[(n) as usize])) && (vec_let_init(src, init_at(src, q)) > 0)) { + lets = (lets + 1); + if (lets == 1) { + na = n; + nb = ne; + } else { + refuse(st, R_VECOUT, n, n, ne); + } + } + } + q = skip_ws(src, e); + } + if ((lets == 0) || (st[(ERR) as usize] != 0)) { + return false; + } + let mut p0: usize = src.len(); + q = lo; + while ((q < hi) && (st[(ERR) as usize] == 0)) { + let e: usize = tok_end(src, q); + if ((q != na) && same_text(src, q, e, na, nb)) { + vecout_mention(src, st, q, e, p0); + } + if word_is(src, q, e, b"return") { + let r: usize = skip_ws(src, e); + let back: bool = (((r < src.len()) && is_alpha(src[(r) as usize])) && same_text(src, r, tok_end(src, r), na, nb)); + if (!(back) && (vec_let_init(src, r) == 0)) { + refuse(st, R_VECOUT, r, r, tok_end(src, r)); + } + } + p0 = q; + q = skip_ws(src, e); + } + if (st[(ERR) as usize] != 0) { + return false; + } + add_name(src, st, K_VECLOC, na, nb, lo, hi); + return true; +} + +pub fn out_vec_cap(src: &[u8], st: &mut [usize], out: &mut [u8], o: usize) -> () { + puts(st, out, b"_ = "); + st[(P) as usize] = skip_ws(src, (o + 1)); + expr(src, st, out, false); + puts(st, out, b";"); +} + +pub fn out_vec_let(src: &[u8], st: &mut [usize], out: &mut [u8]) -> bool { + let a: usize = st[(P) as usize]; + let mut n: usize = skip_ws(src, tok_end(src, a)); + if tok_is(src, n, b"mut") { + n = skip_ws(src, tok_end(src, n)); + } + let ne: usize = tok_end(src, n); + let init: usize = init_at(src, a); + if ((((n >= src.len()) || !(is_alpha(src[(n) as usize]))) || (vec_let_init(src, init) == 0)) || !(loc_in(src, st, K_VECLOC, n, ne))) { + return false; + } + puts(st, out, b"var t27o_w : usize = 0;"); + let o: usize = vec_init_open(src, init); + if vec_cap_arg(src, o) { + newline(st, out); + out_vec_cap(src, st, out, o); + } + st[(P) as usize] = skip_ws(src, (find_at(src, a, B_SEMI) + 1)); + return true; +} + +pub fn out_vec_push(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, arg: usize) -> () { + puts(st, out, b"const t27o_e"); + put_num(st, out, a); + puts(st, out, b" = "); + st[(P) as usize] = arg; + expr(src, st, out, false); + puts(st, out, b";"); + newline(st, out); + puts(st, out, b"if (t27o_w < out.len) { out[t27o_w] = t27o_e"); + put_num(st, out, a); + puts(st, out, b"; }"); + newline(st, out); + puts(st, out, b"t27o_w = t27o_w + 1;"); + want(src, st, b")"); + take(src, st, b";"); + take(src, st, b","); +} + +pub fn out_vec_extend(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize, arg: usize) -> () { + puts(st, out, b"const t27o_x"); + put_num(st, out, a); + puts(st, out, b" = "); + st[(P) as usize] = arg; + expr(src, st, out, false); + puts(st, out, b";"); + newline(st, out); + puts(st, out, b"var t27o_k"); + put_num(st, out, a); + puts(st, out, b" : usize = 0;"); + newline(st, out); + puts(st, out, b"while (t27o_k"); + put_num(st, out, a); + puts(st, out, b" < t27o_x"); + put_num(st, out, a); + puts(st, out, b".len) {"); + st[(IND) as usize] = (st[(IND) as usize] + 1); + newline(st, out); + puts(st, out, b"if (t27o_w < out.len) { out[t27o_w] = t27o_x"); + put_num(st, out, a); + puts(st, out, b"[t27o_k"); + put_num(st, out, a); + puts(st, out, b"]; }"); + newline(st, out); + puts(st, out, b"t27o_w = t27o_w + 1;"); + newline(st, out); + puts(st, out, b"t27o_k"); + put_num(st, out, a); + puts(st, out, b" = t27o_k"); + put_num(st, out, a); + puts(st, out, b" + 1;"); + st[(IND) as usize] = (st[(IND) as usize] - 1); + newline(st, out); + puts(st, out, b"}"); + want(src, st, b")"); + take(src, st, b";"); + take(src, st, b","); +} + +pub fn out_vec_empty(src: &[u8], st: &mut [usize], out: &mut [u8], a: usize) -> () { + let o: usize = vec_init_open(src, a); + if vec_cap_arg(src, o) { + out_vec_cap(src, st, out, o); + newline(st, out); + } + puts(st, out, b"return 0;"); + st[(P) as usize] = skip_ws(src, block_end(src, o)); + take(src, st, b";"); + take(src, st, b","); +} + +pub fn vec_tail_other(src: &[u8], a: usize, b: usize) -> bool { + if (((((word_is(src, a, b, b"if") || word_is(src, a, b, b"match")) || word_is(src, a, b, b"loop")) || word_is(src, a, b, b"while")) || word_is(src, a, b, b"for")) || word_is(src, a, b, b"unsafe")) { + return true; + } + if (((word_is(src, a, b, b"return") || word_is(src, a, b, b"break")) || word_is(src, a, b, b"continue")) || word_is(src, a, b, b"{")) { + return true; + } + return ((is_alpha(src[(a) as usize]) && peek_is(src, a, b"!")) && !(word_is(src, a, b, b"vec"))); +} + +pub fn out_vec_stmt(src: &[u8], st: &mut [usize], out: &mut [u8], m: u8) -> bool { + let a: usize = st[(P) as usize]; + if (a >= src.len()) { + return false; + } + let b: usize = tok_end(src, a); + if word_is(src, a, b, b"let") { + return out_vec_let(src, st, out); + } + if (is_alpha(src[(a) as usize]) && loc_in(src, st, K_VECLOC, a, b)) { + let nx: usize = skip_ws(src, b); + if tok_is(src, nx, b".") { + let m1: usize = skip_ws(src, tok_end(src, nx)); + let m1e: usize = tok_end(src, m1); + let lp: usize = skip_ws(src, m1e); + if (word_is(src, m1, m1e, b"push") && tok_is(src, lp, b"(")) { + out_vec_push(src, st, out, a, skip_ws(src, (lp + 1))); + return true; + } + if (word_is(src, m1, m1e, b"extend_from_slice") && tok_is(src, lp, b"(")) { + out_vec_extend(src, st, out, a, skip_ws(src, (lp + 1))); + return true; + } + } + if (tok_is(src, nx, b";") || tok_is(src, nx, b",")) { + st[(P) as usize] = skip_ws(src, tok_end(src, nx)); + if (m == M_RET) { + puts(st, out, b"return t27o_w;"); + } + return true; + } + if tok_is(src, nx, b"}") { + st[(P) as usize] = nx; + if (m == M_RET) { + puts(st, out, b"return t27o_w;"); + } + return true; + } + refuse(st, R_VECOUT, a, a, b); + return true; + } + if ((m != M_RET) || vec_tail_other(src, a, b)) { + return false; + } + if (vec_let_init(src, a) > 0) { + out_vec_empty(src, st, out, a); + return true; + } + refuse(st, R_VECOUT, a, a, b); + return true; +} + +pub fn out_vec_return(src: &[u8], st: &mut [usize], out: &mut [u8]) -> bool { + let r0: usize = st[(P) as usize]; + step(src, st); + let a: usize = st[(P) as usize]; + if (tok_is(src, a, b";") || tok_is(src, a, b"}")) { + st[(P) as usize] = r0; + return false; + } + let b: usize = tok_end(src, a); + let nx: usize = skip_ws(src, b); + if ((is_alpha(src[(a) as usize]) && loc_in(src, st, K_VECLOC, a, b)) && ((tok_is(src, nx, b";") || tok_is(src, nx, b"}")) || tok_is(src, nx, b","))) { + if tok_is(src, nx, b"}") { + st[(P) as usize] = nx; + } else { + st[(P) as usize] = skip_ws(src, tok_end(src, nx)); + } + puts(st, out, b"return t27o_w;"); + return true; + } + if (vec_let_init(src, a) > 0) { + out_vec_empty(src, st, out, a); + return true; + } + refuse(st, R_VECOUT, a, a, b); + return true; +} + pub fn struct_text(src: &[u8], st: &mut [usize], ilo: usize, ihi: usize, q: usize) -> bool { if ((q >= src.len()) || !(is_upper(src[(q) as usize]))) { return false; @@ -10774,6 +11216,10 @@ pub fn fn_item(src: &[u8], st: &mut [usize], out: &mut [u8], start: usize, is_te if named(src, st, K_STRFN, na, nb) { srow = st[(HIT) as usize]; } + } else { + if ((((bb < src.len()) && (vec_ret_elem(src, pn) < src.len())) && !(param_named_out(src, pn))) && vec_out_form(src, st, skip_ws(src, (bb + 1)), end)) { + st[(OUTFORM) as usize] = 2; + } } let mode: u8 = fn_head(src, st, out, is_pub, na, nb); st[(STUBBING) as usize] = 0; @@ -10819,7 +11265,6 @@ pub fn fn_item(src: &[u8], st: &mut [usize], out: &mut [u8], start: usize, is_te } return (); } - st[(OUTFORM) as usize] = 0; st[(NOK) as usize] = (st[(NOK) as usize] + 1); if is_test { st[(NTEST) as usize] = (st[(NTEST) as usize] + 1); @@ -10836,9 +11281,13 @@ pub fn fn_item(src: &[u8], st: &mut [usize], out: &mut [u8], start: usize, is_te put_item_source(src, st, out, start, item_end(src, start)); puts(st, out, b"\n\n"); } + if (st[(OUTFORM) as usize] == 2) { + add_name(src, st, K_VECFN, na, nb, st[(ILO) as usize], st[(IHI) as usize]); + } if ((st[(MODE) as usize] == M_CALLS) && !(is_test)) { vectors(src, st, out, na, nb, pn); } + st[(OUTFORM) as usize] = 0; st[(P) as usize] = end; } diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index 29674f1215..7949cee092 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -28,7 +28,8 @@ ; `const x_t27o = f(); if (x_t27o == null) { return null; } const x = x_t27o.?;`. `Result<(), E>` is a ; bool (gen-rust and t27b refuse `?void`): `Ok(())` is `true`, an error `false`. ; A read-only `String` / `&String` / `Vec` / `&Vec` parameter is `[]const u8` / `[]const T`; a -; Vec anywhere else, and an owned String outside the out-buffer form (next paragraph), stays refused. +; Vec anywhere else, and an owned String or a Vec result outside the out-buffer forms (the next paragraphs), stays +; refused. ; A `vec![a, b, c]` or `vec![x; N]` that the fn only reads (#10643) is a typed array, `let v = vec![..];` ; as `const v : [3]T = [..];` (`[N]T = [x] ** N` for a repeat; N one number or one const of the file): ; t27 holds no growing list, so T must come from the source -- the `Vec` annotation, the first element @@ -51,6 +52,17 @@ ; calling the named F on each piece), and the local handed back (`return t27o_w`). Anything else ; that reads the String refuses, naming the read -- a call outside the form, an argument to it, any ; other use of the local. +; The Vec out-buffer form (#11073): a fn whose result is `Vec`, T an integer, float or bool type, and whose body +; makes the Vec as one local (`Vec::new()`, `Vec::with_capacity(n)` or `vec![]`), changes it only by `push(e)` and +; `extend_from_slice(s)`, and hands it back (`return v`, the tail `v`, or an empty Vec in an early return), takes a +; last `out: []T` parameter and returns `usize` -- how many elements the Rust fn returns. The capacity is the +; caller's and is never guessed: an element past `out.len` is counted and not written (as the String form counts +; bytes), so the count is the same whatever room the buffer has, and `with_capacity(n)` is a hint, read and dropped. +; The element of a push is evaluated once, whether it fits or not. A read of the Vec (`len`, an index), any other +; change (`insert`, `pop`, `sort`, an index assignment), a borrow, a rebinding, a second Vec local, a result made any +; other way (a collect, a vec! literal, a clone) or a call of a fn in this form (the caller would own a buffer of no +; stated length, R_VECCALL) refuses, naming the use (R_VECOUT): a list that grows without a bound the caller states +; has no t27 form. ; 3. Oracle: `oracle` writes a Rust harness made of the original text of what converted plus calls on ; sample and edge inputs; rustc runs it (on the lab) and its output is t27 `test oracle_` blocks, ; the behaviour the t27 bodies must reproduce. A Rust #[test] fn becomes a t27 `test` as well. @@ -60,7 +72,10 @@ ; literal alone and wrapped in one space, every integer one below, at it, one above (#9102) -- so a ; match arm is reached and not only the default one; a result is a ; scalar, a text, an owned String (the out-buffer form: its bytes into a 256-byte buffer the test -; owns, the written length its result, checked with type_text::same on the written slice), a unit +; owns, the written length its result, checked with type_text::same on the written slice), a Vec of +; integers, floats or bools (the Vec out-buffer form: the count it returns is the Vec's length and every +; element of the written prefix is the Vec's, for a result of at most 256 elements; the same call with a +; zero-length buffer must answer the same count), a unit ; enum or a struct of this file (field by field, array fields element by, ; element), bare or held by an Option or a Result (`f(x).? == v`, `f(x) == null`), or Result<(), E>. ; 4. Reuse, DRY: nothing that exists in t27 is written again. Text equality is tri::t27b::type_text's @@ -235,6 +250,8 @@ pub const V_RVEC : usize = 12; pub const V_STRUCT : usize = 13; pub const V_SLICE : usize = 14; pub const V_MSLICE : usize = 15; +/// A `Vec` result of a fn in the Vec out-buffer form: the count it returns and the elements it wrote (#11073). +pub const V_OUTVEC : usize = 16; /// What a name of the table is: a refused fn, a refused member of an impl (fn or const, with its /// type), a refused struct / enum / const / other item, or a name a `use` brings from another file. @@ -263,6 +280,11 @@ pub const K_BYTECHAR : usize = 13; /// next pass refuses that code. pub const K_NEEDPRE : usize = 14; pub const K_NOPRE : usize = 15; +/// The Vec local of a fn lowered to the Vec out-buffer form (#11073): the one list it pushes to and hands +/// back, matched inside its fn's body range the way K_STRLOC is. And a fn in that form (a free fn, or a +/// method with its impl type as the owner): every call of it is refused, the caller would own the buffer. +pub const K_VECLOC : usize = 16; +pub const K_VECFN : usize = 17; /// What the tail expression of a block becomes: a statement, a `return`, or an assignment to the /// target src[TLO .. THI]. @@ -344,6 +366,10 @@ pub const R_SHIFTCALL : usize = 39; pub const R_OPERAND : usize = 41; /// A struct or enum named like a type t27c's Zig backend renames (GF16 is u16 there, str is []const u8). pub const R_TYPENAME : usize = 42; +/// A Vec result the out-buffer form cannot hold (#11073): not one local built only by push / extend_from_slice +/// and handed back as it is. And a call of a fn that is in that form: the caller would own an unbounded buffer. +pub const R_VECOUT : usize = 44; +pub const R_VECCALL : usize = 45; /// A float literal bound to a `var` with no type: t27c writes a `var` of comptime_float, which Zig rejects, and /// the source leaves f32 or f64 to inference. pub const R_FLOATLIT : usize = 43; @@ -905,10 +931,15 @@ pub fn named(src: []const u8, st: []usize, kind: usize, a: usize, b: usize) -> b /// same-name poison the whole-file table had before the ranges). Its slot goes to st[HIT] (0 when /// none), the way `named` leaves it. pub fn strloc_in(src: []const u8, st: []usize, a: usize, b: usize) -> bool { + return loc_in(src, st, K_STRLOC, a, b); +} + +/// The same for a local of any kind (K_STRLOC, K_VECLOC): the name src[a .. b) of that kind whose fn range holds a. +pub fn loc_in(src: []const u8, st: []usize, kind: usize, a: usize, b: usize) -> bool { var k : usize = 0; while (k < st[NNAMES]) { const s : usize = NAMES + k * NAME_SLOTS; - if (st[s + 4] == K_STRLOC and same_text(src, st[s], st[s + 1], a, b) and a >= st[s + 2] and a < st[s + 3]) { + if (st[s + 4] == kind and same_text(src, st[s], st[s + 1], a, b) and a >= st[s + 2] and a < st[s + 3]) { st[HIT] = s; return true; } @@ -1339,8 +1370,15 @@ pub fn deps(src: []const u8, st: []usize, lo: usize, hi: usize) -> void { hit = same_text(src, st[s + 2], st[s + 3], p1, tok_end(src, p1)) or tok_is(src, p1, "Self"); } if (kind == K_ITEM or kind == K_EXT) { hit = !after_dot and !after_path and !after_fn and !tok_is(src, nx, ":"); } + // a fn in the Vec out-buffer form (#11073): a free fn is hit by a call, a method as K_MEMBER is + if (kind == K_VECFN and st[s + 2] == st[s + 3]) { hit = call and !after_dot and !after_path and !after_fn; } + if (kind == K_VECFN and st[s + 2] != st[s + 3] and after_dot and call) { hit = true; } + if (kind == K_VECFN and st[s + 2] != st[s + 3] and after_path and p1 < src.len) { + hit = same_text(src, st[s + 2], st[s + 3], p1, tok_end(src, p1)) or tok_is(src, p1, "Self"); + } if (hit and kind == K_EXT) { refuse(st, R_EXTERNAL, q, q, e); } - if (hit and kind != K_EXT) { refuse(st, R_DEPENDS, q, q, e); } + if (hit and kind == K_VECFN) { refuse(st, R_VECCALL, q, q, e); } + if (hit and kind != K_EXT and kind != K_VECFN) { refuse(st, R_DEPENDS, q, q, e); } } k = k + 1; } @@ -4900,6 +4938,7 @@ pub fn stmt(src: []const u8, st: []usize, out: []u8, mode: u8) -> void { var m : u8 = M_STMT; if (is_tail(src, a)) { m = mode; } if (st[OUTFORM] == 1 and out_str_stmt(src, st, out, m)) { return; } + if (st[OUTFORM] == 2 and out_vec_stmt(src, st, out, m)) { return; } if (word_is(src, a, b, "let")) { let_stmt(src, st, out); } else if (word_is(src, a, b, "if")) { @@ -6158,6 +6197,7 @@ pub fn for_stmt(src: []const u8, st: []usize, out: []u8) -> void { pub fn return_stmt(src: []const u8, st: []usize, out: []u8) -> void { if (st[OUTFORM] == 1 and out_str_return(src, st, out)) { return; } + if (st[OUTFORM] == 2 and out_vec_return(src, st, out)) { return; } step(src, st); puts(st, out, "return"); if (!at(src, st[P], ";") and !at(src, st[P], "}") and !at(src, st[P], ",")) { @@ -6317,6 +6357,7 @@ pub fn arm_expr(src: []const u8, st: []usize, out: []u8, mode: u8) -> void { step(src, st); } else { if (st[OUTFORM] == 1 and out_str_stmt(src, st, out, mode)) { return; } + if (st[OUTFORM] == 2 and out_vec_stmt(src, st, out, mode)) { return; } const w0 : usize = st[W]; expr_stmt(src, st, out); tail_of(src, st, out, w0, mode); @@ -6851,6 +6892,10 @@ pub fn put_reason(src: []const u8, st: []usize, out: []u8) -> void { puts(st, out, "a float literal in a var of no type, f32 or f64 left to inference"); } else if (c == R_NOPRELUDE) { puts(st, out, "an assert or a comparison of texts in a spec with no test block (t27c writes the std import and assert helper with the first one)"); + } else if (c == R_VECOUT) { + puts(st, out, "a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form)"); + } else if (c == R_VECCALL) { + puts(st, out, "a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own)"); } else if (c == R_SPLICE) { puts(st, out, "a used module t27c cannot splice beside type_text (its own use stays unresolved, or its same clashes with type_text's, the gap specs/compiler/int_type_names.t27 records)"); } else { @@ -7167,10 +7212,16 @@ pub fn fn_head(src: []const u8, st: []usize, out: []u8, is_pub: bool, na: usize, if (!tok_is(src, skip_ws(src, pp + 1), ")")) { puts(st, out, ", "); } puts(st, out, "out: []u8"); } + if (st[OUTFORM] == 2) { + // the same for a Vec result: the buffer holds T, the fn returns how many elements it made + if (!tok_is(src, skip_ws(src, pp + 1), ")")) { puts(st, out, ", "); } + puts(st, out, "out: []"); + put_elem_ty(src, st, out, vec_ret_elem(src, pp)); + } puts(st, out, ")"); var mode : u8 = M_STMT; st[FAILK] = F_NONE; - if (st[OUTFORM] == 1) { + if (st[OUTFORM] == 1 or st[OUTFORM] == 2) { take(src, st, "->"); while (st[P] < src.len and !tok_is(src, st[P], "{") and !tok_is(src, st[P], "where") and !tok_is(src, st[P], ";")) { step(src, st); } puts(st, out, " -> usize"); @@ -7792,6 +7843,14 @@ pub fn put_dc(st: []usize, out: []u8, pre: bool) -> void { /// One sampled call, parameter i taking sample j + i. Rust: `Type::name(args)`, or /// `Type::V.name(args)` for an enum's self; t27: `Type_name(args)` with the variant first. pub fn put_call(src: []const u8, st: []usize, out: []u8, na: usize, nb: usize, n: usize, k0: usize, t0: usize, k1: usize, t1: usize, k2: usize, t2: usize, k3: usize, t3: usize, k4: usize, t4: usize, j: usize, selfp: bool, rust: bool, outp: bool) -> void { + var buf : []const u8 = ""; + if (outp) { buf = "[0 .. 256]"; } + put_call_buf(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, rust, buf); +} + +/// put_call with the out buffer's slice spelled by `buf` (`[0 .. 256]` for the full buffer, `[0 .. 0]` for none), written +/// after the arguments as `t27o_o`; no buffer when `buf` is empty. +pub fn put_call_buf(src: []const u8, st: []usize, out: []u8, na: usize, nb: usize, n: usize, k0: usize, t0: usize, k1: usize, t1: usize, k2: usize, t2: usize, k3: usize, t3: usize, k4: usize, t4: usize, j: usize, selfp: bool, rust: bool, buf: []const u8) -> void { if (rust and selfp) { put_value(src, st, out, k0, t0, j, rust, true); puts(st, out, "."); @@ -7823,11 +7882,11 @@ pub fn put_call(src: []const u8, st: []usize, out: []u8, na: usize, nb: usize, n puts(st, out, ", "); put_arg(src, st, out, k4, t4, j + 4, j, 4, rust); } - if (outp) { - // the t27 side of an owned-String result: the buffer the test owns + if (buf.len > 0) { + // the t27 side of an owned-String or Vec result: the buffer the test owns puts(st, out, ", t27o_o"); put_num(st, out, j); - puts(st, out, "[0 .. 256]"); + puts(st, out, buf); } puts(st, out, ")"); } @@ -7871,6 +7930,34 @@ pub fn put_tv(src: []const u8, st: []usize, out: []u8, opt: bool, fa: usize, fb: } } +/// The harness lines that record the result `r` (a Vec of the scalar type at src[tp]) of sample j of a fn in the Vec out-buffer +/// form: only a result of at most 256 elements (a float one only when every element is finite), as a test that owns a +/// 256-element buffer (the caller's), takes the count the fn returns, asserts it is the Vec's length, asserts every element of +/// the written prefix, and asserts that the same call with the zero-length buffer `z` answers the same count. +pub fn put_vec_record(src: []const u8, st: []usize, out: []u8, j: usize, tp: usize, finite: bool, pre: bool) -> void { + puts(st, out, "if r.len() <= 256"); + if (finite) { puts(st, out, " && r.iter().all(|x| x.is_finite())"); } + puts(st, out, " { let mut s = format!(\""); + if (pre) { puts(st, out, "{}"); } + puts(st, out, " var t27o_o"); + put_num(st, out, j); + puts(st, out, " : [256]"); + put_src(src, st, out, tp, tok_end(src, tp)); + puts(st, out, " = undefined;\\n const t27o_n"); + put_num(st, out, j); + puts(st, out, " = {};\\n assert(t27o_n"); + put_num(st, out, j); + puts(st, out, " == {});\", "); + put_dc(st, out, pre); + puts(st, out, ", r.len()); for (i, x) in r.iter().enumerate() { s.push_str(&format!(\"\\n assert(t27o_o"); + put_num(st, out, j); + puts(st, out, "[{}] == {:?});\", i, x)); } s.push_str(&format!(\"\\n const t27o_m"); + put_num(st, out, j); + puts(st, out, " = {};\\n assert(t27o_m"); + put_num(st, out, j); + puts(st, out, " == {});\", z, r.len())); v.push(s); } "); +} + /// The harness line that records the value `v` (the result, what an Option or a Result holds, or a field /// fa .. fb / tup of it, or an element ix of that) of kind rk as a t27 assert on the call `c`: a scalar by /// `==`, a float only when finite, a text by type_text::same when it needs no escape t27 lacks, a variant @@ -8138,6 +8225,12 @@ pub fn vectors(src: []const u8, st: []usize, out: []u8, na: usize, nb: usize, pn } var rk : usize = vkind(src, bq); if (wrapk == 3) { rk = V_BOOL; } + // a Vec result of a fn in the Vec out-buffer form: the count it returns and the elements it wrote (#11073) + var ovec : usize = V_NONE; + if (wrapk == 0 and st[OUTFORM] == 2 and word_is(src, rq, re, "Vec")) { + ovec = vkind(src, skip_ws(src, skip_ws(src, re) + 1)); + if (ovec == V_UNSIGNED or ovec == V_SIGNED or ovec == V_BOOL or ovec == V_FLOAT) { rk = V_OUTVEC; } + } // a struct of this file whose fields the oracle can record, field by field var so : usize = src.len; if (wrapk != 3) { @@ -8151,7 +8244,7 @@ pub fn vectors(src: []const u8, st: []usize, out: []u8, na: usize, nb: usize, pn } // an owned String records byte for byte (the fn is out-form: it wrote the buffer the test owns); // a String by reference and the Vecs still have no t27 shape to record - if (rk == V_NONE or rk == V_BYTES or rk == V_ARRAY or (rk > V_STRING and rk != V_STRUCT)) { return; } + if (rk == V_NONE or rk == V_BYTES or rk == V_ARRAY or (rk > V_STRING and rk != V_STRUCT and rk != V_OUTVEC)) { return; } var en : usize = 0; if (rk == V_ENUM) { en = enum_at(src, st[ILO], st[IHI], bq); @@ -8185,8 +8278,14 @@ pub fn vectors(src: []const u8, st: []usize, out: []u8, na: usize, nb: usize, pn puts(st, out, " if let Ok(r) = std::panic::catch_unwind(|| "); put_call(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, true, false); puts(st, out, ") { let c = \""); - put_call(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, false, wrapk == 0 and rk == V_STRING); + put_call(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, false, wrapk == 0 and (rk == V_STRING or rk == V_OUTVEC)); puts(st, out, "\"; "); + if (rk == V_OUTVEC) { + // the same call with a zero-length buffer: the count must not depend on the room the caller left + puts(st, out, "let z = \""); + put_call_buf(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, false, "[0 .. 0]"); + puts(st, out, "\"; "); + } // an array argument is a typed local the test declares first (a bare `[x] ** N` is a tuple to Zig) const pre : bool = local_arg(k0) or local_arg(k1) or local_arg(k2) or local_arg(k3) or local_arg(k4); if (pre) { @@ -8215,6 +8314,8 @@ pub fn vectors(src: []const u8, st: []usize, out: []u8, na: usize, nb: usize, pn puts(st, out, "], {:?}));\", "); put_dc(st, out, pre); puts(st, out, ", r)); } "); + } else if (rk == V_OUTVEC) { + put_vec_record(src, st, out, j, skip_ws(src, skip_ws(src, re) + 1), ovec == V_FLOAT, pre); } else if (rk == V_STRUCT) { put_fields(src, st, out, so, "r", false, pre); } else { put_print(src, st, out, rk, en, "r", false, pre, 0, 0, NO_IX, NO_IX); } } else if (wrapk == 3) { puts(st, out, "match r { Ok(()) => v.push(format!(\""); @@ -9159,6 +9260,324 @@ pub fn out_str_return(src: []const u8, st: []usize, out: []u8) -> bool { return true; } +// ---------------------------------------------------------------- the Vec out-buffer form (#11073) + +/// The element type token T of the result `-> Vec {` of the fn whose parameters open at src[pn]: src.len unless the +/// result is exactly that and T is an integer, float or bool type (the types the oracle records element by element). +pub fn vec_ret_elem(src: []const u8, pn: usize) -> usize { + if (!tok_is(src, pn, "(")) { return src.len; } + const r : usize = skip_ws(src, block_end(src, pn)); + if (!tok_is(src, r, "->")) { return src.len; } + const v : usize = skip_ws(src, tok_end(src, r)); + const lt : usize = skip_ws(src, tok_end(src, v)); + if (!tok_is(src, v, "Vec") or !tok_is(src, lt, "<")) { return src.len; } + const t : usize = skip_ws(src, tok_end(src, lt)); + const gt : usize = skip_ws(src, tok_end(src, t)); + if (t >= src.len or !tok_is(src, gt, ">") or !tok_is(src, skip_ws(src, tok_end(src, gt)), "{")) { return src.len; } + const k : usize = vkind(src, t); + if (k == V_UNSIGNED or k == V_SIGNED or k == V_BOOL or k == V_FLOAT) { return t; } + return src.len; +} + +/// The `(` or `[` that opens the argument list of the init at src[q] -- `Vec::new(`, `Vec::with_capacity(`, `vec![` -- +/// src.len when the init is none of them. +pub fn vec_init_open(src: []const u8, q: usize) -> usize { + if (q >= src.len) { return src.len; } + const qe : usize = tok_end(src, q); + if (tok_is(src, q, "Vec") and tok_is(src, skip_ws(src, qe), "::")) { + const m : usize = skip_ws(src, skip_ws(src, qe) + 2); + // `Vec::::new()` has a turbofish where the method name goes: not an init t27o reads + if (m >= src.len or !is_alpha(src[m])) { return src.len; } + return skip_ws(src, tok_end(src, m)); + } + if (tok_is(src, q, "vec") and tok_is(src, skip_ws(src, qe), "!")) { return skip_ws(src, skip_ws(src, qe) + 1); } + return src.len; +} + +/// The form of the init at src[q] that starts an empty Vec the out-buffer form lowers, the init ending its statement: +/// 1 `Vec::new()`, 2 `Vec::with_capacity(n)`, 3 `vec![]`; 0 for anything else (a Vec with elements, a turbofish, a call). +pub fn vec_let_init(src: []const u8, q: usize) -> usize { + const o : usize = vec_init_open(src, q); + if (o >= src.len or !ends_stmt(src, block_end(src, o) - 1)) { return 0; } + if (tok_is(src, q, "vec")) { + if (tok_is(src, o, "[") and peek_is(src, o, "]")) { return 3; } + return 0; + } + const m : usize = skip_ws(src, skip_ws(src, tok_end(src, q)) + 2); + const me : usize = tok_end(src, m); + if (word_is(src, m, me, "new") and tok_is(src, o, "(") and peek_is(src, o, ")")) { return 1; } + if (word_is(src, m, me, "with_capacity") and tok_is(src, o, "(")) { return 2; } + return 0; +} + +/// Does the init that opens at src[o] carry a capacity argument (`Vec::with_capacity(n)`)? +pub fn vec_cap_arg(src: []const u8, o: usize) -> bool { + return o < src.len and tok_is(src, o, "(") and !peek_is(src, o, ")"); +} + +/// May a statement start after the token at src[p0]: after `{`, `;`, `}` or a match arm's `=>`? +pub fn stmt_edge(src: []const u8, p0: usize) -> bool { + return tok_is(src, p0, "{") or tok_is(src, p0, ";") or tok_is(src, p0, "}") or tok_is(src, p0, "=>"); +} + +/// One mention of the Vec local (src[q .. e), the token before it at src[p0]) in the body of a fn that returns it: +/// `v.push(..)` and `v.extend_from_slice(..)` as a statement, `return v` and the tail `v` are the form; anything +/// else refuses (R_VECOUT), naming the method when it is a method call (`len`, `insert`, `sort`) and the local otherwise +/// (an index, a borrow, a rebinding, a use as an argument). +pub fn vecout_mention(src: []const u8, st: []usize, q: usize, e: usize, p0: usize) -> void { + const nx : usize = skip_ws(src, e); + // `x.v` (a field or a method of that name) and a struct literal's `v: ..` are not the local + if (tok_is(src, p0, ".") or tok_is(src, nx, ":")) { return; } + if (tok_is(src, nx, ".")) { + const m : usize = skip_ws(src, tok_end(src, nx)); + const me : usize = tok_end(src, m); + const lp : usize = skip_ws(src, me); + const grows : bool = word_is(src, m, me, "push") or word_is(src, m, me, "extend_from_slice"); + if (grows and tok_is(src, lp, "(") and stmt_edge(src, p0) and ends_stmt(src, block_end(src, lp) - 1)) { return; } + refuse(st, R_VECOUT, m, m, me); + return; + } + if (tok_is(src, p0, "return") and (tok_is(src, nx, ";") or tok_is(src, nx, "}"))) { return; } + if (stmt_edge(src, p0) and tok_is(src, nx, "}")) { return; } + if (tok_is(src, p0, "=>") and tok_is(src, nx, ",")) { return; } + refuse(st, R_VECOUT, q, q, e); +} + +/// Does the fn whose body tokens sit in src[lo .. hi) fit the Vec out-buffer form? Its result is one local made by an +/// empty init (vec_let_init), and every mention of the local is the form's (vecout_mention); every `return` hands back that +/// local or an empty Vec. A second empty-Vec local refuses (R_VECOUT) -- the one buffer is the result, a scratch list has +/// no bound -- and so does any other mention. False (and no refusal) when the fn makes no such local: the old refusal +/// of a Vec result stands. The local is entered as K_VECLOC, matched inside lo .. hi like a String local. +pub fn vec_out_form(src: []const u8, st: []usize, lo: usize, hi: usize) -> bool { + var na : usize = src.len; + var nb : usize = 0; + var lets : usize = 0; + var q : usize = lo; + while (q < hi and st[ERR] == 0) { + const e : usize = tok_end(src, q); + if (word_is(src, q, e, "let")) { + var n : usize = skip_ws(src, e); + if (tok_is(src, n, "mut")) { n = skip_ws(src, tok_end(src, n)); } + const ne : usize = tok_end(src, n); + if (n < src.len and is_alpha(src[n]) and vec_let_init(src, init_at(src, q)) > 0) { + lets = lets + 1; + if (lets == 1) { + na = n; + nb = ne; + } else { + refuse(st, R_VECOUT, n, n, ne); + } + } + } + q = skip_ws(src, e); + } + if (lets == 0 or st[ERR] != 0) { return false; } + var p0 : usize = src.len; + q = lo; + while (q < hi and st[ERR] == 0) { + const e : usize = tok_end(src, q); + if (q != na and same_text(src, q, e, na, nb)) { vecout_mention(src, st, q, e, p0); } + if (word_is(src, q, e, "return")) { + const r : usize = skip_ws(src, e); + const back : bool = r < src.len and is_alpha(src[r]) and same_text(src, r, tok_end(src, r), na, nb); + if (!back and vec_let_init(src, r) == 0) { refuse(st, R_VECOUT, r, r, tok_end(src, r)); } + } + p0 = q; + q = skip_ws(src, e); + } + if (st[ERR] != 0) { return false; } + add_name(src, st, K_VECLOC, na, nb, lo, hi); + return true; +} + +/// `_ = n;` for the capacity of a `Vec::with_capacity(n)` that opens at src[o]: the capacity is a hint, the size of the +/// buffer is the caller's, and n is read for whatever reading it does. +pub fn out_vec_cap(src: []const u8, st: []usize, out: []u8, o: usize) -> void { + puts(st, out, "_ = "); + st[P] = skip_ws(src, o + 1); + expr(src, st, out, false); + puts(st, out, ";"); +} + +/// `let [mut] v = Vec::new();` (or with_capacity, vec![]) of the result local: the cursor, `var t27o_w : usize = 0;` +/// -- how many elements the fn has made, counted past the end of the buffer. False when the let is none of that. +pub fn out_vec_let(src: []const u8, st: []usize, out: []u8) -> bool { + const a : usize = st[P]; + var n : usize = skip_ws(src, tok_end(src, a)); + if (tok_is(src, n, "mut")) { n = skip_ws(src, tok_end(src, n)); } + const ne : usize = tok_end(src, n); + const init : usize = init_at(src, a); + if (n >= src.len or !is_alpha(src[n]) or vec_let_init(src, init) == 0 or !loc_in(src, st, K_VECLOC, n, ne)) { return false; } + puts(st, out, "var t27o_w : usize = 0;"); + const o : usize = vec_init_open(src, init); + if (vec_cap_arg(src, o)) { + newline(st, out); + out_vec_cap(src, st, out, o); + } + st[P] = skip_ws(src, find_at(src, a, B_SEMI) + 1); + return true; +} + +/// `v.push(e)` of the result local at src[a], its argument at src[arg]: e is evaluated once (as Rust does, whatever the +/// buffer holds), stored only while the cursor is inside the buffer, and counted either way. +pub fn out_vec_push(src: []const u8, st: []usize, out: []u8, a: usize, arg: usize) -> void { + puts(st, out, "const t27o_e"); + put_num(st, out, a); + puts(st, out, " = "); + st[P] = arg; + expr(src, st, out, false); + puts(st, out, ";"); + newline(st, out); + puts(st, out, "if (t27o_w < out.len) { out[t27o_w] = t27o_e"); + put_num(st, out, a); + puts(st, out, "; }"); + newline(st, out); + puts(st, out, "t27o_w = t27o_w + 1;"); + want(src, st, ")"); + take(src, st, ";"); + take(src, st, ","); +} + +/// `v.extend_from_slice(s)` of the result local at src[a], the slice at src[arg]: s is read once, then pushed element by element. +pub fn out_vec_extend(src: []const u8, st: []usize, out: []u8, a: usize, arg: usize) -> void { + puts(st, out, "const t27o_x"); + put_num(st, out, a); + puts(st, out, " = "); + st[P] = arg; + expr(src, st, out, false); + puts(st, out, ";"); + newline(st, out); + puts(st, out, "var t27o_k"); + put_num(st, out, a); + puts(st, out, " : usize = 0;"); + newline(st, out); + puts(st, out, "while (t27o_k"); + put_num(st, out, a); + puts(st, out, " < t27o_x"); + put_num(st, out, a); + puts(st, out, ".len) {"); + st[IND] = st[IND] + 1; + newline(st, out); + puts(st, out, "if (t27o_w < out.len) { out[t27o_w] = t27o_x"); + put_num(st, out, a); + puts(st, out, "[t27o_k"); + put_num(st, out, a); + puts(st, out, "]; }"); + newline(st, out); + puts(st, out, "t27o_w = t27o_w + 1;"); + newline(st, out); + puts(st, out, "t27o_k"); + put_num(st, out, a); + puts(st, out, " = t27o_k"); + put_num(st, out, a); + puts(st, out, " + 1;"); + st[IND] = st[IND] - 1; + newline(st, out); + puts(st, out, "}"); + want(src, st, ")"); + take(src, st, ";"); + take(src, st, ","); +} + +/// An empty Vec handed back (`return Vec::new();`, a tail `vec![]`) whose init is at src[a]: the capacity argument is read, +/// the count is 0. +pub fn out_vec_empty(src: []const u8, st: []usize, out: []u8, a: usize) -> void { + const o : usize = vec_init_open(src, a); + if (vec_cap_arg(src, o)) { + out_vec_cap(src, st, out, o); + newline(st, out); + } + puts(st, out, "return 0;"); + st[P] = skip_ws(src, block_end(src, o)); + take(src, st, ";"); + take(src, st, ","); +} + +/// Does a tail that starts with the token src[a .. b) read as a block, a jump or a macro, which the normal path writes +/// (`if`, `match`, `loop`, `return`, `{ .. }`, `panic!(..)`)? +pub fn vec_tail_other(src: []const u8, a: usize, b: usize) -> bool { + if (word_is(src, a, b, "if") or word_is(src, a, b, "match") or word_is(src, a, b, "loop") or word_is(src, a, b, "while") or word_is(src, a, b, "for") or word_is(src, a, b, "unsafe")) { return true; } + if (word_is(src, a, b, "return") or word_is(src, a, b, "break") or word_is(src, a, b, "continue") or word_is(src, a, b, "{")) { return true; } + return is_alpha(src[a]) and peek_is(src, a, "!") and !word_is(src, a, b, "vec"); +} + +/// A statement (or a match arm's value) of a fn in the Vec out-buffer form that touches its result local or hands a Vec +/// back: the `let` of the local, `v.push(e)`, `v.extend_from_slice(s)`, the local itself (a tail: `return t27o_w`), +/// and an empty Vec as a tail. Returns false when the statement is none of these (the normal path); a tail that is +/// anything else refuses (R_VECOUT). +pub fn out_vec_stmt(src: []const u8, st: []usize, out: []u8, m: u8) -> bool { + const a : usize = st[P]; + if (a >= src.len) { return false; } + const b : usize = tok_end(src, a); + if (word_is(src, a, b, "let")) { return out_vec_let(src, st, out); } + if (is_alpha(src[a]) and loc_in(src, st, K_VECLOC, a, b)) { + const nx : usize = skip_ws(src, b); + if (tok_is(src, nx, ".")) { + const m1 : usize = skip_ws(src, tok_end(src, nx)); + const m1e : usize = tok_end(src, m1); + const lp : usize = skip_ws(src, m1e); + if (word_is(src, m1, m1e, "push") and tok_is(src, lp, "(")) { + out_vec_push(src, st, out, a, skip_ws(src, lp + 1)); + return true; + } + if (word_is(src, m1, m1e, "extend_from_slice") and tok_is(src, lp, "(")) { + out_vec_extend(src, st, out, a, skip_ws(src, lp + 1)); + return true; + } + } + if (tok_is(src, nx, ";") or tok_is(src, nx, ",")) { + st[P] = skip_ws(src, tok_end(src, nx)); + if (m == M_RET) { puts(st, out, "return t27o_w;"); } + return true; + } + if (tok_is(src, nx, "}")) { + // the `}` closes the block: it stays for close_block, and P must move past the local or the stmts loop re-runs this + st[P] = nx; + if (m == M_RET) { puts(st, out, "return t27o_w;"); } + return true; + } + refuse(st, R_VECOUT, a, a, b); + return true; + } + if (m != M_RET or vec_tail_other(src, a, b)) { return false; } + if (vec_let_init(src, a) > 0) { + out_vec_empty(src, st, out, a); + return true; + } + refuse(st, R_VECOUT, a, a, b); + return true; +} + +/// `return` of a fn in the Vec out-buffer form: the local (`return t27o_w`) or an empty Vec (`return 0`). Returns false +/// for a `return` with no value; any other value refuses (R_VECOUT). +pub fn out_vec_return(src: []const u8, st: []usize, out: []u8) -> bool { + const r0 : usize = st[P]; + step(src, st); + const a : usize = st[P]; + if (tok_is(src, a, ";") or tok_is(src, a, "}")) { + st[P] = r0; + return false; + } + const b : usize = tok_end(src, a); + const nx : usize = skip_ws(src, b); + if (is_alpha(src[a]) and loc_in(src, st, K_VECLOC, a, b) and (tok_is(src, nx, ";") or tok_is(src, nx, "}") or tok_is(src, nx, ","))) { + // a `;` or `,` is consumed; the fn-closing `}` stays for close_block + if (tok_is(src, nx, "}")) { + st[P] = nx; + } else { + st[P] = skip_ws(src, tok_end(src, nx)); + } + puts(st, out, "return t27o_w;"); + return true; + } + if (vec_let_init(src, a) > 0) { + out_vec_empty(src, st, out, a); + return true; + } + refuse(st, R_VECOUT, a, a, b); + return true; +} + /// Does the struct named by the type token at src[q] (`Self` is the impl's type) have a text field? The /// oracle records one with type_text::same, so a fn that returns the struct makes the spec use it (#10534: /// FieldDesc::new -> Self, whose `name: &'static str` the appended oracle test compares). @@ -9267,6 +9686,8 @@ pub fn fn_item(src: []const u8, st: []usize, out: []u8, start: usize, is_test: b // caller's deps walk must not refuse the call (kind K_STRFN refuses nobody) add_name(src, st, K_STRFN, na, nb, st[ILO], st[IHI]); if (named(src, st, K_STRFN, na, nb)) { srow = st[HIT]; } + } else if (bb < src.len and vec_ret_elem(src, pn) < src.len and !param_named_out(src, pn) and vec_out_form(src, st, skip_ws(src, bb + 1), end)) { + st[OUTFORM] = 2; } const mode : u8 = fn_head(src, st, out, is_pub, na, nb); st[STUBBING] = 0; @@ -9302,7 +9723,6 @@ pub fn fn_item(src: []const u8, st: []usize, out: []u8, start: usize, is_test: b if (!is_test and st[ILO] == st[IHI]) { add_name(src, st, K_FN, na, nb, 0, 0); } return; } - st[OUTFORM] = 0; st[NOK] = st[NOK] + 1; if (is_test) { st[NTEST] = st[NTEST] + 1; } // the oracle compares a text result with type_text::same, so the spec uses it @@ -9312,7 +9732,11 @@ pub fn fn_item(src: []const u8, st: []usize, out: []u8, start: usize, is_test: b put_item_source(src, st, out, start, item_end(src, start)); puts(st, out, "\n\n"); } + // a fn in the Vec out-buffer form is called with a buffer: its callers are refused (deps, K_VECFN), and + // the oracle (which reads OUTFORM) records the written prefix + if (st[OUTFORM] == 2) { add_name(src, st, K_VECFN, na, nb, st[ILO], st[IHI]); } if (st[MODE] == M_CALLS and !is_test) { vectors(src, st, out, na, nb, pn); } + st[OUTFORM] = 0; st[P] = end; } @@ -10488,6 +10912,127 @@ test an_owned_string_outside_the_out_buffer_form_names_the_read { assert(out_is(n, &out, 0, "use tools::t27o::rust_std;\n\n// t27o: refused fn mk (line 1): an owned String outside the out-buffer form `&` at line 4\n\n")); } +test a_vec_result_built_by_push_lowers_to_the_out_buffer_form { + var out : [2048]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // A fn that returns Vec and builds it by push takes the caller's buffer as a last `out: []T` and returns how many + // elements it pushed (#11073); the push writes the element only while it fits and counts it either way. + const n : usize = body("fn evens(n: u32) -> Vec {\n let mut v = Vec::new();\n let mut i = 0;\n while i < n {\n v.push(i * 2);\n i += 1;\n }\n v\n}", &out, &st); + assert(out_is(n, &out, 0, "fn evens(n: u32, out: []u32) -> usize {\n var t27o_w : usize = 0;\n var i = 0;\n while (i < n) {\n const t27o_e104 = i * 2;\n if (t27o_w < out.len) { out[t27o_w] = t27o_e104; }\n t27o_w = t27o_w + 1;\n i += 1;\n }\n return t27o_w;\n}\n\n")); +} + +test the_out_buffer_form_takes_every_empty_vec_init_and_every_way_to_hand_the_vec_back { + var out : [2048]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // Vec::new(), Vec::with_capacity(n) (the capacity is a hint: it is read for its side effects and dropped, the capacity + // is the caller's), vec![] and a typed let all start the empty result; `return v;`, the tail `v` and an empty tail hand it back. + const n : usize = body("fn pick(a: u8, b: u8, take: bool) -> Vec {\n if !take { return Vec::new(); }\n let mut v: Vec = Vec::with_capacity(2);\n v.push(a);\n v.push(b + 1);\n return v;\n}\n\nfn lit() -> Vec { let v = vec![]; v }", &out, &st); + assert(out_is(n, &out, 0, "fn pick(a: u8, b: u8, take: bool, out: []u8) -> usize {\n if (!take) {\n return 0;\n }\n var t27o_w : usize = 0;\n _ = 2;\n const t27o_e135 = a;\n if (t27o_w < out.len) { out[t27o_w] = t27o_e135; }\n t27o_w = t27o_w + 1;\n const t27o_e150 = b + 1;\n if (t27o_w < out.len) { out[t27o_w] = t27o_e150; }\n t27o_w = t27o_w + 1;\n return t27o_w;\n}\n\nfn lit(out: []bool) -> usize {\n var t27o_w : usize = 0;\n return t27o_w;\n}\n\n")); +} + +test extend_from_slice_in_the_out_buffer_form_copies_the_slice_element_by_element { + var out : [2048]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // The slice is read once, then copied element by element through the same guarded store; the count goes on past the end. + const n : usize = body("fn pack(a: &[u8], n: u8) -> Vec {\n let mut v = Vec::with_capacity(a.len() + 1);\n v.push(n);\n v.extend_from_slice(a);\n v.extend_from_slice(&a[1..]);\n v\n}", &out, &st); + assert(out_is(n, &out, 0, "fn pack(a: []const u8, n: u8, out: []u8) -> usize {\n var t27o_w : usize = 0;\n _ = a.len + 1;\n const t27o_e91 = n;\n if (t27o_w < out.len) { out[t27o_w] = t27o_e91; }\n t27o_w = t27o_w + 1;\n const t27o_x106 = a;\n var t27o_k106 : usize = 0;\n while (t27o_k106 < t27o_x106.len) {\n if (t27o_w < out.len) { out[t27o_w] = t27o_x106[t27o_k106]; }\n t27o_w = t27o_w + 1;\n t27o_k106 = t27o_k106 + 1;\n }\n const t27o_x134 = a[1 .. a.len];\n var t27o_k134 : usize = 0;\n while (t27o_k134 < t27o_x134.len) {\n if (t27o_w < out.len) { out[t27o_w] = t27o_x134[t27o_k134]; }\n t27o_w = t27o_w + 1;\n t27o_k134 = t27o_k134 + 1;\n }\n return t27o_w;\n}\n\n")); +} + +test a_returned_vec_the_fn_also_reads_or_changes_is_refused_with_the_use { + var out : [8192]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // Negative controls: the result is one local changed only by push and extend_from_slice. A read (`len`, an index), an + // insert, an index assignment, a sort, a second Vec, a rebinding, a clone, a pop, a returned literal and a push whose own + // argument reads the Vec are each refused, and the reason names the use. Nothing is converted with a capacity the source + // does not state. + const n : usize = body("fn r1(n: u8) -> Vec { let mut v = Vec::new(); v.push(n); if v.len() > 1 { v.push(n); } v }\nfn r2(n: u8) -> Vec { let mut v = Vec::new(); v.push(n); v.insert(0, n); v }\nfn r3(n: u8) -> Vec { let mut v = Vec::new(); v.push(n); v[0] = 1; v }\nfn r4(n: u8) -> Vec { let mut v = Vec::new(); v.push(n); v.sort(); v }\nfn r5(n: u8) -> Vec { let mut v = Vec::new(); let mut w = Vec::new(); v.push(n); w.push(n); v }\nfn r6(n: u8) -> Vec { let mut v = Vec::new(); v.push(n); let w = v; w }\nfn r7(n: u8) -> Vec { let mut v = Vec::new(); v.push(n); let c = v.clone(); c }\nfn r8(n: u8) -> Vec { let mut v = Vec::new(); v.push(n); v.pop(); v }\nfn r9(n: u8) -> Vec { let mut v = Vec::new(); v.push(n); vec![n] }\nfn r10(n: u8) -> Vec { let mut v = Vec::new(); v.push(v.len() as u8); v }", &out, &st); + assert(out_text_has(&out, n, "// t27o: refused fn r1 (line 1): a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form) `len` at line 1\n")); + assert(out_text_has(&out, n, "// t27o: refused fn r2 (line 2): a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form) `insert` at line 2\n")); + assert(out_text_has(&out, n, "// t27o: refused fn r3 (line 3): a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form) `v` at line 3\n")); + assert(out_text_has(&out, n, "// t27o: refused fn r4 (line 4): a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form) `sort` at line 4\n")); + assert(out_text_has(&out, n, "// t27o: refused fn r5 (line 5): a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form) `w` at line 5\n")); + assert(out_text_has(&out, n, "// t27o: refused fn r6 (line 6): a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form) `v` at line 6\n")); + assert(out_text_has(&out, n, "// t27o: refused fn r7 (line 7): a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form) `clone` at line 7\n")); + assert(out_text_has(&out, n, "// t27o: refused fn r8 (line 8): a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form) `pop` at line 8\n")); + assert(out_text_has(&out, n, "// t27o: refused fn r9 (line 9): a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form) `vec` at line 9\n")); + assert(out_text_has(&out, n, "// t27o: refused fn r10 (line 10): a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form) `len` at line 10\n")); + assert(!out_text_has(&out, n, "fn r1(")); + assert(!out_text_has(&out, n, "fn r2(")); + assert(!out_text_has(&out, n, "fn r5(")); +} + +test a_vec_result_the_out_buffer_form_does_not_cover_stays_refused_as_before { + var out : [8192]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // Negative controls: an element that is a String, a tuple or a char, a result made by collect, a vec! literal, a vec! + // with a length and an index write, an Option>, and a parameter already named `out` are not the form: they keep the + // refusal they had (type `Vec`). A scratch Vec the fn does not return keeps `path Vec::new`. A rebinding of the pushed + // Vec is the form's own refusal. + const n : usize = body("fn t1(n: u8) -> Vec { let mut v = Vec::new(); v.push(n.to_string()); v }\nfn t2(n: u8) -> Vec<(u8, u8)> { let mut v = Vec::new(); v.push((n, n)); v }\nfn t3(n: char) -> Vec { let mut v = Vec::new(); v.push(n); v }\nfn t4(a: &[u8]) -> Vec { a.iter().map(|x| x + 1).collect() }\nfn t5(n: u8, out: u8) -> Vec { let mut v = Vec::new(); v.push(out); v }\nfn t6(n: u8) -> Vec { vec![n, n] }\nfn t7(n: u8) -> Vec { let mut v = vec![0u8; 4]; v[0] = n; v }\nfn t8(n: u8) -> Option> { let mut v = Vec::new(); v.push(n); Some(v) }\nfn t9(n: u8) -> u8 { let mut v = Vec::new(); v.push(n); v[0] }\nfn t10(n: u8) -> Vec { let mut v = Vec::new(); v.push(n); let w = v; w }", &out, &st); + assert(out_text_has(&out, n, "// t27o: refused fn t1 (line 1): type `Vec` at line 1\n")); + assert(out_text_has(&out, n, "// t27o: refused fn t2 (line 2): type `Vec` at line 2\n")); + assert(out_text_has(&out, n, "// t27o: refused fn t3 (line 3): type `Vec` at line 3\n")); + assert(out_text_has(&out, n, "// t27o: refused fn t4 (line 4): type `Vec` at line 4\n")); + assert(out_text_has(&out, n, "// t27o: refused fn t5 (line 5): type `Vec` at line 5\n")); + assert(out_text_has(&out, n, "// t27o: refused fn t6 (line 6): type `Vec` at line 6\n")); + assert(out_text_has(&out, n, "// t27o: refused fn t7 (line 7): type `Vec` at line 7\n")); + assert(out_text_has(&out, n, "// t27o: refused fn t8 (line 8): type `Vec` at line 8\n")); + assert(out_text_has(&out, n, "// t27o: refused fn t9 (line 9): path `Vec::new` at line 9\n")); + assert(out_text_has(&out, n, "// t27o: refused fn t10 (line 10): a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form) `v` at line 10\n")); +} + +test a_call_of_an_out_buffer_fn_is_refused_because_the_caller_would_own_the_buffer { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // A caller would need a buffer of a length nobody stated, so it is refused with a reason that names the call, in whichever + // order the file declares them. (The String form leaves its callers to call it without the buffer: #11073 notes it.) + const n : usize = body("fn evens(n: u32) -> Vec { let mut v = Vec::new(); v.push(n); v }\nfn total(n: u32) -> usize { evens(n).len() }\nfn first(n: u32) -> u32 { let e = evens(n); e[0] }\nfn fine(n: u32) -> u32 { n + 1 }\n", &out, &st); + assert(out_text_has(&out, n, "fn evens(n: u32, out: []u32) -> usize {")); + assert(out_text_has(&out, n, "// t27o: refused fn total (line 2): a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own) `evens` at line 2\n")); + assert(out_text_has(&out, n, "// t27o: refused fn first (line 3): a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own) `evens` at line 3\n")); + assert(out_text_has(&out, n, "fn fine(n: u32) -> u32 {\n return n + 1;\n}")); +} + +test a_call_of_an_out_buffer_method_is_refused_by_method_and_by_path { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // The same for a method, called as `x.bytes()` or `S::bytes(x)`. + const n : usize = body("struct S { a: u8 }\nimpl S {\n fn bytes(&self) -> Vec { let mut v = Vec::new(); v.push(self.a); v }\n fn n(&self) -> usize { self.bytes().len() }\n fn twice(&self) -> u8 { self.a + self.a }\n}\nfn user(s: &S) -> usize { S::bytes(s).len() }\n", &out, &st); + assert(out_text_has(&out, n, "fn S_bytes(self: S, out: []u8) -> usize {")); + assert(out_text_has(&out, n, "// t27o: refused fn n (line 4): a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own) `bytes` at line 4\n")); + assert(out_text_has(&out, n, "// t27o: refused fn user (line 7): a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own) `bytes` at line 7\n")); + assert(out_text_has(&out, n, "fn S_twice(self: S) -> u8 {")); +} + +test the_oracle_records_the_written_prefix_of_a_vec_result_in_a_full_buffer_and_in_none { + var out : [20000]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // The Rust side runs the original; the test it prints owns a 256-element buffer, takes the count the fn returns and + // compares it with the length of the Vec, then every element of the written prefix with the Vec's. A second call with a + // zero-length buffer must answer the same count: the elements past the buffer are counted, not stored. + const n : usize = oracle("fn evens(n: u32) -> Vec { let mut v = Vec::new(); let mut i = 0; while i < n { v.push(i * 2); i += 1; } v }", &out, &st); + assert(out_text_has(&out, n, "catch_unwind(|| evens(0))")); + assert(out_text_has(&out, n, "let c = \"evens(0, t27o_o0[0 .. 256])\"; let z = \"evens(0, t27o_o0[0 .. 0])\";")); + assert(out_text_has(&out, n, "if r.len() <= 256 {")); + assert(out_text_has(&out, n, "var t27o_o0 : [256]u32 = undefined;\\n const t27o_n0 = {};\\n assert(t27o_n0 == {});")); + assert(out_text_has(&out, n, "for (i, x) in r.iter().enumerate()")); + assert(out_text_has(&out, n, "\\n assert(t27o_o0[{}] == {:?});")); + assert(out_text_has(&out, n, "\\n const t27o_m0 = {};\\n assert(t27o_m0 == {});")); + assert(out_text_has(&out, n, "test oracle_evens {")); +} + +test the_oracle_skips_a_non_finite_float_element_and_names_the_element_type { + var out : [20000]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // A float result is recorded only when every element is finite (as a scalar float is); the buffer has the element's own type. + const n : usize = oracle("fn half(x: f64) -> Vec { let mut v = Vec::new(); v.push(x / 2.0); v }\nfn flags(x: u8) -> Vec { let mut v = Vec::new(); v.push(x > 3); v }", &out, &st); + assert(out_text_has(&out, n, "if r.len() <= 256 && r.iter().all(|x| x.is_finite()) {")); + assert(out_text_has(&out, n, "var t27o_o0 : [256]f64 = undefined;")); + assert(out_text_has(&out, n, "var t27o_o0 : [256]bool = undefined;")); + assert(out_text_has(&out, n, "test oracle_half {")); + assert(out_text_has(&out, n, "test oracle_flags {")); +} + test a_string_local_of_one_fn_does_not_poison_a_later_fns_name { var out : [2048]u8 = undefined; var st : [1024]usize = [0] ** 1024; @@ -17536,13 +18081,13 @@ test put_docs_writes_the_doc_lines_above_an_item_and_nothing_else { } test put_reason_names_the_refusal_and_the_text_it_points_at { - const codes : [43]usize = [R_SYNTAX, R_GENERIC, R_TRAIT, R_CLOSURE, R_TRY, R_MACRO, R_TYPE, R_METHOD, R_TUPLE, R_IFLET, R_ITER, R_PATTERN, R_MUTREF, R_DEREF, R_VALUE, R_LITERAL, R_LABEL, R_ITEM, R_UNSAFE, R_PATH, R_WHERE, R_SELFMUT, R_UPDATE, R_DATA, R_DEPENDS, R_EXTERNAL, R_UNDECL, R_CLASH, R_STRUCTEQ, R_STRUSE, R_SPLICE, R_EMPTY, R_NOTYPE, R_VECMUT, R_VECMOVE, R_VECTYPE, R_LIBM, R_SLICEGEN, R_SHIFTCALL, R_OPERAND, R_TYPENAME, R_FLOATLIT, R_NOPRELUDE]; - const why : [43]str = ["syntax", "generics", "trait impl", "closure", "? operator", "macro", "type", "method", "tuple", "if let / while let", "for over an iterator", "match pattern", "&mut of a value", "deref", "block as a value", "literal", "label or break value", "item", "unsafe", "path", "where clause", "mut self", "struct update", "enum variant with data", "uses refused", "uses external", "undeclared", "a used module's name, declared here too (use splices flat, see #7176)", "== of structs", "an owned String outside the out-buffer form", "a used module t27c cannot splice beside type_text (its own use stays unresolved, or its same clashes with type_text's, the gap specs/compiler/int_type_names.t27 records)", "empty slice with no known element type", "`!` of an operand whose type t27o cannot tell", "a vec! the fn changes (push, resize, assign into, &mut)", "a vec! the fn hands on or returns", "a vec! or array literal whose element type or length t27o cannot tell", "libm function not bit-exact", "slice contains on", "a compound shift of a place that calls something", "an operator, cast, borrow or deref t27 has no form for on an enum, struct, array or text", "a type named like one t27c renames (GF16, str, int ..)", "a float literal in a var of no type, f32 or f64 left to inference", "an assert or a comparison of texts in a spec with no test block (t27c writes the std import and assert helper with the first one)"]; + const codes : [45]usize = [R_SYNTAX, R_GENERIC, R_TRAIT, R_CLOSURE, R_TRY, R_MACRO, R_TYPE, R_METHOD, R_TUPLE, R_IFLET, R_ITER, R_PATTERN, R_MUTREF, R_DEREF, R_VALUE, R_LITERAL, R_LABEL, R_ITEM, R_UNSAFE, R_PATH, R_WHERE, R_SELFMUT, R_UPDATE, R_DATA, R_DEPENDS, R_EXTERNAL, R_UNDECL, R_CLASH, R_STRUCTEQ, R_STRUSE, R_SPLICE, R_EMPTY, R_NOTYPE, R_VECMUT, R_VECMOVE, R_VECTYPE, R_LIBM, R_SLICEGEN, R_SHIFTCALL, R_OPERAND, R_TYPENAME, R_FLOATLIT, R_NOPRELUDE, R_VECOUT, R_VECCALL]; + const why : [45]str = ["syntax", "generics", "trait impl", "closure", "? operator", "macro", "type", "method", "tuple", "if let / while let", "for over an iterator", "match pattern", "&mut of a value", "deref", "block as a value", "literal", "label or break value", "item", "unsafe", "path", "where clause", "mut self", "struct update", "enum variant with data", "uses refused", "uses external", "undeclared", "a used module's name, declared here too (use splices flat, see #7176)", "== of structs", "an owned String outside the out-buffer form", "a used module t27c cannot splice beside type_text (its own use stays unresolved, or its same clashes with type_text's, the gap specs/compiler/int_type_names.t27 records)", "empty slice with no known element type", "`!` of an operand whose type t27o cannot tell", "a vec! the fn changes (push, resize, assign into, &mut)", "a vec! the fn hands on or returns", "a vec! or array literal whose element type or length t27o cannot tell", "libm function not bit-exact", "slice contains on", "a compound shift of a place that calls something", "an operator, cast, borrow or deref t27 has no form for on an enum, struct, array or text", "a type named like one t27c renames (GF16, str, int ..)", "a float literal in a var of no type, f32 or f64 left to inference", "an assert or a comparison of texts in a spec with no test block (t27c writes the std import and assert helper with the first one)", "a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form)", "a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own)"]; var out : [512]u8 = undefined; var st : [64]usize = [0] ** 64; var w : [512]u8 = [0] ** 512; var i : usize = 0; - while (i < 43) { + while (i < 45) { clear(&st); st[ERR] = codes[i]; st[AT] = 3; @@ -18890,6 +19435,242 @@ test string_out_form_finds_the_string_locals_and_the_out_str_statements_write_th } } +test loc_in_matches_a_local_of_its_kind_inside_its_fns_range { + var st : [1024]usize = [0] ** 1024; + const src : str = "v a v b v"; + add_name(src, &st, K_VECLOC, 0, 1, 2, 7); + add_name(src, &st, K_STRLOC, 8, 9, 6, 9); + assert(st[NNAMES] == 2); + assert(loc_in(src, &st, K_VECLOC, 4, 5)); + assert(st[HIT] == NAMES); + assert(!loc_in(src, &st, K_VECLOC, 0, 1)); + assert(!loc_in(src, &st, K_VECLOC, 8, 9)); + assert(!loc_in(src, &st, K_VECLOC, 2, 3)); + assert(!loc_in(src, &st, K_STRLOC, 4, 5)); + assert(loc_in(src, &st, K_STRLOC, 8, 9)); + assert(strloc_in(src, &st, 8, 9)); + assert(!strloc_in(src, &st, 4, 5)); +} + +test vec_ret_elem_and_the_empty_vec_inits_read_the_signature_and_the_let { + const sigs : [10]str = ["fn f() -> Vec {", "fn f(a: u8) -> Vec {", "fn f() -> Vec {", "fn f() -> Vec {", "fn f() -> Vec {", "fn f() -> Vec<(u8, u8)> {", "fn f() -> Option> {", "fn f() -> Vec where T: X {", "fn f() -> u8 {", "fn f() {"]; + const want : [10]bool = [true, true, true, true, false, false, false, false, false, false]; + var i : usize = 0; + while (i < 10) { + const t : usize = vec_ret_elem(sigs[i], find_at(sigs[i], 0, B_LPAREN)); + assert((t < sigs[i].len) == want[i]); + if (want[i]) { assert(t == find_tok(sigs[i], 0, "Vec") + 4); } + i = i + 1; + } + assert(vec_ret_elem("fn f", 0) == 4); + const inits : [11]str = ["Vec::new();", "Vec::with_capacity(n * 2);", "vec![];", "vec![1, 2];", "Vec::from(a);", "Vec::new().len();", "foo();", "Vec::::new();", "Vec::new() }", "vec![0; 4];", "Vec::new(),"]; + const kind : [11]usize = [1, 2, 3, 0, 0, 0, 0, 0, 1, 0, 1]; + const cap : [11]bool = [false, true, false, false, true, false, false, false, false, false, false]; + var j : usize = 0; + while (j < 11) { + assert(vec_let_init(inits[j], 0) == kind[j]); + const o : usize = vec_init_open(inits[j], 0); + assert(vec_cap_arg(inits[j], o) == cap[j]); + if (kind[j] > 0) { assert(o < inits[j].len); } + j = j + 1; + } + assert(vec_init_open("foo();", 0) == 6); + assert(vec_init_open("Vec::::new();", 0) == 17); + assert(vec_init_open("Vec::new();", 0) == 8); + assert(vec_init_open("vec![];", 0) == 4); + assert(vec_init_open("", 0) == 0); +} + +test stmt_edge_and_vecout_mention_allow_the_pushes_and_the_hand_back_and_refuse_the_rest { + assert(stmt_edge("{ x", 0)); + assert(stmt_edge("; x", 0)); + assert(stmt_edge("} x", 0)); + assert(stmt_edge("=> x", 0)); + assert(!stmt_edge("let x", 0)); + assert(!stmt_edge(". x", 0)); + // the token before the mention, then the text from the mention on + const prev : [16]str = ["{", ";", "return", "{", "=>", ".", "{", "{", "{", "{", "&", "=", "=", "in", "(", ";"]; + const rest : [16]str = ["v.push(1);", "v.extend_from_slice(a);", "v;", "v }", "v,", "v.push(1);", "v: 1 }", "v.len() }", "v.insert(0, 1);", "v[0] = 1;", "v)", "v;", "v.push(1);", "v {", "v, 1)", "v.push(1) + 1;"]; + const code : [16]usize = [0, 0, 0, 0, 0, 0, 0, R_VECOUT, R_VECOUT, R_VECOUT, R_VECOUT, R_VECOUT, R_VECOUT, R_VECOUT, R_VECOUT, R_VECOUT]; + const tok : [16]str = ["", "", "", "", "", "", "", "len", "insert", "v", "v", "v", "push", "v", "v", "push"]; + var buf : [64]u8 = [0] ** 64; + var i : usize = 0; + while (i < 16) { + var bs : [64]usize = [0] ** 64; + puts(&bs, &buf, prev[i]); + puts(&bs, &buf, " "); + const q : usize = bs[W]; + puts(&bs, &buf, rest[i]); + const src : []const u8 = buf[0 .. bs[W]]; + var st : [64]usize = [0] ** 64; + vecout_mention(src, &st, q, tok_end(src, q), 0); + assert(st[ERR] == code[i]); + if (code[i] != 0) { assert(word_is(src, st[NLO], st[NHI], tok[i])); } + i = i + 1; + } +} + +test vec_out_form_finds_the_one_result_local_and_refuses_every_other_use { + const rows : [12]str = ["{ let mut v = Vec::new(); v.push(1); v }", "{ let mut v = Vec::new(); let mut w = Vec::new(); v }", "{ let mut v = Vec::new(); if v.is_empty() { return v; } v }", "{ let mut v = Vec::new(); return other; }", "{ let mut v = Vec::new(); if c { return Vec::new(); } v }", "{ let mut v = Vec::new(); if c { return vec![]; } v }", "{ other() }", "{ let v = vec![1]; v }", "{ let mut v = Vec::new(); v = x; v }", "{ let mut v = Vec::with_capacity(4); v.push(1); v }", "{ let mut v = Vec::new(); let v = 2; v }", "{ let mut v = Vec::new(); return v; }"]; + const ok : [12]bool = [true, false, false, false, true, true, false, false, false, true, false, true]; + const code : [12]usize = [0, R_VECOUT, R_VECOUT, R_VECOUT, 0, 0, 0, 0, R_VECOUT, 0, R_VECOUT, 0]; + const tok : [12]str = ["", "w", "is_empty", "other", "", "", "", "", "v", "", "v", ""]; + const names : [12]usize = [1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1]; + var buf : [128]u8 = [0] ** 128; + var i : usize = 0; + while (i < 12) { + var bs : [64]usize = [0] ** 64; + puts(&bs, &buf, "fn f() -> Vec "); + const lo : usize = bs[W] + 1; + puts(&bs, &buf, rows[i]); + const src : []const u8 = buf[0 .. bs[W]]; + var st : [1024]usize = [0] ** 1024; + assert(vec_out_form(src, &st, lo, src.len) == ok[i]); + assert(st[ERR] == code[i]); + if (code[i] != 0) { assert(word_is(src, st[NLO], st[NHI], tok[i])); } + assert(st[NNAMES] == names[i]); + if (ok[i]) { + assert(st[NAMES + 4] == K_VECLOC); + assert(st[NAMES + 2] == lo); + assert(st[NAMES + 3] == src.len); + assert(loc_in(src, &st, K_VECLOC, st[NAMES], st[NAMES] + 1)); + } + i = i + 1; + } +} + +test the_out_vec_writers_write_the_cursor_the_push_the_slice_copy_and_the_empty_hand_back { + var out : [512]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // a capacity is a hint: it is read for what reading it does, and dropped + const cp : str = "Vec::with_capacity(n * 2);"; + out_vec_cap(cp, &st, &out, vec_init_open(cp, 0)); + assert(out_is(st[W], &out, 0, "_ = n * 2;")); + assert(tok_is(cp, st[P], ")")); + // the cursor stands where the local is declared, with the capacity read after it + const lt : str = "let mut v = Vec::with_capacity(a.len());"; + clear(&st); + add_name(lt, &st, K_VECLOC, 8, 9, 0, lt.len); + assert(out_vec_let(lt, &st, &out)); + assert(out_is(st[W], &out, 0, "var t27o_w : usize = 0;\n_ = a.len;")); + assert(st[P] == lt.len); + const l1 : str = "let mut v = Vec::new();"; + clear(&st); + add_name(l1, &st, K_VECLOC, 8, 9, 0, l1.len); + assert(out_vec_let(l1, &st, &out)); + assert(out_is(st[W], &out, 0, "var t27o_w : usize = 0;")); + // another name, a let that is not an empty Vec, and a let nobody registered are the normal path + const l2 : str = "let mut w = Vec::new();"; + clear(&st); + add_name(l2, &st, K_VECLOC, 8, 9, 0, 4); + assert(!out_vec_let(l2, &st, &out)); + const l3 : str = "let mut v = 5;"; + clear(&st); + add_name(l3, &st, K_VECLOC, 8, 9, 0, l3.len); + assert(!out_vec_let(l3, &st, &out)); + assert(st[W] == 0); + // push: the element is evaluated once, stored while it fits, counted either way + const ps : str = "v.push(a + 1);"; + clear(&st); + out_vec_push(ps, &st, &out, 0, 7); + assert(out_is(st[W], &out, 0, "const t27o_e0 = a + 1;\nif (t27o_w < out.len) { out[t27o_w] = t27o_e0; }\nt27o_w = t27o_w + 1;")); + assert(st[P] == ps.len); + // extend_from_slice: the slice read once, then copied element by element + const es : str = "v.extend_from_slice(&s[1..]);"; + clear(&st); + out_vec_extend(es, &st, &out, 0, 20); + assert(out_is(st[W], &out, 0, "const t27o_x0 = s[1 .. s.len];\nvar t27o_k0 : usize = 0;\nwhile (t27o_k0 < t27o_x0.len) {\n if (t27o_w < out.len) { out[t27o_w] = t27o_x0[t27o_k0]; }\n t27o_w = t27o_w + 1;\n t27o_k0 = t27o_k0 + 1;\n}")); + assert(st[P] == es.len); + assert(st[IND] == 0); + // an empty Vec handed back counts 0 + const e1 : str = "Vec::new();"; + clear(&st); + out_vec_empty(e1, &st, &out, 0); + assert(out_is(st[W], &out, 0, "return 0;")); + assert(st[P] == e1.len); + const e2 : str = "Vec::with_capacity(n);"; + clear(&st); + out_vec_empty(e2, &st, &out, 0); + assert(out_is(st[W], &out, 0, "_ = n;\nreturn 0;")); + const e3 : str = "vec![],"; + clear(&st); + out_vec_empty(e3, &st, &out, 0); + assert(out_is(st[W], &out, 0, "return 0;")); + assert(st[P] == e3.len); +} + +test vec_tail_other_leaves_blocks_jumps_and_macros_to_the_normal_path { + const rows : [11]str = ["if c { v } else { w }", "match x { A => v }", "loop { }", "while c { }", "return v;", "{ v }", "panic!(\"x\")", "vec![]", "v", "foo()", "Vec::new()"]; + const want : [11]bool = [true, true, true, true, true, true, true, false, false, false, false]; + var i : usize = 0; + while (i < 11) { + assert(vec_tail_other(rows[i], 0, tok_end(rows[i], 0)) == want[i]); + i = i + 1; + } +} + +test out_vec_stmt_and_out_vec_return_write_the_statements_of_the_result_local { + var out : [512]u8 = undefined; + const rows : [14]str = ["v.push(1); x", "v.extend_from_slice(a); x", "v; x", "v }", "v }", "v.len() }", "Vec::new() }", "Vec::new() }", "foo() }", "if c { v } else { w }", "let mut v = Vec::new(); v", "x = 1; v", "vec![1] }", "let w = 5; v"]; + const mode : [14]u8 = [M_STMT, M_STMT, M_RET, M_RET, M_STMT, M_STMT, M_RET, M_STMT, M_RET, M_RET, M_STMT, M_STMT, M_RET, M_STMT]; + const nv : [14]usize = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 0, 0]; + const taken : [14]bool = [true, true, true, true, true, true, true, false, true, false, true, false, true, false]; + const code : [14]usize = [0, 0, 0, 0, 0, R_VECOUT, 0, 0, R_VECOUT, 0, 0, 0, R_VECOUT, 0]; + const text : [14]str = ["const t27o_e0 = 1;\nif (t27o_w < out.len) { out[t27o_w] = t27o_e0; }\nt27o_w = t27o_w + 1;", "const t27o_x0 = a;\nvar t27o_k0 : usize = 0;\nwhile (t27o_k0 < t27o_x0.len) {\n if (t27o_w < out.len) { out[t27o_w] = t27o_x0[t27o_k0]; }\n t27o_w = t27o_w + 1;\n t27o_k0 = t27o_k0 + 1;\n}", "return t27o_w;", "return t27o_w;", "", "", "return 0;", "", "", "", "var t27o_w : usize = 0;", "", "", ""]; + const at : [14]usize = [11, 24, 3, 2, 2, 0, 11, 0, 0, 0, 24, 0, 0, 0]; + var i : usize = 0; + while (i < 14) { + var st : [1024]usize = [0] ** 1024; + add_name(rows[i], &st, K_VECLOC, nv[i], nv[i] + 1, 0, rows[i].len); + st[P] = 0; + assert(out_vec_stmt(rows[i], &st, &out, mode[i]) == taken[i]); + assert(st[ERR] == code[i]); + if (taken[i] and code[i] == 0) { + assert(out_is(st[W], &out, 0, text[i])); + assert(st[P] == at[i]); + } + if (!taken[i]) { assert(st[W] == 0); } + i = i + 1; + } + const rr : [7]str = ["return v;", "return v }", "return Vec::new();", "return;", "return 5;", "return vec![];", "return w;"]; + const rt : [7]bool = [true, true, true, false, true, true, true]; + const rc : [7]usize = [0, 0, 0, 0, R_VECOUT, 0, R_VECOUT]; + const rx : [7]str = ["return t27o_w;", "return t27o_w;", "return 0;", "", "", "return 0;", ""]; + const rp : [7]usize = [9, 9, 18, 0, 0, 14, 0]; + const rn : [7]usize = [7, 7, 0, 0, 0, 0, 0]; + var j : usize = 0; + while (j < 7) { + var st : [1024]usize = [0] ** 1024; + add_name(rr[j], &st, K_VECLOC, rn[j], rn[j] + 1, 0, rr[j].len); + st[P] = 0; + assert(out_vec_return(rr[j], &st, &out) == rt[j]); + assert(st[ERR] == rc[j]); + if (rt[j] and rc[j] == 0) { + assert(out_is(st[W], &out, 0, rx[j])); + assert(st[P] == rp[j]); + } + if (!rt[j]) { assert(st[P] == 0); } + j = j + 1; + } +} + +test put_call_buf_spells_the_out_buffer_and_put_vec_record_records_the_prefix { + var out : [1024]u8 = undefined; + var st : [64]usize = [0] ** 64; + const f : str = "fn f(a: u8, b: bool, c: &str, d: [u8; 2], e: i16) { }"; + put_call_buf(f, &st, &out, 3, 4, 1, V_UNSIGNED, 8, 0, 0, 0, 0, 0, 0, 0, 0, 1, false, false, "[0 .. 0]"); + assert(out_is(st[W], &out, 0, "f(1, t27o_o1[0 .. 0])")); + clear(&st); + put_call_buf(f, &st, &out, 3, 4, 1, V_UNSIGNED, 8, 0, 0, 0, 0, 0, 0, 0, 0, 1, false, false, ""); + assert(out_is(st[W], &out, 0, "f(1)")); + clear(&st); + put_vec_record("u8", &st, &out, 0, 0, false, false); + assert(out_is(st[W], &out, 0, "if r.len() <= 256 { let mut s = format!(\" var t27o_o0 : [256]u8 = undefined;\\n const t27o_n0 = {};\\n assert(t27o_n0 == {});\", c, r.len()); for (i, x) in r.iter().enumerate() { s.push_str(&format!(\"\\n assert(t27o_o0[{}] == {:?});\", i, x)); } s.push_str(&format!(\"\\n const t27o_m0 = {};\\n assert(t27o_m0 == {});\", z, r.len())); v.push(s); } ")); + clear(&st); + put_vec_record("f64", &st, &out, 3, 0, true, true); + assert(out_is(st[W], &out, 0, "if r.len() <= 256 && r.iter().all(|x| x.is_finite()) { let mut s = format!(\"{} var t27o_o3 : [256]f64 = undefined;\\n const t27o_n3 = {};\\n assert(t27o_n3 == {});\", d, c, r.len()); for (i, x) in r.iter().enumerate() { s.push_str(&format!(\"\\n assert(t27o_o3[{}] == {:?});\", i, x)); } s.push_str(&format!(\"\\n const t27o_m3 = {};\\n assert(t27o_m3 == {});\", z, r.len())); v.push(s); } ")); +} + test out_str_return_and_out_split_join_write_the_hand_back_of_a_string { var out : [512]u8 = undefined; var st : [1024]usize = [0] ** 1024; From d26c6d6b1bdd2a9577f244b375825a43cbc5e826 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 22:10:18 +0700 Subject: [PATCH 2/5] t27o: the oracle runs a Vec-result fn on a guarded thread -- 3 s and 16 MiB, a Vec that never stops growing is skipped; a fn with no parameter takes the buffer without a comma; a body that says out for something else is not the form (Closes #11073) Found by the first replay of --check on a real file: crates/trios-phd regex_lite_global (s[i..].find(needle) with the sampled needle "") pushes for ever, and its harness ate the memory of the machine. A Vec result is an input-dependent allocation, so the harness now carries t27o_guard (VEC_GUARD) when some fn is in the Vec out-buffer form: the original runs on a thread with 3 seconds and a counting global allocator that parks the thread at 16 MiB; the sample is skipped like one on which Rust panics, two such samples end the sampling of that fn, and the cost of a skipped sample is a bounded 16 MiB. The same replay found padding_length_distribution_guard uniform_lengths() written as uniform_lengths(, t27o_o0[0 .. 256]) -- the comma was written before the buffer even with no argument (the String form had the same latent defect). And a body that says out for something else (not the Vec local, not a field) would meet the buffer parameter of that name: it is not the form, the old refusal stands. Tests first (each failed before its fix): a zero-parameter call, the guard in the harness and its absence for a String result, has_vecfn, three out-name rows. Co-Authored-By: Claude Sonnet 5.5 --- gen/rust/tools/t27o/rust_convert.rs | 37 ++++++++++++++-- specs/tools/t27o/rust_convert.t27 | 67 +++++++++++++++++++++++------ 2 files changed, 89 insertions(+), 15 deletions(-) diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index 7b9daa357f..5ee660cd18 100644 --- a/gen/rust/tools/t27o/rust_convert.rs +++ b/gen/rust/tools/t27o/rust_convert.rs @@ -9145,7 +9145,10 @@ pub fn put_call_buf(src: &[u8], st: &mut [usize], out: &mut [u8], na: usize, nb: put_arg(src, st, out, k4, t4, (j + 4), j, 4, rust); } if (buf.len() > 0) { - puts(st, out, b", t27o_o"); + if (n > 0) { + puts(st, out, b", "); + } + puts(st, out, b"t27o_o"); put_num(st, out, j); puts(st, out, buf); } @@ -9582,9 +9585,16 @@ pub fn vectors(src: &[u8], st: &mut [usize], out: &mut [u8], na: usize, nb: usiz nv = slen(src, st, k4, t4); } puts(st, out, b" {\n let mut v: Vec = Vec::new();\n"); + if (rk == V_OUTVEC) { + puts(st, out, b" t27o_guard::clear();\n"); + } let mut j: usize = 0; while (j < nv) { - puts(st, out, b" if let Ok(r) = std::panic::catch_unwind(|| "); + if (rk == V_OUTVEC) { + puts(st, out, b" if let Some(r) = t27o_guard::guard(|| "); + } else { + puts(st, out, b" if let Ok(r) = std::panic::catch_unwind(|| "); + } put_call(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, true, false); puts(st, out, b") { let c = \""); put_call(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, false, ((wrapk == 0) && ((rk == V_STRING) || (rk == V_OUTVEC)))); @@ -10752,6 +10762,20 @@ pub fn out_str_return(src: &[u8], st: &mut [usize], out: &mut [u8]) -> bool { return true; } +pub const VEC_GUARD: &'static str = "// t27o: the original of a fn that returns a Vec runs on a thread with 3 s and 16 MiB: a call that does not stop, or keeps growing its Vec, is skipped, not run to the end of the machine\nmod t27o_guard {\n use std::alloc::{GlobalAlloc, Layout, System};\n use std::cell::Cell;\n use std::sync::atomic::{AtomicUsize, Ordering};\n thread_local! { static BYTES: Cell = const { Cell::new(usize::MAX) }; }\n static HUNG: AtomicUsize = AtomicUsize::new(0);\n fn charge(n: usize) { let _ = BYTES.try_with(|g| { let c = g.get(); if c != usize::MAX { g.set(c + n); if c + n > (16 << 20) { loop { std::thread::sleep(std::time::Duration::from_secs(3600)); } } } }); }\n fn refund(n: usize) { let _ = BYTES.try_with(|g| { let c = g.get(); if c != usize::MAX { g.set(c.saturating_sub(n)); } }); }\n struct Guarded;\n unsafe impl GlobalAlloc for Guarded {\n unsafe fn alloc(&self, l: Layout) -> *mut u8 { charge(l.size()); System.alloc(l) }\n unsafe fn dealloc(&self, p: *mut u8, l: Layout) { refund(l.size()); System.dealloc(p, l) }\n unsafe fn realloc(&self, p: *mut u8, l: Layout, n: usize) -> *mut u8 { charge(n); refund(l.size()); System.realloc(p, l, n) }\n }\n #[global_allocator]\n static ALLOC: Guarded = Guarded;\n pub fn clear() { HUNG.store(0, Ordering::SeqCst); }\n pub fn guard(f: impl FnOnce() -> T + Send + 'static) -> Option {\n if HUNG.load(Ordering::SeqCst) >= 2 { return None; }\n let (tx, rx) = std::sync::mpsc::channel();\n std::thread::spawn(move || { BYTES.with(|g| g.set(0)); let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)); let _ = tx.send(r.ok()); });\n match rx.recv_timeout(std::time::Duration::from_secs(3)) { Ok(r) => r, Err(_) => { HUNG.fetch_add(1, Ordering::SeqCst); None } }\n }\n}\n\n"; + +pub fn has_vecfn(st: impl AsRef<[usize]>) -> bool { + let st: &[usize] = st.as_ref(); + let mut k: usize = 0; + while (k < st[(NNAMES) as usize]) { + if (st[(((NAMES + (k * NAME_SLOTS)) + 4)) as usize] == K_VECFN) { + return true; + } + k = (k + 1); + } + return false; +} + pub fn vec_ret_elem(src: &[u8], pn: usize) -> usize { if !(tok_is(src, pn, b"(")) { return src.len(); @@ -10882,12 +10906,16 @@ pub fn vec_out_form(src: &[u8], st: &mut [usize], lo: usize, hi: usize) -> bool return false; } let mut p0: usize = src.len(); + let mut clash: bool = false; q = lo; while ((q < hi) && (st[(ERR) as usize] == 0)) { let e: usize = tok_end(src, q); if ((q != na) && same_text(src, q, e, na, nb)) { vecout_mention(src, st, q, e, p0); } + if (((word_is(src, q, e, b"out") && !(same_text(src, q, e, na, nb))) && !(tok_is(src, p0, b"."))) && !(tok_is(src, skip_ws(src, e), b":"))) { + clash = true; + } if word_is(src, q, e, b"return") { let r: usize = skip_ws(src, e); let back: bool = (((r < src.len()) && is_alpha(src[(r) as usize])) && same_text(src, r, tok_end(src, r), na, nb)); @@ -10898,7 +10926,7 @@ pub fn vec_out_form(src: &[u8], st: &mut [usize], lo: usize, hi: usize) -> bool p0 = q; q = skip_ws(src, e); } - if (st[(ERR) as usize] != 0) { + if ((st[(ERR) as usize] != 0) || clash) { return false; } add_name(src, st, K_VECLOC, na, nb, lo, hi); @@ -12304,6 +12332,9 @@ pub fn oracle(src: &[u8], out: &mut [u8], st: &mut [usize]) -> usize { st[(NNAMES) as usize] = n; st[(MODE) as usize] = M_SOURCE; puts(st, out, b"// t27o oracle harness (specs/tools/t27o/rust_convert.t27): the converted items, run to record\n// their behaviour as t27 tests.\n#![allow(warnings)]\n\n"); + if has_vecfn(st) { + puts(st, out, VEC_GUARD.as_bytes()); + } st[(P) as usize] = skip_ws(src, 0); items(src, st, out, false); let w: usize = st[(W) as usize]; diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index 7949cee092..c627382d35 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -7883,8 +7883,9 @@ pub fn put_call_buf(src: []const u8, st: []usize, out: []u8, na: usize, nb: usiz put_arg(src, st, out, k4, t4, j + 4, j, 4, rust); } if (buf.len > 0) { - // the t27 side of an owned-String or Vec result: the buffer the test owns - puts(st, out, ", t27o_o"); + // the t27 side of an owned-String or Vec result: the buffer the test owns (the only argument of a fn with none) + if (n > 0) { puts(st, out, ", "); } + puts(st, out, "t27o_o"); put_num(st, out, j); puts(st, out, buf); } @@ -8273,9 +8274,11 @@ pub fn vectors(src: []const u8, st: []usize, out: []u8, na: usize, nb: usize, pn if (n > 3 and slen(src, st, k3, t3) > nv) { nv = slen(src, st, k3, t3); } if (n > 4 and slen(src, st, k4, t4) > nv) { nv = slen(src, st, k4, t4); } puts(st, out, " {\n let mut v: Vec = Vec::new();\n"); + if (rk == V_OUTVEC) { puts(st, out, " t27o_guard::clear();\n"); } var j : usize = 0; while (j < nv) { - puts(st, out, " if let Ok(r) = std::panic::catch_unwind(|| "); + // a Vec result is run through t27o_guard (VEC_GUARD): the original may not stop growing it on a sample + if (rk == V_OUTVEC) { puts(st, out, " if let Some(r) = t27o_guard::guard(|| "); } else { puts(st, out, " if let Ok(r) = std::panic::catch_unwind(|| "); } put_call(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, true, false); puts(st, out, ") { let c = \""); put_call(src, st, out, na, nb, n, k0, t0, k1, t1, k2, t2, k3, t3, k4, t4, j, selfp, false, wrapk == 0 and (rk == V_STRING or rk == V_OUTVEC)); @@ -9262,6 +9265,23 @@ pub fn out_str_return(src: []const u8, st: []usize, out: []u8) -> bool { // ---------------------------------------------------------------- the Vec out-buffer form (#11073) +/// The Rust the oracle harness carries when a fn is in the Vec out-buffer form (#11073): the original of a Vec-result fn +/// runs on a thread that has 3 s and 16 MiB (a counting global allocator parks a thread that asks for more), so a sample +/// on which Rust never stops growing its Vec -- `s[i..].find("")` pushing for ever -- is skipped, as one on which Rust +/// panics is, and costs a bounded 16 MiB; two such samples of a fn end its sampling (`clear` starts the next fn over). +pub const VEC_GUARD : str = "// t27o: the original of a fn that returns a Vec runs on a thread with 3 s and 16 MiB: a call that does not stop, or keeps growing its Vec, is skipped, not run to the end of the machine\nmod t27o_guard {\n use std::alloc::{GlobalAlloc, Layout, System};\n use std::cell::Cell;\n use std::sync::atomic::{AtomicUsize, Ordering};\n thread_local! { static BYTES: Cell = const { Cell::new(usize::MAX) }; }\n static HUNG: AtomicUsize = AtomicUsize::new(0);\n fn charge(n: usize) { let _ = BYTES.try_with(|g| { let c = g.get(); if c != usize::MAX { g.set(c + n); if c + n > (16 << 20) { loop { std::thread::sleep(std::time::Duration::from_secs(3600)); } } } }); }\n fn refund(n: usize) { let _ = BYTES.try_with(|g| { let c = g.get(); if c != usize::MAX { g.set(c.saturating_sub(n)); } }); }\n struct Guarded;\n unsafe impl GlobalAlloc for Guarded {\n unsafe fn alloc(&self, l: Layout) -> *mut u8 { charge(l.size()); System.alloc(l) }\n unsafe fn dealloc(&self, p: *mut u8, l: Layout) { refund(l.size()); System.dealloc(p, l) }\n unsafe fn realloc(&self, p: *mut u8, l: Layout, n: usize) -> *mut u8 { charge(n); refund(l.size()); System.realloc(p, l, n) }\n }\n #[global_allocator]\n static ALLOC: Guarded = Guarded;\n pub fn clear() { HUNG.store(0, Ordering::SeqCst); }\n pub fn guard(f: impl FnOnce() -> T + Send + 'static) -> Option {\n if HUNG.load(Ordering::SeqCst) >= 2 { return None; }\n let (tx, rx) = std::sync::mpsc::channel();\n std::thread::spawn(move || { BYTES.with(|g| g.set(0)); let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)); let _ = tx.send(r.ok()); });\n match rx.recv_timeout(std::time::Duration::from_secs(3)) { Ok(r) => r, Err(_) => { HUNG.fetch_add(1, Ordering::SeqCst); None } }\n }\n}\n\n"; + +/// Is some fn of the file in the Vec out-buffer form (a K_VECFN name in the table)? +pub fn has_vecfn(st: []usize) -> bool { + var k : usize = 0; + while (k < st[NNAMES]) { + if (st[NAMES + k * NAME_SLOTS + 4] == K_VECFN) { return true; } + k = k + 1; + } + return false; +} + + /// The element type token T of the result `-> Vec {` of the fn whose parameters open at src[pn]: src.len unless the /// result is exactly that and T is an integer, float or bool type (the types the oracle records element by element). pub fn vec_ret_elem(src: []const u8, pn: usize) -> usize { @@ -9373,10 +9393,13 @@ pub fn vec_out_form(src: []const u8, st: []usize, lo: usize, hi: usize) -> bool } if (lets == 0 or st[ERR] != 0) { return false; } var p0 : usize = src.len; + var clash : bool = false; q = lo; while (q < hi and st[ERR] == 0) { const e : usize = tok_end(src, q); if (q != na and same_text(src, q, e, na, nb)) { vecout_mention(src, st, q, e, p0); } + // a name `out` of its own (not a field, not a label) would meet the buffer parameter of that name + if (word_is(src, q, e, "out") and !same_text(src, q, e, na, nb) and !tok_is(src, p0, ".") and !tok_is(src, skip_ws(src, e), ":")) { clash = true; } if (word_is(src, q, e, "return")) { const r : usize = skip_ws(src, e); const back : bool = r < src.len and is_alpha(src[r]) and same_text(src, r, tok_end(src, r), na, nb); @@ -9385,7 +9408,7 @@ pub fn vec_out_form(src: []const u8, st: []usize, lo: usize, hi: usize) -> bool p0 = q; q = skip_ws(src, e); } - if (st[ERR] != 0) { return false; } + if (st[ERR] != 0 or clash) { return false; } add_name(src, st, K_VECLOC, na, nb, lo, hi); return true; } @@ -10624,6 +10647,7 @@ pub fn oracle(src: []const u8, out: []u8, st: []usize) -> usize { st[NNAMES] = n; st[MODE] = M_SOURCE; puts(st, out, "// t27o oracle harness (specs/tools/t27o/rust_convert.t27): the converted items, run to record\n// their behaviour as t27 tests.\n#![allow(warnings)]\n\n"); + if (has_vecfn(st)) { puts(st, out, VEC_GUARD); } st[P] = skip_ws(src, 0); items(src, st, out, false); const w : usize = st[W]; @@ -11011,7 +11035,13 @@ test the_oracle_records_the_written_prefix_of_a_vec_result_in_a_full_buffer_and_ // compares it with the length of the Vec, then every element of the written prefix with the Vec's. A second call with a // zero-length buffer must answer the same count: the elements past the buffer are counted, not stored. const n : usize = oracle("fn evens(n: u32) -> Vec { let mut v = Vec::new(); let mut i = 0; while i < n { v.push(i * 2); i += 1; } v }", &out, &st); - assert(out_text_has(&out, n, "catch_unwind(|| evens(0))")); + // the call runs through t27o_guard: a Vec the original never stops growing is skipped, not run to the end of the machine + assert(out_text_has(&out, n, "if let Some(r) = t27o_guard::guard(|| evens(0)) {")); + assert(!out_text_has(&out, n, "catch_unwind(|| evens(0))")); + assert(out_text_has(&out, n, "let mut v: Vec = Vec::new();\n t27o_guard::clear();")); + assert(out_text_has(&out, n, "mod t27o_guard {")); + assert(out_text_has(&out, n, "#[global_allocator]")); + assert(out_text_has(&out, n, "rx.recv_timeout(std::time::Duration::from_secs(3))")); assert(out_text_has(&out, n, "let c = \"evens(0, t27o_o0[0 .. 256])\"; let z = \"evens(0, t27o_o0[0 .. 0])\";")); assert(out_text_has(&out, n, "if r.len() <= 256 {")); assert(out_text_has(&out, n, "var t27o_o0 : [256]u32 = undefined;\\n const t27o_n0 = {};\\n assert(t27o_n0 == {});")); @@ -11211,6 +11241,7 @@ test the_oracle_records_an_owned_string_result_byte_for_byte { // the Rust side runs the original; the recorded call names the out-buffer form, and the test it // prints owns a 256-byte buffer, takes the bytes written and compares them with the String assert(out_text_has(&out, n, "catch_unwind(|| tag(0))")); + assert(!out_text_has(&out, n, "t27o_guard")); assert(out_text_has(&out, n, "\"tag(0, t27o_o0[0 .. 256])\"")); assert(out_text_has(&out, n, "var t27o_o0 : [256]u8 = undefined;\\n const t27o_n0 = {};\\n assert(type_text::same(t27o_o0[0 .. t27o_n0], {:?}));")); assert(out_text_has(&out, n, "test oracle_tag {")); @@ -19450,6 +19481,10 @@ test loc_in_matches_a_local_of_its_kind_inside_its_fns_range { assert(loc_in(src, &st, K_STRLOC, 8, 9)); assert(strloc_in(src, &st, 8, 9)); assert(!strloc_in(src, &st, 4, 5)); + // has_vecfn: some fn of the file is in the Vec out-buffer form + assert(!has_vecfn(&st)); + add_name(src, &st, K_VECFN, 2, 3, 0, 0); + assert(has_vecfn(&st)); } test vec_ret_elem_and_the_empty_vec_inits_read_the_signature_and_the_let { @@ -19511,14 +19546,15 @@ test stmt_edge_and_vecout_mention_allow_the_pushes_and_the_hand_back_and_refuse_ } test vec_out_form_finds_the_one_result_local_and_refuses_every_other_use { - const rows : [12]str = ["{ let mut v = Vec::new(); v.push(1); v }", "{ let mut v = Vec::new(); let mut w = Vec::new(); v }", "{ let mut v = Vec::new(); if v.is_empty() { return v; } v }", "{ let mut v = Vec::new(); return other; }", "{ let mut v = Vec::new(); if c { return Vec::new(); } v }", "{ let mut v = Vec::new(); if c { return vec![]; } v }", "{ other() }", "{ let v = vec![1]; v }", "{ let mut v = Vec::new(); v = x; v }", "{ let mut v = Vec::with_capacity(4); v.push(1); v }", "{ let mut v = Vec::new(); let v = 2; v }", "{ let mut v = Vec::new(); return v; }"]; - const ok : [12]bool = [true, false, false, false, true, true, false, false, false, true, false, true]; - const code : [12]usize = [0, R_VECOUT, R_VECOUT, R_VECOUT, 0, 0, 0, 0, R_VECOUT, 0, R_VECOUT, 0]; - const tok : [12]str = ["", "w", "is_empty", "other", "", "", "", "", "v", "", "v", ""]; - const names : [12]usize = [1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1]; + const rows : [15]str = ["{ let mut v = Vec::new(); v.push(1); v }", "{ let mut v = Vec::new(); let mut w = Vec::new(); v }", "{ let mut v = Vec::new(); if v.is_empty() { return v; } v }", "{ let mut v = Vec::new(); return other; }", "{ let mut v = Vec::new(); if c { return Vec::new(); } v }", "{ let mut v = Vec::new(); if c { return vec![]; } v }", "{ other() }", "{ let v = vec![1]; v }", "{ let mut v = Vec::new(); v = x; v }", "{ let mut v = Vec::with_capacity(4); v.push(1); v }", "{ let mut v = Vec::new(); let v = 2; v }", "{ let mut v = Vec::new(); return v; }", "{ let mut v = Vec::new(); let out = 3; v.push(out); v }", "{ let mut v = Vec::new(); v.push(x.out); v }", "{ let mut out = Vec::new(); out.push(1); out }"]; + // a body that already says `out` for something else cannot take the parameter of that name: not the form, the old refusal stands + const ok : [15]bool = [true, false, false, false, true, true, false, false, false, true, false, true, false, true, true]; + const code : [15]usize = [0, R_VECOUT, R_VECOUT, R_VECOUT, 0, 0, 0, 0, R_VECOUT, 0, R_VECOUT, 0, 0, 0, 0]; + const tok : [15]str = ["", "w", "is_empty", "other", "", "", "", "", "v", "", "v", "", "", "", ""]; + const names : [15]usize = [1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1]; var buf : [128]u8 = [0] ** 128; var i : usize = 0; - while (i < 12) { + while (i < 15) { var bs : [64]usize = [0] ** 64; puts(&bs, &buf, "fn f() -> Vec "); const lo : usize = bs[W] + 1; @@ -19533,7 +19569,7 @@ test vec_out_form_finds_the_one_result_local_and_refuses_every_other_use { assert(st[NAMES + 4] == K_VECLOC); assert(st[NAMES + 2] == lo); assert(st[NAMES + 3] == src.len); - assert(loc_in(src, &st, K_VECLOC, st[NAMES], st[NAMES] + 1)); + assert(loc_in(src, &st, K_VECLOC, st[NAMES], st[NAMES + 1])); } i = i + 1; } @@ -19663,6 +19699,13 @@ test put_call_buf_spells_the_out_buffer_and_put_vec_record_records_the_prefix { clear(&st); put_call_buf(f, &st, &out, 3, 4, 1, V_UNSIGNED, 8, 0, 0, 0, 0, 0, 0, 0, 0, 1, false, false, ""); assert(out_is(st[W], &out, 0, "f(1)")); + // a fn with no parameter takes the buffer as its only argument (no comma before it), String result or Vec result + clear(&st); + put_call_buf(f, &st, &out, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, false, false, "[0 .. 256]"); + assert(out_is(st[W], &out, 0, "f(t27o_o2[0 .. 256])")); + clear(&st); + put_call(f, &st, &out, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, false, false, true); + assert(out_is(st[W], &out, 0, "f(t27o_o1[0 .. 256])")); clear(&st); put_vec_record("u8", &st, &out, 0, 0, false, false); assert(out_is(st[W], &out, 0, "if r.len() <= 256 { let mut s = format!(\" var t27o_o0 : [256]u8 = undefined;\\n const t27o_n0 = {};\\n assert(t27o_n0 == {});\", c, r.len()); for (i, x) in r.iter().enumerate() { s.push_str(&format!(\"\\n assert(t27o_o0[{}] == {:?});\", i, x)); } s.push_str(&format!(\"\\n const t27o_m0 = {};\\n assert(t27o_m0 == {});\", z, r.len())); v.push(s); } ")); From abe5f281368d727a0b7d5b44890b0cea8f926a5f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 22:12:10 +0700 Subject: [PATCH 3/5] t27o: has_vecfn leaves the slot in HIT like named, so the generated Rust takes the state by &mut and compiles (Closes #11073) The read-only form of the previous commit made t27c gen-rust type the state as impl AsRef<[usize]>, which moves the &mut [usize] of oracle() (E0382). The generated file is now compiled with rustc (the driver of cmd_t27o's calls) before every commit. Co-Authored-By: Claude Sonnet 5.5 --- gen/rust/tools/t27o/rust_convert.rs | 8 +++++--- specs/tools/t27o/rust_convert.t27 | 13 ++++++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index 5ee660cd18..44974d8388 100644 --- a/gen/rust/tools/t27o/rust_convert.rs +++ b/gen/rust/tools/t27o/rust_convert.rs @@ -10764,15 +10764,17 @@ pub fn out_str_return(src: &[u8], st: &mut [usize], out: &mut [u8]) -> bool { pub const VEC_GUARD: &'static str = "// t27o: the original of a fn that returns a Vec runs on a thread with 3 s and 16 MiB: a call that does not stop, or keeps growing its Vec, is skipped, not run to the end of the machine\nmod t27o_guard {\n use std::alloc::{GlobalAlloc, Layout, System};\n use std::cell::Cell;\n use std::sync::atomic::{AtomicUsize, Ordering};\n thread_local! { static BYTES: Cell = const { Cell::new(usize::MAX) }; }\n static HUNG: AtomicUsize = AtomicUsize::new(0);\n fn charge(n: usize) { let _ = BYTES.try_with(|g| { let c = g.get(); if c != usize::MAX { g.set(c + n); if c + n > (16 << 20) { loop { std::thread::sleep(std::time::Duration::from_secs(3600)); } } } }); }\n fn refund(n: usize) { let _ = BYTES.try_with(|g| { let c = g.get(); if c != usize::MAX { g.set(c.saturating_sub(n)); } }); }\n struct Guarded;\n unsafe impl GlobalAlloc for Guarded {\n unsafe fn alloc(&self, l: Layout) -> *mut u8 { charge(l.size()); System.alloc(l) }\n unsafe fn dealloc(&self, p: *mut u8, l: Layout) { refund(l.size()); System.dealloc(p, l) }\n unsafe fn realloc(&self, p: *mut u8, l: Layout, n: usize) -> *mut u8 { charge(n); refund(l.size()); System.realloc(p, l, n) }\n }\n #[global_allocator]\n static ALLOC: Guarded = Guarded;\n pub fn clear() { HUNG.store(0, Ordering::SeqCst); }\n pub fn guard(f: impl FnOnce() -> T + Send + 'static) -> Option {\n if HUNG.load(Ordering::SeqCst) >= 2 { return None; }\n let (tx, rx) = std::sync::mpsc::channel();\n std::thread::spawn(move || { BYTES.with(|g| g.set(0)); let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)); let _ = tx.send(r.ok()); });\n match rx.recv_timeout(std::time::Duration::from_secs(3)) { Ok(r) => r, Err(_) => { HUNG.fetch_add(1, Ordering::SeqCst); None } }\n }\n}\n\n"; -pub fn has_vecfn(st: impl AsRef<[usize]>) -> bool { - let st: &[usize] = st.as_ref(); +pub fn has_vecfn(st: &mut [usize]) -> bool { let mut k: usize = 0; while (k < st[(NNAMES) as usize]) { - if (st[(((NAMES + (k * NAME_SLOTS)) + 4)) as usize] == K_VECFN) { + let s: usize = (NAMES + (k * NAME_SLOTS)); + if (st[((s + 4)) as usize] == K_VECFN) { + st[(HIT) as usize] = s; return true; } k = (k + 1); } + st[(HIT) as usize] = 0; return false; } diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index c627382d35..425f7fd31a 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -9271,17 +9271,22 @@ pub fn out_str_return(src: []const u8, st: []usize, out: []u8) -> bool { /// panics is, and costs a bounded 16 MiB; two such samples of a fn end its sampling (`clear` starts the next fn over). pub const VEC_GUARD : str = "// t27o: the original of a fn that returns a Vec runs on a thread with 3 s and 16 MiB: a call that does not stop, or keeps growing its Vec, is skipped, not run to the end of the machine\nmod t27o_guard {\n use std::alloc::{GlobalAlloc, Layout, System};\n use std::cell::Cell;\n use std::sync::atomic::{AtomicUsize, Ordering};\n thread_local! { static BYTES: Cell = const { Cell::new(usize::MAX) }; }\n static HUNG: AtomicUsize = AtomicUsize::new(0);\n fn charge(n: usize) { let _ = BYTES.try_with(|g| { let c = g.get(); if c != usize::MAX { g.set(c + n); if c + n > (16 << 20) { loop { std::thread::sleep(std::time::Duration::from_secs(3600)); } } } }); }\n fn refund(n: usize) { let _ = BYTES.try_with(|g| { let c = g.get(); if c != usize::MAX { g.set(c.saturating_sub(n)); } }); }\n struct Guarded;\n unsafe impl GlobalAlloc for Guarded {\n unsafe fn alloc(&self, l: Layout) -> *mut u8 { charge(l.size()); System.alloc(l) }\n unsafe fn dealloc(&self, p: *mut u8, l: Layout) { refund(l.size()); System.dealloc(p, l) }\n unsafe fn realloc(&self, p: *mut u8, l: Layout, n: usize) -> *mut u8 { charge(n); refund(l.size()); System.realloc(p, l, n) }\n }\n #[global_allocator]\n static ALLOC: Guarded = Guarded;\n pub fn clear() { HUNG.store(0, Ordering::SeqCst); }\n pub fn guard(f: impl FnOnce() -> T + Send + 'static) -> Option {\n if HUNG.load(Ordering::SeqCst) >= 2 { return None; }\n let (tx, rx) = std::sync::mpsc::channel();\n std::thread::spawn(move || { BYTES.with(|g| g.set(0)); let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)); let _ = tx.send(r.ok()); });\n match rx.recv_timeout(std::time::Duration::from_secs(3)) { Ok(r) => r, Err(_) => { HUNG.fetch_add(1, Ordering::SeqCst); None } }\n }\n}\n\n"; -/// Is some fn of the file in the Vec out-buffer form (a K_VECFN name in the table)? +/// Is some fn of the file in the Vec out-buffer form (a K_VECFN name in the table)? The slot of the first goes to +/// st[HIT] (0 when none), the way `named` leaves it. pub fn has_vecfn(st: []usize) -> bool { var k : usize = 0; while (k < st[NNAMES]) { - if (st[NAMES + k * NAME_SLOTS + 4] == K_VECFN) { return true; } + const s : usize = NAMES + k * NAME_SLOTS; + if (st[s + 4] == K_VECFN) { + st[HIT] = s; + return true; + } k = k + 1; } + st[HIT] = 0; return false; } - /// The element type token T of the result `-> Vec {` of the fn whose parameters open at src[pn]: src.len unless the /// result is exactly that and T is an integer, float or bool type (the types the oracle records element by element). pub fn vec_ret_elem(src: []const u8, pn: usize) -> usize { @@ -19483,8 +19488,10 @@ test loc_in_matches_a_local_of_its_kind_inside_its_fns_range { assert(!strloc_in(src, &st, 4, 5)); // has_vecfn: some fn of the file is in the Vec out-buffer form assert(!has_vecfn(&st)); + assert(st[HIT] == 0); add_name(src, &st, K_VECFN, 2, 3, 0, 0); assert(has_vecfn(&st)); + assert(st[HIT] == NAMES + 2 * NAME_SLOTS); } test vec_ret_elem_and_the_empty_vec_inits_read_the_signature_and_the_let { From 98910f284b524677ea4591f1991317939fa1a19a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 00:19:45 +0700 Subject: [PATCH 4/5] t27o: re-save the seal of rust_convert.t27 after the rebase onto master -- 350 of 350 tests, vacuous 0 (Closes #11073) t27c seal --save of specs/tools/t27o/rust_convert.t27; --verify MATCHes on rust_convert, rust_std, zig_convert, spice_convert, zig_std, ledger, golden, install and service, so no dependent seal was re-saved. gen/rust/tools/t27o/rust_convert.rs is byte-identical to t27c gen-rust of the spec. Co-Authored-By: Claude Sonnet 5.5 --- .trinity/seals/t27o_T27oRust.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.trinity/seals/t27o_T27oRust.json b/.trinity/seals/t27o_T27oRust.json index f57a7506e5..26497750e2 100644 --- a/.trinity/seals/t27o_T27oRust.json +++ b/.trinity/seals/t27o_T27oRust.json @@ -2,27 +2,27 @@ "built_by": "t27c-bootstrap@0.5.2+b258b9007", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:b4e7ec08069e19be5797b930c0ea253f6142b58bd123bf16ca3bf6c59c64fba8", - "gen_hash_rust": "sha256:3a7fd83dc3d1ac9245fc22a556457988333908b8812b1b9a9f63feedc92430d3", - "gen_hash_verilog": "sha256:d37444f6ae63399bfede14a121f6a011406a4a8e934f7f56c9e780f2439f63c2", - "gen_hash_zig": "sha256:84e13ecf8b026abac4e9b9d9f1f579c5d348adeb92832955c1755f7a98ae0095", + "gen_hash_c": "sha256:f9d45c7211c78576e5bb5e86d93558101d0cd0889ffeacae15982264be708aa3", + "gen_hash_rust": "sha256:a6c9327cd2cf5e39267ada99df08b106058fb13baa23c5dd640f143b7fdcff00", + "gen_hash_verilog": "sha256:3f6732106561d277c96acd2b0d1952658f6c3c186989f02e57efa17201105f8f", + "gen_hash_zig": "sha256:5e2e2005cea94805918afcea95fd0ba32351392e18084e5c21b228322f1d9b76", "module": "T27oRust", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T13:57:04Z", + "sealed_at": "2026-10-19T17:18:01Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:88af40441411b4a2cf839b0024cbae7a5ac47d7e5ccaaeb15cd611c4d60eac9c", + "spec_hash": "sha256:1bfa4130193f8a31d32af64781273e97b0f287ae63f7cc9d8cdc9f2a044169cf", "spec_path": "specs/tools/t27o/rust_convert.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 333, - "total": 333, + "passed": 350, + "total": 350, "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "test_runner": "missing", "zig": "0.16.0" } } \ No newline at end of file From 0bc68a58b2749149cdfb151751433dfa0e48d634 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 00:30:40 +0700 Subject: [PATCH 5/5] t27o: a list looped over whose element type is not told is refused naming the list, and one whose binding is pushed to the Vec result takes the Vec's element type (Closes #11112) let classes = [64, 128, 256, 512]; for &c in &classes { lengths.push(c); } converted to an untyped array constant, which t27c reads as a tuple and a for cannot walk: padding_length_distribution_guard.rs went from a vacuous pass to BLOCKED once uniform_lengths started to convert (#11073). The merge base writes the same untyped constant for any fn with that pattern. - let_array: when the element type is unknown and the list is written into or looped over (array_iterated: by name, by reference, .iter()), the fn is refused (R_VECTYPE), the token now the list's name, not its bracket. A list only indexed by number or handed to a call stays what it was. - put_vec_elem has a new rung, array_push_elem: in a fn in the Vec out-buffer form, a list whose loop binding is pushed as it is to the result Vec (loop_pushes) has the Vec's element type -- what is pushed has it. v.push(c * 2) tells nothing and is not guessed. Tests first (four failed on the previous head): the pinned array-local test, the padding shape, the negative controls (by reference, .iter(), by value, c * 2 refused; a suffixed list typed; a list only indexed by number unchanged), and a direct test of the three new fns. The spec t27o writes for the padding file now passes t27c test-report (1 of 1, 10 runtime asserts, vacuous 0) instead of BLOCKED. Co-Authored-By: Claude Sonnet 5.5 --- .trinity/seals/t27o_T27oRust.json | 16 ++-- gen/rust/tools/t27o/rust_convert.rs | 68 ++++++++++++-- specs/tools/t27o/rust_convert.t27 | 135 +++++++++++++++++++++++++--- 3 files changed, 194 insertions(+), 25 deletions(-) diff --git a/.trinity/seals/t27o_T27oRust.json b/.trinity/seals/t27o_T27oRust.json index 26497750e2..8dc7a5e9d2 100644 --- a/.trinity/seals/t27o_T27oRust.json +++ b/.trinity/seals/t27o_T27oRust.json @@ -2,23 +2,23 @@ "built_by": "t27c-bootstrap@0.5.2+b258b9007", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:f9d45c7211c78576e5bb5e86d93558101d0cd0889ffeacae15982264be708aa3", - "gen_hash_rust": "sha256:a6c9327cd2cf5e39267ada99df08b106058fb13baa23c5dd640f143b7fdcff00", - "gen_hash_verilog": "sha256:3f6732106561d277c96acd2b0d1952658f6c3c186989f02e57efa17201105f8f", - "gen_hash_zig": "sha256:5e2e2005cea94805918afcea95fd0ba32351392e18084e5c21b228322f1d9b76", + "gen_hash_c": "sha256:eb5230aa674e89d821c62094685b31e15bb3f1e6e0fa14c0f99198d1fa248770", + "gen_hash_rust": "sha256:15fce2e7366e1bb135ae9fcf6d34e8b7b8177e4e22d09069112b06b584635a69", + "gen_hash_verilog": "sha256:ea35ca3d1c18655fac83f20b17c3c8a04c78e727da9e18ae074f95870c8cba1b", + "gen_hash_zig": "sha256:1f0284cf8883747e351d13a0e4e8d6de036c37b4633fa1d8fd6890c741d4aaf5", "module": "T27oRust", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T17:18:01Z", + "sealed_at": "2026-10-19T17:29:12Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:1bfa4130193f8a31d32af64781273e97b0f287ae63f7cc9d8cdc9f2a044169cf", + "spec_hash": "sha256:ad23e8ee46f5bda72c23c1a428b323660b5cf66da17ed942286834c0bc0b60e2", "spec_path": "specs/tools/t27o/rust_convert.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 350, - "total": 350, + "passed": 353, + "total": 353, "vacuous": 0 }, "toolchain": { diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index 44974d8388..1b506f40cf 100644 --- a/gen/rust/tools/t27o/rust_convert.rs +++ b/gen/rust/tools/t27o/rust_convert.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 350 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 353 test(s), 0 invariant(s), 0 function(s) with no body. // This backend emits declarations only. The spec's checks live in // the Zig and Verilog outputs; do not read this file as verified. @@ -3003,9 +3003,13 @@ pub fn fn_named(src: &[u8], q: usize, a: usize, b: usize) -> bool { return same_text(src, n, tok_end(src, n), a, b); } -pub fn fn_ret_of(src: &[u8], q: usize) -> usize { +pub fn fn_params_open(src: &[u8], q: usize) -> usize { let n: usize = skip_ws(src, tok_end(src, q)); - let lp: usize = find_at(src, tok_end(src, n), B_LPAREN); + return find_at(src, tok_end(src, n), B_LPAREN); +} + +pub fn fn_ret_of(src: &[u8], q: usize) -> usize { + let lp: usize = fn_params_open(src, q); if (lp >= src.len()) { return src.len(); } @@ -6011,8 +6015,8 @@ pub fn let_array(src: &[u8], st: &mut [usize], out: &mut [u8], mutable: bool, na if !(known) { st[(W) as usize] = w0; st[(P) as usize] = p0; - if written { - refuse(st, R_VECTYPE, init, init, tok_end(src, init)); + if (written || array_iterated(src, close, item_end(src, st[(FLO) as usize]), na, nb)) { + refuse(st, R_VECTYPE, na, na, nb); return true; } return false; @@ -6390,6 +6394,55 @@ pub fn vec_first_type(src: &[u8], st: &mut [usize], out: &mut [u8], e0: usize) - return false; } +pub fn loop_pushes(src: &[u8], st: &mut [usize], lo: usize, hi: usize, ba: usize, bb: usize) -> bool { + let mut p0: usize = src.len(); + let mut p1: usize = src.len(); + let mut q: usize = lo; + while (q < hi) { + let e: usize = tok_end(src, q); + if ((((word_is(src, q, e, b"push") && tok_is(src, p0, b".")) && (p1 < src.len())) && is_alpha(src[(p1) as usize])) && loc_in(src, st, K_VECLOC, p1, tok_end(src, p1))) { + let lp: usize = skip_ws(src, e); + let a: usize = skip_ws(src, (lp + 1)); + if ((tok_is(src, lp, b"(") && same_text(src, a, tok_end(src, a), ba, bb)) && tok_is(src, skip_ws(src, tok_end(src, a)), b")")) { + return true; + } + } + p1 = p0; + p0 = q; + q = skip_ws(src, e); + } + return false; +} + +pub fn array_push_elem(src: &[u8], st: &mut [usize], lo: usize, hi: usize, na: usize, nb: usize) -> usize { + if (st[(OUTFORM) as usize] != 2) { + return src.len(); + } + let mut q: usize = lo; + while (q < hi) { + let e: usize = tok_end(src, q); + if word_is(src, q, e, b"for") { + let mut b: usize = skip_ws(src, e); + if tok_is(src, b, b"&") { + b = skip_ws(src, tok_end(src, b)); + } + let be: usize = tok_end(src, b); + let kw: usize = skip_ws(src, be); + let mut x: usize = skip_ws(src, tok_end(src, kw)); + if tok_is(src, x, b"&") { + x = skip_ws(src, tok_end(src, x)); + } + let lb: usize = find_at(src, x, B_LBRACE); + let walks: bool = (((((b < src.len()) && is_alpha(src[(b) as usize])) && tok_is(src, kw, b"in")) && (x < src.len())) && same_text(src, x, tok_end(src, x), na, nb)); + if ((walks && (lb < hi)) && loop_pushes(src, st, lb, block_end(src, lb), b, be)) { + return vec_ret_elem(src, fn_params_open(src, st[(FLO) as usize])); + } + } + q = skip_ws(src, e); + } + return src.len(); +} + pub fn put_vec_elem(src: &[u8], st: &mut [usize], out: &mut [u8], typed: bool, e0: usize, lo: usize, hi: usize, na: usize, nb: usize) -> bool { if typed { let t: usize = st[(LETTY) as usize]; @@ -6411,6 +6464,11 @@ pub fn put_vec_elem(src: &[u8], st: &mut [usize], out: &mut [u8], typed: bool, e put_elem_ty(src, st, out, el); return !(bad(st[(ERR) as usize])); } + let pe: usize = array_push_elem(src, st, lo, hi, na, nb); + if (pe < src.len()) { + put_elem_ty(src, st, out, pe); + return !(bad(st[(ERR) as usize])); + } return false; } diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index 425f7fd31a..ac741af417 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -2661,11 +2661,16 @@ pub fn fn_named(src: []const u8, q: usize, a: usize, b: usize) -> bool { return same_text(src, n, tok_end(src, n), a, b); } +/// The `(` that opens the parameters of the `fn` at src[q], src.len when there is none. +pub fn fn_params_open(src: []const u8, q: usize) -> usize { + const n : usize = skip_ws(src, tok_end(src, q)); + return find_at(src, tok_end(src, n), B_LPAREN); +} + /// The return type token of the `fn` at src[q] that fn_hit found: the one after `->`, src.len when it has /// none. pub fn fn_ret_of(src: []const u8, q: usize) -> usize { - const n : usize = skip_ws(src, tok_end(src, q)); - const lp : usize = find_at(src, tok_end(src, n), B_LPAREN); + const lp : usize = fn_params_open(src, q); if (lp >= src.len) { return src.len; } const r : usize = skip_ws(src, block_end(src, lp)); if (tok_is(src, r, "->")) { return skip_ws(src, tok_end(src, r)); } @@ -5266,9 +5271,9 @@ pub fn array_written(src: []const u8, lo: usize, hi: usize, na: usize, nb: usize /// `let [mut] v = [a, b, c];` / `[x; n]` as the typed array `const v : [3]T = [a, b, c];` / `var v : [n]T = [x] ** n;`, /// T from the first element or from the slice parameter the array is handed to (put_vec_elem). When T is unknown -/// the let goes the way it always did -- unless an element is written (`written`): the untyped tuple would be -/// written into, which Zig refuses, so it is refused as a vec! of unknown type is. st[P] is after the name; init -/// is the `[`. +/// the let goes the way it always did -- unless an element is written (`written`) or the list is looped over: the +/// untyped tuple would be written into, or walked by a for, which Zig refuses (#11112), so it is refused as a vec! +/// of unknown type is, naming the list. st[P] is after the name; init is the `[`. pub fn let_array(src: []const u8, st: []usize, out: []u8, mutable: bool, na: usize, nb: usize, init: usize, written: bool) -> bool { const close : usize = block_end(src, init); const rb : usize = close - 1; @@ -5293,8 +5298,10 @@ pub fn let_array(src: []const u8, st: []usize, out: []u8, mutable: bool, na: usi if (!known) { st[W] = w0; st[P] = p0; - if (written) { - refuse(st, R_VECTYPE, init, init, tok_end(src, init)); + // an untyped array is a tuple to t27c: it cannot be written into, and a for cannot walk it (#11112) -- the list is + // refused, by name; a list only read by number or handed to a call stays what it was + if (written or array_iterated(src, close, item_end(src, st[FLO]), na, nb)) { + refuse(st, R_VECTYPE, na, na, nb); return true; } return false; @@ -5633,9 +5640,55 @@ pub fn vec_first_type(src: []const u8, st: []usize, out: []u8, e0: usize) -> boo return false; } +/// Does the loop body src[lo .. hi) push the binding src[ba .. bb) as it is to the result Vec of a fn in the Vec out-buffer +/// form: `v.push(c)`, v the K_VECLOC local? +pub fn loop_pushes(src: []const u8, st: []usize, lo: usize, hi: usize, ba: usize, bb: usize) -> bool { + var p0 : usize = src.len; + var p1 : usize = src.len; + var q : usize = lo; + while (q < hi) { + const e : usize = tok_end(src, q); + if (word_is(src, q, e, "push") and tok_is(src, p0, ".") and p1 < src.len and is_alpha(src[p1]) and loc_in(src, st, K_VECLOC, p1, tok_end(src, p1))) { + const lp : usize = skip_ws(src, e); + const a : usize = skip_ws(src, lp + 1); + if (tok_is(src, lp, "(") and same_text(src, a, tok_end(src, a), ba, bb) and tok_is(src, skip_ws(src, tok_end(src, a)), ")")) { return true; } + } + p1 = p0; + p0 = q; + q = skip_ws(src, e); + } + return false; +} + +/// The element type token of the Vec that a fn in the Vec out-buffer form returns, for the list local src[na .. nb) that src[lo .. hi) +/// loops over (`for c in name`, `for &c in &name`, `name.iter()`) and whose loop binding it pushes as it is to that Vec: what is +/// pushed has the Vec's element type, so the list has it (Rust says the same by inference). src.len when the fn is not in that +/// form or no loop pushes its binding as it is -- `v.push(c * 2)` says nothing of c's type to t27o, and is not guessed. +pub fn array_push_elem(src: []const u8, st: []usize, lo: usize, hi: usize, na: usize, nb: usize) -> usize { + if (st[OUTFORM] != 2) { return src.len; } + var q : usize = lo; + while (q < hi) { + const e : usize = tok_end(src, q); + if (word_is(src, q, e, "for")) { + var b : usize = skip_ws(src, e); + if (tok_is(src, b, "&")) { b = skip_ws(src, tok_end(src, b)); } + const be : usize = tok_end(src, b); + const kw : usize = skip_ws(src, be); + var x : usize = skip_ws(src, tok_end(src, kw)); + if (tok_is(src, x, "&")) { x = skip_ws(src, tok_end(src, x)); } + const lb : usize = find_at(src, x, B_LBRACE); + const walks : bool = b < src.len and is_alpha(src[b]) and tok_is(src, kw, "in") and x < src.len and same_text(src, x, tok_end(src, x), na, nb); + if (walks and lb < hi and loop_pushes(src, st, lb, block_end(src, lb), b, be)) { return vec_ret_elem(src, fn_params_open(src, st[FLO])); } + } + q = skip_ws(src, e); + } + return src.len; +} + /// Write the element type of the vec! local src[na .. nb) whose first element is src[e0]: the annotation /// `Vec` first (typed; st[LETTY] is its start), then what the first element says, then the parameter -/// of the call that takes `&v` (within src[lo .. hi)). false when none of them tells. +/// of the call that takes `&v` (within src[lo .. hi)), then the Vec result a loop over it pushes to +/// (array_push_elem). false when none of them tells. pub fn put_vec_elem(src: []const u8, st: []usize, out: []u8, typed: bool, e0: usize, lo: usize, hi: usize, na: usize, nb: usize) -> bool { if (typed) { const t : usize = st[LETTY]; @@ -5653,6 +5706,11 @@ pub fn put_vec_elem(src: []const u8, st: []usize, out: []u8, typed: bool, e0: us put_elem_ty(src, st, out, el); return !bad(st[ERR]); } + const pe : usize = array_push_elem(src, st, lo, hi, na, nb); + if (pe < src.len) { + put_elem_ty(src, st, out, pe); + return !bad(st[ERR]); + } return false; } @@ -16264,22 +16322,75 @@ test types_t27c_cannot_read_are_refused_and_float_vars_and_slice_consts_are_type assert(st[NTEST] == 0); } -/// `let cmds = [Cmd::Nop, ..]; for c in cmds` is a typed array (#10905); an unsuffixed int list has no type to give and is left as it was. +/// `let cmds = [Cmd::Nop, ..]; for c in cmds` is a typed array (#10905); an unsuffixed int list that is looped over has no type to give, +/// and t27c rejects the untyped array it used to be left as (a tuple, which a for cannot walk): the fn is refused, naming the array (#11112). test an_array_local_that_is_looped_over_written_borrowed_or_indexed_is_a_typed_array { var out : [4096]u8 = undefined; var st : [1024]usize = [0] ** 1024; - const n : usize = body("pub enum Cmd { Nop, Reset }\nfn sum(v: &[f64]) -> f64 { v.len() as f64 }\n#[test]\nfn loops() {\n let cmds = [Cmd::Nop, Cmd::Reset];\n for c in cmds { assert!(true); }\n let words = [\"a\", \"b\"];\n for w in &words { assert!(w.len() == 1); }\n let nums = [1, 2, 3];\n for n in nums { assert!(n > 0); }\n let only = [Cmd::Nop];\n assert!(only.len() == 1);\n}\n#[test]\nfn writes() {\n let mut scores = [0.0f64; 4];\n scores[0] = 1.0;\n assert!(scores.len() == 4);\n}\n#[test]\nfn borrows() {\n let input = [1.0, 2.0, 3.0];\n assert!(sum(&input) > 0.0);\n let idx = [Cmd::Nop, Cmd::Reset];\n let j = 1;\n let c = &idx[j];\n}", &out, &st); - assert(out_is(n, &out, 0, "pub const Cmd = enum(u32) {\n Nop,\n Reset,\n};\n\nfn sum(v: []const f64) -> f64 {\n return v.len as f64;\n}\n\ntest loops {\n const cmds : [2]Cmd = [Cmd.Nop, Cmd.Reset];\n for c in cmds {\n assert(true);\n }\n const words : [2][]const u8 = [\"a\", \"b\"];\n for w in words {\n assert(w.len == 1);\n }\n const nums = [1, 2, 3];\n for n in nums {\n assert(n > 0);\n }\n const only = [Cmd.Nop];\n assert(only.len == 1);\n}\n\ntest writes {\n var scores : [4]f64 = [@as(f64, 0.0)] ** 4;\n scores[0] = 1.0;\n assert(scores.len == 4);\n}\n\ntest borrows {\n const input : [3]f64 = [1.0, 2.0, 3.0];\n assert(sum(&input) > 0.0);\n const idx : [2]Cmd = [Cmd.Nop, Cmd.Reset];\n const j = 1;\n const c = idx[j];\n}\n\n")); + const n : usize = body("pub enum Cmd { Nop, Reset }\nfn sum(v: &[f64]) -> f64 { v.len() as f64 }\n#[test]\nfn loops() {\n let cmds = [Cmd::Nop, Cmd::Reset];\n for c in cmds { assert!(true); }\n let words = [\"a\", \"b\"];\n for w in &words { assert!(w.len() == 1); }\n let only = [Cmd::Nop];\n assert!(only.len() == 1);\n}\n#[test]\nfn untyped() {\n let nums = [1, 2, 3];\n for n in nums { assert!(n > 0); }\n}\n#[test]\nfn writes() {\n let mut scores = [0.0f64; 4];\n scores[0] = 1.0;\n assert!(scores.len() == 4);\n}\n#[test]\nfn borrows() {\n let input = [1.0, 2.0, 3.0];\n assert!(sum(&input) > 0.0);\n let idx = [Cmd::Nop, Cmd::Reset];\n let j = 1;\n let c = &idx[j];\n}", &out, &st); + assert(out_is(n, &out, 0, "pub const Cmd = enum(u32) {\n Nop,\n Reset,\n};\n\nfn sum(v: []const f64) -> f64 {\n return v.len as f64;\n}\n\ntest loops {\n const cmds : [2]Cmd = [Cmd.Nop, Cmd.Reset];\n for c in cmds {\n assert(true);\n }\n const words : [2][]const u8 = [\"a\", \"b\"];\n for w in words {\n assert(w.len == 1);\n }\n const only = [Cmd.Nop];\n assert(only.len == 1);\n}\n\n// t27o: refused fn untyped (line 13): a vec! or array literal whose element type or length t27o cannot tell `nums` at line 14\n\ntest writes {\n var scores : [4]f64 = [@as(f64, 0.0)] ** 4;\n scores[0] = 1.0;\n assert(scores.len == 4);\n}\n\ntest borrows {\n const input : [3]f64 = [1.0, 2.0, 3.0];\n assert(sum(&input) > 0.0);\n const idx : [2]Cmd = [Cmd.Nop, Cmd.Reset];\n const j = 1;\n const c = idx[j];\n}\n\n")); assert(st[NOK] == 4); assert(st[NTEST] == 3); } +test a_list_pushed_to_the_vec_result_as_it_is_takes_the_element_type_of_the_vec { + var out : [2048]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // padding_length_distribution_guard.rs (#11112): `for &c in &classes { lengths.push(c) }` -- what is pushed has the Vec's element type, so the + // unsuffixed list has it. Before, the list stayed an untyped constant, which t27c reads as a tuple and a for cannot walk (BLOCKED). + const n : usize = body("fn uniform() -> Vec {\n let classes = [64, 128, 256, 512];\n let mut lengths = Vec::new();\n for _ in 0..2 {\n for &c in &classes {\n lengths.push(c);\n }\n }\n lengths\n}", &out, &st); + assert(out_is(n, &out, 0, "fn uniform(out: []usize) -> usize {\n const classes : [4]usize = [64, 128, 256, 512];\n var t27o_w : usize = 0;\n for _ in 0..2 {\n for c in classes {\n const t27o_e163 = c;\n if (t27o_w < out.len) { out[t27o_w] = t27o_e163; }\n t27o_w = t27o_w + 1;\n }\n }\n return t27o_w;\n}\n\n")); +} + +test a_list_that_is_looped_over_and_has_no_element_type_is_refused_naming_the_list { + var out : [8192]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // Negative controls: an unsuffixed list that is looped over (by reference, by `.iter()`, by value) and whose binding gives it no type is refused, + // and so is one whose binding is pushed to the Vec result only after a computation (`c * 2`) -- the type is never guessed. A suffixed list + // is typed by its first element, and a list that is only indexed by a number stays what it was. + const n : usize = body("fn total() -> u32 { let classes = [64, 128, 256]; let mut t = 0u32; for &c in &classes { t += c; } t }\nfn by_iter() -> u32 { let classes = [64, 128]; let mut t = 0u32; for c in classes.iter() { t += c; } t }\nfn by_value() -> u32 { let classes = [1, 2]; let mut t = 0u32; for c in classes { t += c; } t }\nfn twice() -> Vec { let classes = [64, 128]; let mut v = Vec::new(); for &c in &classes { v.push(c * 2); } v }\nfn typed() -> u32 { let classes = [64u32, 128, 256]; let mut t = 0u32; for &c in &classes { t += c; } t }\nfn first() -> u32 { let a = [1, 2, 3]; a[0] }", &out, &st); + assert(out_text_has(&out, n, "// t27o: refused fn total (line 1): a vec! or array literal whose element type or length t27o cannot tell `classes` at line 1\n")); + assert(out_text_has(&out, n, "// t27o: refused fn by_iter (line 2): a vec! or array literal whose element type or length t27o cannot tell `classes` at line 2\n")); + assert(out_text_has(&out, n, "// t27o: refused fn by_value (line 3): a vec! or array literal whose element type or length t27o cannot tell `classes` at line 3\n")); + assert(out_text_has(&out, n, "// t27o: refused fn twice (line 4): a vec! or array literal whose element type or length t27o cannot tell `classes` at line 4\n")); + assert(out_text_has(&out, n, "fn typed() -> u32 {\n const classes : [3]u32 = [@as(u32, 64), 128, 256];")); + assert(out_text_has(&out, n, "fn first() -> u32 {\n const a = [1, 2, 3];\n return a[0];\n}")); +} + +test fn_params_open_loop_pushes_and_array_push_elem_type_a_list_from_the_vec_it_is_pushed_to { + assert(fn_params_open("fn f() -> u8 {", 0) == 4); + assert(fn_params_open("pub fn g(a: u8) {", 4) == 8); + assert(fn_params_open("fn", 0) == 2); + const srcs : [8]str = ["fn f() -> Vec { let classes = [64, 128]; let mut v = Vec::new(); for &c in &classes { v.push(c); } v }", "fn f() -> Vec { let classes = [64, 128]; let mut v = Vec::new(); for &c in &classes { v.push(c * 2); } v }", "fn f() -> Vec { let classes = [64, 128]; let mut v = Vec::new(); for &c in &classes { o.push(c); } v }", "fn f() -> Vec { let classes = [64, 128]; let mut v = Vec::new(); for &c in &other { v.push(c); } v }", "fn f() -> Vec { let classes = [64, 128]; let mut v = Vec::new(); for c in classes.iter() { v.push(c); } v }", "fn f() -> Vec { let classes = [64, 128]; let mut v = Vec::new(); for c in classes { v.push(c); } v }", "fn f() -> Vec { let classes = [64, 128]; let mut v = Vec::new(); for &c in &classes { v.push(c); } v }", "fn f() -> Vec { let classes = [64, 128]; let mut v = Vec::new(); for _ in 0..2 { v.push(1); } v }"]; + const na : [8]usize = [27, 27, 27, 27, 27, 27, 27, 27]; + const vp : [8]usize = [56, 56, 56, 56, 56, 56, 56, 56]; + const lo : [8]usize = [47, 47, 47, 47, 47, 47, 47, 47]; + const outform : [8]usize = [2, 2, 2, 2, 2, 2, 0, 2]; + const ba : [8]usize = [77, 77, 77, 77, 76, 76, 77, 76]; + const bl : [8]usize = [91, 91, 91, 89, 96, 89, 91, 86]; + const bh : [8]usize = [105, 109, 105, 103, 110, 103, 105, 100]; + const want : [8]usize = [14, 113, 109, 107, 14, 14, 109, 104]; + var i : usize = 0; + while (i < 8) { + var st : [1024]usize = [0] ** 1024; + add_name(srcs[i], &st, K_VECLOC, vp[i], vp[i] + 1, 0, srcs[i].len); + st[FLO] = 0; + st[OUTFORM] = outform[i]; + assert(array_push_elem(srcs[i], &st, lo[i], srcs[i].len, na[i], na[i] + 7) == want[i]); + // the loop alone: its body pushes the binding as it is to the result local (rows 0, 3, 4, 5 and 6 do; the others push c * 2, + // push to a name that is not the result, or push a literal) + const pushes : bool = loop_pushes(srcs[i], &st, bl[i], bh[i], ba[i], ba[i] + 1); + assert(pushes == (i == 0 or i == 3 or i == 4 or i == 5 or i == 6)); + i = i + 1; + } +} + /// The untyped tuple would be written into, which Zig refuses. test a_written_array_of_no_known_type_is_refused { var out : [4096]u8 = undefined; var st : [1024]usize = [0] ** 1024; const n : usize = body("#[test]\nfn writes() {\n let mut plain = [0; 3];\n plain[1] = 2;\n assert!(plain.len() == 3);\n}", &out, &st); - assert(out_is(n, &out, 0, "// t27o: refused fn writes (line 2): a vec! or array literal whose element type or length t27o cannot tell `[` at line 3\n\n")); + assert(out_is(n, &out, 0, "// t27o: refused fn writes (line 2): a vec! or array literal whose element type or length t27o cannot tell `plain` at line 3\n\n")); assert(st[NOK] == 0); assert(st[NTEST] == 0); }