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:
@@ -10330,6 +10330,30 @@ export fn scopedefineinmodule(s: *scope, name: str, mod: str, k: skind, t: *tinf
|
||||
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;
|
||||
};
|
||||
|
||||
// selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c.
|
||||
//
|
||||
// Status: name-resolution + primitive-type seeding only. Full type
|
||||
@@ -10592,16 +10616,12 @@ fn checkmoduleshadow(c: *checker, name: str, kindstr: str) void = {
|
||||
// scopelookupuselocal (lib/ww/sym.ww) to the SK_USE that coexists in the
|
||||
// landed scope — the coexistence-equivalent of cstage's use_alias bit.
|
||||
// Cite: project memory module_type_name_collision (cstage fix
|
||||
// 2026-05-13). #11 (wwstage checkfile pass) revisits the dup-decl errors
|
||||
// below when wwstage grows a real check pass on the cgen path.
|
||||
// TODO(#11): cstage check.c errors on duplicate top-level type/def/fn
|
||||
// (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate <kind>") and on
|
||||
// duplicate top-level let (cmd/wcc/check.c L1880, "duplicate let %s")
|
||||
// once #32 lands. Wwstage's installdecl just drops the second insert
|
||||
// silently. Add `if (s == nil) err(...)` here once #11 wires checkfile
|
||||
// into w6c_ww. Silent-accept matches the deferred-check design — see
|
||||
// test/wcc/708 and test/wcc/696 for the same cstage-only neg-case
|
||||
// precedent.
|
||||
// 2026-05-13).
|
||||
// #23: top-level duplicate type/def/fn/let now reject loud here, keyed
|
||||
// on (name, mod) exactly as cstage's install pass does
|
||||
// (cmd/wcc/check.c:2852/2911/2932/2955) — see dupdecl below. (Same-scope
|
||||
// LOCAL dup `let a=1; let a=2;` is a different path and stays deferred to
|
||||
// #11; test/wcc/708 + test/wcc/696 are that cstage-only neg-case.)
|
||||
fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
if (d == nil) { return; };
|
||||
let k: nkind = d.kind;
|
||||
@@ -10619,10 +10639,43 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
};
|
||||
scopedefine(c.top, nm, skind.SK_USE, nil, d); return;
|
||||
};
|
||||
if (k == nkind.N_DEF) { scopedefineinmodule(c.top, nm, mod, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_FN, nil, d); return; };
|
||||
if (k == nkind.N_LET) { scopedefineinmodule(c.top, nm, mod, skind.SK_VAR, nil, d); return; };
|
||||
if (k == nkind.N_DEF) { installtop(c, d, nm, mod, skind.SK_DEF, "def"); return; };
|
||||
if (k == nkind.N_TYPEDECL) { installtop(c, d, nm, mod, skind.SK_TYPE, "type"); return; };
|
||||
if (k == nkind.N_FNDECL) { installtop(c, d, nm, mod, skind.SK_FN, "fn"); return; };
|
||||
if (k == nkind.N_LET) { installtop(c, d, nm, mod, skind.SK_VAR, "let"); return; };
|
||||
};
|
||||
|
||||
// installtop — install a top-level decl name into c.top, rejecting a
|
||||
// genuine within-module duplicate loud (#23) with cstage's exact
|
||||
// "duplicate <kind> %s" wording (cmd/wcc/check.c:2852/2911/2932/2955).
|
||||
//
|
||||
// scopedefineinmodule returns nil only on a same-(name, mod) re-install.
|
||||
// Same-leaf cross-package decls carry distinct mods (the flat-bundle
|
||||
// model) and an imported-module bareword's SK_USE keys on mod="", so a
|
||||
// coexisting same-leaf type/fn in its own package never collides.
|
||||
//
|
||||
// The one nil that is NOT a user duplicate: a redeclaration of a
|
||||
// pre-seeded builtin (the predeclared `nomem`, or a primtype name). cstage
|
||||
// keeps no builtins in the scope at all — resolve_typename consults
|
||||
// lookup_builtin FIRST (cmd/wcc/check.c:69) and the builtin always wins,
|
||||
// so a user `type nomem = !void` / `type int = ...` installs dead and is
|
||||
// silently ignored, never a duplicate error. wwstage seeds builtins INTO
|
||||
// c.top, so the same redeclaration surfaces here as a collision; we mirror
|
||||
// cstage by dropping it (the seeded builtin stays, and wins resolution)
|
||||
// rather than erroring. A pre-seeded builtin is identified by its sym
|
||||
// carrying no real source decl (primtypes: decl=nil; `nomem`: a
|
||||
// checkinit-synthesized N_TYPEDECL with an empty .file) — user decls
|
||||
// always carry their parsed source file.
|
||||
fn installtop(c: *checker, d: *node, nm: str, mod: str, k: skind, kind: str) void = {
|
||||
if (scopedefineinmodule(c.top, nm, mod, k, nil, d) != nil) { return; };
|
||||
let prev: *sym = scopesamekeysym(c.top, nm, mod);
|
||||
if (prev != nil) {
|
||||
if (prev.decl == nil) { return; };
|
||||
if (prev.decl.file.len == 0) { return; };
|
||||
};
|
||||
cerr(d.file); cerr(": error: duplicate "); cerr(kind);
|
||||
cerr(" "); cerr(nm); cerr("\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
|
||||
// stamptuplebinds — distribute a tuple's per-element types onto a
|
||||
@@ -15918,8 +15971,13 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
tab.lhs = tsl;
|
||||
tab.rhs = arr;
|
||||
// pass 1 already ran; install the table name now so main
|
||||
// resolves it (declmod returns "" for tab.nmod="").
|
||||
installdecl(c, file, tab);
|
||||
// resolves it (declmod returns "" for tab.nmod=""). Goes
|
||||
// direct to scopedefineinmodule, NOT installdecl: cstage's
|
||||
// synth install (cmd/wcc/check.c:3079) bypasses the
|
||||
// duplicate-decl reject (#23) the same way, so a pathological
|
||||
// user `const __wwtests` stays a downstream type error in
|
||||
// both stages rather than a dup-diagnostic in only one.
|
||||
scopedefineinmodule(c.top, tab.str, "", skind.SK_VAR, nil, tab);
|
||||
|
||||
let arg: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
||||
arg.str = "__wwtests";
|
||||
|
||||
@@ -260,16 +260,12 @@ fn checkmoduleshadow(c: *checker, name: str, kindstr: str) void = {
|
||||
// scopelookupuselocal (lib/ww/sym.ww) to the SK_USE that coexists in the
|
||||
// landed scope — the coexistence-equivalent of cstage's use_alias bit.
|
||||
// Cite: project memory module_type_name_collision (cstage fix
|
||||
// 2026-05-13). #11 (wwstage checkfile pass) revisits the dup-decl errors
|
||||
// below when wwstage grows a real check pass on the cgen path.
|
||||
// TODO(#11): cstage check.c errors on duplicate top-level type/def/fn
|
||||
// (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate <kind>") and on
|
||||
// duplicate top-level let (cmd/wcc/check.c L1880, "duplicate let %s")
|
||||
// once #32 lands. Wwstage's installdecl just drops the second insert
|
||||
// silently. Add `if (s == nil) err(...)` here once #11 wires checkfile
|
||||
// into w6c_ww. Silent-accept matches the deferred-check design — see
|
||||
// test/wcc/708 and test/wcc/696 for the same cstage-only neg-case
|
||||
// precedent.
|
||||
// 2026-05-13).
|
||||
// #23: top-level duplicate type/def/fn/let now reject loud here, keyed
|
||||
// on (name, mod) exactly as cstage's install pass does
|
||||
// (cmd/wcc/check.c:2852/2911/2932/2955) — see dupdecl below. (Same-scope
|
||||
// LOCAL dup `let a=1; let a=2;` is a different path and stays deferred to
|
||||
// #11; test/wcc/708 + test/wcc/696 are that cstage-only neg-case.)
|
||||
fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
if (d == nil) { return; };
|
||||
let k: nkind = d.kind;
|
||||
@@ -287,10 +283,43 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
};
|
||||
scopedefine(c.top, nm, skind.SK_USE, nil, d); return;
|
||||
};
|
||||
if (k == nkind.N_DEF) { scopedefineinmodule(c.top, nm, mod, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_FN, nil, d); return; };
|
||||
if (k == nkind.N_LET) { scopedefineinmodule(c.top, nm, mod, skind.SK_VAR, nil, d); return; };
|
||||
if (k == nkind.N_DEF) { installtop(c, d, nm, mod, skind.SK_DEF, "def"); return; };
|
||||
if (k == nkind.N_TYPEDECL) { installtop(c, d, nm, mod, skind.SK_TYPE, "type"); return; };
|
||||
if (k == nkind.N_FNDECL) { installtop(c, d, nm, mod, skind.SK_FN, "fn"); return; };
|
||||
if (k == nkind.N_LET) { installtop(c, d, nm, mod, skind.SK_VAR, "let"); return; };
|
||||
};
|
||||
|
||||
// installtop — install a top-level decl name into c.top, rejecting a
|
||||
// genuine within-module duplicate loud (#23) with cstage's exact
|
||||
// "duplicate <kind> %s" wording (cmd/wcc/check.c:2852/2911/2932/2955).
|
||||
//
|
||||
// scopedefineinmodule returns nil only on a same-(name, mod) re-install.
|
||||
// Same-leaf cross-package decls carry distinct mods (the flat-bundle
|
||||
// model) and an imported-module bareword's SK_USE keys on mod="", so a
|
||||
// coexisting same-leaf type/fn in its own package never collides.
|
||||
//
|
||||
// The one nil that is NOT a user duplicate: a redeclaration of a
|
||||
// pre-seeded builtin (the predeclared `nomem`, or a primtype name). cstage
|
||||
// keeps no builtins in the scope at all — resolve_typename consults
|
||||
// lookup_builtin FIRST (cmd/wcc/check.c:69) and the builtin always wins,
|
||||
// so a user `type nomem = !void` / `type int = ...` installs dead and is
|
||||
// silently ignored, never a duplicate error. wwstage seeds builtins INTO
|
||||
// c.top, so the same redeclaration surfaces here as a collision; we mirror
|
||||
// cstage by dropping it (the seeded builtin stays, and wins resolution)
|
||||
// rather than erroring. A pre-seeded builtin is identified by its sym
|
||||
// carrying no real source decl (primtypes: decl=nil; `nomem`: a
|
||||
// checkinit-synthesized N_TYPEDECL with an empty .file) — user decls
|
||||
// always carry their parsed source file.
|
||||
fn installtop(c: *checker, d: *node, nm: str, mod: str, k: skind, kind: str) void = {
|
||||
if (scopedefineinmodule(c.top, nm, mod, k, nil, d) != nil) { return; };
|
||||
let prev: *sym = scopesamekeysym(c.top, nm, mod);
|
||||
if (prev != nil) {
|
||||
if (prev.decl == nil) { return; };
|
||||
if (prev.decl.file.len == 0) { return; };
|
||||
};
|
||||
cerr(d.file); cerr(": error: duplicate "); cerr(kind);
|
||||
cerr(" "); cerr(nm); cerr("\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
|
||||
// stamptuplebinds — distribute a tuple's per-element types onto a
|
||||
@@ -5586,8 +5615,13 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
tab.lhs = tsl;
|
||||
tab.rhs = arr;
|
||||
// pass 1 already ran; install the table name now so main
|
||||
// resolves it (declmod returns "" for tab.nmod="").
|
||||
installdecl(c, file, tab);
|
||||
// resolves it (declmod returns "" for tab.nmod=""). Goes
|
||||
// direct to scopedefineinmodule, NOT installdecl: cstage's
|
||||
// synth install (cmd/wcc/check.c:3079) bypasses the
|
||||
// duplicate-decl reject (#23) the same way, so a pathological
|
||||
// user `const __wwtests` stays a downstream type error in
|
||||
// both stages rather than a dup-diagnostic in only one.
|
||||
scopedefineinmodule(c.top, tab.str, "", skind.SK_VAR, nil, tab);
|
||||
|
||||
let arg: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
||||
arg.str = "__wwtests";
|
||||
|
||||
@@ -10330,6 +10330,30 @@ export fn scopedefineinmodule(s: *scope, name: str, mod: str, k: skind, t: *tinf
|
||||
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;
|
||||
};
|
||||
|
||||
// selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c.
|
||||
//
|
||||
// Status: name-resolution + primitive-type seeding only. Full type
|
||||
@@ -10592,16 +10616,12 @@ fn checkmoduleshadow(c: *checker, name: str, kindstr: str) void = {
|
||||
// scopelookupuselocal (lib/ww/sym.ww) to the SK_USE that coexists in the
|
||||
// landed scope — the coexistence-equivalent of cstage's use_alias bit.
|
||||
// Cite: project memory module_type_name_collision (cstage fix
|
||||
// 2026-05-13). #11 (wwstage checkfile pass) revisits the dup-decl errors
|
||||
// below when wwstage grows a real check pass on the cgen path.
|
||||
// TODO(#11): cstage check.c errors on duplicate top-level type/def/fn
|
||||
// (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate <kind>") and on
|
||||
// duplicate top-level let (cmd/wcc/check.c L1880, "duplicate let %s")
|
||||
// once #32 lands. Wwstage's installdecl just drops the second insert
|
||||
// silently. Add `if (s == nil) err(...)` here once #11 wires checkfile
|
||||
// into w6c_ww. Silent-accept matches the deferred-check design — see
|
||||
// test/wcc/708 and test/wcc/696 for the same cstage-only neg-case
|
||||
// precedent.
|
||||
// 2026-05-13).
|
||||
// #23: top-level duplicate type/def/fn/let now reject loud here, keyed
|
||||
// on (name, mod) exactly as cstage's install pass does
|
||||
// (cmd/wcc/check.c:2852/2911/2932/2955) — see dupdecl below. (Same-scope
|
||||
// LOCAL dup `let a=1; let a=2;` is a different path and stays deferred to
|
||||
// #11; test/wcc/708 + test/wcc/696 are that cstage-only neg-case.)
|
||||
fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
if (d == nil) { return; };
|
||||
let k: nkind = d.kind;
|
||||
@@ -10619,10 +10639,43 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
};
|
||||
scopedefine(c.top, nm, skind.SK_USE, nil, d); return;
|
||||
};
|
||||
if (k == nkind.N_DEF) { scopedefineinmodule(c.top, nm, mod, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_FN, nil, d); return; };
|
||||
if (k == nkind.N_LET) { scopedefineinmodule(c.top, nm, mod, skind.SK_VAR, nil, d); return; };
|
||||
if (k == nkind.N_DEF) { installtop(c, d, nm, mod, skind.SK_DEF, "def"); return; };
|
||||
if (k == nkind.N_TYPEDECL) { installtop(c, d, nm, mod, skind.SK_TYPE, "type"); return; };
|
||||
if (k == nkind.N_FNDECL) { installtop(c, d, nm, mod, skind.SK_FN, "fn"); return; };
|
||||
if (k == nkind.N_LET) { installtop(c, d, nm, mod, skind.SK_VAR, "let"); return; };
|
||||
};
|
||||
|
||||
// installtop — install a top-level decl name into c.top, rejecting a
|
||||
// genuine within-module duplicate loud (#23) with cstage's exact
|
||||
// "duplicate <kind> %s" wording (cmd/wcc/check.c:2852/2911/2932/2955).
|
||||
//
|
||||
// scopedefineinmodule returns nil only on a same-(name, mod) re-install.
|
||||
// Same-leaf cross-package decls carry distinct mods (the flat-bundle
|
||||
// model) and an imported-module bareword's SK_USE keys on mod="", so a
|
||||
// coexisting same-leaf type/fn in its own package never collides.
|
||||
//
|
||||
// The one nil that is NOT a user duplicate: a redeclaration of a
|
||||
// pre-seeded builtin (the predeclared `nomem`, or a primtype name). cstage
|
||||
// keeps no builtins in the scope at all — resolve_typename consults
|
||||
// lookup_builtin FIRST (cmd/wcc/check.c:69) and the builtin always wins,
|
||||
// so a user `type nomem = !void` / `type int = ...` installs dead and is
|
||||
// silently ignored, never a duplicate error. wwstage seeds builtins INTO
|
||||
// c.top, so the same redeclaration surfaces here as a collision; we mirror
|
||||
// cstage by dropping it (the seeded builtin stays, and wins resolution)
|
||||
// rather than erroring. A pre-seeded builtin is identified by its sym
|
||||
// carrying no real source decl (primtypes: decl=nil; `nomem`: a
|
||||
// checkinit-synthesized N_TYPEDECL with an empty .file) — user decls
|
||||
// always carry their parsed source file.
|
||||
fn installtop(c: *checker, d: *node, nm: str, mod: str, k: skind, kind: str) void = {
|
||||
if (scopedefineinmodule(c.top, nm, mod, k, nil, d) != nil) { return; };
|
||||
let prev: *sym = scopesamekeysym(c.top, nm, mod);
|
||||
if (prev != nil) {
|
||||
if (prev.decl == nil) { return; };
|
||||
if (prev.decl.file.len == 0) { return; };
|
||||
};
|
||||
cerr(d.file); cerr(": error: duplicate "); cerr(kind);
|
||||
cerr(" "); cerr(nm); cerr("\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
|
||||
// stamptuplebinds — distribute a tuple's per-element types onto a
|
||||
@@ -15918,8 +15971,13 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
tab.lhs = tsl;
|
||||
tab.rhs = arr;
|
||||
// pass 1 already ran; install the table name now so main
|
||||
// resolves it (declmod returns "" for tab.nmod="").
|
||||
installdecl(c, file, tab);
|
||||
// resolves it (declmod returns "" for tab.nmod=""). Goes
|
||||
// direct to scopedefineinmodule, NOT installdecl: cstage's
|
||||
// synth install (cmd/wcc/check.c:3079) bypasses the
|
||||
// duplicate-decl reject (#23) the same way, so a pathological
|
||||
// user `const __wwtests` stays a downstream type error in
|
||||
// both stages rather than a dup-diagnostic in only one.
|
||||
scopedefineinmodule(c.top, tab.str, "", skind.SK_VAR, nil, tab);
|
||||
|
||||
let arg: *node = newnode(nkind.N_IDENT, pf, pl, pc);
|
||||
arg.str = "__wwtests";
|
||||
|
||||
Reference in New Issue
Block a user