diff --git a/.trinity/seals/t27o_T27oRust.json b/.trinity/seals/t27o_T27oRust.json index ce849a261a..d83a4cf3e2 100644 --- a/.trinity/seals/t27o_T27oRust.json +++ b/.trinity/seals/t27o_T27oRust.json @@ -2,23 +2,23 @@ "built_by": "t27c-bootstrap@0.5.2+9b556b7c3", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:29bb116b657fcdc121805ff200cfb290c063ebea7dec2bf12248ce0da72243c2", - "gen_hash_rust": "sha256:76e6a5a1909f62ea0e848bf97396dbb19c7429a01473f7ff4fd363f70e9de621", - "gen_hash_verilog": "sha256:cb1b3ba68e8144c8d99aa30b1131e67b1360a2700aebb6fb7d2da8f75fb0e6e8", - "gen_hash_zig": "sha256:2c4d556d3f5d79f4ef40ffaba73ee640394ac05231c67179f6911475ca11c88f", + "gen_hash_c": "sha256:e61cbefe9d5600f5cce1f93821ae49c128548c11dd97e158d5502bd9aae64691", + "gen_hash_rust": "sha256:ed93b8afe9a382abe392f7972b24f150c8d8596daba8321889acf8875638e74b", + "gen_hash_verilog": "sha256:c55a89b710d4317f1b1c0f0108dc9a3ecc45afe5eeb7bed185f58aae1d88c099", + "gen_hash_zig": "sha256:6e8870fffd8349958695106cb01d9ecc3884eb4d66de37eed098bd8bf1c9fa2d", "module": "T27oRust", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T18:02:34Z", + "sealed_at": "2026-10-18T18:59:54Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:dea290dff726c285eb490368c99ef12eb289202c5ffdf5f17b54266072c56ea1", + "spec_hash": "sha256:811b808e8cee4a8ab3576a3be5e23832e1bdf139df02dbb356dd313ee5f194c8", "spec_path": "specs/tools/t27o/rust_convert.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 81, - "total": 81, + "passed": 90, + "total": 90, "vacuous": 0 }, "toolchain": { diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index 9b01d8a036..ccbd99d425 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: 81 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 90 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. @@ -278,6 +278,8 @@ pub const R_SPLICE: usize = 31; pub const R_EMPTY: usize = 32; +pub const R_NOTYPE: usize = 33; + pub const B_TAB: u8 = 9; pub const B_NL: u8 = 10; @@ -1716,14 +1718,37 @@ pub fn unary(src: &[u8], st: &mut [usize], out: &mut [u8], ns: bool) -> usize { return L_UNARY; } if take(src, st, b"!") { - if at(src, st[(P) as usize], b"(") { - puts(st, out, b"("); - unary(src, st, out, ns); + let oa: usize = st[(P) as usize]; + let ws: usize = st[(W) as usize]; + unary(src, st, out, ns); + if bad(st[(ERR) as usize]) { + return L_ATOM; + } + let ty0: usize = st[(TYLO) as usize]; + let ty1: usize = st[(TYHI) as usize]; + let ty2: usize = st[(TYARR) as usize]; + let cls: u8 = expr_class(src, st, oa, st[(P) as usize], true); + st[(TYLO) as usize] = ty0; + st[(TYHI) as usize] = ty1; + st[(TYARR) as usize] = ty2; + if (cls == C_INT) { + insert(st, out, ws, b"~"); + return L_UNARY; + } + if (cls != C_BOOL) { + let mut oz: usize = st[(P) as usize]; + while ((oz > oa) && is_space(src[((oz - 1)) as usize])) { + oz = (oz - 1); + } + refuse(st, R_NOTYPE, oa, oa, oz); + return L_ATOM; + } + if tok_is(src, oa, b"(") { + insert(st, out, ws, b"("); puts(st, out, b" == false)"); return L_ATOM; } - puts(st, out, b"!"); - unary(src, st, out, ns); + insert(st, out, ws, b"!"); return L_UNARY; } if take(src, st, b"*") { @@ -2135,6 +2160,9 @@ pub fn type_class(src: &[u8], st: &mut [usize], d: usize) -> u8 { if (word_is(src, q, e, b"str") || word_is(src, q, e, b"String")) { return C_STR; } + if word_is(src, q, e, b"bool") { + return C_BOOL; + } if (word_is(src, q, e, b"f32") || word_is(src, q, e, b"f64")) { st[(TYLO) as usize] = q; st[(TYHI) as usize] = e; @@ -2232,6 +2260,9 @@ pub fn name_class(src: &[u8], st: &mut [usize], a: usize, b: usize, field: bool) if ((init < src.len()) && ((src[(init) as usize] == B_QUOTE) || raw_str_at(src, init))) { return C_STR; } + if ((init < src.len()) && (expr_class(src, st, init, find_at(src, init, B_SEMI), false) == C_BOOL)) { + return C_BOOL; + } if ((init < src.len()) && is_alpha(src[(init) as usize])) { let semi: usize = find_at(src, init, B_SEMI); if str_chain(src, st, init, semi) { @@ -2474,6 +2505,340 @@ pub fn recv_class(src: &[u8], st: &mut [usize], ra: usize, rb: usize) -> u8 { return cls; } +pub fn std_bool(src: &[u8], a: usize, b: usize) -> bool { + let w: &'static str = " is_empty starts_with ends_with contains is_some is_none is_ok is_err eq is_ascii_alphabetic is_ascii_alphanumeric is_ascii_digit all "; + return listed(w, &src[(a) as usize..(b) as usize]); +} + +pub fn ret_class(src: &[u8], st: &mut [usize], a: usize, b: usize) -> u8 { + let mut cls: u8 = C_NONE; + let mut q: usize = skip_ws(src, 0); + while (q < src.len()) { + let e: usize = tok_end(src, q); + if word_is(src, q, e, b"fn") { + let n: usize = skip_ws(src, e); + let ne: usize = tok_end(src, n); + let lp: usize = find_at(src, ne, B_LPAREN); + if (same_text(src, n, ne, a, b) && (lp < src.len())) { + let r: usize = skip_ws(src, block_end(src, lp)); + if tok_is(src, r, b"->") { + let c: u8 = type_class(src, st, skip_ws(src, tok_end(src, r))); + if (cls == C_NONE) { + cls = c; + } else { + if (cls != c) { + cls = C_ANY; + } + } + } + } + } + q = skip_ws(src, e); + } + return cls; +} + +pub fn callee_class(src: &[u8], st: &mut [usize], a: usize, b: usize, method: bool) -> u8 { + let f: u8 = ret_class(src, st, a, b); + let s: bool = (method && std_bool(src, a, b)); + if (f == C_NONE) { + if s { + return C_BOOL; + } + return C_ANY; + } + if (s && (f != C_BOOL)) { + return C_ANY; + } + return f; +} + +pub fn lit_at(src: &[u8], q: usize) -> bool { + if (q >= src.len()) { + return false; + } + if (((src[(q) as usize] == B_QUOTE) || (src[(q) as usize] == B_APOS)) || raw_str_at(src, q)) { + return true; + } + return (((src[(q) as usize] == B_LOWER_B) && ((q + 1) < src.len())) && ((src[((q + 1)) as usize] == B_QUOTE) || (src[((q + 1)) as usize] == B_APOS))); +} + +pub fn head_struct(src: &[u8], flo: usize, ilo: usize, ihi: usize, xa: usize, xe: usize) -> usize { + if word_is(src, xa, xe, b"self") { + if (ilo == ihi) { + return src.len(); + } + return struct_open(src, ilo, ihi); + } + let mut d: usize = decl_type(src, flo, xa, xa, xe); + if (d >= src.len()) { + d = const_type(src, xa, xe); + } + if (d >= src.len()) { + d = let_owner(src, xa, xe); + } + if (d >= src.len()) { + return src.len(); + } + while (tok_is(src, d, b"&") || tok_is(src, d, b"&&")) { + d = skip_ws(src, tok_end(src, d)); + if ((d < src.len()) && (src[(d) as usize] == B_APOS)) { + d = skip_ws(src, tok_end(src, d)); + } + if tok_is(src, d, b"mut") { + d = skip_ws(src, tok_end(src, d)); + } + } + if ((d >= src.len()) || !(is_upper(src[(d) as usize]))) { + return src.len(); + } + return struct_open(src, d, tok_end(src, d)); +} + +pub fn chain_class(src: &[u8], st: &mut [usize], a: usize, b: usize, deep: bool) -> u8 { + let mut q: usize = a; + if ((q >= b) || (q >= src.len())) { + return C_ANY; + } + if ((((tok_is(src, q, b"!") || tok_is(src, q, b"-")) || tok_is(src, q, b"*")) || tok_is(src, q, b"&")) || tok_is(src, q, b"&&")) { + q = skip_ws(src, tok_end(src, q)); + if tok_is(src, q, b"mut") { + q = skip_ws(src, tok_end(src, q)); + } + return expr_class(src, st, q, b, deep); + } + if lit_at(src, q) { + return C_ANY; + } + let mut kind: usize = CK_NONE; + let mut na: usize = q; + let mut nb: usize = q; + let mut xa: usize = q; + let mut xe: usize = q; + let mut ga: usize = q; + let mut dots: usize = 0; + let mut plain: bool = false; + let e0: usize = tok_end(src, q); + if (src[(q) as usize] == B_LPAREN) { + kind = CK_GROUP; + q = skip_ws(src, block_end(src, q)); + } else { + if is_digit(src[(q) as usize]) { + kind = CK_NUM; + nb = e0; + q = skip_ws(src, e0); + } else { + if is_alpha(src[(q) as usize]) { + kind = CK_NAME; + plain = true; + nb = e0; + xe = e0; + q = skip_ws(src, e0); + if (word_is(src, na, nb, b"true") || word_is(src, na, nb, b"false")) { + if (q >= b) { + return C_BOOL; + } + return C_ANY; + } + if ((word_is(src, na, nb, b"matches") || word_is(src, na, nb, b"cfg")) && tok_is(src, q, b"!")) { + q = skip_ws(src, tok_end(src, q)); + if ((q < src.len()) && (src[(q) as usize] == B_LPAREN)) { + q = skip_ws(src, block_end(src, q)); + } + if (q >= b) { + return C_BOOL; + } + return C_ANY; + } + } else { + return C_ANY; + } + } + } + while (q < b) { + if tok_is(src, q, b"::") { + let sg: usize = skip_ws(src, tok_end(src, q)); + if ((sg >= src.len()) || !(is_alpha(src[(sg) as usize]))) { + return C_ANY; + } + na = sg; + nb = tok_end(src, sg); + kind = CK_PATH; + plain = false; + q = skip_ws(src, nb); + } else { + if tok_is(src, q, b".") { + let f: usize = skip_ws(src, tok_end(src, q)); + if ((f >= src.len()) || !((is_alpha(src[(f) as usize]) || is_digit(src[(f) as usize])))) { + return C_ANY; + } + na = f; + nb = tok_end(src, f); + kind = CK_FIELD; + dots = (dots + 1); + q = skip_ws(src, nb); + } else { + if tok_is(src, q, b"(") { + if ((kind == CK_NAME) || (kind == CK_PATH)) { + kind = CK_CALL; + } else { + if (kind == CK_FIELD) { + kind = CK_METHOD; + } else { + return C_ANY; + } + } + plain = false; + q = skip_ws(src, block_end(src, q)); + } else { + if tok_is(src, q, b"[") { + kind = CK_INDEX; + plain = false; + q = skip_ws(src, block_end(src, q)); + } else { + return C_ANY; + } + } + } + } + } + if (kind == CK_GROUP) { + return expr_class(src, st, (ga + 1), (block_end(src, ga) - 1), deep); + } + if (kind == CK_NUM) { + if ((suffix_len(src, na, nb) > 0) && (literal_class(src, st, na) == C_INT)) { + return C_INT; + } + return C_ANY; + } + if (kind == CK_CALL) { + return callee_class(src, st, na, nb, false); + } + if (kind == CK_METHOD) { + return callee_class(src, st, na, nb, true); + } + if !(deep) { + return C_ANY; + } + if (kind == CK_NAME) { + return name_class(src, st, na, nb, false); + } + if (((kind == CK_FIELD) && (dots == 1)) && plain) { + let o: usize = head_struct(src, st[(FLO) as usize], st[(ILO) as usize], st[(IHI) as usize], xa, xe); + if (o >= src.len()) { + return C_ANY; + } + if is_digit(src[(na) as usize]) { + if ((src[(o) as usize] != B_LPAREN) || (nb != (na + 1))) { + return C_ANY; + } + let t: usize = field_type(src, st, o, ((src[(na) as usize] - B_ZERO) as usize)); + if (t >= src.len()) { + return C_ANY; + } + return type_class(src, st, t); + } + if (src[(o) as usize] != B_LBRACE) { + return C_ANY; + } + let f: usize = decl_type(src, o, block_end(src, o), na, nb); + if (f >= src.len()) { + return C_ANY; + } + return type_class(src, st, f); + } + if (kind == CK_PATH) { + let c: usize = const_type(src, na, nb); + if (c < src.len()) { + return type_class(src, st, c); + } + } + return C_ANY; +} + +pub fn expr_class(src: &[u8], st: &mut [usize], a: usize, b: usize, deep: bool) -> u8 { + let mut q: usize = a; + let mut val: bool = false; + let mut lv: usize = 100; + let mut oa: usize = b; + let mut oe: usize = b; + while ((q < b) && (q < src.len())) { + let e: usize = tok_end(src, q); + if ((e == (q + 1)) && is_open(src[(q) as usize])) { + q = skip_ws(src, block_end(src, q)); + val = true; + } else { + if (word_is(src, q, e, b"as") && val) { + if (L_AS <= lv) { + lv = L_AS; + oa = q; + oe = e; + } + q = skip_ws(src, tok_end(src, skip_ws(src, e))); + val = true; + } else { + if (val && (op_level(src, q, e) != 0)) { + let l: usize = op_level(src, q, e); + if ((l as usize) <= lv) { + lv = l; + oa = q; + oe = e; + } + q = skip_ws(src, e); + val = false; + } else { + if (!(val) && ((word_is(src, q, e, b"|") || word_is(src, q, e, b"||")) || word_is(src, q, e, b"move"))) { + return C_ANY; + } else { + if (!(val) && word_is(src, q, e, b"mut")) { + q = skip_ws(src, e); + } else { + if (((is_alpha(src[(q) as usize]) || is_digit(src[(q) as usize])) || (src[(q) as usize] == B_QUOTE)) || (src[(q) as usize] == B_APOS)) { + if val { + return C_ANY; + } + val = true; + q = skip_ws(src, e); + } else { + if !((val && (word_is(src, q, e, b"?") || word_is(src, q, e, b"!")))) { + val = false; + } + q = skip_ws(src, e); + } + } + } + } + } + } + } + if (lv == 100) { + return chain_class(src, st, a, b, deep); + } + if (((lv == L_OR) || (lv == L_AND)) || (lv == L_CMP)) { + return C_BOOL; + } + if (((lv == L_BOR) || (lv == L_XOR)) || (lv == L_BAND)) { + let l: u8 = expr_class(src, st, a, oa, deep); + if ((l == C_INT) || (l == C_BOOL)) { + return l; + } + let r: u8 = expr_class(src, st, oe, b, deep); + if ((r == C_INT) || (r == C_BOOL)) { + return r; + } + return C_ANY; + } + if (lv == L_AS) { + let ta: usize = skip_ws(src, oe); + let tb: usize = tok_end(src, ta); + if (int_type(src, ta, tb) || word_is(src, ta, tb, b"usize")) { + return C_INT; + } + return C_ANY; + } + return C_INT; +} + pub fn let_int(src: &[u8], st: &mut [usize], d: usize) -> usize { if ((d == 0) || (d >= src.len())) { return src.len(); @@ -2505,6 +2870,26 @@ pub const C_SLICE: u8 = 4; pub const C_BOOL: u8 = 5; +pub const C_NONE: u8 = 255; + +pub const CK_NONE: usize = 0; + +pub const CK_GROUP: usize = 1; + +pub const CK_NUM: usize = 2; + +pub const CK_NAME: usize = 3; + +pub const CK_FIELD: usize = 4; + +pub const CK_PATH: usize = 5; + +pub const CK_CALL: usize = 6; + +pub const CK_METHOD: usize = 7; + +pub const CK_INDEX: usize = 8; + pub const STD_MODS: [&'static str; 5] = ["", "tools::t27o::rust_std", "hosting::toolchain", "tri::mutate::plant", "tri::t27b::type_text"]; pub const MOD_TTEXT: u8 = 4; @@ -5504,10 +5889,14 @@ pub fn put_reason(src: &[u8], st: &mut [usize], out: &mut [u8]) -> () { if (c == R_EMPTY) { puts(st, out, b"empty slice with no known element type"); } else { - if (c == R_SPLICE) { - puts(st, out, b"a used module's own use t27c does not splice (its type_text:: names stay unresolved, the gap specs/compiler/int_type_names.t27 records)"); + if (c == R_NOTYPE) { + puts(st, out, b"`!` of an operand whose type t27o cannot tell"); } else { - puts(st, out, b"syntax"); + if (c == R_SPLICE) { + puts(st, out, b"a used module's own use t27c does not splice (its type_text:: names stay unresolved, the gap specs/compiler/int_type_names.t27 records)"); + } else { + puts(st, out, b"syntax"); + } } } } @@ -5585,41 +5974,77 @@ pub fn std_derive(src: &[u8], a: usize, b: usize) -> bool { return ((((((((word_is(src, a, b, b"Debug") || word_is(src, a, b, b"Clone")) || word_is(src, a, b, b"Copy")) || word_is(src, a, b, b"PartialEq")) || word_is(src, a, b, b"Eq")) || word_is(src, a, b, b"Hash")) || word_is(src, a, b, b"PartialOrd")) || word_is(src, a, b, b"Ord")) || word_is(src, a, b, b"Default")); } +pub fn host_word(src: &[u8], a: usize, b: usize) -> bool { + return (((word_is(src, a, b, b"no_std") || word_is(src, a, b, b"no_main")) || word_is(src, a, b, b"deny")) || word_is(src, a, b, b"forbid")); +} + +pub fn host_attr(src: &[u8], ob: usize) -> bool { + let ia: usize = skip_ws(src, (ob + 1)); + let ie: usize = tok_end(src, ia); + if host_word(src, ia, ie) { + return true; + } + let lp: usize = skip_ws(src, ie); + if (!(word_is(src, ia, ie, b"cfg_attr")) || !(tok_is(src, lp, b"("))) { + return false; + } + let rp: usize = block_end(src, lp); + let mut q: usize = find_at(src, (lp + 1), B_COMMA); + while (q < rp) { + q = skip_ws(src, (q + 1)); + if host_word(src, q, tok_end(src, q)) { + return true; + } + q = find_at(src, q, B_COMMA); + } + return false; +} + pub fn put_item_source(src: &[u8], st: &mut [usize], out: &mut [u8], start: usize, end: usize) -> () { let mut from: usize = start; let mut q: usize = start; while (q < end) { let e: usize = tok_end(src, q); let lb: usize = skip_ws(src, e); - if (word_is(src, q, e, b"#") && tok_is(src, lb, b"[")) { - put_src(src, st, out, from, q); - let close: usize = block_end(src, lb); - let d: usize = skip_ws(src, (lb + 1)); - let lp: usize = skip_ws(src, tok_end(src, d)); - if (tok_is(src, d, b"derive") && tok_is(src, lp, b"(")) { - let mut n: usize = 0; - let mut t: usize = skip_ws(src, (lp + 1)); - while (t < close) { - let te: usize = tok_end(src, t); - if (is_alpha(src[(t) as usize]) && std_derive(src, t, te)) { - if (n == 0) { - puts(st, out, b"#[derive("); - } else { - puts(st, out, b", "); + if ((word_is(src, q, e, b"#") && tok_is(src, lb, b"!")) && tok_is(src, skip_ws(src, tok_end(src, lb)), b"[")) { + let ob: usize = skip_ws(src, tok_end(src, lb)); + let ic: usize = block_end(src, ob); + if host_attr(src, ob) { + put_src(src, st, out, from, q); + from = ic; + } + q = skip_ws(src, ic); + } else { + if (word_is(src, q, e, b"#") && tok_is(src, lb, b"[")) { + put_src(src, st, out, from, q); + let close: usize = block_end(src, lb); + let d: usize = skip_ws(src, (lb + 1)); + let lp: usize = skip_ws(src, tok_end(src, d)); + if (tok_is(src, d, b"derive") && tok_is(src, lp, b"(")) { + let mut n: usize = 0; + let mut t: usize = skip_ws(src, (lp + 1)); + while (t < close) { + let te: usize = tok_end(src, t); + if (is_alpha(src[(t) as usize]) && std_derive(src, t, te)) { + if (n == 0) { + puts(st, out, b"#[derive("); + } else { + puts(st, out, b", "); + } + put_src(src, st, out, t, te); + n = (n + 1); } - put_src(src, st, out, t, te); - n = (n + 1); + t = skip_ws(src, te); + } + if (n > 0) { + puts(st, out, b")]"); } - t = skip_ws(src, te); - } - if (n > 0) { - puts(st, out, b")]"); } + from = close; + q = skip_ws(src, close); + } else { + q = skip_ws(src, e); } - from = close; - q = skip_ws(src, close); - } else { - q = skip_ws(src, e); } } put_src(src, st, out, from, end); @@ -8263,7 +8688,35 @@ pub fn out_str_return(src: &[u8], st: &mut [usize], out: &mut [u8]) -> bool { return true; } -pub fn ret_has_str(src: &[u8], pn: usize) -> bool { +pub fn struct_text(src: &[u8], ilo: usize, ihi: usize, q: usize) -> bool { + if ((q >= src.len()) || !(is_upper(src[(q) as usize]))) { + return false; + } + let mut na: usize = q; + let mut nb: usize = tok_end(src, q); + if word_is(src, na, nb, b"Self") { + if (ilo == ihi) { + return false; + } + na = ilo; + nb = ihi; + } + let o: usize = struct_open(src, na, nb); + if (o >= src.len()) { + return false; + } + let close: usize = block_end(src, o); + let mut t: usize = skip_ws(src, (o + 1)); + while (t < close) { + if tok_is(src, t, b"str") { + return true; + } + t = skip_ws(src, tok_end(src, t)); + } + return false; +} + +pub fn ret_has_str(src: &[u8], ilo: usize, ihi: usize, pn: usize) -> bool { if !(tok_is(src, pn, b"(")) { return false; } @@ -8276,7 +8729,7 @@ pub fn ret_has_str(src: &[u8], pn: usize) -> bool { } let mut q: usize = skip_ws(src, tok_end(src, r)); while ((((q < src.len()) && !(tok_is(src, q, b"{"))) && !(tok_is(src, q, b"where"))) && !(tok_is(src, q, b";"))) { - if tok_is(src, q, b"str") { + if (tok_is(src, q, b"str") || struct_text(src, ilo, ihi, q)) { return true; } q = skip_ws(src, tok_end(src, q)); @@ -8407,7 +8860,7 @@ pub fn fn_item(src: &[u8], st: &mut [usize], out: &mut [u8], start: usize, is_te if is_test { st[(NTEST) as usize] = (st[(NTEST) as usize] + 1); } - if (!(is_test) && ret_has_str(src, pn)) { + if (!(is_test) && ret_has_str(src, st[(ILO) as usize], st[(IHI) as usize], pn)) { st[(NEQ) as usize] = 1; } if (st[(MODE) as usize] == M_T27) { @@ -9215,3 +9668,11 @@ pub fn out_text_has(out: &[u8], n: usize, s: &[u8]) -> bool { return false; } +pub fn pin_clear_bit(value: u32, bit: u8) -> u32 { + return (value & !(((1 as u32) << bit))); +} + +pub fn pin_clear_mask(value: u32, m: u32) -> u32 { + return (value & !(m)); +} + diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index 86bfab58c2..79b4ae3d8c 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -294,6 +294,9 @@ pub const R_STRUSE : usize = 30; pub const R_SPLICE : usize = 31; /// An empty array or slice literal (`[]`, `&[]`) whose element type is not known where it stands. pub const R_EMPTY : usize = 32; +/// A unary `!` whose operand is neither a bool nor an integer t27o can name: Rust's `!` is a boolean NOT +/// on the first and a bitwise NOT on the second, t27's `!` and `~` are the two, and a guess is a rewrite. +pub const R_NOTYPE : usize = 33; // The bytes the converter reads, as numbers (#7604). pub const B_TAB : u8 = 9; @@ -1521,15 +1524,41 @@ pub fn unary(src: []const u8, st: []usize, out: []u8, ns: bool) -> usize { unary(src, st, out, ns); return L_UNARY; } + // Rust's `!` is a boolean NOT on a bool and a bitwise NOT on an integer; t27's `!` is the first and + // `~` the second, so the operand's class says which is written, read from the source before it is + // trusted: a name's declared type, a fn's return type, a comparison, a suffixed literal. An operand + // whose class t27o cannot tell is refused -- a guess would be a rewrite (#10534). The parentheses of + // `!(x)` are written as `(x == false)`: t27c's Zig backend dropped them (#6549, since fixed), and the + // other spelling reads the same on every backend; `~(x)` keeps its parentheses on all of them + // (bitwise_not_keeps_the_parentheses_of_its_operand below runs it under t27b and t27c). if (take(src, st, "!")) { - if (at(src, st[P], "(")) { - puts(st, out, "("); - unary(src, st, out, ns); + const oa : usize = st[P]; + const ws : usize = st[W]; + unary(src, st, out, ns); + if (bad(st[ERR])) { return L_ATOM; } + const ty0 : usize = st[TYLO]; + const ty1 : usize = st[TYHI]; + const ty2 : usize = st[TYARR]; + const cls : u8 = expr_class(src, st, oa, st[P], true); + st[TYLO] = ty0; + st[TYHI] = ty1; + st[TYARR] = ty2; + if (cls == C_INT) { + insert(st, out, ws, "~"); + return L_UNARY; + } + if (cls != C_BOOL) { + var oz : usize = st[P]; + while (oz > oa and is_space(src[oz - 1])) { oz = oz - 1; } + refuse(st, R_NOTYPE, oa, oa, oz); + return L_ATOM; + } + if (tok_is(src, oa, "(")) { + insert(st, out, ws, "("); puts(st, out, " == false)"); return L_ATOM; } - puts(st, out, "!"); - unary(src, st, out, ns); + insert(st, out, ws, "!"); return L_UNARY; } // A shared reference is passed as its value (&T is T), so reading through one drops the `*`; @@ -1892,6 +1921,7 @@ pub fn type_class(src: []const u8, st: []usize, d: usize) -> u8 { } const e : usize = tok_end(src, q); if (word_is(src, q, e, "str") or word_is(src, q, e, "String")) { return C_STR; } + if (word_is(src, q, e, "bool")) { return C_BOOL; } if (word_is(src, q, e, "f32") or word_is(src, q, e, "f64")) { st[TYLO] = q; st[TYHI] = e; @@ -1971,6 +2001,9 @@ pub fn name_class(src: []const u8, st: []usize, a: usize, b: usize, field: bool) } if (init < src.len and is_digit(src[init])) { return literal_class(src, st, init); } if (init < src.len and (src[init] == B_QUOTE or raw_str_at(src, init))) { return C_STR; } + // `let hit = a == b;`, `let ok = s.is_empty();`, `let on = true;`: the init says the name is a bool (read + // without looking names up, so a name cannot be classed by its own init) + if (init < src.len and expr_class(src, st, init, find_at(src, init, B_SEMI), false) == C_BOOL) { return C_BOOL; } if (init < src.len and is_alpha(src[init])) { // FR-002: the class of `let v = ;` is the call's -- a text-keeping chain is a text, // a fn call is its return type @@ -2169,6 +2202,264 @@ pub fn recv_class(src: []const u8, st: []usize, ra: usize, rb: usize) -> u8 { return cls; } +/// Is the Rust method src[a .. b] one of the std methods t27o converts whose result is a bool? +pub fn std_bool(src: []const u8, a: usize, b: usize) -> bool { + const w : str = " is_empty starts_with ends_with contains is_some is_none is_ok is_err eq is_ascii_alphabetic is_ascii_alphanumeric is_ascii_digit all "; + return listed(w, src[a .. b]); +} + +/// The class of what the fns named src[a .. b] in the file return: C_NONE when it declares none that +/// returns a value, C_ANY when they disagree or return a type t27o does not class (two impls may share +/// a method name). +pub fn ret_class(src: []const u8, st: []usize, a: usize, b: usize) -> u8 { + var cls : u8 = C_NONE; + var q : usize = skip_ws(src, 0); + while (q < src.len) { + const e : usize = tok_end(src, q); + if (word_is(src, q, e, "fn")) { + const n : usize = skip_ws(src, e); + const ne : usize = tok_end(src, n); + const lp : usize = find_at(src, ne, B_LPAREN); + if (same_text(src, n, ne, a, b) and lp < src.len) { + // a fn with no `->` returns `()`, which no `!` can be applied to: a test named like a + // method (`fn is_readonly()` beside `fn is_readonly(&self) -> bool`) says nothing of it + const r : usize = skip_ws(src, block_end(src, lp)); + if (tok_is(src, r, "->")) { + const c : u8 = type_class(src, st, skip_ws(src, tok_end(src, r))); + if (cls == C_NONE) { cls = c; } else if (cls != c) { cls = C_ANY; } + } + } + } + q = skip_ws(src, e); + } + return cls; +} + +/// The class of the value a call of src[a .. b] gives: the file's fn by its return type, or a bool-valued +/// std method (`method` is set for a `.name(..)` call); C_ANY when t27o cannot tell, or when the file's +/// fn of that name and the std method would give two classes. +pub fn callee_class(src: []const u8, st: []usize, a: usize, b: usize, method: bool) -> u8 { + const f : u8 = ret_class(src, st, a, b); + const s : bool = method and std_bool(src, a, b); + if (f == C_NONE) { + if (s) { return C_BOOL; } + return C_ANY; + } + if (s and f != C_BOOL) { return C_ANY; } + return f; +} + +/// Does a literal (a text, a char, a byte string or char, a raw text) start at src[q]? +pub fn lit_at(src: []const u8, q: usize) -> bool { + if (q >= src.len) { return false; } + if (src[q] == B_QUOTE or src[q] == B_APOS or raw_str_at(src, q)) { return true; } + return src[q] == B_LOWER_B and q + 1 < src.len and (src[q + 1] == B_QUOTE or src[q + 1] == B_APOS); +} + +/// The `{` (or the `(` of a tuple struct) of the struct the name src[xa .. xe] is of: `self` is the impl's +/// type, any other name the type it is declared with or the one a `let x = Type::new(..)` names; src.len +/// when t27o cannot tell. +pub fn head_struct(src: []const u8, flo: usize, ilo: usize, ihi: usize, xa: usize, xe: usize) -> usize { + if (word_is(src, xa, xe, "self")) { + if (ilo == ihi) { return src.len; } + return struct_open(src, ilo, ihi); + } + var d : usize = decl_type(src, flo, xa, xa, xe); + if (d >= src.len) { d = const_type(src, xa, xe); } + if (d >= src.len) { d = let_owner(src, xa, xe); } + if (d >= src.len) { return src.len; } + while (tok_is(src, d, "&") or tok_is(src, d, "&&")) { + d = skip_ws(src, tok_end(src, d)); + if (d < src.len and src[d] == B_APOS) { d = skip_ws(src, tok_end(src, d)); } + if (tok_is(src, d, "mut")) { d = skip_ws(src, tok_end(src, d)); } + } + if (d >= src.len or !is_upper(src[d])) { return src.len; } + return struct_open(src, d, tok_end(src, d)); +} + +/// The class of the expression src[a .. b] that has no binary operator at its top: a prefix `!`, `*`, `&` +/// or `-`, a literal, a name, a field, a call, a bool-valued method, a parenthesized expression. C_ANY +/// where t27o cannot tell. With `deep` unset a name is not looked up (a `let`'s own init is read that +/// way, so a name cannot be classed through itself). +pub fn chain_class(src: []const u8, st: []usize, a: usize, b: usize, deep: bool) -> u8 { + var q : usize = a; + if (q >= b or q >= src.len) { return C_ANY; } + if (tok_is(src, q, "!") or tok_is(src, q, "-") or tok_is(src, q, "*") or tok_is(src, q, "&") or tok_is(src, q, "&&")) { + q = skip_ws(src, tok_end(src, q)); + if (tok_is(src, q, "mut")) { q = skip_ws(src, tok_end(src, q)); } + return expr_class(src, st, q, b, deep); + } + if (lit_at(src, q)) { return C_ANY; } + var kind : usize = CK_NONE; + var na : usize = q; + var nb : usize = q; + var xa : usize = q; + var xe : usize = q; + var ga : usize = q; + var dots : usize = 0; + var plain : bool = false; + const e0 : usize = tok_end(src, q); + if (src[q] == B_LPAREN) { + kind = CK_GROUP; + q = skip_ws(src, block_end(src, q)); + } else if (is_digit(src[q])) { + kind = CK_NUM; + nb = e0; + q = skip_ws(src, e0); + } else if (is_alpha(src[q])) { + kind = CK_NAME; + plain = true; + nb = e0; + xe = e0; + q = skip_ws(src, e0); + if (word_is(src, na, nb, "true") or word_is(src, na, nb, "false")) { + if (q >= b) { return C_BOOL; } + return C_ANY; + } + // `matches!(..)` and `cfg!(..)` are a bool + if ((word_is(src, na, nb, "matches") or word_is(src, na, nb, "cfg")) and tok_is(src, q, "!")) { + q = skip_ws(src, tok_end(src, q)); + if (q < src.len and src[q] == B_LPAREN) { q = skip_ws(src, block_end(src, q)); } + if (q >= b) { return C_BOOL; } + return C_ANY; + } + } else { + return C_ANY; + } + while (q < b) { + if (tok_is(src, q, "::")) { + const sg : usize = skip_ws(src, tok_end(src, q)); + if (sg >= src.len or !is_alpha(src[sg])) { return C_ANY; } + na = sg; + nb = tok_end(src, sg); + kind = CK_PATH; + plain = false; + q = skip_ws(src, nb); + } else if (tok_is(src, q, ".")) { + const f : usize = skip_ws(src, tok_end(src, q)); + if (f >= src.len or !(is_alpha(src[f]) or is_digit(src[f]))) { return C_ANY; } + na = f; + nb = tok_end(src, f); + kind = CK_FIELD; + dots = dots + 1; + q = skip_ws(src, nb); + } else if (tok_is(src, q, "(")) { + if (kind == CK_NAME or kind == CK_PATH) { + kind = CK_CALL; + } else if (kind == CK_FIELD) { + kind = CK_METHOD; + } else { + return C_ANY; + } + plain = false; + q = skip_ws(src, block_end(src, q)); + } else if (tok_is(src, q, "[")) { + kind = CK_INDEX; + plain = false; + q = skip_ws(src, block_end(src, q)); + } else { + return C_ANY; + } + } + if (kind == CK_GROUP) { return expr_class(src, st, ga + 1, block_end(src, ga) - 1, deep); } + // a bare number takes its type from where it stands, which t27o does not see; a suffix names it + if (kind == CK_NUM) { + if (suffix_len(src, na, nb) > 0 and literal_class(src, st, na) == C_INT) { return C_INT; } + return C_ANY; + } + if (kind == CK_CALL) { return callee_class(src, st, na, nb, false); } + if (kind == CK_METHOD) { return callee_class(src, st, na, nb, true); } + if (!deep) { return C_ANY; } + if (kind == CK_NAME) { return name_class(src, st, na, nb, false); } + // `x.f` and `x.0` of a struct this file declares: the field's declared type + if (kind == CK_FIELD and dots == 1 and plain) { + const o : usize = head_struct(src, st[FLO], st[ILO], st[IHI], xa, xe); + if (o >= src.len) { return C_ANY; } + if (is_digit(src[na])) { + if (src[o] != B_LPAREN or nb != na + 1) { return C_ANY; } + const t : usize = field_type(src, st, o, (src[na] - B_ZERO) as usize); + if (t >= src.len) { return C_ANY; } + return type_class(src, st, t); + } + if (src[o] != B_LBRACE) { return C_ANY; } + const f : usize = decl_type(src, o, block_end(src, o), na, nb); + if (f >= src.len) { return C_ANY; } + return type_class(src, st, f); + } + if (kind == CK_PATH) { + const c : usize = const_type(src, na, nb); + if (c < src.len) { return type_class(src, st, c); } + } + return C_ANY; +} + +/// The class of the expression src[a .. b], told from its top operator: `&&`, `||` and a comparison give +/// a bool; a shift or arithmetic an integer (`!` is defined for nothing else); `&`, `|`, `^` the class of +/// the operand that has one; `as T` an integer when T is one; with no operator at the top, chain_class. +/// Two operands in a row, a closure or a keyword form (`if a > b { .. }`) is not an expression of +/// operators: C_ANY. +pub fn expr_class(src: []const u8, st: []usize, a: usize, b: usize, deep: bool) -> u8 { + var q : usize = a; + var val : bool = false; + var lv : usize = 100; + var oa : usize = b; + var oe : usize = b; + while (q < b and q < src.len) { + const e : usize = tok_end(src, q); + if (e == q + 1 and is_open(src[q])) { + q = skip_ws(src, block_end(src, q)); + val = true; + } else if (word_is(src, q, e, "as") and val) { + if (L_AS <= lv) { + lv = L_AS; + oa = q; + oe = e; + } + // the type after `as` is one name + q = skip_ws(src, tok_end(src, skip_ws(src, e))); + val = true; + } else if (val and op_level(src, q, e) != 0) { + const l : usize = op_level(src, q, e); + if (l <= lv) { + lv = l; + oa = q; + oe = e; + } + q = skip_ws(src, e); + val = false; + } else if (!val and (word_is(src, q, e, "|") or word_is(src, q, e, "||") or word_is(src, q, e, "move"))) { + return C_ANY; + } else if (!val and word_is(src, q, e, "mut")) { + q = skip_ws(src, e); + } else if (is_alpha(src[q]) or is_digit(src[q]) or src[q] == B_QUOTE or src[q] == B_APOS) { + if (val) { return C_ANY; } + val = true; + q = skip_ws(src, e); + } else { + // `.`, `::`, a prefix operator: the next token goes on from here; `?` and the `!` of a + // macro call (`matches!(..)`) end nothing + if (!(val and (word_is(src, q, e, "?") or word_is(src, q, e, "!")))) { val = false; } + q = skip_ws(src, e); + } + } + if (lv == 100) { return chain_class(src, st, a, b, deep); } + if (lv == L_OR or lv == L_AND or lv == L_CMP) { return C_BOOL; } + if (lv == L_BOR or lv == L_XOR or lv == L_BAND) { + const l : u8 = expr_class(src, st, a, oa, deep); + if (l == C_INT or l == C_BOOL) { return l; } + const r : u8 = expr_class(src, st, oe, b, deep); + if (r == C_INT or r == C_BOOL) { return r; } + return C_ANY; + } + if (lv == L_AS) { + const ta : usize = skip_ws(src, oe); + const tb : usize = tok_end(src, ta); + if (int_type(src, ta, tb) or word_is(src, ta, tb, "usize")) { return C_INT; } + return C_ANY; + } + return C_INT; +} + /// The integer type a typed `let` at src[d] gives a `.parse()` (`u32`, `Result`, `Option`), /// src.len when none. pub fn let_int(src: []const u8, st: []usize, d: usize) -> usize { @@ -2219,8 +2510,20 @@ pub const C_FLOAT : u8 = 1; pub const C_INT : u8 = 2; pub const C_STR : u8 = 3; pub const C_SLICE : u8 = 4; -/// A call of a fn returning `Result<(), E>`, which t27o writes as a bool. +/// A value t27 holds as a bool: a `bool`, or a call of a fn returning `Result<(), E>` (written as one). pub const C_BOOL : u8 = 5; +/// ret_class: the file declares no fn of that name. +pub const C_NONE : u8 = 255; +/// The parts of an operand chain_class tells apart: the kind of the last step of `a.b(c)[d]`. +pub const CK_NONE : usize = 0; +pub const CK_GROUP : usize = 1; +pub const CK_NUM : usize = 2; +pub const CK_NAME : usize = 3; +pub const CK_FIELD : usize = 4; +pub const CK_PATH : usize = 5; +pub const CK_CALL : usize = 6; +pub const CK_METHOD : usize = 7; +pub const CK_INDEX : usize = 8; /// The modules a template names; STD_USE indexes this list (0: none). The output gets a `use` of each. pub const STD_MODS : [5]str = ["", "tools::t27o::rust_std", "hosting::toolchain", "tri::mutate::plant", "tri::t27b::type_text"]; @@ -4846,6 +5149,8 @@ pub fn put_reason(src: []const u8, st: []usize, out: []u8) -> void { puts(st, out, "an owned String outside the out-buffer form"); } else if (c == R_EMPTY) { puts(st, out, "empty slice with no known element type"); + } else if (c == R_NOTYPE) { + puts(st, out, "`!` of an operand whose type t27o cannot tell"); } else if (c == R_SPLICE) { puts(st, out, "a used module's own use t27c does not splice (its type_text:: names stay unresolved, the gap specs/compiler/int_type_names.t27 records)"); } else { @@ -4900,6 +5205,33 @@ pub fn std_derive(src: []const u8, a: usize, b: usize) -> bool { return word_is(src, a, b, "Debug") or word_is(src, a, b, "Clone") or word_is(src, a, b, "Copy") or word_is(src, a, b, "PartialEq") or word_is(src, a, b, "Eq") or word_is(src, a, b, "Hash") or word_is(src, a, b, "PartialOrd") or word_is(src, a, b, "Ord") or word_is(src, a, b, "Default"); } +/// Is the attribute word src[a .. b] one the oracle harness does not carry: `no_std` / `no_main` (the harness +/// runs on the host, with std and a main of its own), or a lint level that can fail the build, `deny` / +/// `forbid` (the harness sets `#![allow(warnings)]`, and `deny(missing_docs)` reads the docs of items the +/// harness leaves out)? +pub fn host_word(src: []const u8, a: usize, b: usize) -> bool { + return word_is(src, a, b, "no_std") or word_is(src, a, b, "no_main") or word_is(src, a, b, "deny") or word_is(src, a, b, "forbid"); +} + +/// Is the inner attribute whose `[` is src[ob] one the harness drops (host_word), bare or carried by a +/// cfg_attr (`#![cfg_attr(not(test), no_std)]`: ring-090-rust's lib.rs)? Only the attributes a cfg_attr +/// applies count, not its predicate: `cfg_attr(not(no_std), allow(x))` stays (#10534). +pub fn host_attr(src: []const u8, ob: usize) -> bool { + const ia : usize = skip_ws(src, ob + 1); + const ie : usize = tok_end(src, ia); + if (host_word(src, ia, ie)) { return true; } + const lp : usize = skip_ws(src, ie); + if (!word_is(src, ia, ie, "cfg_attr") or !tok_is(src, lp, "(")) { return false; } + const rp : usize = block_end(src, lp); + var q : usize = find_at(src, lp + 1, B_COMMA); + while (q < rp) { + q = skip_ws(src, q + 1); + if (host_word(src, q, tok_end(src, q))) { return true; } + q = find_at(src, q, B_COMMA); + } + return false; +} + /// The Rust text of an item for the oracle harness: attributes dropped (serde and the like need their /// crates), `#[derive(..)]` kept with its std derives only. pub fn put_item_source(src: []const u8, st: []usize, out: []u8, start: usize, end: usize) -> void { @@ -4908,7 +5240,17 @@ pub fn put_item_source(src: []const u8, st: []usize, out: []u8, start: usize, en while (q < end) { const e : usize = tok_end(src, q); const lb : usize = skip_ws(src, e); - if (word_is(src, q, e, "#") and tok_is(src, lb, "[")) { + // a crate-level `#![no_std]` / `#![no_main]` is the target's, not the host's, and a `deny` / + // `forbid` lint level is the crate's own policy: the harness carries none of them (#10534) + if (word_is(src, q, e, "#") and tok_is(src, lb, "!") and tok_is(src, skip_ws(src, tok_end(src, lb)), "[")) { + const ob : usize = skip_ws(src, tok_end(src, lb)); + const ic : usize = block_end(src, ob); + if (host_attr(src, ob)) { + put_src(src, st, out, from, q); + from = ic; + } + q = skip_ws(src, ic); + } else if (word_is(src, q, e, "#") and tok_is(src, lb, "[")) { put_src(src, st, out, from, q); const close : usize = block_end(src, lb); const d : usize = skip_ws(src, lb + 1); @@ -6142,8 +6484,6 @@ pub fn vectors(src: []const u8, st: []usize, out: []u8, na: usize, nb: usize, pn puts(st, out, " {{\"); for l in &v { println!(\"{}\", l); } println!(\"}}\\n\"); }\n }\n"); } -/// Does the return type of the fn whose parameters open at src[pn] name `str`? - /// Does the fn whose parameters open at src[pn] return exactly an owned String (`-> String {`)? pub fn ret_is_string(src: []const u8, pn: usize) -> bool { if (!tok_is(src, pn, "(")) { return false; } @@ -7055,7 +7395,32 @@ pub fn out_str_return(src: []const u8, st: []usize, out: []u8) -> bool { return true; } -pub fn ret_has_str(src: []const u8, pn: usize) -> bool { +/// 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). +pub fn struct_text(src: []const u8, ilo: usize, ihi: usize, q: usize) -> bool { + if (q >= src.len or !is_upper(src[q])) { return false; } + var na : usize = q; + var nb : usize = tok_end(src, q); + if (word_is(src, na, nb, "Self")) { + if (ilo == ihi) { return false; } + na = ilo; + nb = ihi; + } + const o : usize = struct_open(src, na, nb); + if (o >= src.len) { return false; } + const close : usize = block_end(src, o); + var t : usize = skip_ws(src, o + 1); + while (t < close) { + if (tok_is(src, t, "str")) { return true; } + t = skip_ws(src, tok_end(src, t)); + } + return false; +} + +/// Does the return type of the fn whose parameters open at src[pn] name `str`, an owned String, or a +/// struct of this file with a text field? The oracle compares each with type_text::same. +pub fn ret_has_str(src: []const u8, ilo: usize, ihi: usize, pn: usize) -> bool { if (!tok_is(src, pn, "(")) { return false; } // an owned String held by an Option or a Result is a text the oracle samples, and so is the // out-buffer form (the fn returns how many bytes it wrote into the caller's buffer) @@ -7064,7 +7429,7 @@ pub fn ret_has_str(src: []const u8, pn: usize) -> bool { if (!tok_is(src, r, "->")) { return false; } var q : usize = skip_ws(src, tok_end(src, r)); while (q < src.len and !tok_is(src, q, "{") and !tok_is(src, q, "where") and !tok_is(src, q, ";")) { - if (tok_is(src, q, "str")) { return true; } + if (tok_is(src, q, "str") or struct_text(src, ilo, ihi, q)) { return true; } q = skip_ws(src, tok_end(src, q)); } return false; @@ -7175,7 +7540,7 @@ pub fn fn_item(src: []const u8, st: []usize, out: []u8, start: usize, is_test: b 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 - if (!is_test and ret_has_str(src, pn)) { st[NEQ] = 1; } + if (!is_test and ret_has_str(src, st[ILO], st[IHI], pn)) { st[NEQ] = 1; } if (st[MODE] == M_T27) { puts(st, out, "\n\n"); } else { st[W] = w0; } if (st[MODE] == M_SOURCE and !is_test) { put_item_source(src, st, out, start, item_end(src, start)); @@ -8638,3 +9003,123 @@ test a_line_continuation_in_a_format_string_is_joined_and_a_last_argument_may_en assert(out_is(n, &out, 0, "use tools::t27o::rust_std;\nuse tri::t27b::type_text;\n\nfn spec_(stmt: []const u8, out: []u8) -> usize {\n var t27o_w : usize = 0;\n t27o_w = rust_std::append_text(out, t27o_w, \"module m;\\npub const A : u32 = 4;\\ntest t \");\n t27o_w = rust_std::append_text(out, t27o_w, \"{\");\n t27o_w = rust_std::append_text(out, t27o_w, \"\\n \");\n t27o_w = rust_std::append_text(out, t27o_w, stmt);\n t27o_w = rust_std::append_text(out, t27o_w, \"\\n\");\n t27o_w = rust_std::append_text(out, t27o_w, \"}\");\n t27o_w = rust_std::append_text(out, t27o_w, \"\\n\");\n return t27o_w;\n}\n\nfn two(n: u32, out: []u8) -> usize {\n var t27o_w : usize = 0;\n t27o_w = rust_std::append_text(out, t27o_w, \"a b \");\n t27o_w = rust_std::append_uint(out, t27o_w, n as u64);\n t27o_w = rust_std::append_text(out, t27o_w, \" cd\");\n return t27o_w;\n}\n\n")); assert(st[NOK] == 2); } + +test an_integer_not_is_a_bitwise_not_and_a_bool_not_stays_a_boolean_one { + var out : [2048]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // bootstrap/src/host/bitmask.rs lines 13-28: `value & !m` and `value & !(1u32 << bit)` are the + // integer `!`, which t27 writes `~` (it wrote the boolean NOT: `((1u32 << bit) == false)`, a + // different function); a bool, even one named like an integer's neighbour, keeps `!` + const n : usize = body("pub const fn field_mask(high: u8, low: u8) -> u32 { 0xFFFF_FFFF << low }\n\npub const fn insert(value: u32, high: u8, low: u8, field: u32) -> u32 {\n let m = field_mask(high, low);\n (value & !m) | ((field << low) & m)\n}\n\npub const fn clear_bit(value: u32, bit: u8) -> u32 {\n value & !(1u32 << bit)\n}\n\npub fn same(a: bool, flag: bool, v: &[u8]) -> bool { !flag && !(a == flag) && !v.is_empty() }\npub fn byte(x: u8) -> u8 { !x }\npub fn flag(x: bool) -> bool { !x }\n", &out, &st); + assert(out_is(n, &out, 0, "pub fn field_mask(high: u8, low: u8) -> u32 {\n _ = high;\n return 0xFFFFFFFF << low;\n}\n\npub fn insert(value: u32, high: u8, low: u8, field: u32) -> u32 {\n const m = field_mask(high, low);\n return (value & ~m) | ((field << low) & m);\n}\n\npub fn clear_bit(value: u32, bit: u8) -> u32 {\n return value & ~(@as(u32, 1) << bit);\n}\n\npub fn same(a: bool, flag: bool, v: []const u8) -> bool {\n return !flag and ((a == flag) == false) and !(v.len == 0);\n}\n\npub fn byte(x: u8) -> u8 {\n return ~x;\n}\n\npub fn flag(x: bool) -> bool {\n return !x;\n}\n\n")); + assert(st[NOK] == 6); +} + +test negative_control_a_bang_on_an_operand_t27o_cannot_class_is_refused_and_says_so { + var out : [1024]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // a bare literal takes its type from where it stands (`x & !0` is a u32 NOT only because x is one), + // a fn of another file has a return type this file does not show, and a `for` binding has no + // declared type: none is guessed, none is rewritten as a boolean NOT + const n : usize = body("fn a(x: u32) -> u32 { x & !0 }\nfn b(x: u32) -> bool { !other(x) }\nfn c(v: &[bool]) -> bool { for b in v { if !b { return false; } } true }\n", &out, &st); + assert(out_text_has(&out, n, "refused fn a (line 1): `!` of an operand whose type t27o cannot tell `0` at line 1")); + assert(out_text_has(&out, n, "refused fn b (line 2): `!` of an operand whose type t27o cannot tell `other(x)` at line 2")); + assert(out_text_has(&out, n, "refused fn c (line 3): `!` of an operand whose type t27o cannot tell `b` at line 3")); + assert(!out_text_has(&out, n, "== false")); + assert(st[NOK] == 0); +} + +/// bootstrap/src/host/bitmask.rs `clear_bit` and `insert`'s `!m` as t27o now writes them. +fn pin_clear_bit(value: u32, bit: u8) -> u32 { + return value & ~(@as(u32, 1) << bit); +} + +fn pin_clear_mask(value: u32, m: u32) -> u32 { + return value & ~m; +} + +test bitwise_not_keeps_the_parentheses_of_its_operand { + // the integer `!` is written `~(x)`; this runs under t27b test and t27c test-report (through + // calls, so t27c counts runtime asserts and not folded ones), so a backend that dropped the + // parentheses (t27c's Zig backend did for `!(x)`, #6549) would read `~1 << 3` and fail here + assert(pin_clear_bit(0xFF, 3) == 0xF7); + assert(pin_clear_bit(0xFF, 0) == 0xFE); + assert(pin_clear_bit(0xFFFFFFFF, 31) == 0x7FFFFFFF); + assert(pin_clear_mask(0xFF, 0x0F) == 0xF0); +} + +test a_struct_result_with_a_text_field_makes_the_spec_use_type_text { + var out : [2048]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // bootstrap/src/host/bitmask.rs: `FieldDesc::new -> Self` returns a struct whose `name` is a + // `&'static str`; the oracle test appended by --check compares it with type_text::same, so the + // spec must `use` it even though no converted body does (the t27b / t27c check said + // `type_text` was undeclared) + const n : usize = body("pub struct F { pub name: &'static str, pub n: u8 }\nimpl F {\n pub const fn new(name: &'static str, n: u8) -> Self { Self { name, n } }\n}\npub fn mk(n: u8) -> F { F::new(\"a\", n) }\n", &out, &st); + assert(out_is(n, &out, 0, "use tri::t27b::type_text;\n\npub const F = struct {\n name : []const u8,\n n : u8,\n};\n\npub fn F_new(name: []const u8, n: u8) -> F {\n return F{ .name = name, .n = n };\n}\n\npub fn mk(n: u8) -> F {\n return F_new(\"a\", n);\n}\n\n")); +} + +test negative_control_a_struct_result_without_a_text_field_adds_no_use { + var out : [2048]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // the same shape with a number for the field: nothing the oracle compares with type_text, so no + // `use`; and a text field no fn returns adds none either + const n : usize = body("pub struct G { pub n: u8 }\nimpl G {\n pub const fn new(n: u8) -> Self { Self { n } }\n}\npub struct H { pub name: &'static str }\n", &out, &st); + assert(out_is(n, &out, 0, "pub const G = struct {\n n : u8,\n};\n\npub fn G_new(n: u8) -> G {\n return G{ .n = n };\n}\n\npub const H = struct {\n name : []const u8,\n};\n\n")); +} + +test the_oracle_harness_drops_a_crate_level_no_std_no_main_deny_and_forbid { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // rings/ring-090-rust/src/lib.rs opens with `#![cfg_attr(not(test), no_std)]`; the harness is built + // by plain rustc (not --test), so the attribute applied and format! / println! / Vec / String + // vanished (415 errors). The host has std and the harness has its own main: both go. So do the + // lib's `deny(missing_docs)` and `forbid(..)`: the harness leaves out the docs they read (30 more + // errors once std was back). The crate's other attributes stay + const n : usize = oracle("#![forbid(unsafe_code)]\n#![cfg_attr(not(test), no_std)]\n#![no_main]\n#![deny(missing_docs)]\n#![allow(dead_code)]\npub fn f(x: u32) -> u32 { x + 1 }\n", &out, &st); + assert(out_text_has(&out, n, "#![allow(dead_code)]")); + assert(out_text_has(&out, n, "pub fn f(x: u32) -> u32 { x + 1 }")); + assert(!out_text_has(&out, n, "no_std")); + assert(!out_text_has(&out, n, "no_main")); + assert(!out_text_has(&out, n, "cfg_attr")); + assert(!out_text_has(&out, n, "missing_docs")); + assert(!out_text_has(&out, n, "unsafe_code")); + const m : usize = oracle("#![no_std]\n#![cfg_attr(feature = \"alloc\", allow(dead_code), no_std, deny(missing_docs))]\npub fn f(x: u32) -> u32 { x + 1 }\n", &out, &st); + assert(!out_text_has(&out, m, "no_std")); + assert(!out_text_has(&out, m, "missing_docs")); + assert(out_text_has(&out, m, "pub fn f(x: u32) -> u32 { x + 1 }")); +} + +test negative_control_an_attribute_that_only_names_a_dropped_word_stays_in_the_harness { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // `no_std` or `deny` as a cfg predicate, or inside a text, is not the attribute: only what a + // cfg_attr applies, or a bare attribute, is dropped; `warn` and `allow` are no lint level that + // can fail the build + const n : usize = oracle("#![cfg_attr(not(no_std), allow(dead_code))]\n#![cfg_attr(feature = \"deny\", allow(unused))]\n#![doc = \"no_std\"]\n#![warn(missing_docs)]\npub fn f(x: u32) -> u32 { x + 1 }\n", &out, &st); + assert(out_text_has(&out, n, "#![cfg_attr(not(no_std), allow(dead_code))]")); + assert(out_text_has(&out, n, "#![cfg_attr(feature = \"deny\", allow(unused))]")); + assert(out_text_has(&out, n, "#![doc = \"no_std\"]")); + assert(out_text_has(&out, n, "#![warn(missing_docs)]")); +} + +test the_operand_of_a_bang_may_be_a_tuple_field_a_let_owned_field_or_a_method_a_test_shares_a_name_with { + var out : [2048]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // bootstrap/src/host/capflags.rs `!self.0` (a newtype over u32), a local built by `Cfg::new()` + // whose field is read, and a method `is_on` beside a unit test fn of the same name + const n : usize = body("pub struct Caps(u32);\npub struct Cfg { pub on: bool, pub bits: u8 }\nimpl Cfg {\n pub fn new() -> Self { Cfg { on: true, bits: 3 } }\n pub fn is_on(&self) -> bool { self.on }\n}\nimpl Caps {\n pub fn none(&self) -> u32 { !self.0 }\n pub fn mask(&self, m: u32) -> u32 { self.0 & !m }\n}\npub fn live() -> bool { let c = Cfg::new(); !c.on }\npub fn bits() -> u8 { let c = Cfg::new(); !c.bits }\npub fn pick(c: &Cfg) -> bool { !c.is_on() }\nfn is_on() { }\npub fn both(c: &Cfg) -> bool { !c.is_on() && !c.on }\n", &out, &st); + assert(out_is(n, &out, 0, "pub const Caps = struct {\n _0 : u32,\n};\n\npub const Cfg = struct {\n on : bool,\n bits : u8,\n};\n\npub fn Cfg_new() -> Cfg {\n return Cfg{ .on = true, .bits = 3 };\n}\n\npub fn Cfg_is_on(self: Cfg) -> bool {\n return self.on;\n}\n\npub fn Caps_none(self: Caps) -> u32 {\n return ~self._0;\n}\n\npub fn Caps_mask(self: Caps, m: u32) -> u32 {\n return self._0 & ~m;\n}\n\npub fn live() -> bool {\n const c = Cfg_new();\n return !c.on;\n}\n\npub fn bits() -> u8 {\n const c = Cfg_new();\n return ~c.bits;\n}\n\npub fn pick(c: Cfg) -> bool {\n return !Cfg_is_on(c);\n}\n\nfn is_on() -> void {\n}\n\npub fn both(c: Cfg) -> bool {\n return !Cfg_is_on(c) and !c.on;\n}\n\n")); + assert(st[NOK] == 9); +} + +test negative_control_a_method_two_types_share_and_a_field_of_an_untyped_local_stay_refused { + var out : [2048]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // `dup` returns a bool for Cfg and a u32 for Other, so a call of it says nothing; `s` is built from + // a call whose return type this file does not show, so its field `on` is nobody's + const n : usize = body("pub struct Cfg { pub on: bool }\nimpl Cfg {\n pub fn dup(&self) -> bool { self.on }\n}\npub struct Other { pub n: u32 }\nimpl Other {\n pub fn dup(&self) -> u32 { self.n }\n}\npub fn a(c: &Cfg) -> bool { !c.dup() }\npub fn b(v: &[u8]) -> bool { let s = first(v).unwrap(); !s.on }\n", &out, &st); + assert(out_text_has(&out, n, "refused fn a (line 9): `!` of an operand whose type t27o cannot tell `c.dup()` at line 9")); + assert(out_text_has(&out, n, "refused fn b (line 10): `!` of an operand whose type t27o cannot tell `s.on` at line 10")); + assert(!out_text_has(&out, n, "== false")); +}