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:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
9
test/wcc/data/attest_userrun.ww
Normal file
9
test/wcc/data/attest_userrun.ww
Normal 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);
|
||||
};
|
||||
14
test/wcc/data/builtin_redecl_ok.ww
Normal file
14
test/wcc/data/builtin_redecl_ok.ww
Normal 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
5
test/wcc/data/dup_def.ww
Normal 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
5
test/wcc/data/dup_fn.ww
Normal 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
5
test/wcc/data/dup_let.ww
Normal 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; };
|
||||
5
test/wcc/data/dup_type.ww
Normal file
5
test/wcc/data/dup_type.ww
Normal 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; };
|
||||
13
test/wcc/data/dup_xpkg_ok.ww
Normal file
13
test/wcc/data/dup_xpkg_ok.ww
Normal 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(); };
|
||||
Reference in New Issue
Block a user