wcc/ww: reject duplicate top-level decls, aligned to cstage

installdecl routes all four kinds (fn/type/def/let) through installtop,
which turns scopedefineinmodule's nil return into cstage's exact
"duplicate <kind> <name>" reject, keyed (name,mod) so cross-package
same-leaf decls coexist. Builtin redecls are dropped, not dup-errored:
cstage never scopes builtins (lookup_builtin first, check.c:69), so a
user redecl is dead there — wwstage mirrors via scopesamekeysym +
no-source-decl test. -T synth __wwtests installs direct, mirroring
check.c:3079. Closes the silent dup-fn hole (user fn run vs lib/test
run built a broken test binary with no diagnostic). Per-kind reject
rows + cross-package/builtin accept byte-id rows + -T collision parity
row in 910/997. (#23-team, category-A addendum closed)
This commit is contained in:
2026-06-11 00:57:43 +09:00
parent 16c83e70d3
commit 19e9535ef4
13 changed files with 448 additions and 50 deletions

View File

@@ -265,6 +265,58 @@ linkfail(const char *bin, const char *comp)
return rc;
}
/* accept — `<comp> <fixture>` (NON-T) must exit zero. The #23 cross-package
* legal control: distinct-mod same-leaf decls coexist, so the dup reject
* keyed on (name, mod) must NOT fire. */
static int
accept(const char *bin, const char *comp, const char *fixture, const char *what)
{
char cmd[4096];
snprintf(cmd, sizeof cmd, "%s/%s %s -o /dev/null 2>/dev/null",
bin, comp, fixture);
if (runwait(cmd) != 0) {
fprintf(stderr, "910 FAIL: %s rejected %s (expected accept)\n",
comp, what);
return 1;
}
return 0;
}
/* collide_run — a @test unit defining a user `fn run` collides with
* lib/test's bound runner once `<drv> test -c` bundles it; `<comp> -T` of
* the combined must loud-reject the duplicate (#23). Pre-fix wwstage built
* a binary that called the user run and silently skipped every @test. */
static int
collide_run(const char *bin, const char *comp, const char *drv)
{
int pid = getpid();
char stem[256], comb[300], cmd[4096];
snprintf(stem, sizeof stem, "/tmp/at910cr_%s_%d", comp, pid);
snprintf(comb, sizeof comb, "%s.combined.ww", stem);
snprintf(cmd, sizeof cmd,
"%s/%s test -c -o %s test/wcc/data/attest_userrun.ww > /dev/null 2>&1",
bin, drv, stem);
runwait(cmd);
if (access(comb, 0) != 0) {
fprintf(stderr, "910 FAIL: %s build produced no %s\n", drv, comb);
return 1;
}
snprintf(cmd, sizeof cmd, "%s/%s -T %s -o /dev/null 2>/dev/null",
bin, comp, comb);
int rc = 0;
if (runwait(cmd) == 0) {
fprintf(stderr, "910 FAIL: %s -T accepted user `fn run` collision "
"(expected duplicate-fn reject)\n", comp);
rc = 1;
}
unlink(comb);
char tmp[320];
snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp);
snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp);
unlink(stem);
return rc;
}
int
main(void)
{
@@ -280,7 +332,26 @@ main(void)
"undefined symbol in @test body") != 0) return 1;
if (linkfail(bin, "w6c") != 0) return 1;
/* #23 — top-level duplicate-decl reject (one row per kind: all four
* route through installtop, so each deserves its own pin) + legal
* cross-package control + the builtin-redecl carve-out accept. */
if (reject_plain(bin, "w6c", "test/wcc/data/dup_fn.ww",
"same-module duplicate fn") != 0) return 1;
if (reject_plain(bin, "w6c", "test/wcc/data/dup_type.ww",
"same-module duplicate type") != 0) return 1;
if (reject_plain(bin, "w6c", "test/wcc/data/dup_def.ww",
"same-module duplicate def") != 0) return 1;
if (reject_plain(bin, "w6c", "test/wcc/data/dup_let.ww",
"same-module duplicate let") != 0) return 1;
if (accept(bin, "w6c", "test/wcc/data/dup_xpkg_ok.ww",
"cross-package same-name") != 0) return 1;
if (accept(bin, "w6c", "test/wcc/data/builtin_redecl_ok.ww",
"builtin nomem redecl (carve-out)") != 0) return 1;
if (collide_run(bin, "w6c", "ww") != 0) return 1;
printf("@test -T: run ok + user-main and bad-signature rejected + "
"non-T @test drop + checked-body + dangling-call link-fail (#6)\n");
"non-T @test drop + checked-body + dangling-call link-fail (#6) + "
"dup fn/type/def/let reject + xpkg + builtin-redecl accept + "
"fn-run collision (#23)\n");
return 0;
}

