Repository navigation
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
Conversation
…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>
|
Review (orchestrator, Opus):
|
…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>
|
Fix for the duplicate-bodies check, pushed as 05b4785 (the ledger was not blessed). Removed
dupe_scan ( Gates: Corrected measure (tri rebuilt from 05b4785; same five source commits, master tri b94c869)
Converted fns over the same files: 1830 -> 1889 (+59; it was +65 with the per-type
The PR description is updated to match (no scalar-slice |
|
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. |
…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>
… 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>
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.t27and the std table ofrust_convert.t27, each fn proven against values rustc 1.99 printed. Closes #10764.What converts
count_ones,leading_zeros,trailing_zeros<method>_<T>-> u32powpow_<T>, Rust's own loopsaturating_add/sub/mulsaturating_<op>_<T>div_ceildiv_ceil_<T>sqrt@sqrt(correctly rounded)is_finite,to_bitsis_finite_<T>,to_bits_<T>is_ascii&str,&[u8], u8is_ascii_text,is_ascii_u8to_ascii_lowercase/uppercaseto_ascii_*_u8unwrap_or?T)if (r != null) { r.? } else { d }get(i)get_<T>->?Tcontains(&x)bytes_contain)contains(c)on a text, c acharvariable&strstr_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 testand ont27c test-report(Zig); the fns of every family butto_bitsalso 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@logis Zig's own port, IEEE 754 does not fix a log's last bit, and gen-rust prints@logas.log()(E0061). Evidence only, not a promise: Zig 0.16@logand rustcf64::lnagree bit for bit on 200000 probed inputs on this host.containson 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, andtools/dupe_scan.pyrefuses a copy ofbytes_containper type.to_ascii_lowercase(owned String), acharreceiver's (t27o has no char class), the signeddiv_ceil,getwith a range or on a text,containson a slice of texts.unwrap_orinside 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 noorelse, the form reads the receiver twice, and the reference prints anifused as a value without parentheses, so its last arm takes in what follows ((if ..) * 2.0is 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
powis 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 everya * bthe converter already writes (probed onpow_u8(16, 2)). A refusal would have kept10u64.pow(n)and(n as u64).pow(2)out whilea * bconverts under the same rule.to_bitsconverts although gen-rust cannot spell it: the fn is an@bitCast, exact in Zig and t27b (pinned byspecs/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_u64is built onchecked_add_u64, not a copy ofhosting::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)withn: u32namedpow_u32.x.len()is a usize (unless the file declares a fn namedlen).(v.len() - 1)withv: &[u8]was read as u8).&[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)withc: charhanded a u32 to a text parameter.Measure
Method first refusals (
refused fn .. : method <name>lines oftri 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.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(--repofor 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 testrust_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/--verifyon both specs: all hashes match;check_devhome_paths.pyOK; commit hooks passed on every commit.t27c gen-rust) and the two seals.Found on the way, not fixed here
fn emit() -> String) converts toconst 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 aResult<(), E>becomesf(..).?;on a bool: t27b refuses it and Zig rejects the ignored value.Some(..)but not a returned index (return xs[i];for-> ?T), and passes[]const Tby value as aVecwhen the body usesfor e in xs.🤖 Generated with Claude Code