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

@@ -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(); };