wcc/ww: reject a duplicate top-level main (F-D)

A second top-level decl named `main` (fn/let/def/type) collides with
the entry main on the single bare `main` symbol: today both lower to a
bare `main`, w6l silently accepts the duplicate, and the program links
rc=0 then segfaults (or runs wrong), in both stages. The existing
duplicate-decl rejects key on (name, module), so a cross-module
`foo.main` vs the bare entry `main` read as distinct and slip through.

Add a program-global, name-only, cross-module uniqueness check on
`main` in the checker (both stages), colocated with the duplicate-decl
rejects and counting user decls before the -T synthesized test main.
Corpus-safe: a lone `fn main` in any package stays legal (ww has no
package-main convention -- cmatrix/lisp/mandelbrot are non-main-package
entries and keep building). This converts the silent segfault to a
loud compile error and subsumes the w6l silent-dup-main case (#31);
correct package-aware mangling of a non-entry main is deferred to the
root-unit entry-detection work (#22/#32).

Regenerates the w6c and wwdump combined.ww. Table-driven 842 test:
reject rows for let/fn/def/type main (genuine cross-module import form)
plus a negative single-main corpus-safe row that must still build+run.
This commit is contained in:
2026-06-14 17:05:46 +09:00
parent c86c6a3bbf
commit 34c1051a63
6 changed files with 373 additions and 0 deletions

View File

@@ -6227,6 +6227,35 @@ export fn checkfile(c: *checker, file: *node) void = {
d = d.next;
};
// Program-global, name-only, cross-module uniqueness on `main`.
// `main` lowers to ONE bare entry symbol, so a second top-level
// decl named `main` (any kind, any package) collides with the
// entry at link time — today a silent segfault / link-fail in
// both stages. The (name, module) duplicate rejects in installtop
// read a cross-package `foo.main` and the bare entry as distinct,
// so they miss this. Correct multi-main mangling (entry stays bare,
// the rest qualify) is deferred (task #32); reject loudly meanwhile
// (rule 7). Walks USER decls only — runs before the -T synth main
// is appended below — so a hosted-test build never false-counts.
// Twin of cmd/wcc/check.c.
let firstmain: *node = nil;
let mm: *node = file.list;
for (mm != nil) {
let ismain: bool = (mm.kind == nkind.N_FNDECL
|| mm.kind == nkind.N_LET || mm.kind == nkind.N_DEF
|| mm.kind == nkind.N_TYPEDECL) && streq(mm.str, "main");
if (ismain) {
if (firstmain == nil) {
firstmain = mm;
} else {
cerr(mm.file);
cerr(": error: duplicate top-level main: only the entry main may exist (task #32)\n");
c.errs += 1;
};
};
mm = mm.next;
};
// #15 @test harness — under `w6c_ww -T`, synthesize the entry the
// driver would otherwise hand-wire. We sit at the seam between
// fn-install (Pass 1, all names now in scope so the synth callees