Skip to content

check: a test block is a declaration too - #3480

Merged
gHashTag merged 1 commit into
masterfrom
c/test-block-is-a-declaration
Sep 8, 2026
Merged

check: a test block is a declaration too#3480
gHashTag merged 1 commit into
masterfrom
c/test-block-is-a-declaration

Conversation

@gHashTag

@gHashTag gHashTag commented Sep 8, 2026

Copy link
Copy Markdown
Owner

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_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.

🤖 Generated with Claude Code

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

github-actions Bot commented Sep 8, 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 8, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-08 10:39:39 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)=d0bfdbd5a5fc != 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 4f04d2d into master Sep 8, 2026
30 of 33 checks passed
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>
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.

check: a test block is a declaration too -- 314 duplicated test names invisible to the duplicate gate

1 participant