test/wcc: 989_declns_sep decl-namespace gate — dup reject / builtin redecl / dir-pkg modfn coexist (M4 E2-C2c, #22)
Final E2-C2 commit. New decl-namespace gate, both stages: dup fn/type/def/let build-FAIL with a right-reason "duplicate <kind> <name>" diag (shared substring; ww has no line:col); builtin_redecl ACCEPT exit 7 (shadowing a pre-seeded builtin is redeclarable); modfn coexist reshaped to a real directory package (696 precedent) via ww build --sep on two layouts (modfn_coexist + _vbu use-before-value) so both run to 6, cs==ww per-pkg .s/.wwi/.unit.ww, cross-order byte-id on .s/.wwi (.unit.ww excluded: verbatim source flips by construction, order-independence is codegen). __root.s carries both CALL aa.helper and CALL main.aa distinctly. Test-only; all 5 pins hold; 443 to 444.
This commit is contained in:
392
test/wcc/989_declns_sep.c
Normal file
392
test/wcc/989_declns_sep.c
Normal file
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* 989_declns_sep — #23/#30 declaration-namespace + module-fn coexistence
|
||||
* under the directory-package model (task #83, M4 E2-C2c).
|
||||
*
|
||||
* Re-hosts every decl-namespace assertion that today lives ONLY in
|
||||
* 910/997 onto a gate that survives the M4 flip (910/997 are deleted at
|
||||
* the flip — they feed combined.ww / amalgamation inputs). Three legs:
|
||||
*
|
||||
* dup family — a duplicate top-level fn/type/def/let in one (flat)
|
||||
* module must build-FAIL on BOTH stages. The fixtures
|
||||
* are flat single-package (the surviving shape) and feed
|
||||
* `w6c` / `w6c_ww` DIRECTLY — no driver resolution; the
|
||||
* dup is an install-pass property, not a build-model one.
|
||||
* 712 covered the cstage let arm; 997 covered the wwstage
|
||||
* arm — this gate asserts BOTH stages reject every kind,
|
||||
* closing the ww arm that dies with 997.
|
||||
* builtin_redecl— the carve-out: a user redecl of a pre-seeded builtin
|
||||
* name (`nomem`) is NOT a duplicate. cstage keeps no
|
||||
* builtins in scope (lookup_builtin wins first), wwstage
|
||||
* seeds them into c.top so installtop must DROP the redecl
|
||||
* rather than error. Both stages accept + run to 7.
|
||||
* modfn coexist — a top-level value-namespace `fn aa` and an imported
|
||||
* MODULE `aa` coexist: bare `aa()` binds the fn (a module
|
||||
* is not callable), `aa.helper()` binds through the module.
|
||||
* RESHAPED from the dying multi-`package`-single-file
|
||||
* 910/997 fixtures to dir-packages (696 precedent), built
|
||||
* via `ww build --sep` + run. The _vbu twin flips the decl
|
||||
* order (`fn aa` BEFORE `import aa`): cstage installs every
|
||||
* SK_USE in an order-independent first pass, wwstage in
|
||||
* source order, so vbu exercises a DISTINCT promote path —
|
||||
* its asm must be byte-identical to the use-before-value
|
||||
* order, the teeth against silent order-dependence.
|
||||
*
|
||||
* dup_xpkg (same leaf in distinct packages = legal) RIDES on 696/697's
|
||||
* dir-package coverage — no port (spec C2c).
|
||||
*
|
||||
* Light wwstage-driver test (CLAUDE.md rule 14): every modfn intermediate
|
||||
* is `-o`-redirected to /tmp, so it is phase-1 parallel-safe; the dup +
|
||||
* builtin legs feed w6c directly with `-o /tmp`. Models 989_sepbuild_run +
|
||||
* 911_attest_drop conventions; 989 prefix per the sep-gate precedent (the
|
||||
* 7xx cgen/check range is exhausted).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const char *
|
||||
absbin(void)
|
||||
{
|
||||
const char *b = getenv("BIN");
|
||||
if (!b) b = "out/bin";
|
||||
if (b[0] == '/') return b;
|
||||
static char buf[2048];
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
|
||||
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int
|
||||
slurp(const char *path, char **outbuf, size_t *outlen)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return -1;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long n = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (n < 0) { fclose(f); return -1; }
|
||||
char *b = malloc((size_t)n + 1);
|
||||
if (!b) { fclose(f); return -1; }
|
||||
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
|
||||
b[n] = '\0';
|
||||
fclose(f);
|
||||
*outbuf = b;
|
||||
*outlen = (size_t)n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
files_eq(const char *a, const char *b)
|
||||
{
|
||||
char *ba = NULL, *bb = NULL;
|
||||
size_t na = 0, nb = 0;
|
||||
if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) {
|
||||
free(ba); free(bb);
|
||||
return -1;
|
||||
}
|
||||
int eq = (na == nb && memcmp(ba, bb, na) == 0);
|
||||
free(ba); free(bb);
|
||||
return eq ? 0 : 1;
|
||||
}
|
||||
|
||||
/* 0 if `needle` occurs in the file at `path`, else 1 (or -1 on read err). */
|
||||
static int
|
||||
file_contains(const char *path, const char *needle)
|
||||
{
|
||||
char *b = NULL;
|
||||
size_t n = 0;
|
||||
if (slurp(path, &b, &n) < 0) return -1;
|
||||
int found = (strstr(b, needle) != NULL);
|
||||
free(b);
|
||||
return found ? 0 : 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* dup family — direct-w6c/w6c_ww. Each fixture is a flat single-package
|
||||
* with one duplicate top-level decl; both stages must build-FAIL, and the
|
||||
* diagnostic must name the duplicate (non-vacuity: a fixture that failed
|
||||
* for an unrelated reason would pass an exit-only check). cstage prefixes
|
||||
* line:col, wwstage does not, so the asserted substring is the shared tail.
|
||||
*/
|
||||
static const struct {
|
||||
const char *fixture;
|
||||
const char *diag; /* shared "duplicate <kind> <name>" substring */
|
||||
} dup_rows[] = {
|
||||
{ "test/wcc/data/dup_fn.ww", "duplicate fn foo" },
|
||||
{ "test/wcc/data/dup_type.ww", "duplicate type t" },
|
||||
{ "test/wcc/data/dup_def.ww", "duplicate def D" },
|
||||
{ "test/wcc/data/dup_let.ww", "duplicate let g" },
|
||||
};
|
||||
|
||||
static int
|
||||
dup_family(const char *bin)
|
||||
{
|
||||
int pid = getpid();
|
||||
char err[256], cmd[4096];
|
||||
snprintf(err, sizeof err, "/tmp/declns_dup_%d.err", pid);
|
||||
|
||||
int rc = 0;
|
||||
const char *comps[] = { "w6c", "w6c_ww" };
|
||||
for (size_t r = 0; r < sizeof dup_rows / sizeof dup_rows[0]; r++) {
|
||||
for (size_t c = 0; c < sizeof comps / sizeof comps[0]; c++) {
|
||||
snprintf(cmd, sizeof cmd, "%s/%s %s -o /dev/null 2>%s",
|
||||
bin, comps[c], dup_rows[r].fixture, err);
|
||||
if (runwait(cmd) == 0) {
|
||||
fprintf(stderr, "declns FAIL: %s accepted %s (expected "
|
||||
"build-fail)\n", comps[c], dup_rows[r].fixture);
|
||||
rc = 1;
|
||||
} else if (file_contains(err, dup_rows[r].diag) != 0) {
|
||||
fprintf(stderr, "declns FAIL: %s rejected %s but not on "
|
||||
"`%s` (wrong reason)\n", comps[c], dup_rows[r].fixture,
|
||||
dup_rows[r].diag);
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
unlink(err);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* builtin_redecl — the carve-out. Import-free + flat, so it feeds the
|
||||
* direct w6c→w6a→w6l pipeline (mirrors 911's linkfail staging). The
|
||||
* builtin `nomem` (= !void) wins, so g()'s i32 arm returns 7; both stages
|
||||
* must accept AND run to that exact code.
|
||||
*/
|
||||
static const struct {
|
||||
const char *comp;
|
||||
const char *asmt;
|
||||
const char *linkt;
|
||||
} stages[] = {
|
||||
{ "w6c", "w6a", "w6l" },
|
||||
{ "w6c_ww", "w6a_ww", "w6l_ww" },
|
||||
};
|
||||
|
||||
static int
|
||||
builtin_redecl(const char *bin)
|
||||
{
|
||||
int pid = getpid();
|
||||
char rt[1024], asmf[256], obj[256], exe[256], cmd[4096];
|
||||
snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin);
|
||||
|
||||
int rc = 0;
|
||||
for (size_t i = 0; i < sizeof stages / sizeof stages[0]; i++) {
|
||||
snprintf(asmf, sizeof asmf, "/tmp/declns_br_%s_%d.s", stages[i].comp, pid);
|
||||
snprintf(obj, sizeof obj, "/tmp/declns_br_%s_%d.o", stages[i].comp, pid);
|
||||
snprintf(exe, sizeof exe, "/tmp/declns_br_%s_%d.exe", stages[i].comp, pid);
|
||||
|
||||
int ok = 1;
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s/%s test/wcc/data/builtin_redecl_ok.ww -o %s 2>/dev/null",
|
||||
bin, stages[i].comp, asmf);
|
||||
if (ok && runwait(cmd) != 0) {
|
||||
fprintf(stderr, "declns FAIL: %s rejected builtin_redecl "
|
||||
"(carve-out must accept)\n", stages[i].comp);
|
||||
ok = 0; rc = 1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/%s -o %s %s 2>/dev/null",
|
||||
bin, stages[i].asmt, obj, asmf);
|
||||
if (ok && runwait(cmd) != 0) {
|
||||
fprintf(stderr, "declns FAIL: %s asm of builtin_redecl\n",
|
||||
stages[i].asmt);
|
||||
ok = 0; rc = 1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/%s -o %s %s %s 2>/dev/null",
|
||||
bin, stages[i].linkt, exe, obj, rt);
|
||||
if (ok && runwait(cmd) != 0) {
|
||||
fprintf(stderr, "declns FAIL: %s link of builtin_redecl\n",
|
||||
stages[i].linkt);
|
||||
ok = 0; rc = 1;
|
||||
}
|
||||
if (ok) {
|
||||
int got = runwait(exe);
|
||||
if (got != 7) {
|
||||
fprintf(stderr, "declns FAIL: builtin_redecl (%s) "
|
||||
"exit=%d want=7\n", stages[i].comp, got);
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
unlink(asmf); unlink(obj); unlink(exe);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* modfn coexist — the two dir-package layouts. Build each via `ww build
|
||||
* --sep` + run, both driver stages, expect exit 6 (aa()=1 + aa.helper()=5).
|
||||
* `ww build` (NOT `ww run`) per spec: run is not sep-wired until the driver
|
||||
* flip. Returns 0 on success; leaves <prog>.sepwork in `td` for the byte-id
|
||||
* compares below.
|
||||
*/
|
||||
struct modfn { const char *lay; const char *fixture; };
|
||||
static const struct modfn modfn_lays[] = {
|
||||
{ "coexist", "test/wcc/data/modfn_coexist/main.ww" },
|
||||
{ "coexist_vbu", "test/wcc/data/modfn_coexist_vbu/main.ww" },
|
||||
};
|
||||
|
||||
/* per-package output the sep driver materializes; the root carries no .wwi
|
||||
* (post-#69 it is compiled without -I), so its suffix set omits .wwi. */
|
||||
static const struct {
|
||||
const char *pkg;
|
||||
const char *suf;
|
||||
} parts[] = {
|
||||
{ "aa", ".s" },
|
||||
{ "aa", ".wwi" },
|
||||
{ "aa", ".unit.ww" },
|
||||
{ "__root", ".s" },
|
||||
{ "__root", ".unit.ww" },
|
||||
};
|
||||
|
||||
static int
|
||||
modfn_build(const char *bin, const char *td)
|
||||
{
|
||||
char cmd[4096], prog[1024];
|
||||
int rc = 0;
|
||||
for (size_t l = 0; l < sizeof modfn_lays / sizeof modfn_lays[0]; l++) {
|
||||
for (size_t s = 0; s < sizeof stages / sizeof stages[0]; s++) {
|
||||
const char *drv = (s == 0) ? "ww" : "ww_ww";
|
||||
snprintf(prog, sizeof prog, "%s/%s.%s", td, modfn_lays[l].lay,
|
||||
(s == 0) ? "cs" : "ww");
|
||||
snprintf(cmd, sizeof cmd, "%s/%s build --sep -o %s %s 2>/dev/null",
|
||||
bin, drv, prog, modfn_lays[l].fixture);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "declns FAIL: %s build --sep %s\n", drv,
|
||||
modfn_lays[l].lay);
|
||||
rc = 1;
|
||||
continue;
|
||||
}
|
||||
int got = runwait(prog);
|
||||
if (got != 6) {
|
||||
fprintf(stderr, "declns FAIL: modfn %s (%s) exit=%d want=6 "
|
||||
"(coexistence mis-resolved)\n", modfn_lays[l].lay, drv, got);
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* cs==ww (rule 10): for one layout, every per-package .s/.wwi/.unit.ww the
|
||||
* cstage and wwstage sep drivers emitted must be byte-identical. */
|
||||
static int
|
||||
modfn_cs_eq_ww(const char *td, const char *lay)
|
||||
{
|
||||
int rc = 0;
|
||||
for (size_t p = 0; p < sizeof parts / sizeof parts[0]; p++) {
|
||||
char a[1024], b[1024];
|
||||
snprintf(a, sizeof a, "%s/%s.cs.sepwork/%s%s", td, lay, parts[p].pkg, parts[p].suf);
|
||||
snprintf(b, sizeof b, "%s/%s.ww.sepwork/%s%s", td, lay, parts[p].pkg, parts[p].suf);
|
||||
if (files_eq(a, b) != 0) {
|
||||
fprintf(stderr, "declns FAIL: %s cs!=ww for %s%s (rule 10)\n",
|
||||
lay, parts[p].pkg, parts[p].suf);
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* cross-order byte-id: the value-before-use layout must emit asm/interface
|
||||
* byte-identical to the use-before-value layout (same stage). Silent
|
||||
* order-dependence in the SK_USE/value-sym promote is exactly what regresses;
|
||||
* a divergence here is the teeth. */
|
||||
static int
|
||||
modfn_order_id(const char *td)
|
||||
{
|
||||
int rc = 0;
|
||||
const char *tags[] = { "cs", "ww" };
|
||||
for (size_t t = 0; t < sizeof tags / sizeof tags[0]; t++) {
|
||||
for (size_t p = 0; p < sizeof parts / sizeof parts[0]; p++) {
|
||||
/* `.unit.ww` is the driver's verbatim source-assembly
|
||||
* intermediate — it embeds the two layouts' differently
|
||||
* ORDERED source text, so it differs by construction. The
|
||||
* order-independence claim is about CODEGEN, so it is tested
|
||||
* on the compiled `.s`/`.wwi` only. */
|
||||
if (strcmp(parts[p].suf, ".unit.ww") == 0) continue;
|
||||
char a[1024], b[1024];
|
||||
snprintf(a, sizeof a, "%s/coexist.%s.sepwork/%s%s",
|
||||
td, tags[t], parts[p].pkg, parts[p].suf);
|
||||
snprintf(b, sizeof b, "%s/coexist_vbu.%s.sepwork/%s%s",
|
||||
td, tags[t], parts[p].pkg, parts[p].suf);
|
||||
if (files_eq(a, b) != 0) {
|
||||
fprintf(stderr, "declns FAIL: vbu!=non-vbu for %s%s (%s) — "
|
||||
"silent decl-order dependence\n", parts[p].pkg,
|
||||
parts[p].suf, tags[t]);
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* non-vacuity: __root.s must carry BOTH calls distinctly — through the
|
||||
* module (`aa.helper`) and to the local value fn (`main.aa`) — proving the
|
||||
* two namespaces resolved to different symbols rather than collapsing. */
|
||||
static int
|
||||
modfn_coexist_present(const char *td)
|
||||
{
|
||||
int rc = 0;
|
||||
const char *tags[] = { "cs", "ww" };
|
||||
for (size_t t = 0; t < sizeof tags / sizeof tags[0]; t++) {
|
||||
char rs[1024];
|
||||
snprintf(rs, sizeof rs, "%s/coexist.%s.sepwork/__root.s", td, tags[t]);
|
||||
if (file_contains(rs, "CALL\taa.helper(SB)") != 0) {
|
||||
fprintf(stderr, "declns FAIL: __root.s (%s) lacks module-qualified "
|
||||
"`CALL aa.helper(SB)`\n", tags[t]);
|
||||
rc = 1;
|
||||
}
|
||||
if (file_contains(rs, "CALL\tmain.aa(SB)") != 0) {
|
||||
fprintf(stderr, "declns FAIL: __root.s (%s) lacks local-fn "
|
||||
"`CALL main.aa(SB)`\n", tags[t]);
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = absbin();
|
||||
if (!bin) { fprintf(stderr, "declns FAIL: getcwd\n"); return 1; }
|
||||
|
||||
int fail = 0;
|
||||
fail += dup_family(bin);
|
||||
fail += builtin_redecl(bin);
|
||||
|
||||
char td[64], cmd[256];
|
||||
snprintf(td, sizeof td, "/tmp/wwdeclns_%d", getpid());
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
mkdir(td, 0755);
|
||||
|
||||
fail += modfn_build(bin, td);
|
||||
fail += modfn_cs_eq_ww(td, "coexist");
|
||||
fail += modfn_cs_eq_ww(td, "coexist_vbu");
|
||||
fail += modfn_order_id(td);
|
||||
fail += modfn_coexist_present(td);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "declns: %d check(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("declns: dup fn/type/def/let build-fail (both stages) + "
|
||||
"builtin_redecl accept→7 + modfn dir-pkg coexist→6 (cs==ww, "
|
||||
"vbu byte-id to use-before-value), both stages (#23/#30)\n");
|
||||
return 0;
|
||||
}
|
||||
5
test/wcc/data/modfn_coexist/aa/aa.ww
Normal file
5
test/wcc/data/modfn_coexist/aa/aa.ww
Normal file
@@ -0,0 +1,5 @@
|
||||
// #30 dir-package — the imported module `aa`. Paired with ../main.ww,
|
||||
// whose primary package also declares a value-namespace `fn aa`. Driver:
|
||||
// the modfn_coexist leg of the decl-namespace gate (NNN_declns_sep.c).
|
||||
package aa;
|
||||
export fn helper() i32 = { return 5; };
|
||||
11
test/wcc/data/modfn_coexist/main.ww
Normal file
11
test/wcc/data/modfn_coexist/main.ww
Normal file
@@ -0,0 +1,11 @@
|
||||
// #30 dir-package coexistence — the value-namespace `fn aa` and the
|
||||
// imported MODULE `aa` (SK_USE) coexist. `aa()` binds the local fn (a
|
||||
// module is not callable); `aa.helper()` binds through the module (the
|
||||
// fn has no field `helper`). Both must resolve, so any mis-resolution
|
||||
// fails to COMPILE; running to 1+5=6 proves both bind. The dir-package
|
||||
// reshape of the dying single-file modfn_coexist_ok.ww (M4 amalgamation
|
||||
// shape retired at the flip).
|
||||
package main;
|
||||
import aa;
|
||||
fn aa() i32 = { return 1; };
|
||||
export fn main() i32 = { return aa() + aa.helper(); };
|
||||
6
test/wcc/data/modfn_coexist_vbu/aa/aa.ww
Normal file
6
test/wcc/data/modfn_coexist_vbu/aa/aa.ww
Normal file
@@ -0,0 +1,6 @@
|
||||
// #30 dir-package — the imported module `aa` for the VALUE-BEFORE-USE
|
||||
// twin. Identical to ../../modfn_coexist/aa/aa.ww; kept as its own dir so
|
||||
// the two layouts are independently buildable. Driver: the
|
||||
// modfn_coexist_vbu leg of the decl-namespace gate (NNN_declns_sep.c).
|
||||
package aa;
|
||||
export fn helper() i32 = { return 5; };
|
||||
12
test/wcc/data/modfn_coexist_vbu/main.ww
Normal file
12
test/wcc/data/modfn_coexist_vbu/main.ww
Normal file
@@ -0,0 +1,12 @@
|
||||
// #30 VALUE-BEFORE-USE — the twin of ../modfn_coexist/main.ww with the
|
||||
// decl order flipped: `fn aa` is declared BEFORE `import aa`. cstage is
|
||||
// order-independent (installs every SK_USE in a first pass); wwstage
|
||||
// installs in source order, so this direction exercises a DISTINCT path
|
||||
// (installdecl's N_USE arm must set use_alias on the pre-installed value
|
||||
// sym). The gate asserts this builds + runs to 6 AND emits asm
|
||||
// byte-identical to the use-before-value order — silent order-dependence
|
||||
// is exactly what regresses.
|
||||
package main;
|
||||
fn aa() i32 = { return 1; };
|
||||
import aa;
|
||||
export fn main() i32 = { return aa() + aa.helper(); };
|
||||
Reference in New Issue
Block a user