fix(rust-backend): lower both spellings of a slice length - #3407
Merged
Conversation
Closes #3406 Zig exposes a slice length as the FIELD `.len`, so specs are written that way and both spellings reached rustc unlowered: `data.len` as 26 E0615 diagnostics ("attempted to take value of method `len`") and `len(data)` as 38 of the corpus's E0425. Both now lower to Rust's method call. Two guards, each with a measured population: * `declared_fns` -- 3 specs declare their own `fn len`; * `field_names` -- 6 declare a struct field genuinely named `len` (compiler/stdlib, numeric/bigint, ternary/bigint, tri/collections/array, tri/pipeline/builder, tri/utils/utf8), and there the access is a field. The existing `bool_fields` could not serve as the second guard: it is filled DURING emission from inside `gen_struct`, so a guard consulted from an expression would depend on emission order. `collect_field_names` is a pre-pass like its neighbours. Measured over all 651 specs against master: rustc accepts 430 -> 433 coded diagnostics 3096 -> 2962 E0615 26 -> 0 E0425 770 -> 732 The three newly accepted files are substantive, not empty: ternary_pattern_ matching (5 functions), ternary_search (6) and ternary_sorting (6), each going from N errors to zero. The cost, stated rather than buried: 9 new E0308. `path.len() > MAX_PATH_LENGTH` is `usize` against a `u32` constant and `analysis.total_tasks = tasks.len()` assigns `usize` to a `u32` field. Those are true statements about the specs, previously hidden behind a name that did not resolve at all. `pow` was declined, not missed. It is the second-largest unresolved name at 48 diagnostics, but 10 of the 29 specs calling it declare their own `fn pow`, and Rust spells it three ways by type (powf / powi / pow) while the corpus writes both `pow(3, k)` and `pow(E, (k as f64))`. A choice there would be a wrong translation that compiles, which is worse than the unresolved name. Filed. Mutation-checked twice, one test each: removing the field guard fails only `a_struct_field_named_len_stays_a_field`; disabling the free-call form fails only `both_spellings_of_length_become_a_method_call`. FROZEN_HASH updated in the same commit, as M5 requires. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gHashTag
enabled auto-merge (squash)
September 7, 2026 19:56
Contributor
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
Contributor
This was referenced Sep 7, 2026
Closed
gHashTag
added a commit
that referenced
this pull request
Sep 7, 2026
…rom now on (#3416) Closes #3415 A seal records four `gen_hash_*` fields -- the hashes of what each backend emitted. Nothing checked them. Measured on master: 109 stale gen_hash_rust, 7 stale gen_hash_zig, 0 C, 0 Verilog, and 0 stale spec_hash. All 116 were left by five merged backend repairs -- #3401, #3403, #3405, #3407, #3411 -- every one of them mine. None touched a spec, so spec_hash stayed correct and every coverage and staleness check in the repository stayed green while a sixth of the Rust seals described output the compiler no longer produces. The existing checks cover the other half. check_seal_coverage.py asks whether a seal describes a spec that exists, unchanged at SOURCE. `Seal Staleness Warning` is about the NMSE manifest and FROZEN_HASH, unrelated to .trinity/seals, and exits 0 by design. `t27c seal --verify` answers this question exactly -- exit 1 with a precise MISMATCH line, exit 0 on a current seal, both used as controls here -- and nothing called it across the corpus. The refresh took three attempts and each failure was informative. `--save` fixed 116 -> 58, not 0, because it writes to .trinity/seals/<module>.json, one name, while 1313 seals cover 728 distinct specs and 547 specs carry more than one seal file. The remaining 58 duplicates are rewritten in place. `sealed_at` is left alone in those: rewriting a hash the tool itself just computed is not a fresh certification event, and moving the timestamp would claim one. gen_hash=none is a different debt and is counted apart -- 169 seals record it for at least one backend, and --save refuses to overwrite them ("4 of 4 backends rejected it"). Conflating the two is how the 116 stayed invisible. tools/check_seal_currency.py now asks the question. Its --self-check plants a wrong hash on a scratch tree and requires exactly that seal to be reported, because a zero from a check that cannot see is indistinguishable from a healthy zero. It exits 2 when t27c is absent rather than 0: a check that could not run has not passed. Filed, not guessed: some duplicate seals are not named after a module at all. specs/tri/utils/logger.t27 carries `"[]const u8".json`, `utils_"[]const u8".json` and `utils_TriLogger.json` -- a seal named after a type string. After this: 1318 seals scanned, 1055 current, 94 whose spec is gone, 169 sealed with none, 0 stale. Co-authored-by: lab <lab@example.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3406
Zig exposes a slice length as the field
.len, so specs are written that way and both spellings reached rustc unlowered.data.lenE0615attempted to take value of methodlenlen(data)E0425cannot find functionlenTwo guards, each with a measured population
declared_fns— 3 specs declare their ownfn len.field_names— 6 declare a struct field genuinely namedlen(compiler/stdlib,numeric/bigint,ternary/bigint,tri/collections/array,tri/pipeline/builder,tri/utils/utf8). There the access is a field and must stay one.The existing
bool_fieldscould not serve as the second guard: it is filled during emission from insidegen_struct, so it is only complete once the structs have been written. A guard consulted from an expression must not depend on emission order —collect_field_namesis a pre-pass like its neighbours.What it is worth — the first positive acceptance delta in this series
E0615E0425The three newly accepted files are substantive, not empty shells:
isa/ternary_pattern_matching(5 functions, 26 → 0 errors),isa/ternary_search(6, 10 → 0),isa/ternary_sorting(6, 7 → 0).The cost, stated rather than buried
9 new
E0308.path.len() > MAX_PATH_LENGTHisusizeagainst au32constant;analysis.total_tasks = tasks.len()assignsusizeto au32field. These are true statements about the specs — a length in Rust isusize— and they were previously hidden behind a name that did not resolve at all. Trading an unresolvable name for a visible type question is the right direction; pretending it costs nothing would not be.powwas declined, not missedIt is the second-largest unresolved name at 48 diagnostics. I did not touch it:
pow(declare their ownfn pow.f64::powf(f64),f64::powi(i32),i32::pow(u32)— and the corpus writes bothpow(3, k)andpow(E, (k as f64)).Picking one would be a wrong translation that compiles, which is strictly worse than the unresolved name. It needs the receiver type, which this emitter does not have there. Filed in #3406.
A measurement error I made and corrected
Mid-way I compared 3745 against 2994 and read a drop of 751. The first counts
^errorlines (includingerror: aborting due to N previous errors); the second countserror[E....]codes. Same directory, same binary, two different questions. Every figure above is coded errors: 3096 → 2962.Tests
Two new, 17 green in the file. One checks both spellings lower; the other compiles and runs a struct that owns a field named
lenand asserts it prints7— a wrong rewrite there would read a method that does not exist.Mutation-checked twice, one test each, so the tests are specific rather than a blanket detector:
a_struct_field_named_len_stays_a_fieldfails;both_spellings_of_length_become_a_method_callfails.bootstrap/stage0/FROZEN_HASHupdated in the same commit, as M5 requires.