Skip to content

String == is the emitter's job: two string names compare with std.mem.eql (Closes #5162) - #5444

Merged
gHashTag merged 2 commits into
masterfrom
fix/5162-string-eq
Oct 1, 2026
Merged

gHashTag merged 2 commits into
masterfrom
fix/5162-string-eq

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Closes #5162

What

Zig has no == for []const u8. The Zig backend already lowered a comparison to std.mem.eql(u8, a, b) when one side was a string literal, a str param or a str struct field. It missed what the automation specs write: two module-level string constants, or a str param against one (if source == SOURCE_MEETING, assert(DIGEST_DOOR != TOOL)). So t27c test-report reported those specs BLOCKED and ran none of their tests.

Which names are strings is read off declarations only. Nothing is inferred and no type checker is involved:

  • a module const declared : str, or untyped and initialised by a string literal (a new arm in the gen_zig prepass);
  • a local declared str, or literal-initialised, in the fn/test that declares it (collect_string_locals). It is removed again when that fn/test ends, so a numeric want in the next fn keeps its plain ==;
  • a call to a fn this spec declares -> str (declared_fn_returns).

Numeric comparisons do not change. All of this lives in bootstrap/src/compiler.rs (+68 lines).

Measured

t27c test-report, base (a3472e383) -> this branch:

spec before after
specs/automation/ball-board.t27 (current master, v5) BLOCKED (spec.zig:218 == on []const u8) 17 tests, 17 pass, 0 FAIL
ball-board.t27 from feat/ball-board-v2 BLOCKED (spec.zig:197) 16 / 16 / 0
specs/automation/mail-push.t27 BLOCKED (spec.zig:134 !=) 11 / 11 / 0
specs/port/tools/run_conformance_vvp.t27 BLOCKED 6 / 6 / 0
specs/trinity/compiler_matrix.t27 BLOCKED 6 / 6 / 0
  • Corpus Zig delta. t27c gen output changes for 10 of 1124 specs. Under zig test --test-no-exec:
    • 4 go FAIL -> OK;
    • the other 6 keep their same, unrelated first error;
    • the error-set diff contains only removals: 0 new errors.
  • Ratchet (W628). t27c suite --ratchet --corpus-only gives an identical verdict on base and fix: ledger 151/152, observed 161, 67 unexpected failures, 57 unexpected passes, discard worsened 7. Master is already red there, for parse, typecheck and Verilog reasons this change does not touch. In the suite JSON, only seal-verify moves (354 -> 353), because of auth.t27 (see Seal).
  • Bootstrap tests. cargo test --release --offline -p t27c --no-fail-fast across 113 integration targets: 1017 passed, 1 failed.
    • The failure is icarus_lowerable::corpus_classifier_matches_lean_completeness (nn_phi_rope and nn_sacred_attention). It is Verilog lowerability, and t27c icarus-lowerable gives the identical verdict with the base binary, so it is a pre-existing gap in the master baseline.
    • Two targets do not compile on master and were excluded: tests/corpus_unresolved.rs (E0716) and the t27c bin unit-test target (axum/tokio/AppState). Neither is touched here.

Seal

compiler.rs changed, so bootstrap/stage0/FROZEN_HASH moves to t27c frozen-digest (FROZEN.md section 5, step 5), as in #3962, #3973 and #4114.

Four spec seals now record the old, invalid Zig: AccountAuth.json and account_AccountAuth.json (specs/account/auth.t27), and the two e2e_full_flow seals (specs/github/tests/e2e_full_flow.t27). In both specs a literal-initialised local is now compared with std.mem.eql, for example listed_issue.title == issue_title becomes std.mem.eql(u8, listed_issue.title, issue_title).

These four are resealed in 11313bf89 (approved by the owner), using this branch's t27c and t27c seal <spec> --save. That command also refreshes the second seal file naming the same spec. No other seal is touched.

check_seal_currency goes 596 -> 592 stale and 646 -> 650 current, the same counts as master (the gate is already red there). On e2e_full_flow, spec_hash also moves: the spec was edited after its 2026-08-28 seal.

Regression guard