View File

@@ -288,6 +288,83 @@ reject_plain(const char *bin, const char *fixture, const char *what)
return 0;
}
/* accept_byteid — a #23 legal-accept control: `<fixture>` must COMPILE on
* BOTH stages (no false dup reject) AND be cs/ww byte-identical (rule 10).
* Used for the cross-package same-leaf control (keying is (name, mod), so
* distinct-package leaves coexist) and the builtin-redecl carve-out (the
* one structural point where wwstage seeds builtins and cstage does not —
* byte-id proves the drop-the-redecl mirror is exact). */
static int
accept_byteid(const char *bin, const char *fixture, const char *what)
{
int pid = getpid();
char cs[256], ws[256], cmd[4096];
snprintf(cs, sizeof cs, "/tmp/at997ab_c_%d.s", pid);
snprintf(ws, sizeof ws, "/tmp/at997ab_w_%d.s", pid);
snprintf(cmd, sizeof cmd,
"%s/w6c %s -o %s 2>/dev/null", bin, fixture, cs);
if (runwait(cmd) != 0) {
fprintf(stderr, "997 FAIL: w6c rejected %s\n", what);
return 1;
}
snprintf(cmd, sizeof cmd,
"%s/w6c_ww %s -o %s 2>/dev/null", bin, fixture, ws);
if (runwait(cmd) != 0) {
fprintf(stderr, "997 FAIL: w6c_ww rejected %s\n", what);
return 1;
}
char *bc = NULL, *bw = NULL;
size_t nc = 0, nw = 0;
int rc = 0;
if (slurp(cs, &bc, &nc) < 0 || slurp(ws, &bw, &nw) < 0) {
fprintf(stderr, "997 FAIL: slurp %s asm\n", what);
rc = 1;
} else if (nc != nw || memcmp(bc, bw, nc) != 0) {
fprintf(stderr, "997 FAIL: %s asm differs (cs %zu, ww %zu)\n",
what, nc, nw);
rc = 1;
}
free(bc); free(bw);
unlink(cs); unlink(ws);
return rc;
}
/* collide_run — a @test unit defining a user `fn run` collides with
* lib/test's bound runner once `ww test -c` bundles it; `w6c_ww -T` of the
* combined must loud-reject the duplicate (#23). Pre-fix wwstage built a
* binary that called the user run and silently skipped every @test. */
static int
collide_run(const char *bin)
{
int pid = getpid();
char stem[256], comb[300], cmd[4096];
snprintf(stem, sizeof stem, "/tmp/at997cr_%d", pid);
snprintf(comb, sizeof comb, "%s.combined.ww", stem);
snprintf(cmd, sizeof cmd,
"%s/ww test -c -o %s test/wcc/data/attest_userrun.ww > /dev/null 2>&1",
bin, stem);
runwait(cmd);
if (access(comb, 0) != 0) {
fprintf(stderr, "997 FAIL: ww build produced no %s\n", comb);
return 1;
}
snprintf(cmd, sizeof cmd, "%s/w6c_ww -T %s -o /dev/null 2>/dev/null",
bin, comb);
int rc = 0;
if (runwait(cmd) == 0) {
fprintf(stderr, "997 FAIL: w6c_ww -T accepted user `fn run` collision "
"(expected duplicate-fn reject)\n");
rc = 1;
}
unlink(comb);
char tmp[320];
snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp);
snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp);
unlink(stem);
return rc;
}
int
main(void)
{
@@ -303,7 +380,27 @@ main(void)
if (reject_plain(bin, "test/wcc/data/attest_undefbody.ww",
"undefined symbol in @test body") != 0) return 1;
/* #23 — wwstage top-level duplicate-decl reject (one row per kind:
* all four route through installtop) + legal cross-package control
* (byte-id) + the builtin-redecl carve-out (byte-id) + the -T
* `fn run` collision parity row. */
if (reject_plain(bin, "test/wcc/data/dup_fn.ww",
"same-module duplicate fn") != 0) return 1;
if (reject_plain(bin, "test/wcc/data/dup_type.ww",
"same-module duplicate type") != 0) return 1;
if (reject_plain(bin, "test/wcc/data/dup_def.ww",
"same-module duplicate def") != 0) return 1;
if (reject_plain(bin, "test/wcc/data/dup_let.ww",
"same-module duplicate let") != 0) return 1;
if (accept_byteid(bin, "test/wcc/data/dup_xpkg_ok.ww",
"cross-package same-name") != 0) return 1;
if (accept_byteid(bin, "test/wcc/data/builtin_redecl_ok.ww",
"builtin nomem redecl (carve-out)") != 0) return 1;
if (collide_run(bin) != 0) return 1;
printf("@test -T (ww_ww): run ok + cs/ww byte-id + rejects + "
"non-T @test drop cs/ww byte-id + checked-body (#6)\n");
"non-T @test drop cs/ww byte-id + checked-body (#6) + "
"dup fn/type/def/let reject + xpkg/builtin-redecl byte-id + "
"fn-run collision (#23)\n");
return 0;
}

