check: a test block is a declaration too - #3480
Merged
Merged
Conversation
Closes #3479 Ranked by DISTINCT LINES rather than diagnostics, `redefinition of 'X'` is the corpus's cleanest class: 361 diagnostics over 361 lines, one error per site and no cascade. 317 of them are `void test_NAME(void)` emitted twice. `collect_top_level_decls` matched StructDecl | EnumDecl | FnDecl -- the three kinds a t27 program can CALL. gen-c emits `void test_{name}(void)` per test block and gen-rust a `#[test] fn`, so two test blocks of one name are a redeclaration in every backend, which is this gate's own stated criterion. MEASURED: specs with test blocks 528 test blocks 12 644 specs declaring one test name twice 29 duplicated test names 314 extra definitions 373 duplicated bench names 2 in 1 spec colliding ONLY after C-identifier sanitisation 0 The last line is why the rule is on the raw name and nothing more. TESTS AND BENCHES GET NAMESPACES OF THEIR OWN, and that half is load-bearing. `struct deque_clear` beside `test deque_clear` is not a conflict in any backend -- the C names are `deque_clear` and `test_deque_clear` -- and 138 such names across 54 specs would each be reported if tests were folded into the type namespace. The weaker "declared as both a type and a function" finding now names that PAIR instead of firing on "more than one namespace", which became false of a test the moment a test had one. Baseline 3 specs / 25 names -> 30 specs / 341 names. NOT new debt: newly visible debt, and the generated C had been saying so all along. THIS REMOVES NO C ERRORS. 12 071 before and after, all 582 generated headers diffed byte for byte with no difference. The pass makes a blind detector see the largest 1:1 class in the corpus and names the debt per spec; the 373 extra definitions are still there. What removing them would take, measured rather than guessed: of the 373 duplicate blocks, 174 have a body IDENTICAL to their twin and are deletable mechanically; 199 differ and each needs a reading -- the same shape as adamw.t27, whose two `AdamWConfig` bodies are not identical either. Tests: 4 new Rust cases, 2 new self-check cases in the gate, full suite 3533 passed 0 failed. Four mutants, all killed, including the two that collapse the namespaces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gHashTag
enabled auto-merge (squash)
September 8, 2026 10:39
Contributor
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
Contributor
This was referenced Sep 8, 2026
gHashTag
added a commit
that referenced
this pull request
Sep 8, 2026
Closes #3481 #3480 made the duplicate-declaration gate see 341 duplicated names in 30 specs and measured that 174 of the blocks had a body byte-identical to their twin. This removes them: only a block whose body is IDENTICAL to an earlier block of the same name in the same spec. Nothing renamed, nothing merged. blocks removed 188 specs touched 29 lines deleted 838 lines inserted 0 errors 12 071 -> 11 780 (-291) redefinition of 'X' 365 -> 204 (-161) baseline 30 / 341 names -> 27 / 172 spec `test` lines 14 613 -> 14 425 (-188) 188 rather than 174: the estimate came from a cruder extractor that mis-parsed the two brace-form specs. THE DECISIVE CONTROL IS A BEFORE/AFTER DIFF OF THE GENERATED C, not of the specs, because that is the artifact the change is about. Across all 582 headers: 29 changed -- exactly the specs edited -- with 1203 removed lines inside a `void test_*(void)` region, 183 blanks beside one, 27 old `printf("All %d tests passed")` lines, and ZERO removed from anywhere else; 29 added, all the same summary line with a smaller count. READING ONE SPEC'S DIFF BY HAND, BEFORE APPLYING ANYTHING, FOUND THE REAL BUG. `invariant` was not among the tokens that end a Gherkin-form block, so a duplicate test ran on through the invariant that followed it and deleting the test would have deleted a live declaration. No aggregate would have shown it: the corpus totals improve either way. MY OWN ROUND-TRIP CONTROL WAS MEASURING ITS OWN JOIN. It compared the re-assembled file as a STRING, and joining pieces with newlines inserts one wherever a piece is empty -- which is wherever two blocks abut. It refused 49 specs on that. Comparing line lists: 0 refused. AND THE COUNTING CONTROL'S SAFE SET HAD TO BE MEASURED. In the brace form a test body legitimately holds `const provider = ...`, `type = ContentType::Text,` (a struct field) and `invariant cov == 100;` (an assertion); counting those anywhere refuses two specs for nothing. Measured: `struct`, `module`, `impl`, `use`, `bench` never occur inside a test body at any indentation, and `invariant`/`fn`/`const`/`type`/ `enum` never at column zero. The tool is kept rather than thrown away, because the job is not finished: 172 duplicated names remain whose bodies DIFFER, and when one is resolved by renaming rather than deleting, this must be re-run to confirm it created no new identical pair. It is idempotent -- a second run reports 0. Gates: specs parse, specs generate, duplicate declarations, assertionless tests, seal currency all pass. Full suite 3533 passed 0 failed. 29 stale seals refreshed. 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.
check: a test block is a declaration too
Closes #3479
Ranked by DISTINCT LINES rather than diagnostics,
redefinition of 'X'is the corpus's cleanest class: 361 diagnostics over 361 lines, one
error per site and no cascade. 317 of them are
void test_NAME(void)emitted twice.
collect_top_level_declsmatched StructDecl | EnumDecl | FnDecl -- thethree kinds a t27 program can CALL. gen-c emits
void test_{name}(void)per test block and gen-rust a
#[test] fn, so two test blocks of onename are a redeclaration in every backend, which is this gate's own
stated criterion.
MEASURED:
specs with test blocks 528
test blocks 12 644
specs declaring one test name twice 29
duplicated test names 314
extra definitions 373
duplicated bench names 2 in 1 spec
colliding ONLY after C-identifier sanitisation 0
The last line is why the rule is on the raw name and nothing more.
TESTS AND BENCHES GET NAMESPACES OF THEIR OWN, and that half is
load-bearing.
struct deque_clearbesidetest deque_clearis not aconflict in any backend -- the C names are
deque_clearandtest_deque_clear-- and 138 such names across 54 specs would each bereported if tests were folded into the type namespace. The weaker
"declared as both a type and a function" finding now names that PAIR
instead of firing on "more than one namespace", which became false of a
test the moment a test had one.
Baseline 3 specs / 25 names -> 30 specs / 341 names. NOT new debt:
newly visible debt, and the generated C had been saying so all along.
THIS REMOVES NO C ERRORS. 12 071 before and after, all 582 generated
headers diffed byte for byte with no difference. The pass makes a blind
detector see the largest 1:1 class in the corpus and names the debt per
spec; the 373 extra definitions are still there.
What removing them would take, measured rather than guessed: of the 373
duplicate blocks, 174 have a body IDENTICAL to their twin and are
deletable mechanically; 199 differ and each needs a reading -- the same
shape as adamw.t27, whose two
AdamWConfigbodies are not identicaleither.
Tests: 4 new Rust cases, 2 new self-check cases in the gate, full suite
3533 passed 0 failed. Four mutants, all killed, including the two that
collapse the namespaces.
🤖 Generated with Claude Code