Conversation
Zig has no empty statement -- it answers "expected statement, found ';'"
-- so a bare `;` does not add noise, it kills the whole file and every
check inside it. Measured: 491 bare `;` lines across 81 generating specs.
Two source forms reach one emitted construct, and the notice now tells
them apart. 475 are a `bench` body's prose line arriving as a CHILDLESS
StmtExpr through gen_bench_block's fallback; 16 are `defer <call>;` in a
test body, arriving WITH a child that gen_expr renders as nothing. The
parser keeps that one deliberately -- "a dropped `defer` silently removes
a release, a close or a free" -- and the emitter dropped it anyway.
475 + 16 = 491: one notice for every semicolon removed.
The remedy is the one used beside it. gen_invariant_block already writes
"NOT CHECKED -- body was not lowered (T43)", and the Verilog backend
already emits benches as comments. Zig was the only backend where a bare
`;` is fatal rather than merely useless, which is why the same emission
escapes consequence in C.
Measured per spec, both directions, zig 0.16.0, no timeouts:
zig test --test-no-exec 165 -> 190 +25 regressions 0
zig build-obj 282 -> 308 +26 regressions 0
+25 is exactly what an adversarial sweep attributed to this line when it
split the class -- 32 for the whole remedy, 25 for this site -- so an
independent implementation reaching 25 confirms that split.
Deletion-equivalence re-derived rather than inherited: 780 bench_*
functions are defined in the generated corpus and 0 have a call site, and
`zig test` runs only test blocks, so emptying one cannot lose an executed
check.
Four mutants. M3 and M4 SURVIVED the first run, which is the useful part.
The "a statement that lowers keeps its semicolon" test used `y = x + 1`,
which reaches StmtAssign and never touches the arm under test -- a test
of an adjacent path that happens to hold; a bare CALL statement is what
goes through StmtExpr with a non-empty rendering. And nothing asserted
the two labels differ, so labelling a statement that HAS a child as
"empty" passed.
The fixture was wrong to start with too: I invented `bench "b" { measure:
... }` from the issue's prose, it does not parse, t27c exits 1, and the
test asserted on an empty string. The real spelling is `bench NAME` with
no braces. Read the construct out of the corpus, not out of the report
about it.
Closes #2974
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
Contributor
An hour of work committed cleanly and then `gh pr create` answered
"No commits between master and w801" -- the branch did not exist. HEAD
had been detached, the commit landed on no branch, and nothing in the
commit path said so.
Verified in a throwaway repository rather than assumed: `git commit` on
a detached HEAD succeeds with no warning, `git branch --show-current`
returns empty, and only `git status` says it outright. `git push -u` is
silent too; the first objection comes from a tool three steps downstream,
about the wrong subject.
One command, beside the freeze check already run before every commit:
test -n "$(git branch --show-current)" || echo DETACHED
Recovery is `git branch -f <name> <sha>`. The commit is not lost, it is
unreferenced -- which looks identical from every command that asks about
branches and nothing like it from `git log`.
What this is NOT evidence of: three background agents were running in the
same repository, and the obvious story is that one moved my HEAD. They
did not -- `git worktree list` shows each in its own worktree. The cause
is unestablished, and 'the agents did it' would have been a cause
invented to fit a symptom, which is the failure this skill records more
often than any other.
Refs #2974
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts: # .claude/skills/ci-gates/SKILL.md
Contributor
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
Contributor
PR DashboardGenerated at: 2026-08-30 19:53:49 UTC
Summary
Seal Status
|
Merging master moved one spec's generated output; `tri seals drift` read 1 and `--fix` repaired it locally -- and the repair was never committed. `coverage` went red on exactly that, and the neighbouring session's PR was green, which is what said the drift was mine. This repository's own lesson, twice recorded and broken again here: the reseal belongs in the SAME commit as whatever moved the output, not in a later one and certainly not only in the working tree. `git status` is the check, and it costs one command. Refs #2974 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts: # .claude/skills/ci-gates/SKILL.md
Contributor
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
Contributor
PR DashboardGenerated at: 2026-08-30 20:17:12 UTC
Summary
Seal Status
|
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.
Zig has no empty statement
A statement that lowered to nothing became a bare
;. That does not merely add noise — onesuch line kills the whole file, and every check inside it with it.
Measured: 491 bare
;lines across 81 generating specs.Two source forms, one emitted construct
The notice now distinguishes them, and the corpus split is exact:
empty statementbenchbody's prose line —target: < 100ms— arriving as a childlessStmtExprthroughgen_bench_block's fallbackStmtExprdefer <call>;in a test body, arriving with a child thatgen_exprrenders as nothing475 + 16 = 491. One notice for every
;removed — nothing lost, nothing invented.The
defercase is deliberate on the parser's side: it keeps the node because "a droppeddefersilently removes a release, a close or a free". It reached an emitter that thendropped it anyway.
The remedy is the one already used beside it
gen_invariant_blockwrites// invariant: X NOT CHECKED -- body was not lowered (T43)when its body was discarded. The Verilog backend already emits benches as comments. Zig
was the only backend where a bare
;is fatal rather than merely useless — in C it is alegal null statement, which is why the same emission escapes consequence there.
So: render the child first (
render_expr, an idiom already in this file), and if it comesback empty, emit the same kind of notice instead of a statement that claims to be one.
Measurement — per spec, both directions, zig 0.16.0, no timeouts
zig test --test-no-execzig build-obj -fno-emit-bin+25 is exactly the number an adversarial sweep attributed to this line when it split the
class: 32 for the whole remedy, 25 for the bare-
;site. An independent implementationreaching precisely 25 is the confirmation that split was real.
The other 7 are bench prose that does parse into an expression, so it renders to something
and this guard leaves it alone. Surveying the 120 still-rejected specs, the remaining error
families are unrelated — undeclared identifiers, initializer syntax, shadowing.
The deletion-equivalence, re-derived rather than inherited
Replacing a bench body with a notice is equivalent to deleting it, which is normally the
inflation trap. It is sound here, and I measured the justification myself rather than taking
it from the issue:
zig testruns onlytest {}blocks, so emptying abench_*cannot lose an executed check.Mutation testing — and two mutants that survived the first round
;the_notice_names_…,zig_accepts_the_generated_file;from every statementa_call_statement_keeps_its_semicolon_and_is_not_a_noticethe_defer_form_is_covered_too_and_labelled_differentlyM3 and M4 survived the first run, and that is the useful part.
y = x + 1, whichreaches
StmtAssignand never touches the arm under test. A test of an adjacent paththat happens to hold. A bare call statement is what goes through
StmtExprwith anon-empty rendering, and it is what the mutant destroys.
empty statementfor astatement that has a child — sending the reader to the wrong construct — passed.
The fixture was also wrong to start with. I invented
bench "b" { measure: ... }from theissue's prose. It does not parse,
t27cexits 1, and the test asserted on an empty string.The real spelling is
bench NAMEwith no braces (compiler/cli/gen.t27:499). Read theconstruct out of the corpus, not out of the report about it.
Closes #2974
Closes #2974
🤖 Generated with Claude Code