Skip to content

t27o: converter trio -- callers of an out-buffer String fn refused, a match in the oracle of a two-text fn, refused receivers named - #11130

Merged
gHashTag merged 3 commits into
masterfrom
t27o/converter-trio-r1
Oct 19, 2026
Merged

gHashTag merged 3 commits into
masterfrom
t27o/converter-trio-r1

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

Closes #11111
Closes #11113
Closes #11128

Lane R1 of the t27o loop (Option A, deepen the Rust converter). Three open items of specs/tools/t27o/rust_convert.t27, one commit each. The rule is in the spec (tests and negative controls in the same file); gen/rust/tools/t27o/rust_convert.rs is t27c gen-rust of it and .trinity/seals/t27o_T27oRust.json is t27c seal --save, neither merged by hand. No hand Rust changed.

1. A caller of an out-buffer String fn is refused (see #11111)

Fix. deps treated a fn in the String out-buffer form (K_STRFN) as a name that refuses nobody, so tag(n).len() came out as tag(n).len and let t = tag(n); as const t = tag(n); -- a call with the out buffer missing. It now treats it as it treats the Vec form (K_VECFN): 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 the new R_STRCALL: "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". 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, the split chain (.map(zig_ident), a name, not a call) still converts.

Failing tests first. a_call_of_an_out_buffer_string_fn_is_refused_because_the_caller_would_own_the_buffer (either declaration order) and a_call_of_an_out_buffer_string_method_is_refused_by_method_and_by_path failed with the rule absent; negative_control_a_caller_of_a_string_fn_outside_the_out_buffer_form_keeps_its_old_reason passes before and after. Direct test deps_refuses_a_call_of_a_fn_in_an_out_buffer_form_naming_the_form (call, bare name, declaration, method, path, wrong owner, Self, the Vec form still R_VECCALL); the reason table test has the new row.

Evidence. The corpus has 13 fns in the String form in 9 files and no call of one in its own file (checked on the converted text before and after), so no output changes there; the evidence is the tests. A method call is matched by name after a dot, as the Vec form does, so a same-named method of another type in the same file is refused too (the reason names the callee).

2. The oracle of a fn with two texts samples a match (see #11113)

Cause. Parameter i of a sampled call takes 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 recorded count was 0. Its body has no string literal, so the literal samples of #9102 could not help.

Fix. vectors counts the direct texts of the fn (text_count, kept in the new PAIRS slot, cleared at the end). With two or more, derived_at gives each text parameter three fixed samples first: a text, a text inside it, the first text again ("abcabc", "abc", "abcabc"), so one call has the needle inside the haystack and the next the haystack second; "abc" occurs twice in "abcabc" (offsets 0 and 3, the second ending at its 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. Bounded: three 6-byte texts, the DCAP of 64 samples a parameter, and the 3 s / 16 MiB guard of #11109 (which still skips the empty needle). A fn with one text, or a text and a number, is sampled as before; a number never reads the count of texts.

Failing tests first. the_oracle_of_a_fn_with_two_texts_samples_a_needle_inside_a_haystack_in_both_orders and a_literal_of_a_fn_with_two_texts_is_sampled_alone_and_inside_a_sample_in_both_orders failed on the merge base; negative_control_a_fn_with_one_text_gets_no_pair_samples_and_the_fixed_ones_stay passes before and after. Direct tests for is_text_kind, text_count, put_pair_sample, derived_at with the count set (texts, bytes, a number, no body), and the cap of 64 with pairs. The state vector has one more slot, so three tests that give the name table exactly one slot got one more ([64] to [65]).

Evidence (rustc, run). The harness of crates/trios-phd/src/main.rs on the merge base records seven calls of regex_lite_global, every count 0 (the eighth, the empty needle, is skipped by the guard); with this change it records ("abcabc", "abc") count 2, elements 0 and 3, and the zero-length buffer answers 2. t27b test of the converted spec plus those records passes 4 of 4, as before. Over the 1,388 files the converted specs are byte for byte the same; 5 oracle harnesses change (bootstrap/src/compiler.rs, cli/tri/src/gates.rs, rings/ring-090-rust lib.rs, trios-phd main.rs, tools/defense_gate lib.rs). The three t27b can judge pass before and after (25 of 25, 4 of 4, 4 of 4); the other two stop at an unsupported construct (@max) before and after.

3. A refusal caused by a refused receiver names its type (see #11128)

Fix. A method of a type the converter refuses is refused with it; the line said "uses refused self" or "Self", names no refusal of the file has, so the sweep's lookup found nothing. deps now refuses at the receiver (the self of the signature, 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"). A static fn that names neither the type nor self, the methods of a type that converts, a method that uses a refused method (still names the method), and an impl of a type declared nowhere in the file ("undeclared", checked first) are unchanged.

Failing tests first. a_refusal_caused_by_a_refused_receiver_type_names_that_type (&self, a Self constructor, a method calling another, a refused enum, the free fn that takes the type, controls) and two new asserts of deps_keeps_a_member_of_a_refused_impl_type_when_it_names_neither_self_nor_the_type failed on the merge base. One older test pinned uses refused Self``; it now pins the type (Gen).

Evidence. Replaying the root attribution of #11127 (root_of: the quoted name is looked up in the same converted text, followed while it is a cascade) in Python over the converted text of all 1,388 files: on the merge base it reproduces that PR's figures (19,448 refusal lines, 4,531 cascade lines, 3,824 attributed, 696 fn and 707 items left, 570 self + 86 Self of them). With this change 58 fn and 69 items are left, none a receiver. Those are names the file's own refusal lines do not carry: 47 type aliases (the generic item refusal prints refused type, not the alias), 9 extern fns, 2 assert; the alias naming is the next lead.

Sweep, merge base 930834e against head (all 1,388 tracked Rust files, 19,392 fn; the A4 driver, not tri t27o-sweep)

merge base head
fn seen 19,392 19,392
fn converted 1,824 1,824
tests converted 619 of 9,230 619 of 9,230
fn first refusals 16,057 16,057
of them "uses refused" 4,468 4,468
of those naming self / Self 563 / 86 0 / 0 (649 now name the type)
first refusals of every other kind unchanged
cascades counted under a root (fn) 3,772 4,410
cascades left unattributed (fn) 696 58
cascades left unattributed (all items) 707 69
files whose converted spec changes 153 of 1,388, in // t27o: refused comment lines only (0 files differ elsewhere)
converted fn that becomes refused 0
refused fn that becomes converted 0
pass to not-pass 0 (the code of every converted spec is identical; the 5 changed harnesses replayed above)
fns in the String form / calls of them in their file 13 / 0 13 / 0

Checks

  • t27b test specs/tools/t27o/rust_convert.t27: 363 passed, 0 failed (353 on the merge base; 357, 362, 363 after the three commits). t27c test-report: 363 of 363, vacuous passes 0 of 363. t27c coverage: 100 % (420 of 420; 417 before, the three new fns have direct tests).
  • t27c seal --save for rust_convert.t27 after each commit; --verify MATCH on rust_convert, rust_std, zig_convert, spice_convert, zig_std, ledger, golden, install, service, so no other seal was re-saved. t27b test of zig_convert (61) and spice_convert (44) pass; neither was edited. The generated file is byte-identical to t27c gen-rust of the spec and grew 439,470 to 441,420 bytes; its fn list differs from the merge base's only by is_text_kind, text_count, put_pair_sample (no spliced fn).
  • Own language: the PR changes gen/rust/tools/t27o/rust_convert.rs, the seal json and the spec; no file under cli/, 0 hand lines. The budget compiled from the merge base's gen/c/policy/own_language.c and fed git diff --numstat --no-renames of the branch exits 0; as a negative control the same binary on 10 9 cli/tri/src/main.rs exits 1.
  • python3 -I tools/dupe_scan.py: merge base "1632 of 17450 bodies in 632 groups, ok"; head "1632 of 17453 in 632 groups, ok" (the 3 bodies are the new fns). t27c dupes --name for each new fn: none elsewhere (put_pair exists in specs/tri/t27b/text_escape.t27, so mine is put_pair_sample); t27c dupes --bodies names none of them. python3 -I tools/check_devhome_paths.py OK; the spec is ASCII.

Not run

  • cargo build -p tri (no cargo build in this lane): so not tri t27o-sweep, not tri t27o --check, and not the commit-time hooks (no tri binary; the hooks printed that nothing was checked). The sweep above is the converter built with rustc from the generated file, called as cmd_t27o calls it.
  • The ledger.rs of t27o-sweep: a cascade refusal is counted under its root, and the files that convert whole are counted #11127 itself (still in CI): its root_of was replayed in Python, which reproduces that PR's figures on the merge base.
  • The full t27c suite / tri test, and CI.
  • An oracle replay of the whole corpus: only 5 harnesses change, 3 reach a t27b verdict (above), the rest of the corpus differs in comment lines only.

🤖 Generated with Claude Code

claude and others added 3 commits October 20, 2026 01:34
…at 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 <noreply@anthropic.com>
…stack, 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 <noreply@anthropic.com>
… 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 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-19 18:55:24 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 47
PRs with All Checks Green 3
READY 2
FAILING 47
PENDING 0
NO CHECKS YET 0

These columns do not partition: 2 + 47 + 0 + 0 = 49, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=e34e1b174c96 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants