From 851b2e50fb2c33f6c2504a7b75793b58474c6bb0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 18:54:13 +0700 Subject: [PATCH 1/3] t27o: a static mut is named by its name, not by the mut before it (Closes #10829) const_item read the item name from the token it stopped at, which for a static mut is the keyword mut: the refused item went into the name table as mut, and every later fn that spells mut (a let mut, a &mut) was refused with it. item_named had the same blind spot for declared_type (a static mut S declared mut, not S), and index_decls added both tokens. const_item now steps over the mut before it reads the name, item_named skips a mut after const and static the way const_hit already did, and index_decls enters only the name. The refusal names COUNT, a fn that spells mut converts, and a fn that reads COUNT is refused as a user of a refused name, not as one that reads an undeclared name. The test that pinned the defect (known_defect_a_static_mut_is_named_mut_...) now states the fixed behaviour; it failed on master before the change. Co-Authored-By: Claude Sonnet 5.5 --- .trinity/seals/t27o_T27oRust.json | 14 ++++---- gen/rust/tools/t27o/rust_convert.rs | 15 +++++---- specs/tools/t27o/rust_convert.t27 | 51 +++++++++++++++++++---------- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/.trinity/seals/t27o_T27oRust.json b/.trinity/seals/t27o_T27oRust.json index 70eccd8fb1..6e543e01ac 100644 --- a/.trinity/seals/t27o_T27oRust.json +++ b/.trinity/seals/t27o_T27oRust.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+ea35d9b72", + "built_by": "t27c-bootstrap@0.5.2+b258b9007", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:d4ad54395165650f1142310418c8b75baf5da83eb167369360e555618fab54a3", - "gen_hash_rust": "sha256:812e54a96c93a3f0a028bda32358e7bbad97b82296c4e68dbdf8873c869abfeb", - "gen_hash_verilog": "sha256:781df430b1e5fefc13499c7997f25753f6290cdf14dd10856cbb126068fa8cd4", - "gen_hash_zig": "sha256:18a94a5f6a080c4db768f632a788e0b390de811a2525fdbd6537778191b8cd1f", + "gen_hash_c": "sha256:af870555c0d282f37d9101aba6c40a8b527e6e2d546fb77fe34dcea6996f29f6", + "gen_hash_rust": "sha256:03a3b28498b247b9e1875391224dccf71b9d1115e92e0134129fddc7cdfbad59", + "gen_hash_verilog": "sha256:68fbd8d3e233425fcdd2a4ba8230f85547e9582a4ac1b927cfc5f3f45f8dda2b", + "gen_hash_zig": "sha256:5cab7fcd2b199f084e3e04937a92e17a6bd1b61e3936dea99b7ede8f7877c50c", "module": "T27oRust", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T09:59:57Z", + "sealed_at": "2026-10-19T11:52:15Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:f95769587da94282b6fb0c99d2e72288c10d5381f0393cbe315e3a334cef7fd1", + "spec_hash": "sha256:c32ab3476c932b0bea6b33551e0414f6c3d42d25c63873ba9fa9e1931ebcb558", "spec_path": "specs/tools/t27o/rust_convert.t27", "tests": { "failed": 0, diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index 833a3c4e58..dbce360e5a 100644 --- a/gen/rust/tools/t27o/rust_convert.rs +++ b/gen/rust/tools/t27o/rust_convert.rs @@ -1169,15 +1169,12 @@ pub fn index_decls(src: &[u8], st: &mut [usize]) -> () { let e: usize = tok_end(src, q); if item_kw(src, q, e) { let mut n: usize = skip_ws(src, e); + if ((word_is(src, q, e, b"const") || word_is(src, q, e, b"static")) && tok_is(src, n, b"mut")) { + n = skip_ws(src, tok_end(src, n)); + } if !(ix_add(src, st, IXK_ITEM, n, tok_end(src, n), q)) { whole = false; } - if ((word_is(src, q, e, b"const") || word_is(src, q, e, b"static")) && tok_is(src, n, b"mut")) { - let m: usize = skip_ws(src, tok_end(src, n)); - if !(ix_add(src, st, IXK_ITEM, m, tok_end(src, m), q)) { - whole = false; - } - } } else { if word_is(src, q, e, b"fn") { let mut n: usize = skip_ws(src, e); @@ -1363,7 +1360,10 @@ pub fn item_named(src: &[u8], q: usize, a: usize, b: usize) -> bool { if !(item_kw(src, q, e)) { return false; } - let n: usize = skip_ws(src, e); + let mut n: usize = skip_ws(src, e); + if ((word_is(src, q, e, b"const") || word_is(src, q, e, b"static")) && tok_is(src, n, b"mut")) { + n = skip_ws(src, tok_end(src, n)); + } return same_text(src, n, tok_end(src, n), a, b); } @@ -10908,6 +10908,7 @@ pub fn const_item(src: &[u8], st: &mut [usize], out: &mut [u8], start: usize, is step(src, st); if at(src, st[(P) as usize], b"mut") { refuse_here(src, st, R_ITEM); + step(src, st); } let na: usize = st[(P) as usize]; let nb: usize = tok_end(src, na); diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index cf01e00c74..3fbf7ee4cf 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -1062,12 +1062,9 @@ pub fn index_decls(src: []const u8, st: []usize) -> void { while (q < src.len and whole) { const e : usize = tok_end(src, q); if (item_kw(src, q, e)) { - const n : usize = skip_ws(src, e); + var n : usize = skip_ws(src, e); + if ((word_is(src, q, e, "const") or word_is(src, q, e, "static")) and tok_is(src, n, "mut")) { n = skip_ws(src, tok_end(src, n)); } if (!ix_add(src, st, IXK_ITEM, n, tok_end(src, n), q)) { whole = false; } - if ((word_is(src, q, e, "const") or word_is(src, q, e, "static")) and tok_is(src, n, "mut")) { - const m : usize = skip_ws(src, tok_end(src, n)); - if (!ix_add(src, st, IXK_ITEM, m, tok_end(src, m), q)) { whole = false; } - } } else if (word_is(src, q, e, "fn")) { const n : usize = skip_ws(src, e); if (!ix_add(src, st, IXK_FN, n, tok_end(src, n), q)) { whole = false; } @@ -1228,11 +1225,13 @@ pub fn assoc_declared(src: []const u8, st: []usize, ilo: usize, ihi: usize, oa: return false; } -/// Is the keyword token at src[q] one that declares the item src[a .. b] (the name right after it)? +/// Is the keyword token at src[q] one that declares the item src[a .. b] (the name right after it; after the +/// `mut` of a `static mut` or `const mut`)? pub fn item_named(src: []const u8, q: usize, a: usize, b: usize) -> bool { const e : usize = tok_end(src, q); if (!item_kw(src, q, e)) { return false; } - const n : usize = skip_ws(src, e); + var n : usize = skip_ws(src, e); + if ((word_is(src, q, e, "const") or word_is(src, q, e, "static")) and tok_is(src, n, "mut")) { n = skip_ws(src, tok_end(src, n)); } return same_text(src, n, tok_end(src, n), a, b); } @@ -9365,7 +9364,10 @@ pub fn const_item(src: []const u8, st: []usize, out: []u8, start: usize, is_pub: st[FLO] = kpos; put_docs(src, st, out, start); step(src, st); - if (at(src, st[P], "mut")) { refuse_here(src, st, R_ITEM); } + if (at(src, st[P], "mut")) { + refuse_here(src, st, R_ITEM); + step(src, st); + } const na : usize = st[P]; const nb : usize = tok_end(src, na); step(src, st); @@ -12295,25 +12297,38 @@ test item_named_checks_the_name_right_after_an_item_keyword { assert(!item_named(src, k, k, tok_end(src, k))); } -test known_defect_a_static_mut_is_named_mut_by_item_named_and_const_item { - // #10829: the name after `static mut` is stepped over. item_named takes the `mut` for the name, and a - // refused static mut is recorded as `mut`, so every later fn that spells `mut` is refused with it. - // When that is fixed these asserts flip (item_named true for S, NOK 1). +test a_static_mut_is_named_by_its_name_not_by_the_mut_before_it { + // #10829: `static mut S` declares S. The name is the token after the `mut`; a refused static mut is + // recorded as S, so a fn that spells `mut` (a `let mut`, a `&mut`) is not refused with it. const src : str = "static mut S: u8 = 0;\n"; const s : usize = find_tok(src, 0, "S"); const mt : usize = find_tok(src, 0, "mut"); - assert(!item_named(src, 0, s, tok_end(src, s))); - assert(item_named(src, 0, mt, tok_end(src, mt))); + assert(item_named(src, 0, s, tok_end(src, s))); + assert(!item_named(src, 0, mt, tok_end(src, mt))); + assert(item_named("const mut K: u8 = 1;", 0, 10, 11)); + assert(!item_named("const mut K: u8 = 1;", 0, 6, 9)); + assert(item_named("static T: u8 = 1;", 0, 7, 8)); var out : [512]u8 = undefined; var st : [1024]usize = [0] ** 1024; const src2 : str = "static mut COUNT: u32 = 0;\nfn sum(n: u32) -> u32 { let mut t = 0u32; t }"; body(src2, &out, &st); assert(st[NFN] == 1); - assert(st[NOK] == 0); + assert(st[NOK] == 1); const m2 : usize = find_tok(src2, 0, "mut"); const c2 : usize = find_tok(src2, 0, "COUNT"); - assert(named(src2, &st, K_ITEM, m2, m2 + 3)); - assert(!named(src2, &st, K_ITEM, c2, c2 + 5)); + assert(!named(src2, &st, K_ITEM, m2, m2 + 3)); + assert(named(src2, &st, K_ITEM, c2, c2 + 5)); + assert(declared_type(src2, &st, c2, c2 + 5)); + assert(!declared_type(src2, &st, m2, m2 + 3)); + // the refusal names COUNT, and a fn that reads COUNT is refused as a user of a refused name, + // not as one that reads an undeclared name + const src3 : str = "static mut COUNT: u32 = 0;\nfn sum(n: u32) -> u32 { let mut t = 0u32; t }\nfn cnt() -> u32 { COUNT }"; + clear(&st); + var big : [2048]u8 = undefined; + const n3 : usize = body(src3, &big, &st); + assert(st[NFN] == 2); + assert(st[NOK] == 1); + assert(out_is(n3, &big, 0, "// t27o: refused const COUNT (line 1): item `mut` at line 1\n\nfn sum(n: u32) -> u32 {\n _ = n;\n var t : u32 = 0;\n return t;\n}\n\n// t27o: refused fn cnt (line 3): uses refused `COUNT` at line 3\n\nfn cnt() -> u32 {\n}\n\n")); } test call_keyword_names_the_keywords_that_may_stand_before_a_paren { @@ -19159,7 +19174,7 @@ test const_item_struct_item_and_enum_item_write_the_declarations { clear(&st); st[P] = 0; const_item("static mut S: u8 = 0;", &st, &out, 0, false); - assert(out_is(st[W], &out, 0, "// t27o: refused const mut (line 1): item `mut` at line 1\n\n")); + assert(out_is(st[W], &out, 0, "// t27o: refused const S (line 1): item `mut` at line 1\n\n")); clear(&st); const k : str = "const G: Ghost = 1;"; st[P] = 0; From b1077e981f599f58abefe1745cc796e424bc0e18 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 19:00:40 +0700 Subject: [PATCH 2/3] t27o: str_chain opens with a text field of the head name's struct, so let t = n.value.trim() binds a text (Closes #10834) The field branch of str_chain asked for a ( right after the field name, i.e. node.value( -- a method call, not a field -- so for node.value.trim() the token after value is a dot, the branch was never taken, base stayed false and the binding was no text. recv_class reads the same step with !tok_is(.., "("); str_chain now does too. let t = n.value.trim(); t.starts_with("a") converts like the inline n.value.trim().starts_with("a"): t is a text and takes the text entry of the std table. A field of another type, a field the struct does not have and a method call (x.value(), x.trim()) are still no text chain. The test that pinned the defect (known_defect_str_chain_...) now states the fixed behaviour with fourteen heads and the end-to-end repro of the issue; it failed on master before the change. Co-Authored-By: Claude Sonnet 5.5 --- .trinity/seals/t27o_T27oRust.json | 12 ++--- gen/rust/tools/t27o/rust_convert.rs | 2 +- specs/tools/t27o/rust_convert.t27 | 70 +++++++++++++++++++++-------- 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/.trinity/seals/t27o_T27oRust.json b/.trinity/seals/t27o_T27oRust.json index 6e543e01ac..f6c041f59c 100644 --- a/.trinity/seals/t27o_T27oRust.json +++ b/.trinity/seals/t27o_T27oRust.json @@ -2,16 +2,16 @@ "built_by": "t27c-bootstrap@0.5.2+b258b9007", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:af870555c0d282f37d9101aba6c40a8b527e6e2d546fb77fe34dcea6996f29f6", - "gen_hash_rust": "sha256:03a3b28498b247b9e1875391224dccf71b9d1115e92e0134129fddc7cdfbad59", - "gen_hash_verilog": "sha256:68fbd8d3e233425fcdd2a4ba8230f85547e9582a4ac1b927cfc5f3f45f8dda2b", - "gen_hash_zig": "sha256:5cab7fcd2b199f084e3e04937a92e17a6bd1b61e3936dea99b7ede8f7877c50c", + "gen_hash_c": "sha256:593e9958f7ac5f0bd3b134fb79e067d06e887ad4593529b23d33b6126d4bcc74", + "gen_hash_rust": "sha256:c49c31c6b18cf4c645664a41cc7b6ba807a2be10103cfe9449cbd4de12631cb6", + "gen_hash_verilog": "sha256:5b29d177cc61da4a0ffe65cfbc1d6e07ee7c921dc960da2f308eedec556d75ae", + "gen_hash_zig": "sha256:773a641feb53116d2847b58dea68e4906dae1c83f7d37f71c9e23827dbcea4fa", "module": "T27oRust", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T11:52:15Z", + "sealed_at": "2026-10-19T12:00:10Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:c32ab3476c932b0bea6b33551e0414f6c3d42d25c63873ba9fa9e1931ebcb558", + "spec_hash": "sha256:685698d58e5ba1c9e213cc3789e3f2863e13ee5e6d8503d69f37aa36bfc89347", "spec_path": "specs/tools/t27o/rust_convert.t27", "tests": { "failed": 0, diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index dbce360e5a..9ddfde9fd9 100644 --- a/gen/rust/tools/t27o/rust_convert.rs +++ b/gen/rust/tools/t27o/rust_convert.rs @@ -3305,7 +3305,7 @@ pub fn str_chain(src: &[u8], st: &mut [usize], a: usize, b: usize) -> bool { if (!(base) && tok_is(src, nx, b".")) { let f: usize = skip_ws(src, (nx + 1)); let fe: usize = tok_end(src, f); - if ((((f < src.len()) && is_alpha(src[(f) as usize])) && (fe <= b)) && tok_is(src, skip_ws(src, fe), b"(")) { + if ((((f < src.len()) && is_alpha(src[(f) as usize])) && (fe <= b)) && !(tok_is(src, skip_ws(src, fe), b"("))) { base = (field_of_class(src, st, a, e, f, fe) == C_STR); c = skip_ws(src, fe); } diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index 3fbf7ee4cf..8634f6c920 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -2895,7 +2895,8 @@ pub fn field_of_class(src: []const u8, st: []usize, xa: usize, xe: usize, fa: us /// Does the initializer src[a .. b] (a `let`'s right side, b its `;`) start at a text and keep it one: /// every method of the chain is text-keeping (trim, trim_start, trim_end, as_str, clone, chars, -/// trim_start_matches, trim_end_matches)? FR-002's carry: `let v = s.trim(); v.starts_with(..)`. +/// trim_start_matches, trim_end_matches)? FR-002's carry: `let v = s.trim(); v.starts_with(..)`. The chain may +/// open with a text field of the head name's own type (`let v = node.value.trim();`), as recv_class reads it. pub fn str_chain(src: []const u8, st: []usize, a: usize, b: usize) -> bool { if (a >= src.len or !is_alpha(src[a])) { return false; } const e : usize = tok_end(src, a); @@ -2905,7 +2906,7 @@ pub fn str_chain(src: []const u8, st: []usize, a: usize, b: usize) -> bool { if (!base and tok_is(src, nx, ".")) { const f : usize = skip_ws(src, nx + 1); const fe : usize = tok_end(src, f); - if (f < src.len and is_alpha(src[f]) and fe <= b and tok_is(src, skip_ws(src, fe), "(")) { + if (f < src.len and is_alpha(src[f]) and fe <= b and !tok_is(src, skip_ws(src, fe), "(")) { base = field_of_class(src, st, a, e, f, fe) == C_STR; c = skip_ws(src, fe); } @@ -14158,24 +14159,55 @@ test str_chain_follows_a_text_through_the_text_keeping_methods { assert(!str_chain(src2, &st, a2 + 1, a2 + 8)); } -test known_defect_str_chain_does_not_open_with_a_text_field { - // #10834: str_chain asks for a `(` after the field name, so `x.value.trim()` (a text field of x's struct, - // trimmed) is not a text chain here although recv_class reads the same chain as one; the `let t = x.value.trim();` - // binding is then no text. When that is fixed the first assert flips. - const head : str = "struct Tx { value: &'static str }\nfn f(x: &Tx) { let t = "; - var buf : [256]u8 = [0] ** 256; - var bs : [64]usize = [0] ** 64; - puts(&bs, &buf, head); - const a : usize = bs[W]; - puts(&bs, &buf, "x.value.trim()"); - const b : usize = bs[W]; - puts(&bs, &buf, "; }"); - const src : []const u8 = buf[0 .. bs[W]]; +test str_chain_opens_with_a_text_field_of_the_head_names_struct { + // #10834: the chain may open with a text field of the head name's own type -- `x.value.trim()` of an + // `x: &Tx` whose `value` is a text is a text chain, as recv_class reads the same chain; a field of + // another type, a field the struct does not have and a method call are not. + const head : str = "struct Tx { value: &'static str, n: u32 }\nfn f(x: &Tx, s: &str) { let t = "; + const inits : [14]str = ["x.value", "x.value.trim()", "x.value.trim().trim_start()", "x.value.chars()", "x . value . trim ( )", "x.value.len()", "x.n", "x.n.trim()", "x.trim()", "x.value()", "x.value.trim", "x.gone.trim()", "s.trim()", "x.value.trim().len()"]; + const want : [14]bool = [true, true, true, true, true, false, false, false, false, false, false, false, true, false]; + var buf : [1024]u8 = [0] ** 1024; var st : [1024]usize = [0] ** 1024; - st[FLO] = find_tok(src, 0, "f") - 3; - assert(!str_chain(src, &st, a, b)); - assert(field_of_class(src, &st, a, a + 1, a + 2, a + 7) == C_STR); - assert(recv_class(src, &st, a, b) == C_STR); + var i : usize = 0; + while (i < 14) { + var bs : [64]usize = [0] ** 64; + puts(&bs, &buf, head); + const a : usize = bs[W]; + puts(&bs, &buf, inits[i]); + const b : usize = bs[W]; + puts(&bs, &buf, "; }"); + const src : []const u8 = buf[0 .. bs[W]]; + clear(&st); + st[FLO] = find_tok(src, 0, "f") - 3; + assert(str_chain(src, &st, a, b) == want[i]); + i = i + 1; + } + var bs2 : [64]usize = [0] ** 64; + puts(&bs2, &buf, head); + const a2 : usize = bs2[W]; + puts(&bs2, &buf, "x.value.trim(); }"); + const src2 : []const u8 = buf[0 .. bs2[W]]; + clear(&st); + st[FLO] = find_tok(src2, 0, "f") - 3; + assert(str_chain(src2, &st, a2, a2 + 14)); + assert(field_of_class(src2, &st, a2, a2 + 1, a2 + 2, a2 + 7) == C_STR); + assert(recv_class(src2, &st, a2, a2 + 14) == C_STR); + // the binding is a text: `t.starts_with(..)` takes the text entry of the std table, as the inline chain does + var out : [2048]u8 = undefined; + var st3 : [1024]usize = [0] ** 1024; + const n3 : usize = body("pub struct Node { pub value: &'static str }\npub fn f(n: &Node) -> bool { let t = n.value.trim(); t.starts_with(\"a\") }\npub fn g(n: &Node) -> bool { n.value.trim().starts_with(\"a\") }", &out, &st3); + assert(st3[NFN] == 2); + assert(st3[NOK] == 2); + const trimmed : str = "n.value[type_text::trim_lo(n.value, 0, n.value.len) .. type_text::trim_hi(n.value, type_text::trim_lo(n.value, 0, n.value.len), n.value.len)]"; + var spec_text : [2048]u8 = undefined; + var ws : [64]usize = [0] ** 64; + puts(&ws, &spec_text, "use hosting::toolchain;\nuse tri::t27b::type_text;\n\npub const Node = struct {\n value : []const u8,\n};\n\npub fn f(n: Node) -> bool {\n const t = "); + puts(&ws, &spec_text, trimmed); + puts(&ws, &spec_text, ";\n return toolchain::starts_with(t, \"a\");\n}\n\npub fn g(n: Node) -> bool {\n return toolchain::starts_with("); + puts(&ws, &spec_text, trimmed); + puts(&ws, &spec_text, ", \"a\");\n}\n\n"); + assert(n3 == ws[W]); + assert(out_is(n3, &out, 0, spec_text[0 .. ws[W]])); } test head_struct_names_the_struct_a_receiver_is_of { From 48b379beedc89a96ad65d793fb1070a5cb4f2206 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 19:06:49 +0700 Subject: [PATCH 3/3] t27o: int_ok answers for an unsigned type of 63 bits or more without computing 2^64, so usize / u64 / u128 parameters are sampled from the body (Closes #10864) int_ok asked an unsigned type for m < bit(w), and bit(64) is 2^64: one doubling past what a usize holds. Under t27b and t27c test-report the call trapped (integer overflow, * on u64); in the compiled converter it wrapped to 0, so m < 0 was false for every sample. Either way a usize, u64 or u128 parameter got none of the body-derived samples of #9102 (the signed 64-bit types and every narrower type were fine). Every sample num_value returns is below 2^62 and the samples beside it are at most 2^62, so an unsigned type of 63 bits or more holds them all; int_ok now says so without computing bit(w). A negative sample is still no sample of an unsigned type, and the narrower widths and the signed types are unchanged. Oracle of fn g(x: usize) -> bool { x == 5 }, and u64 and u128 likewise: before, 0 1 2 7 10 100 18446744073709551615 only; now 4 5 6 follow, as they do for u32. The int_ok test holds the 64-bit unsigned cases (a direct call trapped on master), and a new test walks derived_at and dcount for u32, i64, u64, usize, u128, i128 and isize. Co-Authored-By: Claude Sonnet 5.5 --- .trinity/seals/t27o_T27oRust.json | 16 +++---- gen/rust/tools/t27o/rust_convert.rs | 10 +++- specs/tools/t27o/rust_convert.t27 | 71 ++++++++++++++++++++++++++++- 3 files changed, 85 insertions(+), 12 deletions(-) diff --git a/.trinity/seals/t27o_T27oRust.json b/.trinity/seals/t27o_T27oRust.json index f6c041f59c..f57a7506e5 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:593e9958f7ac5f0bd3b134fb79e067d06e887ad4593529b23d33b6126d4bcc74", - "gen_hash_rust": "sha256:c49c31c6b18cf4c645664a41cc7b6ba807a2be10103cfe9449cbd4de12631cb6", - "gen_hash_verilog": "sha256:5b29d177cc61da4a0ffe65cfbc1d6e07ee7c921dc960da2f308eedec556d75ae", - "gen_hash_zig": "sha256:773a641feb53116d2847b58dea68e4906dae1c83f7d37f71c9e23827dbcea4fa", + "gen_hash_c": "sha256:b4e7ec08069e19be5797b930c0ea253f6142b58bd123bf16ca3bf6c59c64fba8", + "gen_hash_rust": "sha256:3a7fd83dc3d1ac9245fc22a556457988333908b8812b1b9a9f63feedc92430d3", + "gen_hash_verilog": "sha256:d37444f6ae63399bfede14a121f6a011406a4a8e934f7f56c9e780f2439f63c2", + "gen_hash_zig": "sha256:84e13ecf8b026abac4e9b9d9f1f579c5d348adeb92832955c1755f7a98ae0095", "module": "T27oRust", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T12:00:10Z", + "sealed_at": "2026-10-19T13:57:04Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:685698d58e5ba1c9e213cc3789e3f2863e13ee5e6d8503d69f37aa36bfc89347", + "spec_hash": "sha256:88af40441411b4a2cf839b0024cbae7a5ac47d7e5ccaaeb15cd611c4d60eac9c", "spec_path": "specs/tools/t27o/rust_convert.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 332, - "total": 332, + "passed": 333, + "total": 333, "vacuous": 0 }, "toolchain": { diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index 9ddfde9fd9..4f442bd3df 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: 332 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 333 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. @@ -8488,7 +8488,13 @@ pub fn int_ok(mut w: usize, sg: bool, m: usize, neg: bool) -> bool { w = 64; } if !(sg) { - return (!(neg) && (m < bit(w))); + if neg { + return false; + } + if (w >= 63) { + return true; + } + return (m < bit(w)); } if neg { return (m <= bit((w - 1))); diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index 8634f6c920..29674f1215 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -7407,10 +7407,17 @@ pub fn int_type_signed(src: []const u8, tq: usize) -> bool { } /// Does an integer type of `w` bits (signed when `sg`) hold the sample m, or -m? Widths past 64 read -/// as 64: every sample this file can build fits both, so no range is widened by the clamp. +/// as 64: every sample this file can build fits both, so no range is widened by the clamp. An unsigned +/// type of 63 bits or more holds every non-negative sample (num_value stops below 2^62, the samples +/// beside it are at most 2^62), so its range is not computed: bit(64) is 2^64, one doubling past a usize +/// (#10864: u64, usize and u128 got no sample taken from the body). pub fn int_ok(w: usize, sg: bool, m: usize, neg: bool) -> bool { if (w > 64) { w = 64; } - if (!sg) { return !neg and m < bit(w); } + if (!sg) { + if (neg) { return false; } + if (w >= 63) { return true; } + return m < bit(w); + } if (neg) { return m <= bit(w - 1); } return m < bit(w - 1); } @@ -18042,6 +18049,66 @@ test int_type_width_int_type_signed_and_int_ok_say_what_an_integer_type_holds { assert(int_ok(64, true, 4611686018427387903, true)); assert(int_ok(128, true, 5, false)); assert(int_ok(128, true, 5, true)); + // #10864: u64, usize and u128 are 64 bits wide here; their 2^64 is one doubling past what a usize holds, + // so int_ok decides them without it -- every sample is below 2^62 + 2 + assert(int_ok(64, false, 0, false)); + assert(int_ok(64, false, 5, false)); + assert(int_ok(64, false, 4611686018427387903, false)); + assert(int_ok(64, false, 4611686018427387904, false)); + assert(!int_ok(64, false, 0, true)); + assert(!int_ok(64, false, 5, true)); + assert(int_ok(128, false, 5, false)); + assert(!int_ok(128, false, 5, true)); + assert(int_ok(63, false, 4611686018427387904, false)); + assert(int_ok(32, false, 4294967295, false)); + assert(!int_ok(32, false, 4294967296, false)); +} + +test a_64_bit_unsigned_parameter_is_sampled_from_its_body_like_the_narrower_ones { + // #10864: `x == 5` names 4, 5 and 6 whatever the width of x; a usize / u64 / u128 got none of them (the + // 2^64 of its range overflowed), and an i64 / u32 got all three. + const tys : [7]str = ["u32", "i64", "u64", "usize", "u128", "i128", "isize"]; + const kinds : [7]usize = [V_UNSIGNED, V_SIGNED, V_UNSIGNED, V_UNSIGNED, V_UNSIGNED, V_SIGNED, V_SIGNED]; + var buf : [128]u8 = [0] ** 128; + var i : usize = 0; + while (i < 7) { + var bs : [64]usize = [0] ** 64; + puts(&bs, &buf, "fn g(x: "); + const tq : usize = bs[W]; + puts(&bs, &buf, tys[i]); + puts(&bs, &buf, ") -> bool { x == 5 }"); + const src : []const u8 = buf[0 .. bs[W]]; + var st : [1024]usize = [0] ** 1024; + const lb : usize = find_at(src, 0, B_LBRACE); + st[FBLO] = lb + 1; + st[FBHI] = block_end(src, lb) - 1; + assert(dcount(src, &st, kinds[i], tq, 7) == 3); + assert(derived_at(src, &st, kinds[i], tq, 0)); + assert(st[DVAL] == 4); + assert(st[DNEG] == 0); + assert(derived_at(src, &st, kinds[i], tq, 1)); + assert(st[DVAL] == 5); + assert(derived_at(src, &st, kinds[i], tq, 2)); + assert(st[DVAL] == 6); + assert(!derived_at(src, &st, kinds[i], tq, 3)); + i = i + 1; + } + // a negative literal is no sample of an unsigned type of any width, and three per literal of a signed one + const neg : str = "fn h(x: usize, y: i64) -> bool { x == -5 && y == -5 }"; + const nb : usize = find_at(neg, 0, B_LBRACE); + var ns : [1024]usize = [0] ** 1024; + ns[FBLO] = nb + 1; + ns[FBHI] = block_end(neg, nb) - 1; + const ut : usize = 8; + const sgt : usize = 18; + assert(tok_is(neg, ut, "usize")); + assert(tok_is(neg, sgt, "i64")); + assert(dcount(neg, &ns, V_UNSIGNED, ut, 7) == 0); + assert(!derived_at(neg, &ns, V_UNSIGNED, ut, 0)); + assert(dcount(neg, &ns, V_SIGNED, sgt, 7) == 6); + assert(derived_at(neg, &ns, V_SIGNED, sgt, 0)); + assert(ns[DVAL] == 6); + assert(ns[DNEG] == 1); } test dcount_and_scount_count_the_samples_a_parameter_holds_and_cache_the_count {