bootstrap/tests/string_eq_zig.rs (4 tests):

  • a literal operand, const vs const (== and !=), and an untyped literal const;
  • a str param vs a const, a str local, and a -> str call;
  • a numeric control (n == LIMIT, n != LIMIT, LIMIT == 3 stay plain);
  • a no-leak case (a str local does not turn the next fn's numeric param into a string);
  • an end-to-end test-report (6/6; skipped when no zig is on PATH).

On the base binary, test-report on this spec is BLOCKED.

🤖 Generated with Claude Code

….eql

Zig has no == for []const u8. The Zig backend already lowered a comparison
to std.mem.eql(u8, a, b) when one side was a string literal, a str param or a
str struct field. It missed the shape the automation specs write -- two
module-level string constants, or a str param against one -- so
`t27c test-report` reported ball-board and mail-push BLOCKED and ran none of
their tests.

Which names are strings is read off DECLARATIONS only, never inferred:
- a module const declared `: str`, or untyped and initialised by a string
  literal;
- a local declared `str` (or literal-initialised) in the fn/test that declares
  it, removed again when that fn/test ends;
- a call to a fn this spec declares `-> str`.
Numeric comparisons keep their plain operators.

test-report, before -> after:
- specs/automation/ball-board.t27 (master, v5):  BLOCKED -> 17/17
- specs/automation/mail-push.t27:                 BLOCKED -> 11/11
- specs/port/tools/run_conformance_vvp.t27:       BLOCKED -> 6/6
- specs/trinity/compiler_matrix.t27:              BLOCKED -> 6/6

Corpus: Zig output changes for 10 of 1124 specs; under zig test --test-no-exec
4 of them go FAIL -> OK, the other 6 keep their same unrelated first error,
and no new error appears. Ratchet (W628) verdict is identical before and
after (ledger 151/152, 67 unexpected failures, 57 unexpected passes; master
is already red there).

Seal: compiler.rs changed, so bootstrap/stage0/FROZEN_HASH moves to its new
digest (FROZEN.md section 5, step 5; `t27c frozen-digest`), as in #3962,
#3973 and #4114. Four spec seals now record the old, invalid Zig:
AccountAuth (x2, specs/account/auth.t27) and e2e_full_flow (x2,
specs/github/tests/e2e_full_flow.t27) -- in both, a literal-initialised local
is now compared with std.mem.eql. check_seal_currency goes 592 -> 596 stale
(already red on master); not resealed here.

Regression guard: bootstrap/tests/string_eq_zig.rs -- literal, const,
param, local and `-> str` call cases, a numeric control, a no-leak case, and
an end-to-end test-report run (6/6).

Closes #5162

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

github-actions Bot commented Oct 1, 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 Oct 1, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-01 14:50:15 UTC

Summary

Status Count
Total Open PRs 49
PRs with Failing Checks 45
PRs with All Checks Green 4
READY 3
FAILING 45
PENDING 0
NO CHECKS YET 0

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

Seal Status

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

The emitter change in this PR made four spec seals stale: their Zig output
changed. In both specs, a local initialised by a string literal is now
compared with std.mem.eql instead of ==, so the old seals recorded Zig that
does not compile:
- AccountAuth.json and account_AccountAuth.json (specs/account/auth.t27);
- github::tests::e2e_full_flow.json and tests_github::tests::e2e_full_flow.json
  (specs/github/tests/e2e_full_flow.t27).

Resealed with the t27c built from this branch through the repo's own path,
`t27c seal <spec> --save`, which also refreshes the second seal file that
names the same spec. No other seal is touched.

check_seal_currency: 596 -> 592 stale and 646 -> 650 current, the same counts
as master. The gate is already red on master for the remaining 592.

- auth.t27: only gen_hash_zig and sealed_at change.
- e2e_full_flow.t27: spec_hash also changes, because the spec was edited
  after its 2026-08-28 seal. One of its two files also moves sealed_by from
  0.2.0 to 0.4.0.

Closes #5162

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

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-01 15:01:03 UTC

Summary

Status Count
Total Open PRs 49
PRs with Failing Checks 46
PRs with All Checks Green 3
READY 3
FAILING 46
PENDING 0
NO CHECKS YET 0

Seal Status

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

@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

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

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.

String == is the emitter's job, not the corpus's: -21 errors, +2 valid, and a reclassification

1 participant