Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .trinity/seals/t27o_T27oRust.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
{
"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: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-19T09:59:57Z",
"sealed_at": "2026-10-19T13:57:04Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:f95769587da94282b6fb0c99d2e72288c10d5381f0393cbe315e3a334cef7fd1",
"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": {
Expand Down
27 changes: 17 additions & 10 deletions gen/rust/tools/t27o/rust_convert.rs
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)));
Expand Down Expand Up @@ -10908,6 +10914,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);
Expand Down
192 changes: 153 additions & 39 deletions specs/tools/t27o/rust_convert.t27
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -2896,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);
Expand All @@ -2906,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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -9365,7 +9372,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);
Expand Down Expand Up @@ -12295,25 +12305,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 {
Expand Down Expand Up @@ -14143,24 +14166,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 {
Expand Down Expand Up @@ -17995,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 {
Expand Down Expand Up @@ -19159,7 +19273,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;
Expand Down
Loading