View File

@@ -0,0 +1,9 @@
// #23 -T collision — a user `fn run` collides with lib/test's bound
// runner `run` (the synth's callee) once `ww test -c` bundles lib/test.
// Both stages must loud-reject "duplicate fn run" (rc!=0), never silently
// build a binary that calls the wrong run and skips every @test.
fn run() void = { return; };
@test fn t_one() void = {
assert(1 == 1);
};

View File

@@ -0,0 +1,14 @@
// #23 builtin carve-out — a user redecl of a pre-seeded builtin name is
// NOT a duplicate (cstage keeps no builtins in scope: lookup_builtin wins
// first at check.c:69, so the user version installs dead). wwstage seeds
// builtins into c.top, so installtop must DROP this redecl rather than
// erroring. Both stages accept; the builtin `nomem` (= !void) wins, so
// g()'s i32 arm returns 7. Pins the 771/774/926 shape.
type nomem = !void;
fn g() (i32 | nomem) = { return 7; };
export fn main() i32 = {
match (g()) {
case let v: i32 => return v;
case nomem => return 99;
};
};

5
test/wcc/data/dup_def.ww Normal file
View File

@@ -0,0 +1,5 @@
// #23 — two top-level defs of the same name in one (flat) module.
// cstage's install pass rejects "duplicate def D"; wwstage now mirrors.
def D: i32 = 1;
def D: i32 = 2;
export fn main() i32 = { return 0; };

5
test/wcc/data/dup_fn.ww Normal file
View File

@@ -0,0 +1,5 @@
// #23 — two top-level fns of the same name in the same (flat) module.
// cstage's install pass rejects "duplicate fn foo"; wwstage now mirrors.
fn foo() i32 = { return 1; };
fn foo() i32 = { return 2; };
export fn main() i32 = { return foo(); };

5
test/wcc/data/dup_let.ww Normal file
View File

@@ -0,0 +1,5 @@
// #23 — two top-level lets of the same name in one (flat) module.
// cstage's install pass rejects "duplicate let g"; wwstage now mirrors.
let g: i32 = 1;
let g: i32 = 2;
export fn main() i32 = { return 0; };

View File

@@ -0,0 +1,5 @@
// #23 — two top-level types of the same name in one (flat) module.
// cstage's install pass rejects "duplicate type t"; wwstage now mirrors.
type t = i32;
type t = u32;
export fn main() i32 = { return 0; };

View File

@@ -0,0 +1,13 @@
// #23 legal control — same leaf `foo` in DISTINCT packages of one flat
// bundle is legal (the dup key is (name, mod), not bare name). Mirrors a
// driver-emitted *.combined.ww. Must compile clean + byte-identical.
package aa;
export fn foo() i32 = { return 1; };
package bb;
export fn foo() i32 = { return 2; };
package main;
import aa;
import bb;
export fn main() i32 = { return aa.foo() + bb.foo(); };