Skip to content

gen-rust: std macros keep their bang -- assert!, assert_eq!, assert_ne!, panic!, unreachable! (Closes #5987) - #6103

Merged
gHashTag merged 5 commits into
masterfrom
claude/gen-rust-assert-bang
Oct 4, 2026
Merged

gHashTag merged 5 commits into
masterfrom
claude/gen-rust-assert-bang

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Closes #5987

Refs #6092

What was wrong

gen-rust lowered a t27 assert(cond) in a FUNCTION body to assert((cond));. That calls a function named assert, and Rust has no such function: assert is a macro. rustc refused the file with error[E0423]: expected function, found macro 'assert'.

printf 'module p;\nfn f(a : u32) -> u32 {\n    assert(a <= 2);\n    return a;\n}\n' > /tmp/pa.t27
./target/release/t27c gen-rust /tmp/pa.t27 > /tmp/l.rs
rustc --edition 2021 --crate-type lib -o /tmp/l.rlib /tmp/l.rs

RustCodegen::expr_to_rust has a generic call arm that prints name(args) for any call it does not recognise, and an identifier arm that prints the bare name. So every Rust std macro that t27 shares by name came out without its !. I ran one reproducer per name through t27c gen-rust and rustc --edition 2021 --crate-type lib:

t27 in a fn body before rustc before after rustc after
assert(c) assert((c)); E0423 found macro assert assert!((c)); ok
assert(c, "m {x}") assert((c), "m {x}"); E0423 assert!((c), "{}", "m {x}"); ok
assert_eq(a, b) assert_eq(a, b); E0423 assert_eq!(a, b); ok
assert_ne(a, b) assert_ne(a, b); E0423 assert_ne!(a, b); ok
unreachable; unreachable; E0423 expected value, found macro unreachable!(); ok
if (c) unreachable else a { unreachable } E0423 { unreachable!() } ok
panic("m") panic("m"); E0423 panic!("{}", "m"); ok
@panic(msg) @panic(msg); expected expression, found @ panic!("{}", msg); ok

The task text pointed at the self.write("assert(") sites. Those are in the C backend (gen_c_expr, for std.testing.expect*), and assert(...) is valid C there, so they are not changed. See "Seen and not fixed here" for the missing #include.

The fix

All of it is in bootstrap/src/compiler.rs, in RustCodegen:

  • std_macro_call_to_rust, called from the ExprCall arm right after zig_builtin_to_rust, maps:

    • assert/1 to assert!
    • assert/2, assert_eq/2,3 and assert_ne/2,3 to the matching macros
    • panic/0,1 to panic!
    • Any other arity, or any other name, falls through to the old path.
  • A MESSAGE argument goes through "{}", never as the format string. In edition 2021 a non-literal message is an error, and a literal containing {/} would be read as a format directive. The reproducer "a too big {x}" covers this case.

  • zig_builtin_to_rust: @panic(m) becomes panic!("{}", m).

  • Identifier arm: a bare unreachable becomes unreachable!(), both as a statement and as a branch of if .. else.

  • module_declares(name) is the guard. If the spec gives the name its own meaning, nothing is rewritten. That covers:

    • a declared fn
    • a typed parameter or local of the current function
    • a typed const
    • a module var

    Rust resolves assert(c) to the spec's own fn assert, because functions and macros live in separate namespaces. That plain call already compiles, and rewriting it would call the wrong thing. On 2026-10-04 no spec in the corpus declared any of these names. An untyped local is not tracked by this guard.

  • Deliberately NOT mapped: expect, expectEqual and std.testing.*. Rust has no macro with those names (expect is a lint attribute), so nothing is missing a !. Mapping them onto assert! would mean choosing semantics, since Zig's expect returns an error rather than aborting.

  • Unit tests: module tests_5987_rust_std_macro_bang at the end of compiler.rs, 8 tests.

    • It covers every fixed site.
    • Negative controls: a spec-declared fn assert and fn panic, a parameter named unreachable, a constant named unreachable, and expect.
  • FROZEN_HASH is re-pinned.

  • 25 seals (14 specs) are re-sealed for their new gen_hash_rust. After the master merge, Lstm.json and recurrent_Lstm.json were re-sealed again on the merged compiler.

Misread shape: not added

I did not add this to cli/tri/src/misread.rs, for three reasons.

  1. The parser read the source correctly: the AST holds the right ExprCall. The defect is in the emitter, not a misreading.
  2. The rustc acceptance census already sees it as E0423. misread exists for specs that pass every gate while being read wrong.
  3. A misread shape needs a live positive control that the real compiler still produces. After this fix the shape would never fire, so tri misread would refuse to run.

Numbers (measured)

How I measured. These are local measurements, made before the owner rule moved compile work to the Railway lab. The lab's gates do not include a rustc acceptance census, so this table does not come from the lab run. The lab run, linked under "Gates run locally", confirms the gates on the merged head.

  • Corpus: git ls-files specs/ minus specs/scratch/, 1184 specs.
  • Before: a t27c built from master at 4499e27. After: a t27c built from this branch before the master merge.
  • Each spec was run through t27c gen-rust and then rustc --edition 2021 --crate-type lib --crate-name m -A warnings, overwriting a single /tmp rlib path each time.
  • An error count is the number of ^error lines, excluding the "aborting due to" summary line.

Results.

before after
specs that generate 1172 1172 (the same 12 fail in both)
specs whose Rust output changes 17
rustc accepts (whole corpus) 646 647
rustc refuses (whole corpus) 526 525
rustc errors in the 17 changed files 349 253
"found macro" diagnostics in those files 109 9
of which assert 94 0
of which unreachable 6 0
of which print (not touched) 9 9
"expected expression, found @" in those files 9 7
  • The newly accepted spec is specs/tri/search/match.t27. No spec went from accepted to refused: I diffed the two accepted sets.
  • In the generated Rust, master had 95 assert( lines in function bodies across 9 specs. rustc reported 94 E0423 for them, because the 95th sits in a file whose parse stops first.
  • This change rewrites:
    • 95 assert! lines
    • 4 panic! sites (3 from @panic, 1 from panic)
    • 6 unreachable! sites
  • One changed file got worse: port/browseros/.../queen-public-leaderboard went from 4 to 11 errors. Once its assert! compiled, rustc reached the type check, which reports indexing an Option<&str>. That error was already there, hidden behind the earlier ones.

The changed specs:

  • igla/race/backend, eda, yosys
  • isa/ternary_control_flow
  • ml/layers/avgpool2d_layer, embedding_layer
  • ml/recurrent/gru_cell, lstm_cell
  • port/browseros/.../queen-public-leaderboard
  • port/tools/builtin_parity_table, check_graph_law8, check_withdrawn_live
  • tri/collections/circular_buffer
  • tri/graph/graph_bfs
  • tri/io/zip
  • tri/search/match
  • tri/trees/segment_tree

Gates run locally

The lab results come first, then the local runs.

Railway lab, merged head 9835bee1: verdict green, no red gates.
Run: https://t27c-lab-production.up.railway.app/runs/9835bee1165c38227dc4f509ff0696af8396cfde.json

gate result
frozen-hash FROZEN_HASH matches compiler.rs (9ecc318c5583)
build cargo build --release -p t27c -p tri ok
suite suite --corpus-only --ratchet: RATCHET CLEAN
lean icarus_lowerable corpus_classifier_matches_lean_completeness: 1 passed
seal-currency exit 0
seal-coverage OK: 1449 seals, 1325 hold, 124 known-broken
specs-parse ok: this change touches no .t27 file
specs-generate OK: 1253 specs, 1239 generate, 14 known-broken

Local runs, before the owner rule moved builds to the lab.

  • cargo test --release -p t27c tests_5987_rust_std_macro_bang: 8 passed. Run on 9aa0aa729 and again on the merged tree.
  • t27c suite --repo-root . --corpus-only --ratchet --json /tmp/assert_suite.json on 9aa0aa729: RATCHET: CLEAN. GATE FAILURES 0, DISCARD WORSENED 0, DISCARD UNPINNED 0.
  • tools/check_seal_currency.py:
    • On 9aa0aa729: 25 STALE, all gen_hash_rust, covering 14 specs (the changed specs that have seals). I re-sealed them with t27c seal <spec> --save, and the rerun showed 0 stale.
    • On the merged tree: 2 stale (the Lstm twins), re-sealed, then 0 stale.
  • tools/check_seal_coverage.py: OK, on both trees.
  • tools/check_specs_generate.py: OK, on both trees.
  • tools/ci/check_specs_still_parse.py --base origin/master: "ok: this change touches no .t27 file".
  • cargo test --release -p t27c --test icarus_lowerable corpus_classifier_matches_lean_completeness: 1 passed, on both trees.
  • tools/check_now_entry_shape.py: OK, 1 entry, well formed.
  • scripts/ci/now-sync-gate-diff.sh: passed.
  • cargo test --release -p tri misread: not run, because cli/tri/src/misread.rs is not touched.
  • Ledger caps: none moved and no _why_ key was added. The suite reported no drift.
  • A local suite rerun on the merged tree was stopped before it finished, under the owner rule against compiling on this Mac. The lab's suite gate above covers that tree.

Merge state. This branch merged origin/master once (9835bee11). Since then master has moved 24 more commits.

  • A dry run with git merge-tree shows bootstrap/src/compiler.rs auto-merging cleanly.
  • It conflicts in bootstrap/stage0/FROZEN_HASH and in 5 seals:
    • Backend.json
    • TriGraphBfs.json
    • graph_TriGraphBfs.json
    • race_igla-race-backend.json
    • tools_specs::port::tools::builtin_parity_table.json
  • These were not resolved here, because a resolution needs a re-pin plus a re-seal with a merged t27c, and the owner rule forbids that build on this Mac.

Seen and not fixed here

  • gen-c assert has no include. gen-c writes assert(...) without #include <assert.h>, so cc refuses it as an undeclared function. Seen with the same /tmp/pa.t27.
  • gen-zig unreachable. The Zig backend writes @"unreachable" for an unreachable statement or expression. That is an identifier lookup, not the keyword. The re-seal of specs/tri/search/match.t27 records it: "use of undeclared identifier 'unreachable'".
  • Bare print(...). 189 lines in 11 specs, and 9 E0423 in the changed files. One spec declares its own fn print. Rust has print!, but t27's print in the ported tools means a LINE, so choosing print! or println! is a semantic decision, not a missing bang.
  • undefined. assert((x != undefined)) in tri/io/zip: undefined is not Rust (E0425). The C backend writes a W585 comment for the same case.
  • Other untranslated Zig:
    • @compileError (7 sites in graph/knowledge_graph, not one of the changed specs)
    • ++ string concatenation (check_graph_law8)
    • try (try (allocator.alloc(..))), which rustc refuses as the reserved keyword try (segment_tree)
  • expect and friends. expect, expectEqual and std.testing.* in function bodies remain unlowered for Rust (E0423 / E0425). This is intentional; see "The fix".

🤖 Generated with Claude Code

gHashTag and others added 2 commits October 4, 2026 18:16
…ert_ne!, panic!, unreachable! (Closes #5987)

gen-rust printed a t27 `assert(c)` in a function body as `assert((c));`,
a call to a FUNCTION named `assert`, which Rust does not have; rustc
refused the file with E0423 (expected function, found macro `assert`).
The generic call arm prints `name(args)` for any call it does not know,
and the identifier arm prints the name, so `assert_eq`, `assert_ne`,
`panic`, Zig's `@panic` and a bare `unreachable` lost their `!` too.

- `std_macro_call_to_rust` lowers assert/assert_eq/assert_ne/panic to
  the macros; a message argument goes through "{}", never as the format
  string (non-literal messages are an error in edition 2021, and a
  literal holding braces would be read as a format directive).
- `@panic(m)` lowers to `panic!("{}", m)` in `zig_builtin_to_rust`.
- A bare `unreachable` identifier lowers to `unreachable!()`.
- `module_declares`: a spec that gives the name its own meaning (fn,
  typed param or local, typed const, module var) keeps its own call.
- `expect`, `expectEqual`, `std.testing.*` are deliberately unchanged.

Measured over 1184 specs: 17 specs change their Rust output; rustc
--edition 2021 --crate-type lib accepts 646 -> 647; found-macro
diagnostics in the changed files 109 -> 9 (the 9 left are `print`).

Tests: tests_5987_rust_std_macro_bang (8). FROZEN_HASH re-pinned; 25
seals of 14 specs re-sealed for their new gen_hash_rust.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: bootstrap/stage0/FROZEN_HASH (re-pinned to the merged
compiler.rs), .trinity/seals/Lstm.json and recurrent_Lstm.json (master's
version taken, then re-sealed with the merged t27c: master's Rust
backend changes and this branch's assert! both move gen_hash_rust).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_HASH, re-seal

Merged and re-sealed on the Railway t27c lab, not the workstation (owner
rule, 2026-10-04).

Conflicts:
- bootstrap/src/compiler.rs: one hunk at the end of the file, where this
  branch appended tests_5987_rust_std_macro_bang and master (#6003) appended
  tests_5984_gen_zig_discard_call. Resolved as both modules, this branch's
  first. The merged file's changes against master's side are exactly this
  branch's changes against the merge base (same +/- lines).
- bootstrap/stage0/FROZEN_HASH: recomputed as the sha256 of the merged
  compiler.rs (c2d70f5c90ef...).
- Five seal files: each took master's side; then every seal a t27c built
  from this merge reads as newly stale (3: specs/igla/race/backend.t27,
  specs/port/tools/builtin_parity_table.t27, specs/tri/graph/graph_bfs.t27)
  was re-sealed with `t27c seal --save` (zig 0.16.0 on PATH) and
  `tri seals sync-twins`.

On the lab, before committing: `cargo test --release -p t27c tests_59`
14 passed; tools/check_seal_currency.py rc 0; no seal says "zig not on PATH".

Refs #5987, Refs #6092

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

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-04 15:33:20 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 41
PRs with All Checks Green 9
READY 7
FAILING 41
PENDING 0
NO CHECKS YET 0

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

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=c2d70f5c90ef != 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 and others added 2 commits October 4, 2026 23:06
compiler.rs: both sides added test modules after the same `#[cfg(test)]`;
kept tests_5987 and gave master's tests_5923/5949/5968 their own
`#[cfg(test)]`. FROZEN_HASH refrozen.

Refs #6092

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The master merge brought specs/auth/config.t27's seals, made before
gen-rust printed `assert!`; re-sealed on the t27c lab with zig 0.16.0.

Refs #6092

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

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-04 16:17:02 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 44
PRs with All Checks Green 6
READY 4
FAILING 44
PENDING 0
NO CHECKS YET 0

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

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=4a3d2f43c9f5 != 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 556e7c2 into master Oct 4, 2026
25 of 27 checks passed
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-04 16:23:08 UTC

Summary

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

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

Seal Status

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

Development

Successfully merging this pull request may close these issues.

gen-rust: assert(cond) in a fn body is emitted as assert((cond)) -- a macro called without its bang (E0423)

1 participant