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

@@ -276,3 +276,27 @@ export fn scopedefineinmodule(s: *scope, name: str, mod: str, k: skind, t: *tinf
s.last = sy;
return sy;
};
// scopesamekeysym — the entry scopedefineinmodule(name, mod) treats as a
// duplicate (same name, same mod-key), or nil if the key is free. Lets a
// caller that got a nil from scopedefineinmodule learn WHAT it collided
// with (e.g. a pre-seeded builtin vs a genuine user redeclaration). The
// match logic mirrors scopedefineinmodule's reject branch exactly.
export fn scopesamekeysym(s: *scope, name: str, mod: str) *sym = {
let h: u64 = hashstr(name);
let bi: i32 = (h % (s.nbuckets: u64)): i32;
let b: *sym = s.buckets[bi];
for (b != nil) {
if (streq(b.name, name)) {
if (b.mod.len == 0) {
if (mod.len == 0) { return b; };
} else {
if (mod.len > 0) {
if (streq(b.mod, mod)) { return b; };
};
};
};
b = b.hashnext;
};
return nil;
};