Skip to content

fix(rust-backend): lower both spellings of a slice length - #3407

Merged
gHashTag merged 1 commit into
masterfrom
fix/slice-len
Sep 7, 2026
Merged

fix(rust-backend): lower both spellings of a slice length#3407
gHashTag merged 1 commit into
masterfrom
fix/slice-len

Conversation

@gHashTag

@gHashTag gHashTag commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Closes #3406

Zig exposes a slice length as the field .len, so specs are written that way and both spellings reached rustc unlowered.

spelling what rustc said count
data.len E0615 attempted to take value of method len 26
len(data) E0425 cannot find function len 38

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). There the access is a field and must stay one.

The existing bool_fields could not serve as the second guard: it is filled during emission from inside gen_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_names is a pre-pass like its neighbours.

What it is worth — the first positive acceptance delta in this series

all 651 specs master after
rustc accepts 430 433
coded diagnostics 3096 2962
E0615 26 0
E0425 770 732

The 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_LENGTH is usize against a u32 constant; analysis.total_tasks = tasks.len() assigns usize to a u32 field. These are true statements about the specs — a length in Rust is usize — 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.

pow was declined, not missed

It is the second-largest unresolved name at 48 diagnostics. I did not touch it:

  • 10 of the 29 specs that call pow( declare their own fn pow.
  • Rust spells it three ways by type — f64::powf(f64), f64::powi(i32), i32::pow(u32) — and the corpus writes both pow(3, k) and pow(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 ^error lines (including error: aborting due to N previous errors); the second counts error[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 len and asserts it prints 7 — 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:

  • removing the field guard → only a_struct_field_named_len_stays_a_field fails;
  • disabling the free-call form → only both_spellings_of_length_become_a_method_call fails.

bootstrap/stage0/FROZEN_HASH updated in the same commit, as M5 requires.

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
gHashTag enabled auto-merge (squash) September 7, 2026 19:56
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-07 19:56:48 UTC

Summary

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

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

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=5857419820d4 != 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 merged commit 1f06f32 into master Sep 7, 2026
32 of 35 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gen-rust: both spellings of a slice length reach rustc unlowered (x.len and len(x))

1 participant