Skip to content

t27o: the pure std methods convert -- bit counts, pow, saturating, div_ceil, sqrt, is_finite, to_bits, is_ascii, unwrap_or, get, contains (Closes #10764) - #10801

Merged
gHashTag merged 12 commits into
masterfrom
t27o/std-methods-10764
Oct 19, 2026
Merged

gHashTag merged 12 commits into
masterfrom
t27o/std-methods-10764

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Lane A2 of the t27o epic #10522 (option A, backlog row #10602). The pure scalar and text methods of Rust's std that t27o refused now convert through specs/tools/t27o/rust_std.t27 and the std table of rust_convert.t27, each fn proven against values rustc 1.99 printed. Closes #10764.

What converts

Rust method Receiver types t27
count_ones, leading_zeros, trailing_zeros u8 u16 u32 u64 usize i8 i16 i32 i64 <method>_<T> -> u32
pow the same nine pow_<T>, Rust's own loop
saturating_add/sub/mul the same nine saturating_<op>_<T>
div_ceil u8 u16 u32 u64 usize (the signed one is unstable in Rust) div_ceil_<T>
sqrt f32 f64 the builtin @sqrt (correctly rounded)
is_finite, to_bits f32 f64 is_finite_<T>, to_bits_<T>
is_ascii &str, &[u8], u8 is_ascii_text, is_ascii_u8
to_ascii_lowercase/uppercase u8 to_ascii_*_u8
unwrap_or Option and Result (both ?T) if (r != null) { r.? } else { d }
get(i) slice, Vec or array of u8 .. i64, usize, f32, f64 get_<T> -> ?T
contains(&x) slices of u8 only (already bytes_contain) other scalars refused, see below
contains(c) on a text, c a char variable &str str_contains_char (UTF-8 bytes of c)

Every expected value in the tests was printed by rustc: MIN, MAX, 0, 1, -1, NaN, both infinities, -0.0, empty slice and text, the neighbours of every edge. Checked on t27b test and on t27c test-report (Zig); the fns of every family but to_bits also through gen-rust + rustc against Rust's own method.

What stays refused, with the reason

  • ln: its own kind now, "libm function not bit-exact" (R_LIBM). Rust calls the platform libm, t27's @log is Zig's own port, IEEE 754 does not fix a log's last bit, and gen-rust prints @log as .log() (E0061). Evidence only, not a promise: Zig 0.16 @log and rustc f64::ln agree bit for bit on 200000 probed inputs on this host.
  • contains on a slice of any scalar but u8: its own kind, "slice contains on <T> ...: no generic helper, and per-type copies are refused by the DRY ratchet" (R_SLICEGEN); t27 has no generics, and tools/dupe_scan.py refuses a copy of bytes_contain per type.
  • a text's to_ascii_lowercase (owned String), a char receiver's (t27o has no char class), the signed div_ceil, get with a range or on a text, contains on a slice of texts.
  • unwrap_or inside an expression (1 + x.unwrap_or(5), x.unwrap_or(5) as u32, foo(x.unwrap_or(5))) or on a receiver that calls a fn of the file: t27 has no orelse, the form reads the receiver twice, and the reference prints an if used as a value without parentheses, so its last arm takes in what follows ((if ..) * 2.0 is refused by t27b and mistyped by Zig). The call must stand alone as a value (a let, an assignment, a field, a tail) and the receiver may call only the std table's pure helpers.

Deviations from the issue text

  • pow is not refused unless both operands are constants. The fn is Rust's loop step for step, so it overflows where Rust's does: t27b traps ("integer overflow") and Zig panics, exactly as for every a * b the converter already writes (probed on pow_u8(16, 2)). A refusal would have kept 10u64.pow(n) and (n as u64).pow(2) out while a * b converts under the same rule.
  • to_bits converts although gen-rust cannot spell it: the fn is an @bitCast, exact in Zig and t27b (pinned by specs/tri/t27b/conformance/bit_cast.t27), printed as written by gen-rust, which rustc does not read. The header of rust_std.t27 says so. Converted output that calls it is for those two backends.

saturating_add_u64 is built on checked_add_u64, not a copy of hosting::panel's body (same reason).

Receiver typing fixes this needed (all in rust_convert.t27)

  • (expr as T) is typed T: before, the first typed name inside the group won, so (n as u64).pow(2) with n: u32 named pow_u32.
  • x.len() is a usize (unless the file declares a fn named len).
  • an unsuffixed literal no longer inherits the previous name's type span ((v.len() - 1) with v: &[u8] was read as u8).
  • a &[T; N] is written as a slice everywhere, but the slice rows passed it as &v (a pointer to a slice); only an array held by value goes by address.
  • a.is_empty() on an array held by value was (&a.len == 0).
  • s.contains(c) with c: char handed a u32 to a text parameter.

Measure

Method first refusals (refused fn .. : method <name> lines of tri t27o <file> over every tracked .rs of the five repositories, the way the baseline of #10764 counted; that baseline said 3167, this master measures 3207). Before: tri built from origin/master b94c869. After: tri built from this branch (05b4785). Sources: t27 b94c869, trios 483dd9ee, trinity 0770abb9, trinity-fpga 20bf4c418, 999-multibots-telegraf 6e5512881.

method before after
count_ones 8 6
trailing_zeros 1 0
leading_zeros 3 2
pow 8 0
saturating_add 21 9
saturating_sub 29 5
saturating_mul 2 1
div_ceil 12 7
sqrt 49 3
ln 14 0 (14 now "libm function not bit-exact")
is_finite 12 2
to_bits 12 5
is_ascii 12 1
to_ascii_lowercase 11 12 (earlier refusals went away, more fns reach it)
unwrap_or 14 8
get 68 67 (the rest are HashMap / BTreeMap gets)
contains 124 120 (the rest: struct fields of other types, Vec of arrays; 7 more sites are in the new "slice contains on" kind)
all methods 3207 3078

Converted fns, same inputs: 1830 -> 1889 (+59); converted tests 648 -> 668. No file converts fewer fns or tests than before (24 convert more). All 96 changed outputs were compiled again: t27b agrees or improves on all but one (ema.rs now reaches the pre-existing f(..).?; statement that t27b refuses), the Zig runner shows 0 worse and 7 files with more passing tests.

tri t27o-sweep (--repo for the four others, same commits), converted / method kind before -> after: t27 641 -> 679 / 1515 -> 1470; trios 966 -> 984 / 1003 -> 941; trinity-fpga 22 -> 23 / 43 -> 40; trinity 8 -> 8 / 27; 999-multibots-telegraf 32 -> 32 / 78 (+6 and +8 in the new libm kind for t27 and trios, +7 slice contains in trios).

Gates

  • t27b test rust_std.t27: 51 passed; rust_convert.t27: 129 passed. tools/dupe_scan.py: no new group, none grew (682 of 13593 in 195 groups), ledger not blessed.
  • t27c test-report: vacuous passes 0 of 51 and 0 of 129. A test of about 600 asserts is not countable by the counting build ("evaluation exceeded 1000 backwards branches"), so the grids are one test per type.
  • t27c coverage specs/tools/t27o/rust_std.t27: 149 of 149 (100 %).
  • t27c seal --save / --verify on both specs: all hashes match; check_devhome_paths.py OK; commit hooks passed on every commit.
  • Boundary: only rust_std.t27, rust_convert.t27, the rust_convert gen file (regenerated with t27c gen-rust) and the two seals.

Found on the way, not fixed here

  • A caller of an out-buffer fn (fn emit() -> String) converts to const v = emit(1); without the buffer, so any text method on that local is wrongly typed (v.starts_with(..) on master too).
  • t.update(1.0).unwrap(); on a Result<(), E> becomes f(..).?; on a bool: t27b refuses it and Zig rejects the ignored value.
  • gen-rust wraps a returned name in Some(..) but not a returned index (return xs[i]; for -> ?T), and passes []const T by value as a Vec when the body uses for e in xs.

🤖 Generated with Claude Code

claude and others added 12 commits October 19, 2026 08:51
…td fn per integer width, expected values from rustc (Closes #10764)

Lane A2 of #10522, first of the pure std methods of #10764. The table gets three
rows (class integer, the nine widths u8 .. u64, usize, i8 .. i64) naming
count_ones_<T>, leading_zeros_<T> and trailing_zeros_<T> of rust_std, each of
which returns u32 as Rust's do. The unsigned 64-bit fns are the loops; the
narrower unsigned types widen to them, a signed one counts through its
complement (count_ones, via -1 - x, which never overflows), its sign
(leading_zeros) or halving (trailing_zeros, exact while the low bit is clear,
so MIN needs no unsigned cast).

Every expected value (357 asserts: 0, 1, 2, the high bit, all ones, MIN, MAX,
-1, alternating patterns, per width) was printed by rustc 1.99 running the
Rust method on the same input; the same fns also pass through gen-rust and
rustc against the builtin methods. The converter gets a table test and a
negative control (a float receiver stays refused). Regenerated gen file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… cast names the receiver's type (Closes #10764)

Lane A2 of #10522. `x.pow(e)` on an integer of any of the nine widths is
rust_std::pow_<T>(x, e), e a u32 as in Rust. The fn is Rust's algorithm step
for step (the same squarings and products), so it overflows exactly where
Rust's does: a debug build panics, and t27 traps on a `*` that overflows as it
does for every product the converter writes (t27b "integer overflow", Zig
panic -- probed on pow_u8(16, 2)). The issue offered refusing a pow that is
not provably free of overflow; that would refuse the t27o sweep's own
`10u64.pow(n)` and `(n as u64).pow(2)` shapes while every `a * b` already
converts under the same rule, so pow converts and the trap is the semantics.

725 asserts in nine tests (one per type), each value printed by rustc 1.99 (`pow` of base x exponent pairs
that `checked_pow` finds in range, up to MAX and MIN of each type, and 0.pow(0)
= 1); gen-rust output of the fns matches Rust's pow on 1000+ pairs.

recv_class learns `(.. as T)`: the cast names the receiver's type. Before, the
first typed name inside the group won, so `(n as u64).pow(2)` with n: u32 named
pow_u32 -- a type error in the output. Same fix serves abs, checked_* and
every later entry that carries a `%`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ust_std fn per integer width (Closes #10764)

Lane A2 of #10522. The three methods on an integer of any of the nine widths
are rust_std::saturating_<op>_<T>(a, b). The 64-bit unsigned ones are the
direct tests (b > MAX - a, b > a, b > MAX / a); the narrower unsigned types
add and multiply in u64 and clamp; usize is the 64-bit host's; i64 reads the
direction of the overflow off checked_add_i64 / sub / mul (b > 0 for an add,
b > 0 downward for a sub, equal signs upward for a product); i8 / i16 / i32
compute in i64, which holds every sum, difference and product of two of them,
and clamp.

hosting::panel has the u64 add under the same name, but a `use` of it splices
28 modules into the converted spec (#7176), which is why the table refused
saturating arithmetic so far; it is written here once instead, and that
refusal note in rust_convert.t27 is gone.

1128 asserts in nine tests, one per type (the counting build of test-report
cannot count a test of ~600 asserts: "evaluation exceeded 1000 backwards
branches", so a grid is split, as measured), each value printed by rustc 1.99
for the edge values taken pairwise: 0, 1, 7, a mid value, MAX - 1 / MAX and
MIN, -7, -1 for the signed ones. gen-rust output of the fns equals Rust's
methods over 30 x 30 pairs per type. A converter test covers fields of a
struct of the file, a chain and a usize.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t_std, expected values from rustc (Closes #10764)

Lane A2 of #10522. `a.div_ceil(b)` on u8, u16, u32, u64 or usize is
rust_std::div_ceil_<T>(a, b): the quotient, plus one when the remainder is
not zero. The u64 fn is the body; the narrower types widen to it (the
quotient is never above the dividend, so it narrows back) and usize is the
64-bit host's. A zero divisor traps in t27 as it panics in Rust. The signed
div_ceil is unstable in Rust (int_roundings), so a signed receiver has no
row and stays refused, which a negative control pins.

450 asserts in five tests, one per type, every value printed by rustc 1.99 for
the edge values taken pairwise (0, 1, 2, 3, 7, a power of two and its
neighbours, MAX / 2 + 1, MAX), divisor 0 left out. gen-rust output of the fns
equals Rust's div_ceil over 17 x 17 pairs per type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… with its reason (Closes #10764)

Lane A2 of #10522. `x.sqrt()` on a float is @sqrt(x): the IEEE 754 square
root, correctly rounded, so every backend gives Rust's bits. `x.is_finite()`
is rust_std::is_finite_<T>: (x - x) == 0.0, which is 0 for every finite x and
NaN for a NaN and for an infinity, so it needs no infinity constant.
`x.to_bits()` is rust_std::to_bits_<T>, an @bitcast that Zig and t27b run
exactly (specs/tri/t27b/conformance/bit_cast.t27 pins it) and that gen-rust
prints as written, which rustc does not read: converted output that calls it
is for the Zig and t27b backends, and the header of rust_std.t27 says so.
std_bool learns is_finite, so `!x.is_finite()` is a bool.

`x.ln()` stays refused, now with a reason of its own (R_LIBM, kind "libm
function not bit-exact") instead of "method": Rust calls the platform's libm
log, t27's @log is Zig's own port, IEEE 754 does not fix the last bit of a
log, and gen-rust prints @log as .log(), which takes a base (E0061). The two
agree on 200000 inputs probed on this host (Zig 0.16 @log against rustc 1.99
f64::ln, bit for bit), which is evidence, not a promise.

167 asserts in two tests (f64, f32): 24 bit patterns each (0, -0, +-1, halves,
MAX, MIN_POSITIVE, a subnormal, both infinities, quiet and signalling NaNs),
every expected value printed by rustc 1.99; a NaN result is tested as NaN
(x != x), its sign and payload being the CPU's. The converter gets a table
test and a negative control for ln and for sqrt of an integer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…case / to_ascii_uppercase for u8 (Closes #10764)

Lane A2 of #10522. Four small fns in rust_std: is_ascii_text (every byte
below 128, true for the empty text -- str::is_ascii and [u8]::is_ascii),
is_ascii_u8 (b < 128) and the two case maps on a byte (A-Z to a-z, a-z to
A-Z, every other byte untouched). The table maps `is_ascii` on a text, on a
slice of u8 and on a u8, and `to_ascii_lowercase` / `to_ascii_uppercase` on a
u8; std_bool learns is_ascii.

Not converted, by design and pinned by a negative control: a text's
to_ascii_lowercase returns an owned String (heap; the out-buffer form does
not cover a case map), and a char receiver has no class in t27o (type_class
knows no char), so `c.to_ascii_lowercase()` stays a refused method. These
are 10 of the 11 sites the sweep counts for to_ascii_lowercase.

67 asserts in one test: the edges of the letter ranges (63, 64, 65, 90, 91,
96, 97, 122, 123), a digit, control, DEL, 128 and 255 for each of the three
methods, then all 256 bytes at once through three checksums rustc printed,
then texts including the empty one and byte arrays with bytes above 127. The
fns also pass through gen-rust and rustc against Rust's methods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…std; a reference to an array is a slice (Closes #10764)

Lane A2 of #10522. `v.get(i)` on a slice, a Vec or an array of u8 .. i64,
usize, f32 or f64 is rust_std::get_<T>(v, i), an Option<&T> being ?T (a
reference to a scalar is the scalar): the element, or null from i == len on.
A range argument is not this fn (it gives an Option<&[T]>): `v.get(1..3)`
stays refused as the syntax it is, and a text's get stays a refused method;
both are pinned by a negative control. The HashMap / BTreeMap gets (about 60
of the 68 first refusals) are not slices and stay refused.

This also fixes an old defect that every slice row met: a `&[T; N]`
parameter, field, let or const is written as a slice, but type_class marked
it an array and the slice fns were called with `&v` -- a pointer to a slice.
Only an array held by value (`[T; N]`) goes by address now. A test pins both.

99 asserts in eleven tests, one per type (the counting build cannot count a
test of ~600 asserts, as measured): an index in range, at the last element,
one past it, far past it, usize::MAX, and an empty slice, every value printed
by rustc 1.99. The fns also pass through gen-rust and rustc against Rust's get.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…char held in a variable (Closes #10764)

Lane A2 of #10522. Two additions to the contains rows, which already covered
a text with a text or a char-literal needle, a slice of u8, and a constant
array (unrolled):

- `v.contains(&x)` on a slice, Vec or array of u16, u32, u64, usize, i8 ..
  i64, f32 or f64 is rust_std::contains_<T>(v, x) (u8 is bytes_contain, as
  before). The comparison is `==`, so a NaN is never contained and -0.0 is
  0.0, which the f32 / f64 tests pin.
- `s.contains(c)` with c a name declared `char` was converted to
  str_contains(s, c) -- a u32 handed to a text parameter, a type error in the
  output. It is str_contains_char(s, c) now: the char's UTF-8 bytes (one to
  four) searched with find_text. A char literal keeps the one-byte text it
  always was.

A slice of texts still has no row (t27 holds no slice of slices), which a
negative control pins.

92 + 88 asserts in eleven tests: per scalar type a member (first, middle,
last), a non-member and the edge neighbours, an empty slice, and NaN arrays
for the floats; then 8 texts (empty, ASCII, and holding two-, three- and
four-byte chars, entered as byte arrays) against 11 chars. Every value is
printed by rustc 1.99. The fns also pass through gen-rust and rustc against
Rust's contains.

gen-rust trap met on the way: char_var takes the fn's start slot, not st --
a read-only `st: []usize` passed on twice is E0382.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n a receiver that does nothing (Closes #10764)

Lane A2 of #10522. t27 has no `orelse`, so `r.unwrap_or(d)` on an Option or a
Result (both are ?T) is `if (r != null) { r.? } else { d }`, with no type
needed: strings, scalars and enums alike. Two facts bound where that is
right, and the converter checks both, refusing the call as a method when
either fails:

1. The form reads r twice and d only when r is null. A receiver or a default
   that calls anything but the std table's pure helpers (rust_std::, but not
   its appends; type_text::; toolchain::; builtins @name) could do something
   a second, or no, evaluation changes -- pure_out scans the written text.
   `next(s).unwrap_or(0)` with a fn of the file stays refused.
2. The reference prints an `if` used as a value without parentheses, so its
   last arm takes in what follows: `(if ..) * 2.0` is mistyped by Zig and
   refused by t27b ("ExprIf(left operand)"), as `1 + (if ..) - 2` would be
   misparsed. alone_as_value lets it through only as the value of a let, an
   assignment or a field (the output before it ends a single ` = `) or as a
   whole statement (a tail expression, which becomes the return), followed
   by `;`, `,` or `}`. `1 + x.unwrap_or(5)`, `x.unwrap_or(5) as u32` and
   `foo(x.unwrap_or(5))` stay refused; a negative control pins them.

expand learns `^`, the receiver as it was written, once more. It also stops
passing an array receiver by address to a row whose subject is the receiver
itself: `a.is_empty()` on an array held by value was `(&a.len == 0)`.

30 asserts in one test: the receivers the table makes (parse of every width
on in-range, out-of-range and empty text; find with a char and a text;
strip_prefix with a text and a char), each default unwrap_or'd, every value
printed by rustc 1.99; texts compared with type_text::same. Counted at run
time by the counting build, both runners pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…loses #10764)

t27c seal --save on both specs: 61 / 61 and 125 / 125 tests pass, --verify
matches every hash. The sweep's cache keys read these seals, so the numbers in
the PR are for these hashes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing_sub, div_ceil, pow on it; a literal no longer inherits the previous name's type (Closes #10764)

Lane A2 of #10522. `v.len().saturating_sub(1)`, `data.len().saturating_sub(pos)`,
`b.len().div_ceil(3)` are the commonest receivers of the methods this lane
converts (10 of the remaining first refusals), and their receiver has no type
word in the source to point `%` at. recv_class now classes a receiver that
ends in `.len()` as an integer of type usize (st[TYARR] = 2 is the marker; find_ty
and expand write "usize" for it) -- unless the file declares a fn or method named
len, in which case x.len() may be that one and the receiver stays untyped, as a
negative control pins.

Also in recv_class: the generic loop kept the previous token's type span, so
an unsuffixed literal after a typed name inherited it -- `(v.len() - 1)` with
`v: &[u8]` was read as u8 and named saturating_sub_u8 (a type error in the
output). Each token's span is its own lookup's now. One existing test used
`s.len().saturating_sub(1)` as its example of a refused fn; it uses abs_diff,
still a refused method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e and what stays refused, and re-seal both (Closes #10764)

Header notes only, plus the seals: the table note lists the methods of #10764
by what they name and the refusals with their reasons (ln: R_LIBM; a text's or a
char's to_ascii_lowercase; the signed div_ceil; get with a range; unwrap_or inside
an expression or on a call of the file), and rust_std.t27's DRY paragraph names the
new fns. t27c seal --save on both specs: 61 / 61 and 128 / 128 tests pass, --verify
matches every hash, test-report counts 0 vacuous passes of 61 and of 128, and
coverage of rust_std.t27 stays 100 % (159 of 159 fns).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gHashTag

Copy link
Copy Markdown
Owner Author

Review (orchestrator, Opus):

  • Re-ran on the PR head: t27b test rust_convert.t27 128/128 and rust_std.t27 green; t27c coverage specs/tools/t27o/rust_std.t27 159/159; both seals --verify MATCH; cargo check -p tri clean; tools/check_devhome_paths.py OK.
  • DRY: five new names exist elsewhere, none as the same function: get_u32 / get_u64 / get_f32 there read bytes from a buffer (file_io_contract.t27, macho.t27, train_slice_model.t27); contains_u32 (igla/coder/eval.t27, recursive with an index) and saturating_sub_usize (a port of trios gradient) agree in meaning but are private to unrelated specs, so they cannot be used from here.
  • to_bits lowers to @bitCast, which gen-rust prints verbatim; 13 specs on master already use @bitCast, so this adds no new backend gap, but converted output that uses it is for the Zig and t27b paths until gen-rust lowers it.
  • pow converts for any operands and overflows where Rust's debug build panics (t27b traps the same way); the oracle drops a panicking sample, so such vectors read as truncated, never as a pass.
  • Measured by the lane on the five roadmap repositories: converted fns 1,830 -> 1,895 (+65), no file converts fewer; method first refusals 3,207 -> 3,078; ln moved to its own refusal kind ("libm function not bit-exact"). Auto-merge on.

@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-19 03:19:34 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 40
PRs with All Checks Green 10
READY 1
FAILING 40
PENDING 0
NO CHECKS YET 1

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

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=fad12492937a != 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).

@gHashTag
gHashTag enabled auto-merge (squash) October 19, 2026 03:21
@gHashTag
gHashTag merged commit 72f1280 into master Oct 19, 2026
31 of 35 checks passed
gHashTag pushed a commit that referenced this pull request Oct 19, 2026
…ns is bytes_contain for u8 only, saturating_add_u64 is built on checked_add_u64 (Closes #10764)

tools/dupe_scan.py named two new groups on #10801. Both are gone:

- contains_u16 .. contains_f64 (ten fns, the body of bytes_contain once the
  types are normalised; t27 has no generics to share it) and their ten tests
  are deleted. A slice `contains` converts only when the element type is u8,
  through bytes_contain. Any other scalar element type is refused with a
  reason of its own, R_SLICEGEN: "slice contains on `u32` at line N: no
  generic helper, and per-type copies are refused by the DRY ratchet". The
  std table loses the per-type row; a negative control pins the refusal on a
  u32 slice, a Vec<u16> and an f64 slice, and the slice-of-texts control stays.
  str_contains_char duplicates nothing and stays.
- saturating_add_u64 was byte-identical to hosting::panel's saturating_add.
  It is built on this module's checked_add_u64 now (a `use` of panel splices
  28 modules and clashes with toolchain's starts_with / ends_with). The
  other saturating_* fns were not in any group.

dupe_scan: "duplicate bodies: 682 of 13593 in 195 group(s)", ok, no new group
and none grew; `--like specs/tools/t27o/rust_std.t27`: nothing in this file is
written elsewhere. The ledger was not blessed. Tests: t27b 51 / 51 and 129 /
129, test-report vacuous 0 of 51 and 0 of 129, coverage of rust_std.t27 149 of
149, both seals re-saved and verified.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@gHashTag

Copy link
Copy Markdown
Owner Author

Fix for the duplicate-bodies check, pushed as 05b4785 (the ledger was not blessed).

Removed

  • contains_u16 .. contains_f64 (ten fns, the body of bytes_contain once types are normalised) and their ten tests. A slice contains converts only on a u8 element, through bytes_contain; any other scalar element type is refused with its own reason, e.g. "slice contains on u32 at line N: no generic helper, and per-type copies are refused by the DRY ratchet". Negative controls pin it on a [u32], a Vec<u16> and a [f64].
  • saturating_add_u64 was byte-identical to hosting::panel's saturating_add; it is built on checked_add_u64 now. No other saturating_* fn was in a group.
  • str_contains_char duplicates nothing and stays.

dupe_scan (python3 -I tools/dupe_scan.py, exit 0): duplicate bodies: 682 of 13593 in 195 group(s) across 2324 spec(s), ok: no new duplicate body, and none of the known groups grew. --like specs/tools/t27o/rust_std.t27: nothing in this file is written elsewhere.

Gates: t27b test 51 passed (rust_std) and 129 passed (rust_convert); t27c test-report vacuous passes 0 of 51 and 0 of 129; coverage of rust_std.t27 149 of 149 (100 %); both seals re-saved, --verify all hashes MATCH; check_devhome_paths.py OK; cargo check -p tri clean.

Corrected measure (tri rebuilt from 05b4785; same five source commits, master tri b94c869)

method first refusals before after
count_ones 8 6
trailing_zeros 1 0
leading_zeros 3 2
pow 8 0
saturating_add 21 9
saturating_sub 29 5
saturating_mul 2 1
div_ceil 12 7
sqrt 49 3
ln 14 0 (14 in the "libm function not bit-exact" kind)
is_finite 12 2
to_bits 12 5
is_ascii 12 1
to_ascii_lowercase 11 12
unwrap_or 14 8
get 68 67
contains 124 120 (7 more sites sit in the new "slice contains on" kind)
all methods 3207 3078

Converted fns over the same files: 1830 -> 1889 (+59; it was +65 with the per-type contains), converted tests 648 -> 668; no file converts fewer, 24 convert more.

tri t27o-sweep (converted, "method" kind), before -> after: t27 641 -> 679, 1515 -> 1470 (+6 libm); trios 966 -> 984, 1003 -> 941 (+8 libm, +7 slice contains); trinity-fpga 22 -> 23, 43 -> 40; trinity 8 -> 8, 27; 999-multibots-telegraf 32 -> 32, 78. Sweep total converted 1669 -> 1726.

The PR description is updated to match (no scalar-slice contains rows; numbers above).

@gHashTag

Copy link
Copy Markdown
Owner Author

Correction to my comment above: this PR was squash-merged at 11b672a (03:22:44Z, auto-merge enabled at 03:21:53Z), before the dedup fix 05b4785 was pushed, so master 72f1280 still has both duplicate groups. The same fix commit, cherry-picked onto master, is #10817 (auto-merge not enabled). The numbers in my comment above are for that content.

gHashTag added a commit that referenced this pull request Oct 19, 2026
…ns is bytes_contain for u8 only, saturating_add_u64 is built on checked_add_u64 (Closes #10764) (#10817)

tools/dupe_scan.py named two new groups on #10801. Both are gone:

- contains_u16 .. contains_f64 (ten fns, the body of bytes_contain once the
  types are normalised; t27 has no generics to share it) and their ten tests
  are deleted. A slice `contains` converts only when the element type is u8,
  through bytes_contain. Any other scalar element type is refused with a
  reason of its own, R_SLICEGEN: "slice contains on `u32` at line N: no
  generic helper, and per-type copies are refused by the DRY ratchet". The
  std table loses the per-type row; a negative control pins the refusal on a
  u32 slice, a Vec<u16> and an f64 slice, and the slice-of-texts control stays.
  str_contains_char duplicates nothing and stays.
- saturating_add_u64 was byte-identical to hosting::panel's saturating_add.
  It is built on this module's checked_add_u64 now (a `use` of panel splices
  28 modules and clashes with toolchain's starts_with / ends_with). The
  other saturating_* fns were not in any group.

dupe_scan: "duplicate bodies: 682 of 13593 in 195 group(s)", ok, no new group
and none grew; `--like specs/tools/t27o/rust_std.t27`: nothing in this file is
written elsewhere. The ledger was not blessed. Tests: t27b 51 / 51 and 129 /
129, test-report vacuous 0 of 51 and 0 of 129, coverage of rust_std.t27 149 of
149, both seals re-saved and verified.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
gHashTag added a commit that referenced this pull request Oct 19, 2026
… sweep and Zig front end, build traps (Closes #10826) (#10827)

What the overnight loop of #10522 paid for after #10542: dupe_scan for
bodies (#10801 passed a name check with 13 copied bodies), the
own-language gate compiled from the merge base, devhome literals
(#10646), seals of specs that use rust_convert, drafts as the only hold
(auto-merge off did not stop #10801), the stacked-PR rebase recipe, a
writable cloned CARGO_HOME, a private zig cache per lane, zsh traps, the
splice that matches string literals (#10824); tri t27o-sweep and the Zig
front end documented; stale timings updated.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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