Skip to content

check: report a top-level name declared twice - #3439

Merged
gHashTag merged 1 commit into
masterfrom
use/no-duplicate-emission
Sep 8, 2026
Merged

check: report a top-level name declared twice#3439
gHashTag merged 1 commit into
masterfrom
use/no-duplicate-emission

Conversation

@gHashTag

@gHashTag gHashTag commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Closes #3438

Every backend rejects a redeclaration and t27c check exited 0.

The issue I filed was wrong, and I wrote it

I diagnosed this as a type arriving twice through use. It does not.
specs/ml/optimizer/adamw.t27 declares AdamWConfig itself, at lines 28
and 483, with different bodies — one ends use_phi_betas : bool, the other
phi_variant : PhiVariant. My grep looked for struct AdamWConfig while the
spec uses the Zig spelling pub const Name = struct { … }; finding zero
matches I concluded it must be imported. use_resolve.rs emits nothing twice.

The compiler is right and the spec is wrong — the fourth time this
campaign, after #3420, #3422 and #3428.

Two findings, because one message would be false

Measured against the real compilers, not assumed:

declaration pair cc rustc zig iverilog
struct A + struct A redefinition of 'S' E0428 duplicate … already been declared
struct A + enum A tag type that does not match E0428
struct A + fn A accepts accepts duplicate …

Types and values are separate namespaces in Rust and C; a Zig container has
one. So a same-namespace collision gets "every backend rejects a
redeclaration"
and the cross-namespace case gets its own weaker, true message.

The corpus carries 25 of the first and 0 of the second. That zero is
printed only because a test proves the second message is reachable — a zero
from a message nothing could emit is worth nothing.

Population

From the compiler's own AST: 3 specs, 25 names, agreeing exactly with an
independent count taken from the generated Rust two passes ago.

spec names
specs/igla/coder/benchmark.t27 13
specs/ml/optimizer/adamw.t27 11
specs/file/operations.t27 1

The detector walks the AST because a line-based matcher I wrote while scoping
this said 8 specs, 35 names. Five of the eight were its own false
positives: impl-block methods in files written in Rust rather than t27
(Vec<PinToken>, &self, Option<&PinToken>), which do not parse and emit
0 bytes. Scope is not visible to a regex.

Corpus effect

Old binary against new, spec by spec:

before after
specs checked 651 651
exit 0 573 573
exit 1 78 78
specs whose exit code moved 0

It is a warning. Promoting it to an error is a separate decision and the test
pins the current one.

The guard runs

Not another check that only runs when someone types it. Wired into
Spec Guards as a per-spec ratchet, not a total — a ceiling on the sum is
satisfied by zero and says nothing about a spec that grew while another shrank.
Cost 7s, measured.

Self-check, three halves: the planted duplicate must be found, the clean spec
must stay quiet, and the cross-namespace case must take the weaker message.

The gate itself was controlled three ways:

control expected got
a new spec with a duplicate red NEW: specs/_ctl_dup.t27 … exit 1
a baselined spec that grows red GREW: …adamw.t27 10 -> 11 exit 1
a spec that shrinks green + reported progress: …adamw.t27 12 -> 11 exit 0

Mutation

mutant dead tests
detector returns nothing 3
all kinds share one namespace 1
enum dropped from the population 1
the weak finding removed 1

Full suite: 2598 passed, 0 failed.

Closes #3438

Every backend rejects a redeclaration and `t27c check` exited 0. Measured
against the real compilers rather than assumed:

    struct A + struct A   cc  "redefinition of 'S'"     rustc error[E0428]
                          zig "duplicate ..."           iverilog "already been declared"
    struct A + enum A     cc  "tag type that does not match"   rustc error[E0428]
    struct A + fn A       rust and C ACCEPT it -- types and values are separate
                          namespaces; zig rejects it, one namespace per container

So there are TWO findings, because one message would be false for half the
cases. A same-namespace collision gets "every backend rejects a
redeclaration"; a type and a function sharing a name gets "zig rejects this;
rust and C do not". The corpus carries 25 of the first and 0 of the second,
and that zero is printed only because a test proves the second message is
reachable.

The issue this closes was filed by me with the wrong diagnosis: I said the
name arrived through `use`. It does not. `specs/ml/optimizer/adamw.t27`
declares `AdamWConfig` itself at lines 28 and 483, with different bodies --
one ends `use_phi_betas`, the other `phi_variant`. My grep looked for
`struct AdamWConfig` while the spec uses the Zig spelling
`pub const Name = struct { ... }`, and finding zero I concluded it was
imported. `use_resolve.rs` emits nothing twice. The compiler is right and the
spec is wrong, the fourth time this campaign after #3420, #3422 and #3428.

Population, from the compiler's own AST: 3 specs, 25 names -- agreeing exactly
with an independent count taken from the generated Rust two passes ago. The
detector walks the AST because a line-based matcher written while scoping this
reported 8 specs and 35 names, and five of the eight were its own false
positives: `impl`-block methods in files written in Rust rather than t27, which
do not parse and emit 0 bytes. Scope is not visible to a regex.

Corpus effect, old binary against new, spec by spec: 651 specs, 573 exit 0 and
78 exit 1 on both sides, and NOT ONE spec's exit code moved. The finding is a
warning; promoting it is a separate, test-visible decision.

Wired into Spec Guards as a per-spec ratchet rather than a total -- a ceiling
on the sum is satisfied by zero and says nothing about a spec that grew while
another shrank. Cost 7s, measured. The gate's self-check has three halves (the
planted duplicate must be found, the clean spec must stay quiet, the
cross-namespace case must take the weaker message), and the gate itself was
controlled three ways: a new spec turns it red, a baselined spec that grows
turns it red, and a shrink is reported as progress and passes.

Four mutants, four dead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gHashTag
gHashTag enabled auto-merge (squash) September 8, 2026 00:29
@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 00:30:09 UTC

Summary

Status Count
Total Open PRs 16
PRs with Failing Checks 14
PRs with All Checks Green 2
READY 2
FAILING 14
PENDING 0
NO CHECKS YET 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=a3e0592ab67e != 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 b519f90 into master Sep 8, 2026
27 of 33 checks passed
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.

t27c check is silent on a spec that declares the same top-level name twice

1 participant