From 6714d0fc833eec8bba00ccc154cdc61bc11517dc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 01:34:46 +0700 Subject: [PATCH 1/3] t27o: a caller of an out-buffer String fn is refused with a reason that names the form -- R_STRCALL, as the Vec form does with R_VECCALL (Closes #11111) A fn whose result is an owned String lowers to the out-buffer form: a last out: []u8, the result the byte count. The Rust source calls it without that argument, so a caller came out as tag(n).len or const t = tag(n); -- a call with the buffer missing, which t27c rejects at best and at worst reads as another meaning of the same arity. The converter must emit valid output or refuse (#10976). deps now treats a fn in the String form (K_STRFN, registered before its body is read) the way it treats one in the Vec form: a free fn is hit by a call, a method by a call after a dot or a path through its owner or Self, and the caller is refused with R_STRCALL, whose reason names the form and whose token is the callee. The buffer is not guessed: a caller that owned one would have to state a length the source never states. A fn outside the form keeps its old reason (uses refused), the split chain, which names the fn it calls on each piece without calling it, still converts. Tests first (two of the three new convert tests failed on the merge base, the third is the negative control and passed): a free fn in either declaration order, a method called as x.m() and S::m(x), a caller of a String fn outside the form, and a direct deps test with K_STRFN names (call, bare name, declaration, method, path, wrong owner, Self). The reason table test has the new row. Evidence: t27b test 357 of 357; t27c coverage 100 % (417 of 417); t27c seal --save 357 of 357, vacuous 0, --verify MATCH on rust_convert, rust_std, zig_convert, spice_convert, zig_std, ledger, golden, install, service. The converter over all 1,388 tracked Rust files of the five repositories changes no output: 13 fns in 9 files are in the String form and none is called in its own file. Co-Authored-By: Claude Sonnet 5.5 --- .trinity/seals/t27o_T27oRust.json | 18 ++-- gen/rust/tools/t27o/rust_convert.rs | 26 ++++-- specs/tools/t27o/rust_convert.t27 | 125 +++++++++++++++++++++++++--- 3 files changed, 142 insertions(+), 27 deletions(-) diff --git a/.trinity/seals/t27o_T27oRust.json b/.trinity/seals/t27o_T27oRust.json index 8dc7a5e9d2..dc0e96b81b 100644 --- a/.trinity/seals/t27o_T27oRust.json +++ b/.trinity/seals/t27o_T27oRust.json @@ -2,27 +2,27 @@ "built_by": "t27c-bootstrap@0.5.2+b258b9007", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:eb5230aa674e89d821c62094685b31e15bb3f1e6e0fa14c0f99198d1fa248770", - "gen_hash_rust": "sha256:15fce2e7366e1bb135ae9fcf6d34e8b7b8177e4e22d09069112b06b584635a69", - "gen_hash_verilog": "sha256:ea35ca3d1c18655fac83f20b17c3c8a04c78e727da9e18ae074f95870c8cba1b", - "gen_hash_zig": "sha256:1f0284cf8883747e351d13a0e4e8d6de036c37b4633fa1d8fd6890c741d4aaf5", + "gen_hash_c": "sha256:836d6ade82815c020072e116e6edfde894fde8475e1f7fa7120b5705dbed1c95", + "gen_hash_rust": "sha256:3ae2e7c392ccf3b00cc79ad1673d7a6b5cda33233206e79eef06d083b7897317", + "gen_hash_verilog": "sha256:691d40f0f9329a87ac3a78bc4f2f029cc7f264cb6fbfdfb49ddba803ea76f170", + "gen_hash_zig": "sha256:cbcdae011e73c2498c8bbadaced4dc3d7d333c1d9382ef7f6b4d92a6840959b5", "module": "T27oRust", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T17:29:12Z", + "sealed_at": "2026-10-19T18:34:18Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:ad23e8ee46f5bda72c23c1a428b323660b5cf66da17ed942286834c0bc0b60e2", + "spec_hash": "sha256:2569eec3f6d7d8b5a7f7bc6b2f942da11fc0964fc9100aef090815a4e95c83fc", "spec_path": "specs/tools/t27o/rust_convert.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 353, - "total": 353, + "passed": 357, + "total": 357, "vacuous": 0 }, "toolchain": { - "test_runner": "missing", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index 1b506f40cf..c38ddbc2b3 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: 353 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 357 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. @@ -326,6 +326,8 @@ pub const R_VECOUT: usize = 44; pub const R_VECCALL: usize = 45; +pub const R_STRCALL: usize = 46; + pub const R_FLOATLIT: usize = 43; pub const R_NOPRELUDE: usize = 40; @@ -1489,13 +1491,14 @@ pub fn deps(src: &[u8], st: &mut [usize], lo: usize, hi: usize) -> () { if ((kind == K_ITEM) || (kind == K_EXT)) { hit = (((!(after_dot) && !(after_path)) && !(after_fn)) && !(tok_is(src, nx, b":"))); } - if ((kind == K_VECFN) && (st[((s + 2)) as usize] == st[((s + 3)) as usize])) { + let outfn: bool = ((kind == K_VECFN) || (kind == K_STRFN)); + if (outfn && (st[((s + 2)) as usize] == st[((s + 3)) as usize])) { hit = (((call && !(after_dot)) && !(after_path)) && !(after_fn)); } - if ((((kind == K_VECFN) && (st[((s + 2)) as usize] != st[((s + 3)) as usize])) && after_dot) && call) { + if (((outfn && (st[((s + 2)) as usize] != st[((s + 3)) as usize])) && after_dot) && call) { hit = true; } - if ((((kind == K_VECFN) && (st[((s + 2)) as usize] != st[((s + 3)) as usize])) && after_path) && (p1 < src.len())) { + if (((outfn && (st[((s + 2)) as usize] != st[((s + 3)) as usize])) && after_path) && (p1 < src.len())) { hit = (same_text(src, st[((s + 2)) as usize], st[((s + 3)) as usize], p1, tok_end(src, p1)) || tok_is(src, p1, b"Self")); } if (hit && (kind == K_EXT)) { @@ -1504,7 +1507,10 @@ pub fn deps(src: &[u8], st: &mut [usize], lo: usize, hi: usize) -> () { if (hit && (kind == K_VECFN)) { refuse(st, R_VECCALL, q, q, e); } - if ((hit && (kind != K_EXT)) && (kind != K_VECFN)) { + if (hit && (kind == K_STRFN)) { + refuse(st, R_STRCALL, q, q, e); + } + if ((hit && (kind != K_EXT)) && !(outfn)) { refuse(st, R_DEPENDS, q, q, e); } } @@ -7870,10 +7876,14 @@ pub fn put_reason(src: &[u8], st: &mut [usize], out: &mut [u8]) -> () { if (c == R_VECCALL) { puts(st, out, b"a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own)"); } else { - if (c == R_SPLICE) { - puts(st, out, b"a used module t27c cannot splice beside type_text (its own use stays unresolved, or its same clashes with type_text's, the gap specs/compiler/int_type_names.t27 records)"); + if (c == R_STRCALL) { + puts(st, out, b"a call of a fn whose String result is the caller's out buffer (the caller would own a buffer of no stated length)"); } else { - puts(st, out, b"syntax"); + if (c == R_SPLICE) { + puts(st, out, b"a used module t27c cannot splice beside type_text (its own use stays unresolved, or its same clashes with type_text's, the gap specs/compiler/int_type_names.t27 records)"); + } else { + puts(st, out, b"syntax"); + } } } } diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index ac741af417..fdc538ae28 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -51,7 +51,8 @@ ; a `s.split(SEP).map(F).collect::>().join(J)` chain handed back (a byte loop over the text ; calling the named F on each piece), and the local handed back (`return t27o_w`). Anything else ; that reads the String refuses, naming the read -- a call outside the form, an argument to it, any -; other use of the local. +; other use of the local. A call of a fn in this form refuses too (R_STRCALL, #11111): the source calls +; it without the buffer, and the caller would own one of no stated length. ; The Vec out-buffer form (#11073): a fn whose result is `Vec`, T an integer, float or bool type, and whose body ; makes the Vec as one local (`Vec::new()`, `Vec::with_capacity(n)` or `vec![]`), changes it only by `push(e)` and ; `extend_from_slice(s)`, and hands it back (`return v`, the tail `v`, or an empty Vec in an early return), takes a @@ -370,6 +371,9 @@ pub const R_TYPENAME : usize = 42; /// and handed back as it is. And a call of a fn that is in that form: the caller would own an unbounded buffer. pub const R_VECOUT : usize = 44; pub const R_VECCALL : usize = 45; +/// A call of a fn that is in the String out-buffer form (#11111): it takes the caller's buffer as a last `out: []u8`, and the +/// call the Rust source writes has no such argument, so the caller would own a buffer of no stated length. +pub const R_STRCALL : usize = 46; /// A float literal bound to a `var` with no type: t27c writes a `var` of comptime_float, which Zig rejects, and /// the source leaves f32 or f64 to inference. pub const R_FLOATLIT : usize = 43; @@ -1370,15 +1374,18 @@ pub fn deps(src: []const u8, st: []usize, lo: usize, hi: usize) -> void { hit = same_text(src, st[s + 2], st[s + 3], p1, tok_end(src, p1)) or tok_is(src, p1, "Self"); } if (kind == K_ITEM or kind == K_EXT) { hit = !after_dot and !after_path and !after_fn and !tok_is(src, nx, ":"); } - // a fn in the Vec out-buffer form (#11073): a free fn is hit by a call, a method as K_MEMBER is - if (kind == K_VECFN and st[s + 2] == st[s + 3]) { hit = call and !after_dot and !after_path and !after_fn; } - if (kind == K_VECFN and st[s + 2] != st[s + 3] and after_dot and call) { hit = true; } - if (kind == K_VECFN and st[s + 2] != st[s + 3] and after_path and p1 < src.len) { + // a fn in an out-buffer form, the Vec one (#11073) or the String one (#11111): a free fn is hit by a call, a + // method as K_MEMBER is. Its caller would call it without the buffer, so it is refused, naming the form. + const outfn : bool = kind == K_VECFN or kind == K_STRFN; + if (outfn and st[s + 2] == st[s + 3]) { hit = call and !after_dot and !after_path and !after_fn; } + if (outfn and st[s + 2] != st[s + 3] and after_dot and call) { hit = true; } + if (outfn and st[s + 2] != st[s + 3] and after_path and p1 < src.len) { hit = same_text(src, st[s + 2], st[s + 3], p1, tok_end(src, p1)) or tok_is(src, p1, "Self"); } if (hit and kind == K_EXT) { refuse(st, R_EXTERNAL, q, q, e); } if (hit and kind == K_VECFN) { refuse(st, R_VECCALL, q, q, e); } - if (hit and kind != K_EXT and kind != K_VECFN) { refuse(st, R_DEPENDS, q, q, e); } + if (hit and kind == K_STRFN) { refuse(st, R_STRCALL, q, q, e); } + if (hit and kind != K_EXT and !outfn) { refuse(st, R_DEPENDS, q, q, e); } } k = k + 1; } @@ -6954,6 +6961,8 @@ pub fn put_reason(src: []const u8, st: []usize, out: []u8) -> void { puts(st, out, "a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form)"); } else if (c == R_VECCALL) { puts(st, out, "a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own)"); + } else if (c == R_STRCALL) { + puts(st, out, "a call of a fn whose String result is the caller's out buffer (the caller would own a buffer of no stated length)"); } else if (c == R_SPLICE) { puts(st, out, "a used module t27c cannot splice beside type_text (its own use stays unresolved, or its same clashes with type_text's, the gap specs/compiler/int_type_names.t27 records)"); } else { @@ -11072,7 +11081,7 @@ test a_call_of_an_out_buffer_fn_is_refused_because_the_caller_would_own_the_buff var out : [4096]u8 = undefined; var st : [1024]usize = [0] ** 1024; // A caller would need a buffer of a length nobody stated, so it is refused with a reason that names the call, in whichever - // order the file declares them. (The String form leaves its callers to call it without the buffer: #11073 notes it.) + // order the file declares them. (The String form refuses its callers the same way: R_STRCALL, #11111.) const n : usize = body("fn evens(n: u32) -> Vec { let mut v = Vec::new(); v.push(n); v }\nfn total(n: u32) -> usize { evens(n).len() }\nfn first(n: u32) -> u32 { let e = evens(n); e[0] }\nfn fine(n: u32) -> u32 { n + 1 }\n", &out, &st); assert(out_text_has(&out, n, "fn evens(n: u32, out: []u32) -> usize {")); assert(out_text_has(&out, n, "// t27o: refused fn total (line 2): a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own) `evens` at line 2\n")); @@ -11091,6 +11100,49 @@ test a_call_of_an_out_buffer_method_is_refused_by_method_and_by_path { assert(out_text_has(&out, n, "fn S_twice(self: S) -> u8 {")); } +test a_call_of_an_out_buffer_string_fn_is_refused_because_the_caller_would_own_the_buffer { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // The out-buffer String form takes the caller's buffer as a last `out: []u8` and returns the byte count. A caller that + // reads the String would call it without that argument -- `tag(n).len`, `const t = tag(n);` -- which t27c rejects, or reads + // another meaning (#11111). So it is refused with a reason that names the form, in whichever order the file declares the + // two, and the buffer is not guessed. A fn that is not in the form is called as before. + const n : usize = body("fn width(n: u32) -> usize { tag(n).len() }\nfn tag(n: u32) -> String { format!(\"<{}>\", n) }\nfn show(n: u32) -> String { let t = tag(n); t }\nfn plain(n: u32) -> u32 { n + 1 }\nfn user(n: u32) -> u32 { plain(n) + 1 }\n", &out, &st); + assert(out_text_has(&out, n, "fn tag(n: u32, out: []u8) -> usize {")); + assert(out_text_has(&out, n, "// t27o: refused fn width (line 1): a call of a fn whose String result is the caller's out buffer (the caller would own a buffer of no stated length) `tag` at line 1\n")); + assert(out_text_has(&out, n, "// t27o: refused fn show (line 3): a call of a fn whose String result is the caller's out buffer (the caller would own a buffer of no stated length) `tag` at line 3\n")); + assert(out_text_has(&out, n, "fn user(n: u32) -> u32 {\n return plain(n) + 1;\n}")); + assert(!out_text_has(&out, n, "tag(n).len")); + assert(!out_text_has(&out, n, "const t = tag(n)")); + assert(!out_text_has(&out, n, "tag(n);")); +} + +test a_call_of_an_out_buffer_string_method_is_refused_by_method_and_by_path { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // The same for a method, called as `x.label(k)` or `S::label(x, k)`; a method of the same impl that is not in the form is + // converted. + const n : usize = body("struct S { a: u32 }\nimpl S {\n fn label(&self, k: u32) -> String { format!(\"s{}\", k) }\n fn n(&self) -> usize { self.label(1).len() }\n fn twice(&self) -> u32 { self.a + self.a }\n}\nfn user(s: &S) -> usize { S::label(s, 2).len() }\n", &out, &st); + assert(out_text_has(&out, n, "fn S_label(self_: S, k: u32, out: []u8) -> usize {")); + assert(out_text_has(&out, n, "// t27o: refused fn n (line 4): a call of a fn whose String result is the caller's out buffer (the caller would own a buffer of no stated length) `label` at line 4\n")); + assert(out_text_has(&out, n, "// t27o: refused fn user (line 7): a call of a fn whose String result is the caller's out buffer (the caller would own a buffer of no stated length) `label` at line 7\n")); + assert(out_text_has(&out, n, "fn S_twice(self: S) -> u32 {")); + assert(!out_text_has(&out, n, "S_label(self, 1)")); + assert(!out_text_has(&out, n, "S_label(s, 2)")); +} + +test negative_control_a_caller_of_a_string_fn_outside_the_out_buffer_form_keeps_its_old_reason { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // mk reads its String, so it is not in the form (an owned String outside it): its caller is refused as a user of a refused fn, + // not as the caller of an out buffer. The split chain, which names the fn it calls on each piece without calling it, + // converts. + const n : usize = body("fn mk(x: u32) -> String { let mut s = String::new(); s.push_str(\"x=\"); s.push_str(&x.to_string()); s }\nfn use_mk(x: u32) -> usize { mk(x).len() }\nfn zig_ident(name: &str) -> String { if name.contains(\"::\") { return name.split(\"::\").map(zig_ident).collect::>().join(\".\"); } name.to_string() }\n", &out, &st); + assert(out_text_has(&out, n, "// t27o: refused fn use_mk (line 2): uses refused `mk` at line 2\n")); + assert(!out_text_has(&out, n, "out buffer")); + assert(out_text_has(&out, n, "fn zig_ident(name: []const u8, out: []u8) -> usize {")); +} + test the_oracle_records_the_written_prefix_of_a_vec_result_in_a_full_buffer_and_in_none { var out : [20000]u8 = undefined; var st : [1024]usize = [0] ** 1024; @@ -13124,6 +13176,59 @@ test deps_reads_a_name_after_a_dot_or_a_path_as_a_member_not_a_free_fn { assert(j[ERR] == 0); } +test deps_refuses_a_call_of_a_fn_in_an_out_buffer_form_naming_the_form { + var a : [128]usize = [0] ** 128; + const s1 : str = "tag(1)"; + add_name(s1, &a, K_STRFN, 0, 3, 0, 0); + deps(s1, &a, 0, s1.len); + assert(a[ERR] == R_STRCALL); + assert(a[AT] == 0); + assert(a[NLO] == 0); + assert(a[NHI] == 3); + // the same name of the Vec form is the Vec form's refusal + var b : [128]usize = [0] ** 128; + add_name(s1, &b, K_VECFN, 0, 3, 0, 0); + deps(s1, &b, 0, s1.len); + assert(b[ERR] == R_VECCALL); + // a free fn is hit by a call only: not by its name alone, by its declaration, or by a method of that name after a dot + var c : [128]usize = [0] ** 128; + const s2 : str = "tag + 1"; + add_name(s2, &c, K_STRFN, 0, 3, 0, 0); + deps(s2, &c, 0, s2.len); + assert(c[ERR] == 0); + var d : [128]usize = [0] ** 128; + const s3 : str = "fn tag(n: u32) { }"; + add_name(s3, &d, K_STRFN, 3, 6, 0, 0); + deps(s3, &d, 0, s3.len); + assert(d[ERR] == 0); + var e : [128]usize = [0] ** 128; + const s4 : str = "x.tag(1)"; + add_name(s4, &e, K_STRFN, 2, 5, 0, 0); + deps(s4, &e, 0, s4.len); + assert(e[ERR] == 0); + // a method (it has an owner type) is hit by a call after a dot and by a path through its owner or Self, not through another type + var f : [128]usize = [0] ** 128; + add_name(s4, &f, K_STRFN, 2, 5, 0, 1); + deps(s4, &f, 0, s4.len); + assert(f[ERR] == R_STRCALL); + assert(f[AT] == 2); + var g : [128]usize = [0] ** 128; + const s5 : str = "struct T {} fn f() { T::go(1); }"; + add_name(s5, &g, K_STRFN, 24, 26, 7, 8); + deps(s5, &g, 12, s5.len); + assert(g[ERR] == R_STRCALL); + assert(g[AT] == 24); + var h : [128]usize = [0] ** 128; + add_name(s5, &h, K_STRFN, 24, 26, 3, 4); + deps(s5, &h, 12, s5.len); + assert(h[ERR] == 0); + var i : [128]usize = [0] ** 128; + const s6 : str = "Self::go(1)"; + add_name(s6, &i, K_STRFN, 6, 8, 0, 4); + deps(s6, &i, 0, s6.len); + assert(i[ERR] == R_STRCALL); +} + test deps_refuses_a_use_of_a_refused_type_a_foreign_name_and_an_undeclared_one { var a : [128]usize = [0] ** 128; const s1 : str = "fn f(a: Ghost) {}"; @@ -18228,13 +18333,13 @@ test put_docs_writes_the_doc_lines_above_an_item_and_nothing_else { } test put_reason_names_the_refusal_and_the_text_it_points_at { - const codes : [45]usize = [R_SYNTAX, R_GENERIC, R_TRAIT, R_CLOSURE, R_TRY, R_MACRO, R_TYPE, R_METHOD, R_TUPLE, R_IFLET, R_ITER, R_PATTERN, R_MUTREF, R_DEREF, R_VALUE, R_LITERAL, R_LABEL, R_ITEM, R_UNSAFE, R_PATH, R_WHERE, R_SELFMUT, R_UPDATE, R_DATA, R_DEPENDS, R_EXTERNAL, R_UNDECL, R_CLASH, R_STRUCTEQ, R_STRUSE, R_SPLICE, R_EMPTY, R_NOTYPE, R_VECMUT, R_VECMOVE, R_VECTYPE, R_LIBM, R_SLICEGEN, R_SHIFTCALL, R_OPERAND, R_TYPENAME, R_FLOATLIT, R_NOPRELUDE, R_VECOUT, R_VECCALL]; - const why : [45]str = ["syntax", "generics", "trait impl", "closure", "? operator", "macro", "type", "method", "tuple", "if let / while let", "for over an iterator", "match pattern", "&mut of a value", "deref", "block as a value", "literal", "label or break value", "item", "unsafe", "path", "where clause", "mut self", "struct update", "enum variant with data", "uses refused", "uses external", "undeclared", "a used module's name, declared here too (use splices flat, see #7176)", "== of structs", "an owned String outside the out-buffer form", "a used module t27c cannot splice beside type_text (its own use stays unresolved, or its same clashes with type_text's, the gap specs/compiler/int_type_names.t27 records)", "empty slice with no known element type", "`!` of an operand whose type t27o cannot tell", "a vec! the fn changes (push, resize, assign into, &mut)", "a vec! the fn hands on or returns", "a vec! or array literal whose element type or length t27o cannot tell", "libm function not bit-exact", "slice contains on", "a compound shift of a place that calls something", "an operator, cast, borrow or deref t27 has no form for on an enum, struct, array or text", "a type named like one t27c renames (GF16, str, int ..)", "a float literal in a var of no type, f32 or f64 left to inference", "an assert or a comparison of texts in a spec with no test block (t27c writes the std import and assert helper with the first one)", "a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form)", "a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own)"]; + const codes : [46]usize = [R_SYNTAX, R_GENERIC, R_TRAIT, R_CLOSURE, R_TRY, R_MACRO, R_TYPE, R_METHOD, R_TUPLE, R_IFLET, R_ITER, R_PATTERN, R_MUTREF, R_DEREF, R_VALUE, R_LITERAL, R_LABEL, R_ITEM, R_UNSAFE, R_PATH, R_WHERE, R_SELFMUT, R_UPDATE, R_DATA, R_DEPENDS, R_EXTERNAL, R_UNDECL, R_CLASH, R_STRUCTEQ, R_STRUSE, R_SPLICE, R_EMPTY, R_NOTYPE, R_VECMUT, R_VECMOVE, R_VECTYPE, R_LIBM, R_SLICEGEN, R_SHIFTCALL, R_OPERAND, R_TYPENAME, R_FLOATLIT, R_NOPRELUDE, R_VECOUT, R_VECCALL, R_STRCALL]; + const why : [46]str = ["syntax", "generics", "trait impl", "closure", "? operator", "macro", "type", "method", "tuple", "if let / while let", "for over an iterator", "match pattern", "&mut of a value", "deref", "block as a value", "literal", "label or break value", "item", "unsafe", "path", "where clause", "mut self", "struct update", "enum variant with data", "uses refused", "uses external", "undeclared", "a used module's name, declared here too (use splices flat, see #7176)", "== of structs", "an owned String outside the out-buffer form", "a used module t27c cannot splice beside type_text (its own use stays unresolved, or its same clashes with type_text's, the gap specs/compiler/int_type_names.t27 records)", "empty slice with no known element type", "`!` of an operand whose type t27o cannot tell", "a vec! the fn changes (push, resize, assign into, &mut)", "a vec! the fn hands on or returns", "a vec! or array literal whose element type or length t27o cannot tell", "libm function not bit-exact", "slice contains on", "a compound shift of a place that calls something", "an operator, cast, borrow or deref t27 has no form for on an enum, struct, array or text", "a type named like one t27c renames (GF16, str, int ..)", "a float literal in a var of no type, f32 or f64 left to inference", "an assert or a comparison of texts in a spec with no test block (t27c writes the std import and assert helper with the first one)", "a Vec result that is not one local built only by push / extend_from_slice and returned as it is (the out-buffer form holds no read of it, no other change, no second Vec: a list that grows without a bound the caller states has no t27 form)", "a call of a fn whose Vec result is the caller's out buffer (an unbounded list the caller would own)", "a call of a fn whose String result is the caller's out buffer (the caller would own a buffer of no stated length)"]; var out : [512]u8 = undefined; var st : [64]usize = [0] ** 64; var w : [512]u8 = [0] ** 512; var i : usize = 0; - while (i < 45) { + while (i < 46) { clear(&st); st[ERR] = codes[i]; st[AT] = 3; From d93e4bc22ad6f8f11f36fd5b1c9fe9feffade759 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 01:47:09 +0700 Subject: [PATCH 2/3] t27o: the oracle of a fn with two texts samples a needle inside a haystack, in both orders, and each body literal beside a sample that holds it -- regex_lite_global records a match (Closes #11113) The oracle calls a fn with parameter i taking sample j + i. regex_lite_global(s, needle) of trios-phd was called with ("", "a"), ("a", "abc"), ("abc", "0") ... : the needle was never inside the haystack, so every count the harness recorded was 0, the oracle test asserted only the empty case, and a conversion that returned nothing for every input would have passed it. Its body has no string literal, so the literal samples of #9102 could not help. vectors now counts the direct texts of the fn (text_count, kept in the new PAIRS slot and cleared at the end). With two or more, derived_at gives each text parameter three fixed samples first, a text, a text inside it and the first text again (put_pair_sample: "abcabc", "abc", "abcabc"), so one call has the needle inside the haystack, the next the haystack second, and "abc" occurs twice in "abcabc" (offsets 0 and 3, the second ending at the end). Each string literal of the body then comes alone, wrapped in one space, alone, so the wrapped sample sits next to the literal in both orders. The sampler stays bounded: three fixed 6-byte texts, the DCAP of 64 samples a parameter (a test with thirty literals), and the guard of 3 s and 16 MiB of #11109, which still skips the empty needle. A fn with one text, or a text and a number, is sampled as before (negative control); a number never reads the count of texts. Evidence: the harness of crates/trios-phd/src/main.rs now records count 2 and the offsets 0 and 3 for regex_lite_global (rustc, run), the zero-length buffer answers 2, and t27b test of the converted spec passes 4 of 4 as before. Over the 1,388 files of the five repositories the converted specs are byte for byte the same; 5 oracle harnesses change (compiler.rs, gates.rs, ring-090 lib.rs, trios-phd main.rs, defense_gate lib.rs); the three that t27b judges (ring-090 25 of 25, trios-phd 4 of 4, defense_gate 4 of 4) pass before and after, the two it cannot judge (unsupported construct, @max) stay so. Tests first (the two oracle tests failed on the merge base, the negative control passed), a direct test for each new fn (is_text_kind, text_count, put_pair_sample), derived_at with the count set, the cap of 64 with pairs. Three tests that give the name table exactly one slot got one more: the state vector has one more slot (PAIRS). t27b test 362 of 362, t27c coverage 100 % (420 of 420), seal --save 362 of 362 vacuous 0, --verify MATCH on rust_convert, rust_std, zig_convert, spice_convert, zig_std, ledger, golden, install, service. Co-Authored-By: Claude Sonnet 5.5 --- .trinity/seals/t27o_T27oRust.json | 16 +-- gen/rust/tools/t27o/rust_convert.rs | 87 ++++++++---- specs/tools/t27o/rust_convert.t27 | 200 +++++++++++++++++++++++++--- 3 files changed, 256 insertions(+), 47 deletions(-) diff --git a/.trinity/seals/t27o_T27oRust.json b/.trinity/seals/t27o_T27oRust.json index dc0e96b81b..22add8d8f2 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:836d6ade82815c020072e116e6edfde894fde8475e1f7fa7120b5705dbed1c95", - "gen_hash_rust": "sha256:3ae2e7c392ccf3b00cc79ad1673d7a6b5cda33233206e79eef06d083b7897317", - "gen_hash_verilog": "sha256:691d40f0f9329a87ac3a78bc4f2f029cc7f264cb6fbfdfb49ddba803ea76f170", - "gen_hash_zig": "sha256:cbcdae011e73c2498c8bbadaced4dc3d7d333c1d9382ef7f6b4d92a6840959b5", + "gen_hash_c": "sha256:9e4b3abfefcb323fcbf425c16e320cc0ee7a77c515335def96be66cc21ffe100", + "gen_hash_rust": "sha256:68cd29da62d9e4e6667f57dfff4be72e7d6c9496fa9f034e0a6a8005292a0e1f", + "gen_hash_verilog": "sha256:cd09a5b52ba17d76db2d6944d154b0d1970d5d40be45cea74778eecf62f12bf9", + "gen_hash_zig": "sha256:17985cb3d9cf868fb3f5f9eb30a56b1a562b08b7c70293bf31e54d93ad80749e", "module": "T27oRust", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T18:34:18Z", + "sealed_at": "2026-10-19T18:46:35Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:2569eec3f6d7d8b5a7f7bc6b2f942da11fc0964fc9100aef090815a4e95c83fc", + "spec_hash": "sha256:b273204b53ae63129754d48ae09fabdc71a8e41604ed9a6abcd18d59b25c29d6", "spec_path": "specs/tools/t27o/rust_convert.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 357, - "total": 357, + "passed": 362, + "total": 362, "vacuous": 0 }, "toolchain": { diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index c38ddbc2b3..99e2d35493 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: 357 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 362 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. @@ -108,6 +108,8 @@ pub const CCNT: usize = 50; pub const DCAP: usize = 64; +pub const PAIR_N: usize = 3; + pub const LNPOS: usize = 51; pub const LNNL: usize = 52; @@ -126,7 +128,9 @@ pub const IXBN: usize = 57; pub const IXTOP: usize = 58; -pub const NAMES: usize = 59; +pub const PAIRS: usize = 59; + +pub const NAMES: usize = 60; pub const NAME_SLOTS: usize = 5; @@ -8691,17 +8695,60 @@ pub fn num_value(src: &[u8], q: usize, te: usize) -> usize { return v; } +pub fn is_text_kind(kind: usize) -> bool { + return ((((kind == V_STR) || (kind == V_BYTES)) || (kind == V_STRING)) || (kind == V_RSTRING)); +} + +pub fn text_count(k0: usize, k1: usize, k2: usize, k3: usize, k4: usize) -> usize { + let mut n: usize = 0; + if is_text_kind(k0) { + n = (n + 1); + } + if is_text_kind(k1) { + n = (n + 1); + } + if is_text_kind(k2) { + n = (n + 1); + } + if is_text_kind(k3) { + n = (n + 1); + } + if is_text_kind(k4) { + n = (n + 1); + } + return n; +} + +pub fn put_pair_sample(st: &mut [usize], out: &mut [u8], k: usize) -> () { + if (k == 1) { + puts(st, out, b"abc"); + } else { + puts(st, out, b"abcabc"); + } +} + pub fn derived_at(src: &[u8], st: &mut [usize], kind: usize, tq: usize, d: usize) -> bool { st[(DCSP) as usize] = 0; st[(DNEG) as usize] = 0; if (st[(FBLO) as usize] >= st[(FBHI) as usize]) { return false; } - let text: bool = ((((kind == V_STR) || (kind == V_BYTES)) || (kind == V_STRING)) || (kind == V_RSTRING)); + let text: bool = is_text_kind(kind); let ints: bool = ((kind == V_UNSIGNED) || (kind == V_SIGNED)); if (!(text) && !(ints)) { return false; } + let mut c: usize = 0; + let mut per: usize = 2; + if (text && (st[(PAIRS) as usize] >= 2)) { + if (d < PAIR_N) { + st[(DCSP) as usize] = 2; + st[(DVAL) as usize] = d; + return true; + } + c = PAIR_N; + per = 3; + } let mut w: usize = 0; let mut sg: bool = false; if ints { @@ -8711,7 +8758,6 @@ pub fn derived_at(src: &[u8], st: &mut [usize], kind: usize, tq: usize, d: usize return false; } } - let mut c: usize = 0; let mut q: usize = st[(FBLO) as usize]; let mut neg: bool = false; while (q < st[(FBHI) as usize]) { @@ -8725,20 +8771,13 @@ pub fn derived_at(src: &[u8], st: &mut [usize], kind: usize, tq: usize, d: usize } } if ((text && (src[(q) as usize] == B_QUOTE)) && clean_text(src, (q + 1), (te - 1))) { - if (c == d) { - st[(DCLA) as usize] = (q + 1); - st[(DCLB) as usize] = (te - 1); - st[(DCSP) as usize] = 0; - return true; - } - c = (c + 1); - if (c == d) { + if ((d >= c) && (d < (c + per))) { st[(DCLA) as usize] = (q + 1); st[(DCLB) as usize] = (te - 1); - st[(DCSP) as usize] = 1; + st[(DCSP) as usize] = ((d - c) % 2); return true; } - c = (c + 1); + c = (c + per); } if (text && ((((src[(q) as usize] == B_LOWER_B) && ((q + 1) < st[(FBHI) as usize])) && (src[((q + 1)) as usize] == B_QUOTE)) || (((src[(q) as usize] == B_LOWER_R) && ((q + 1) < st[(FBHI) as usize])) && ((src[((q + 1)) as usize] == B_QUOTE) || (src[((q + 1)) as usize] == B_HASH))))) { let mut h: usize = 0; @@ -8746,20 +8785,13 @@ pub fn derived_at(src: &[u8], st: &mut [usize], kind: usize, tq: usize, d: usize h = (h + 1); } if clean_text(src, ((q + 2) + h), ((te - 1) - h)) { - if (c == d) { + if ((d >= c) && (d < (c + per))) { st[(DCLA) as usize] = ((q + 2) + h); st[(DCLB) as usize] = ((te - 1) - h); - st[(DCSP) as usize] = 0; + st[(DCSP) as usize] = ((d - c) % 2); return true; } - c = (c + 1); - if (c == d) { - st[(DCLA) as usize] = ((q + 2) + h); - st[(DCLB) as usize] = ((te - 1) - h); - st[(DCSP) as usize] = 1; - return true; - } - c = (c + 1); + c = (c + per); } } if ((((((text && (src[(q) as usize] == B_LOWER_B)) && ((q + 1) < st[(FBHI) as usize])) && (src[((q + 1)) as usize] == B_APOS)) && (te == (q + 4))) && (src[((q + 3)) as usize] == B_APOS)) && clean_text(src, (q + 2), (q + 3))) { @@ -9067,6 +9099,11 @@ pub fn put_value(src: &[u8], st: &mut [usize], out: &mut [u8], kind: usize, tq: } put_quote(st, out, rust); if ((k >= base) && derived_at(src, st, kind, tq, (k - base))) { + if (st[(DCSP) as usize] == 2) { + put_pair_sample(st, out, st[(DVAL) as usize]); + put_quote(st, out, rust); + return (); + } if (st[(DCSP) as usize] == 1) { puts(st, out, b" "); } @@ -9636,6 +9673,7 @@ pub fn vectors(src: &[u8], st: &mut [usize], out: &mut [u8], na: usize, nb: usiz st[(FBHI) as usize] = (be - 1); } } + st[(PAIRS) as usize] = text_count(k0, k1, k2, k3, k4); let mut nv: usize = 1; if (n > 0) { nv = slen(src, st, k0, t0); @@ -9768,6 +9806,7 @@ pub fn vectors(src: &[u8], st: &mut [usize], out: &mut [u8], na: usize, nb: usiz } put_src(src, st, out, na, nb); puts(st, out, b" {{\"); for l in &v { println!(\"{}\", l); } println!(\"}}\\n\"); }\n }\n"); + st[(PAIRS) as usize] = 0; } pub fn ret_is_string(src: &[u8], pn: usize) -> bool { diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index fdc538ae28..4c07ada6d0 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -71,7 +71,11 @@ ; file and self of one, arrays and slices of scalars as typed locals of the test); a direct text or ; integer parameter is also sampled with the literals its fn body compares against -- every string ; literal alone and wrapped in one space, every integer one below, at it, one above (#9102) -- so a -; match arm is reached and not only the default one; a result is a +; match arm is reached and not only the default one; a fn with two or more direct texts (a haystack and a +; needle) also takes a text, a text inside it and the first text again right after the fixed samples +; (parameter i takes sample j + i, so one call has the needle inside the haystack and the next the haystack +; second), and each of its literals comes alone, wrapped, alone, so the wrapped one is next to it in both +; orders (#11113: every count regex_lite_global recorded was 0); a result is a ; scalar, a text, an owned String (the out-buffer form: its bytes into a 256-byte buffer the test ; owns, the written length its result, checked with type_text::same on the written slice), a Vec of ; integers, floats or bools (the Vec out-buffer form: the count it returns is the Vec's length and every @@ -205,6 +209,9 @@ pub const CFB : usize = 49; pub const CCNT : usize = 50; /// The most samples one parameter holds: its fixed ones plus the body-derived ones (FR-003 of #9102). pub const DCAP : usize = 64; +/// The pair samples of a text parameter of a fn with two or more texts: a text, a text inside it, the first text again +/// (see derived_at). A derived sample in the DCSP slot reads 2 for them, and DVAL is which. +pub const PAIR_N : usize = 3; /// The line cache of line_at: the position of the last line asked for, and the newlines before it. pub const LNPOS : usize = 51; pub const LNNL : usize = 52; @@ -220,7 +227,10 @@ pub const IXON : usize = 55; pub const IXB : usize = 56; pub const IXBN : usize = 57; pub const IXTOP : usize = 58; -pub const NAMES : usize = 59; +/// How many parameters of the fn vectors is sampling are direct texts (#11113): two or more and the texts that make a +/// needle sit inside a haystack come after the fixed samples of each of them. vectors sets it and clears it. +pub const PAIRS : usize = 59; +pub const NAMES : usize = 60; pub const NAME_SLOTS : usize = 5; pub const F_NONE : usize = 0; @@ -7590,6 +7600,28 @@ pub fn num_value(src: []const u8, q: usize, te: usize) -> usize { return v; } +/// Is `kind` a text the oracle samples with strings: &str, a byte slice, an owned or borrowed String? +pub fn is_text_kind(kind: usize) -> bool { + return kind == V_STR or kind == V_BYTES or kind == V_STRING or kind == V_RSTRING; +} + +/// How many of the five parameter kinds are texts (the unused ones are V_NONE). +pub fn text_count(k0: usize, k1: usize, k2: usize, k3: usize, k4: usize) -> usize { + var n : usize = 0; + if (is_text_kind(k0)) { n = n + 1; } + if (is_text_kind(k1)) { n = n + 1; } + if (is_text_kind(k2)) { n = n + 1; } + if (is_text_kind(k3)) { n = n + 1; } + if (is_text_kind(k4)) { n = n + 1; } + return n; +} + +/// Write pair sample `k` (the DVAL of a derived sample whose DCSP is 2): the haystack `abcabc` for 0 and 2, the needle +/// `abc` for 1. The needle is at the start of the haystack and again at its end. +pub fn put_pair_sample(st: []usize, out: []u8, k: usize) -> void { + if (k == 1) { puts(st, out, "abc"); } else { puts(st, out, "abcabc"); } +} + /// Derived sample `d` of a text or integer parameter whose type is src[tq], read from the fn body /// st[FBLO .. FBHI] (#9102): every string literal of the body twice -- alone, and wrapped in one /// space -- plus every byte and char literal once as a one-byte text; every integer literal (a `-` @@ -7599,13 +7631,25 @@ pub fn num_value(src: []const u8, q: usize, te: usize) -> usize { /// next to it. The sample found: a text src[DCLA .. DCLB), wrapped when st[DCSP] is 1; an integer of /// magnitude st[DVAL], negative when st[DNEG] is 1. False when `d` is past the last sample. A plain /// left-to-right token walk: deterministic, no clock, no randomness. +/// A fn with two or more direct texts (st[PAIRS], a haystack and a needle) meets a match only when one text is inside the +/// other, and parameter i takes sample j + i, so its first PAIR_N derived samples are fixed texts, a text, a text inside it +/// and the first text again (st[DCSP] is 2 and st[DVAL] which one, see put_pair_sample): the next call has the needle inside the +/// haystack, the one after it the haystack second, and a count or an offset the fn records is not 0 for every sample +/// (#11113). Each literal of the body then comes alone, wrapped, alone, so its wrapped sample is next to it in both orders. pub fn derived_at(src: []const u8, st: []usize, kind: usize, tq: usize, d: usize) -> bool { st[DCSP] = 0; st[DNEG] = 0; if (st[FBLO] >= st[FBHI]) { return false; } - const text : bool = kind == V_STR or kind == V_BYTES or kind == V_STRING or kind == V_RSTRING; + const text : bool = is_text_kind(kind); const ints : bool = kind == V_UNSIGNED or kind == V_SIGNED; if (!text and !ints) { return false; } + var c : usize = 0; + var per : usize = 2; + if (text and st[PAIRS] >= 2) { + if (d < PAIR_N) { st[DCSP] = 2; st[DVAL] = d; return true; } + c = PAIR_N; + per = 3; + } var w : usize = 0; var sg : bool = false; if (ints) { @@ -7613,7 +7657,6 @@ pub fn derived_at(src: []const u8, st: []usize, kind: usize, tq: usize, d: usize sg = int_type_signed(src, tq); if (w == 0) { return false; } } - var c : usize = 0; var q : usize = st[FBLO]; var neg : bool = false; while (q < st[FBHI]) { @@ -7628,10 +7671,8 @@ pub fn derived_at(src: []const u8, st: []usize, kind: usize, tq: usize, d: usize } } if (text and src[q] == B_QUOTE and clean_text(src, q + 1, te - 1)) { - if (c == d) { st[DCLA] = q + 1; st[DCLB] = te - 1; st[DCSP] = 0; return true; } - c = c + 1; - if (c == d) { st[DCLA] = q + 1; st[DCLB] = te - 1; st[DCSP] = 1; return true; } - c = c + 1; + if (d >= c and d < c + per) { st[DCLA] = q + 1; st[DCLB] = te - 1; st[DCSP] = (d - c) % 2; return true; } + c = c + per; } // a byte string b"..." and a raw string r"..." / r#"..."#: one token, its content between // the quotes (the hashes of the raw one are not content) @@ -7639,10 +7680,8 @@ pub fn derived_at(src: []const u8, st: []usize, kind: usize, tq: usize, d: usize var h : usize = 0; while (src[q] == B_LOWER_R and q + 1 + h < te and src[q + 1 + h] == B_HASH) { h = h + 1; } if (clean_text(src, q + 2 + h, te - 1 - h)) { - if (c == d) { st[DCLA] = q + 2 + h; st[DCLB] = te - 1 - h; st[DCSP] = 0; return true; } - c = c + 1; - if (c == d) { st[DCLA] = q + 2 + h; st[DCLB] = te - 1 - h; st[DCSP] = 1; return true; } - c = c + 1; + if (d >= c and d < c + per) { st[DCLA] = q + 2 + h; st[DCLB] = te - 1 - h; st[DCSP] = (d - c) % 2; return true; } + c = c + per; } } // a one-byte byte literal b'x' or char literal 'x' (a lifetime is a word, not three bytes) @@ -7843,6 +7882,11 @@ pub fn put_value(src: []const u8, st: []usize, out: []u8, kind: usize, tq: usize if (kind == V_BYTES and rust) { puts(st, out, "b"); } put_quote(st, out, rust); if (k >= base and derived_at(src, st, kind, tq, k - base)) { + if (st[DCSP] == 2) { + put_pair_sample(st, out, st[DVAL]); + put_quote(st, out, rust); + return; + } if (st[DCSP] == 1) { puts(st, out, " "); } put_src(src, st, out, st[DCLA], st[DCLB]); if (st[DCSP] == 1) { puts(st, out, " "); } @@ -8334,6 +8378,7 @@ pub fn vectors(src: []const u8, st: []usize, out: []u8, na: usize, nb: usize, pn st[FBHI] = be - 1; } } + st[PAIRS] = text_count(k0, k1, k2, k3, k4); var nv : usize = 1; if (n > 0) { nv = slen(src, st, k0, t0); } if (n > 1 and slen(src, st, k1, t1) > nv) { nv = slen(src, st, k1, t1); } @@ -8417,6 +8462,7 @@ pub fn vectors(src: []const u8, st: []usize, out: []u8, na: usize, nb: usize, pn } put_src(src, st, out, na, nb); puts(st, out, " {{\"); for l in &v { println!(\"{}\", l); } println!(\"}}\\n\"); }\n }\n"); + st[PAIRS] = 0; } /// Does the fn whose parameters open at src[pn] return exactly an owned String (`-> String {`)? @@ -11495,6 +11541,125 @@ test byte_and_char_literals_are_one_byte_texts { assert(slen(src, &st, V_BYTES, 0) == 8); } +test the_oracle_of_a_fn_with_two_texts_samples_a_needle_inside_a_haystack_in_both_orders { + var out : [40000]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // A fn that takes a haystack and a needle as text (regex_lite_global of trios-phd) was called only with pairs in which the + // needle is not inside the haystack, so every count it recorded was 0 (#11113). Parameter i takes sample j + i, so the + // texts that follow the fixed ones are a text, a text inside it, and the first text again: the next call has the needle + // inside the haystack, the one after it the haystack second, the two orders a fn can take its texts in. "abc" occurs twice in + // "abcabc" (at 0 and at 3), the second time right at its end. + const n : usize = oracle("fn find_all(s: &str, needle: &str) -> Vec {\n let mut out = Vec::new();\n let mut i = 0usize;\n while let Some(p) = s[i..].find(needle) {\n out.push(i + p);\n i = i + p + needle.len();\n }\n out\n}", &out, &st); + assert(out_text_has(&out, n, "t27o_guard::guard(|| find_all(\"-7\", \"abcabc\"))")); + assert(out_text_has(&out, n, "t27o_guard::guard(|| find_all(\"abcabc\", \"abc\"))")); + assert(out_text_has(&out, n, "t27o_guard::guard(|| find_all(\"abc\", \"abcabc\"))")); + // the fixed samples are still there, and the empty needle (which the guard skips) is the last call + assert(out_text_has(&out, n, "t27o_guard::guard(|| find_all(\"\", \"a\"))")); + assert(out_text_has(&out, n, "t27o_guard::guard(|| find_all(\"abcabc\", \"\"))")); + assert(out_text_has(&out, n, "let c = \"find_all(\\\"abcabc\\\", \\\"abc\\\", t27o_o8[0 .. 256])\";")); + // vectors clears the count of texts it set + assert(st[PAIRS] == 0); + // String and &String are texts too, bytes the same with b"..." + const m : usize = oracle("fn two(a: &String, b: String) -> usize { a.len() + b.len() }\nfn bytes(a: &[u8], b: &[u8]) -> usize { a.len() + b.len() }", &out, &st); + assert(out_text_has(&out, m, "catch_unwind(|| two(&String::from(\"abcabc\"), String::from(\"abc\")))")); + assert(out_text_has(&out, m, "catch_unwind(|| bytes(b\"abcabc\", b\"abc\"))")); +} + +test negative_control_a_fn_with_one_text_gets_no_pair_samples_and_the_fixed_ones_stay { + var out : [40000]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // One text, or a text and a number, has no needle and no haystack to pair: the samples are the fixed eight, the body's + // literals, and nothing else. + const n : usize = oracle("fn one_text(s: &str) -> usize { s.len() }\nfn text_and_int(s: &str, k: usize) -> usize { s.len() + k }\nfn text_and_bool(s: &str, b: bool) -> bool { s.is_empty() && b }", &out, &st); + assert(!out_text_has(&out, n, "abcabc")); + assert(out_text_has(&out, n, "catch_unwind(|| one_text(\"-7\"))")); + assert(!out_text_has(&out, n, "one_text(\"abc\") ")); + assert(out_text_has(&out, n, "catch_unwind(|| text_and_int(\"a b\", 0))")); + assert(out_text_has(&out, n, "catch_unwind(|| text_and_bool(\"-7\", false))")); +} + +test a_literal_of_a_fn_with_two_texts_is_sampled_alone_and_inside_a_sample_in_both_orders { + var out : [40000]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // Each string literal of the body is a sample alone and a sample that contains it (wrapped in one space); with two texts + // the literal comes again after the wrapped one, so the next call has the wrapped text first and the literal second, as + // the one before it has the literal first (#11113). + const n : usize = oracle("fn has_either(s: &str, t: &str) -> bool { s.contains(\"foo\") || t == \"bar\" }", &out, &st); + assert(out_text_has(&out, n, "catch_unwind(|| has_either(\"foo\", \" foo \"))")); + assert(out_text_has(&out, n, "catch_unwind(|| has_either(\" foo \", \"foo\"))")); + assert(out_text_has(&out, n, "catch_unwind(|| has_either(\"foo\", \"bar\"))")); + assert(out_text_has(&out, n, "catch_unwind(|| has_either(\"bar\", \" bar \"))")); + assert(out_text_has(&out, n, "catch_unwind(|| has_either(\" bar \", \"bar\"))")); +} + +test is_text_kind_text_count_and_put_pair_sample_name_the_texts_a_fn_takes { + var st : [64]usize = [0] ** 64; + var out : [32]u8 = undefined; + assert(is_text_kind(V_STR) and is_text_kind(V_BYTES) and is_text_kind(V_STRING) and is_text_kind(V_RSTRING)); + assert(!is_text_kind(V_UNSIGNED) and !is_text_kind(V_VEC) and !is_text_kind(V_NONE) and !is_text_kind(V_ENUM) and !is_text_kind(V_OUTVEC)); + assert(text_count(V_NONE, V_NONE, V_NONE, V_NONE, V_NONE) == 0); + assert(text_count(V_STR, V_UNSIGNED, V_NONE, V_NONE, V_NONE) == 1); + assert(text_count(V_STR, V_STRING, V_NONE, V_NONE, V_NONE) == 2); + assert(text_count(V_STR, V_UNSIGNED, V_BYTES, V_BOOL, V_RSTRING) == 3); + assert(text_count(V_STR, V_STR, V_STR, V_STR, V_STR) == 5); + // the haystack for 0 and 2, the needle for 1: the needle is at the start of the haystack and again at its end + put_pair_sample(&st, &out, 0); + assert(out_is(st[W], &out, 0, "abcabc")); + put_pair_sample(&st, &out, 1); + assert(out_is(st[W], &out, 6, "abc")); + put_pair_sample(&st, &out, 2); + assert(out_is(st[W], &out, 9, "abcabc")); + assert(st[W] == 15); + assert(PAIR_N == 3); +} + +test derived_at_puts_the_pair_samples_first_for_a_fn_with_two_texts_and_gives_each_literal_three { + var st : [1024]usize = [0] ** 1024; + const src : []const u8 = "fn arm(s: &str, t: &str) -> bool { s == \"+=\" }"; + const fb : usize = find_at(src, 0, B_LBRACE); + st[FBLO] = fb + 1; + st[FBHI] = block_end(src, fb) - 1; + // one text: the literal alone and wrapped, no pair sample + assert(slen(src, &st, V_STR, 0) == 10); + assert(derived_at(src, &st, V_STR, 0, 0) and st[DCSP] == 0); + assert(derived_at(src, &st, V_STR, 0, 1) and st[DCSP] == 1); + assert(!derived_at(src, &st, V_STR, 0, 2)); + // two texts: a text, a text inside it, the first again; then the literal alone, wrapped, alone + st[PAIRS] = 2; + assert(derived_at(src, &st, V_STR, 0, 0) and st[DCSP] == 2 and st[DVAL] == 0); + assert(derived_at(src, &st, V_STR, 0, 1) and st[DCSP] == 2 and st[DVAL] == 1); + assert(derived_at(src, &st, V_STR, 0, 2) and st[DCSP] == 2 and st[DVAL] == 2); + assert(derived_at(src, &st, V_STR, 0, 3) and st[DCSP] == 0 and st[DCLB] - st[DCLA] == 2 and src[st[DCLA]] == B_PLUS); + assert(derived_at(src, &st, V_STR, 0, 4) and st[DCSP] == 1 and st[DCLB] - st[DCLA] == 2); + assert(derived_at(src, &st, V_STR, 0, 5) and st[DCSP] == 0 and st[DCLB] - st[DCLA] == 2); + assert(!derived_at(src, &st, V_STR, 0, 6)); + assert(slen(src, &st, V_STR, 0) == 14); + // a single text is not a pair, whatever else the fn takes + st[PAIRS] = 1; + assert(slen(src, &st, V_STR, 0) == 10); + // bytes: the same, with the byte string b"ab" + const bs : []const u8 = "fn f(a: &[u8], b: &[u8]) -> bool { a == b\"ab\" }"; + const bf : usize = find_at(bs, 0, B_LBRACE); + st[FBLO] = bf + 1; + st[FBHI] = block_end(bs, bf) - 1; + st[PAIRS] = 2; + assert(slen(bs, &st, V_BYTES, 0) == 11); + assert(derived_at(bs, &st, V_BYTES, 0, 2) and st[DCSP] == 2); + assert(derived_at(bs, &st, V_BYTES, 0, 5) and st[DCSP] == 0 and st[DCLB] - st[DCLA] == 2); + // a number ignores the count of texts: its samples are the body's integers, one below, at it and above + const num : []const u8 = "fn lim(a: u8, s: &str, t: &str) -> bool { a == 9 }"; + const nf : usize = find_at(num, 0, B_LBRACE); + st[FBLO] = nf + 1; + st[FBHI] = block_end(num, nf) - 1; + const uq : usize = skip_ws(num, tok_end(num, skip_ws(num, tok_end(num, skip_ws(num, find_at(num, 0, B_LPAREN) + 1))))); + assert(dcount(num, &st, V_UNSIGNED, uq, 7) == 3); + assert(derived_at(num, &st, V_UNSIGNED, uq, 0) and st[DVAL] == 8 and st[DCSP] == 0); + // a fn with no body to read has no derived sample at all, pairs included + st[FBLO] = num.len; + st[FBHI] = num.len; + assert(!derived_at(num, &st, V_STR, 0, 0)); +} + test the_sample_count_caps_at_64 { var st : [1024]usize = [0] ** 1024; const src : []const u8 = "fn many(s: &str) -> bool { s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || s == \"0\" || false }"; @@ -11505,6 +11670,11 @@ test the_sample_count_caps_at_64 { assert(slen(src, &st, V_STR, 0) == 64); assert(derived_at(src, &st, V_STR, 0, 59)); assert(!derived_at(src, &st, V_STR, 0, 60)); + // with two texts the three pair samples come first and each literal gives three samples: 3 + 90 derived, still 64 in all + st[PAIRS] = 2; + assert(slen(src, &st, V_STR, 0) == 64); + assert(derived_at(src, &st, V_STR, 0, 92)); + assert(!derived_at(src, &st, V_STR, 0, 93)); } test the_oracle_samples_the_arms_discriminants { @@ -14571,7 +14741,7 @@ test std_bool_find_and_let_int_read_the_std_table_and_a_typed_let { assert(find("count_ones", C_INT, "u16") == 55); assert(find("nosuch", C_ANY, "") == STD_N); assert(find("", C_ANY, "") == STD_N); - var st : [64]usize = [0] ** 64; + var st : [65]usize = [0] ** 65; assert(let_int(" u32", &st, 1) == 1); assert(let_int(" usize", &st, 1) == 1); assert(let_int(" i8", &st, 1) == 1); @@ -19573,7 +19743,7 @@ test out_fmt_arg_and_out_format_append_each_hole_of_a_format_string { test out_str_push_out_str_plus_and_out_piece_call_append_to_the_buffer { var out : [256]u8 = undefined; - var st : [64]usize = [0] ** 64; + var st : [65]usize = [0] ** 65; out_str_push("'x');", &st, &out, 0); assert(out_is(st[W], &out, 0, "t27o_w = rust_std::append_char(out, t27o_w, 'x');")); assert(st[P] == 5); @@ -20100,7 +20270,7 @@ test prescan_and_reused_enter_the_names_a_file_declares_before_its_items { var none : [1024]usize = [0] ** 1024; prescan("fn f() {}\n//@t27o reuse nopath\n//@t27o reuse \n//@t27o reuse g p\n", &none); assert(none[NNAMES] == 1); - var tiny : [64]usize = [0] ** 64; + var tiny : [65]usize = [0] ** 65; prescan(s, &tiny); assert(tiny[IXON] == 0); assert(tiny[NNAMES] == 1); From 7d20c70707aea510e9b9c244ffeaea8426362d1c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 01:51:33 +0700 Subject: [PATCH 3/3] t27o: a refusal caused by a refused receiver names the receiver type, not `self` or `Self` -- 649 fns of the sweep can be followed to the type's own reason (Closes #11128) A method of a type the converter refuses (a struct with a heap field, an enum with data, a refused item) is refused with it, and its refusal line said "uses refused `self`" or "uses refused `Self`". Neither is the name of any refusal in the file, so a lookup by name found nothing and the sweep, which follows a named callee to its root (#11122), had to leave the cascade as it was: 696 fn of the 4,468 cascades. deps now refuses at the receiver (the `self` of the signature, or the `Self` of a body) with the impl type as the token, the line staying the receiver's: "uses refused `Foo` at line 3", and `Foo` has its own line, "refused struct Foo (line 1): type `String`". Nothing else changes: a static fn of the same impl that names neither the type nor `self` converts, the methods of a type that converts convert, a method that uses a refused method still names that method, and an impl of a type declared nowhere in the file stays "undeclared" (that check comes first). Tests first (the new convert test and the two new asserts of the direct deps test failed on the merge base): `&self`, a `Self` constructor, a method calling another, a refused enum, the free fn that takes the type, and the controls; one pinned line of an older test, `uses refused `Self``, now names the type. Measured on the 1,388 tracked Rust files of the five repositories (19,392 fn): the sweep's root attribution of #11127, replayed on the converted text (its numbers reproduced on the merge base: 4,531 cascade lines, 3,824 attributed, 696 fn and 707 items left, 570 + 86 of them self and Self), leaves 58 fn and 69 items unattributed after this change, none of them a receiver. Fns converted 1,824 before and after; none converted becomes refused, none refused becomes converted; 649 first refusals change their token and nothing else. What is left are names the file's own refusal lines do not carry: the type aliases the generic item refusal prints as "refused type" (47 fn), extern fns and an assert. t27b test 363 of 363, t27c coverage 100 % (420 of 420), seal --save 363 of 363 vacuous 0, --verify MATCH on rust_convert, rust_std, zig_convert, spice_convert, zig_std, ledger, golden, install, service. Co-Authored-By: Claude Sonnet 5.5 --- .trinity/seals/t27o_T27oRust.json | 16 +++++------ gen/rust/tools/t27o/rust_convert.rs | 6 ++-- specs/tools/t27o/rust_convert.t27 | 44 ++++++++++++++++++++++++++--- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/.trinity/seals/t27o_T27oRust.json b/.trinity/seals/t27o_T27oRust.json index 22add8d8f2..f4c63e711f 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:9e4b3abfefcb323fcbf425c16e320cc0ee7a77c515335def96be66cc21ffe100", - "gen_hash_rust": "sha256:68cd29da62d9e4e6667f57dfff4be72e7d6c9496fa9f034e0a6a8005292a0e1f", - "gen_hash_verilog": "sha256:cd09a5b52ba17d76db2d6944d154b0d1970d5d40be45cea74778eecf62f12bf9", - "gen_hash_zig": "sha256:17985cb3d9cf868fb3f5f9eb30a56b1a562b08b7c70293bf31e54d93ad80749e", + "gen_hash_c": "sha256:b8e42e85b2ccd7e591ca3b485fee80244056e0cd01bebeea25b14aa73bd89e68", + "gen_hash_rust": "sha256:fdac1e1c4eeebc210cf0e4787f858970791f93b25b51672fb930e3a86668525e", + "gen_hash_verilog": "sha256:9019dde0e27c80177a221ab7e74eb64d0264c1b52d2d0116cde1bb0c55f5e75f", + "gen_hash_zig": "sha256:531c54a26c50418065283f6c78ea153d2f3b98bc4f2d6531e79cf2d3b5ab776c", "module": "T27oRust", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T18:46:35Z", + "sealed_at": "2026-10-19T18:50:55Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:b273204b53ae63129754d48ae09fabdc71a8e41604ed9a6abcd18d59b25c29d6", + "spec_hash": "sha256:455dc21c58322da101de1ade5ebc45e713e35b895861860b85709706297d345f", "spec_path": "specs/tools/t27o/rust_convert.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 362, - "total": 362, + "passed": 363, + "total": 363, "vacuous": 0 }, "toolchain": { diff --git a/gen/rust/tools/t27o/rust_convert.rs b/gen/rust/tools/t27o/rust_convert.rs index 99e2d35493..3bae742f65 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: 362 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 363 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. @@ -1460,7 +1460,7 @@ pub fn deps(src: &[u8], st: &mut [usize], lo: usize, hi: usize) -> () { while ((sq < hi) && (st[(ERR) as usize] == 0)) { let se: usize = tok_end(src, sq); if word_is(src, sq, se, b"self") { - refuse(st, R_DEPENDS, sq, sq, se); + refuse(st, R_DEPENDS, sq, st[(ILO) as usize], st[(IHI) as usize]); } sq = skip_ws(src, se); } @@ -1525,7 +1525,7 @@ pub fn deps(src: &[u8], st: &mut [usize], lo: usize, hi: usize) -> () { let c2: usize = skip_ws(src, tok_end(src, c1)); let self_call: bool = (((((tok_is(src, c1, b"::") && (c2 < src.len())) && is_alpha(src[(c2) as usize])) && !(is_upper(src[(c2) as usize]))) && tok_is(src, skip_ws(src, tok_end(src, c2)), b"(")) && declared_fn(src, st, c2, tok_end(src, c2))); if !(self_call) { - refuse(st, R_DEPENDS, q, q, e); + refuse(st, R_DEPENDS, q, st[(ILO) as usize], st[(IHI) as usize]); } } if ((((((((st[(ERR) as usize] == 0) && call) && !(after_dot)) && !(after_path)) && !(after_fn)) && !(is_upper(src[(q) as usize]))) && !(call_keyword(src, q, e))) && !(declared_fn(src, st, q, e))) { diff --git a/specs/tools/t27o/rust_convert.t27 b/specs/tools/t27o/rust_convert.t27 index 4c07ada6d0..9297726fe7 100644 --- a/specs/tools/t27o/rust_convert.t27 +++ b/specs/tools/t27o/rust_convert.t27 @@ -113,6 +113,9 @@ ; Vec / Box and other heap types, a `?` inside an expression (only `let x = e?;` and `e?;`), tuples, enum ; variants with data, labels, &mut of a scalar, `mut self`, unsafe, and if / match / blocks used as a ; value without a type annotation. +; A method of a refused type is refused with it, and the refusal names the impl type, at the line of the receiver, not +; `self` or `Self`: the name a refusal of the file's own struct or enum line has, so a reader or the sweep can follow +; the cascade to its reason (#11128). ; -- ; The caller passes the source, an output buffer and a state vector: STATE slots, the index slots (IXHIT .. ; IXTOP), then the name table (NAME_SLOTS a name, as many as fit; the declaration index takes the top of @@ -1348,12 +1351,14 @@ pub fn deps(src: []const u8, st: []usize, lo: usize, hi: usize) -> void { if (st[ILO] != st[IHI] and st[ERR] == 0 and !declared_type(src, st, st[ILO], st[IHI])) { refuse(st, R_UNDECL, st[ILO], st[ILO], st[IHI]); } // a member that takes `self` still belongs to the type that is not in the output: its t27 // form would carry a parameter of the missing type. The acceptance here is the self-less - // associated fn, which names neither the type nor a field of it. + // associated fn, which names neither the type nor a field of it. The refusal names the impl type, the + // thing the fn depends on, at the line of the receiver: `self` is no name any refusal of the file has, so + // the sweep could not follow it to the type's own reason (#11128). if (self_bad) { var sq : usize = lo; while (sq < hi and st[ERR] == 0) { const se : usize = tok_end(src, sq); - if (word_is(src, sq, se, "self")) { refuse(st, R_DEPENDS, sq, sq, se); } + if (word_is(src, sq, se, "self")) { refuse(st, R_DEPENDS, sq, st[ILO], st[IHI]); } sq = skip_ws(src, se); } } @@ -1406,7 +1411,7 @@ pub fn deps(src: []const u8, st: []usize, lo: usize, hi: usize) -> void { const c1 : usize = skip_ws(src, e); const c2 : usize = skip_ws(src, tok_end(src, c1)); const self_call : bool = tok_is(src, c1, "::") and c2 < src.len and is_alpha(src[c2]) and !is_upper(src[c2]) and tok_is(src, skip_ws(src, tok_end(src, c2)), "(") and declared_fn(src, st, c2, tok_end(src, c2)); - if (!self_call) { refuse(st, R_DEPENDS, q, q, e); } + if (!self_call) { refuse(st, R_DEPENDS, q, st[ILO], st[IHI]); } } if (st[ERR] == 0 and call and !after_dot and !after_path and !after_fn and !is_upper(src[q]) and !call_keyword(src, q, e) and !declared_fn(src, st, q, e)) { refuse(st, R_UNDECL, q, q, e); @@ -10933,7 +10938,7 @@ test a_selfless_member_of_a_refused_impl_type_converts { var out : [2048]u8 = undefined; var st : [1024]usize = [0] ** 1024; const n : usize = body("struct Gen { code: Vec }\nimpl Gen {\n fn is_bin(op: &str) -> bool { matches!(op, \"+\" | \"-\") }\n fn wide(w: u32, op: &str) -> u32 { if w > 8 { w } else if Self::is_bin(op) { 1 } else { 0 } }\n fn same(a: &Self, b: u8) -> bool { b > 0 }\n}", &out, &st); - assert(out_is(n, &out, 0, "use tri::t27b::type_text;\n\n// t27o: refused struct Gen (line 1): type `Vec` at line 1\n\nfn Gen_is_bin(op: []const u8) -> bool {\n return (type_text::same(op, \"+\") or type_text::same(op, \"-\"));\n}\n\nfn Gen_wide(w: u32, op: []const u8) -> u32 {\n if (w > 8) {\n return w;\n } else if (Gen_is_bin(op)) {\n return 1;\n } else {\n return 0;\n }\n}\n\n// t27o: refused fn same (line 5): uses refused `Self` at line 5\n\n")); + assert(out_is(n, &out, 0, "use tri::t27b::type_text;\n\n// t27o: refused struct Gen (line 1): type `Vec` at line 1\n\nfn Gen_is_bin(op: []const u8) -> bool {\n return (type_text::same(op, \"+\") or type_text::same(op, \"-\"));\n}\n\nfn Gen_wide(w: u32, op: []const u8) -> u32 {\n if (w > 8) {\n return w;\n } else if (Gen_is_bin(op)) {\n return 1;\n } else {\n return 0;\n }\n}\n\n// t27o: refused fn same (line 5): uses refused `Gen` at line 5\n\n")); assert(st[NOK] == 2); } @@ -13450,6 +13455,32 @@ test deps_refuses_a_use_of_a_refused_type_a_foreign_name_and_an_undeclared_one { assert(i[ERR] == 0); } +test a_refusal_caused_by_a_refused_receiver_type_names_that_type { + var out : [4096]u8 = undefined; + var st : [1024]usize = [0] ** 1024; + // A method of a type the converter refused is refused with it, and the line named `self` or `Self`, a name no refusal in + // the file has, so the sweep could not follow it to the type's own reason (#11128). It names the impl type, at the line + // of the receiver: for a `&self` method, for a `Self` constructor, for a method that calls another one, and for a + // refused enum. A static fn that names neither `self` nor the type, and every method of a type that converts, are + // converted as before. + const n : usize = body("struct Foo { name: String, n: u32 }\nimpl Foo {\n fn len(&self) -> usize { self.n as usize }\n fn unit() -> Self { Self { n: 0 } }\n fn kind() -> u32 { 7 }\n fn twice(&self) -> usize { self.len() * 2 }\n}\nstruct Fine { a: u32 }\nimpl Fine {\n fn a(&self) -> u32 { self.a }\n fn unit() -> Self { Self { a: 0 } }\n}\nenum E { A(u32), B }\nimpl E {\n fn is_b(&self) -> bool { true }\n}\nfn free(f: &Foo) -> usize { f.len() }\nstruct Ok2 { a: u32 }\nimpl Ok2 {\n fn bad(&self) -> Vec { Vec::new() }\n fn uses(&self) -> usize { self.bad().len() }\n}\n", &out, &st); + assert(out_text_has(&out, n, "// t27o: refused struct Foo (line 1): type `String` at line 1\n")); + assert(out_text_has(&out, n, "// t27o: refused fn len (line 3): uses refused `Foo` at line 3\n")); + assert(out_text_has(&out, n, "// t27o: refused fn unit (line 4): uses refused `Foo` at line 4\n")); + assert(out_text_has(&out, n, "// t27o: refused fn twice (line 6): uses refused `Foo` at line 6\n")); + assert(out_text_has(&out, n, "// t27o: refused enum E (line 13): enum variant with data `A` at line 13\n")); + assert(out_text_has(&out, n, "// t27o: refused fn is_b (line 15): uses refused `E` at line 15\n")); + assert(out_text_has(&out, n, "// t27o: refused fn free (line 17): uses refused `Foo` at line 17\n")); + assert(!out_text_has(&out, n, "uses refused `self`")); + assert(!out_text_has(&out, n, "uses refused `Self`")); + // the negative controls: a static member that names neither, the methods of the type that converts, and a method that + // uses a refused method of a type that converts (the callee is the name, as before) + assert(out_text_has(&out, n, "// t27o: refused fn uses (line 21): uses refused `bad` at line 21\n")); + assert(out_text_has(&out, n, "fn Foo_kind() -> u32 {\n return 7;\n}")); + assert(out_text_has(&out, n, "fn Fine_a(self: Fine) -> u32 {\n return self.a;\n}")); + assert(out_text_has(&out, n, "fn Fine_unit() -> Fine {\n return Fine{ .a = 0 };\n}")); +} + test deps_keeps_a_member_of_a_refused_impl_type_when_it_names_neither_self_nor_the_type { var a : [128]usize = [0] ** 128; const s1 : str = "struct Ghost {} impl Ghost { fn m(&self) { } }"; @@ -13459,6 +13490,9 @@ test deps_keeps_a_member_of_a_refused_impl_type_when_it_names_neither_self_nor_t deps(s1, &a, 29, s1.len); assert(a[ERR] == R_DEPENDS); assert(a[AT] == 35); + // the line is at the receiver, the token it names is the impl type (#11128) + assert(a[NLO] == 21); + assert(a[NHI] == 26); var b : [128]usize = [0] ** 128; const s2 : str = "struct Ghost {} impl Ghost { fn s(a: u8) -> u8 { a } }"; add_name(s2, &b, K_ITEM, 7, 12, 0, 0); @@ -13474,6 +13508,8 @@ test deps_keeps_a_member_of_a_refused_impl_type_when_it_names_neither_self_nor_t deps(s3, &c, 29, s3.len); assert(c[ERR] == R_DEPENDS); assert(c[AT] == 39); + assert(c[NLO] == 21); + assert(c[NHI] == 26); var d : [128]usize = [0] ** 128; const s4 : str = "struct Ghost {} impl Ghost { fn u() { Self::s(); } fn s() {} }"; add_name(s4, &d, K_ITEM, 7, 12, 0, 0);