cmd+selfhost+test: gate alloc builtin behind same-module fn alloc

Mirrors the existing abort/assert gates in cstage check.c (strict
same-module lookup rather than scope_lookup_prefer, since lib/os.alloc
under a `use os;` import must not suppress the bare-alloc builtin in
client code). cgen.c shadows the resolution: only fire the rt_alloc
path when the typer left N_CALL.lhs->type == ty_err. wwstage gets a
new samemodfn helper for the matching gate.

Test fixtures: package-main repair for the 3 alloc rows in 700_e2e.c
that the parser was inheriting curmod="os" from the concat'd os.ww;
new shadow-test row asserts a same-module `fn alloc(n: i64) i64`
beats the builtin in cgen.
This commit is contained in:
2026-05-19 18:51:07 +09:00
parent 58e6d349a2
commit 3fe968c8a0
7 changed files with 148 additions and 18 deletions

View File

@@ -14791,10 +14791,19 @@ fn cgcall(c: *cgen, n: *node) void = {
// value's bytes. For struct literals, lower to rt_alloc
// + per-field stores. Mirrors cmd/w6c/cgen.c's N_CALL
// alloc path.
//
// Same-module-scope guard: skip the builtin when a fn
// `alloc` is declared in the current module (lib/os and
// rt/ensure both shadow it). Mirrors cstage check.c's
// scope_lookup_prefer gating on the `abort` precedent;
// without it, the bare same-module call lands in the
// typed-builtin path and shadows the user decl. Task #23.
if (streq(callee.str, "alloc")) {
if (n.list != nil) {
cgalloc(c, n);
return;
if (!samemodfn(c, "alloc")) {
cgalloc(c, n);
return;
};
};
};
};
@@ -21273,6 +21282,25 @@ fn fnparamslookup(c: *cgen, name: str) *node = {
return nil;
};
// samemodfn — true iff `name` is registered as a fn in c.curmod. Used
// by cgcall to suppress the bare-name Hare-style builtins (`alloc(x)`,
// future free/append/len audits) when the current module declares its
// own decl by that name. Mirrors cstage's same-module check at
// cmd/wcc/check.c (alloc gate, task #23) — `scope_lookup_prefer` over
// the flat scope would also match `use os;`-imported decls in a primary,
// suppressing the builtin spuriously; the same-module-tag filter here
// (and `c.curmod && ...` on the cstage side) keeps the gate strict.
fn samemodfn(c: *cgen, name: str) bool = {
let f: *fnret = c.fnrets;
for (f != nil) {
if (streq(f.fname, name)) {
if (streq(f.fmod, c.curmod)) { return true; };
};
f = f.frnext;
};
return false;
};
// fnparamslookupmod — same-module-first leaf walk. Module-qualified
// `mod.fn(...)` calls go through this so a leaf collision (multiple
// modules export the same name, e.g. `os.read` and `io.read`) resolves