/* * 989_wwileaf_run — BUG-C (#11) regression pin: a decl-less / export-less * primary module's `.wwi` `package` line must carry the module's real leaf, * NOT the literal default "main". * * THE BUG: wwi_emit derived the `package` leaf by scanning for the first * PRIMARY decl bearing a module tag. A fully empty (or comment-only, or * export-less) body has NO such decl, so the scan fell through and the leaf * stayed the seed default "main". The dep's `.wwi` then read `package main;` * → when its importer composed the dep under `//ww:module `, the * in-`.wwi` `package main` clause was validated against the import path and * REJECTED: "package main does not match import path " * (cmd/wcc/parse.c, both stages). A decl-less module was simply unimportable. * * THE FIX (cmd/w6c/wwi.c + selfhost/cmd/wcc/wwi.ww + the two parse sites): * parse stamps the primary module identity on the N_FILE node (module/nmod, * "only if empty", never on the imported TK_MODPATH boundary nor the bare * root reset); wwi_emit falls back to that identity's leaf when the decl-scan * finds nothing (`!found` — NOT pkg=="main", so a body whose first tagged * decl legitimately leafs to "main" is unaffected and both stages decide * identically). A real root `package main` arrives via a bare reset, leaves * the stamp empty, and stays "main". * * Table-driven over the three decl-less shapes that all route the fallback: * an empty body, a comment-only body, and a NESTED dotted path (a.b.c → leaf * c, exercising the leaf-slice). Each row drives the REAL producer→importer * flow on BOTH stages: * (a) the dep `.wwi` `package` line == the real leaf (pre-fix: "main"); * (b) the dep `.wwi` is byte-identical w6c vs w6c_ww (rule 10); * (c) a root that `import`s the dep RESOLVES on both stages (pre-fix: * hard REJECT) and the two importer `.s` are byte-identical (rule 10). * * The composed units are fed straight to w6c / w6c_ww (not `ww build`): the * producer-unit shape `//ww:module-reset ` + body is exactly what the * driver's sep_emit_body emits, so this pins the same path without the * driver's filesystem layout. Non-vacuity: neutering the wwi_emit fallback * reddens row (a) ("package main;") and row (c) (the import REJECT). * * Light wwstage-driver test (CLAUDE.md rule 14): every intermediate lands in * a private mkdtemp dir, rm -rf'd at exit — no /tmp scratch leak (task #8). * 989 prefix per the sep-gate precedent; twin of 989_modresetadj_run (#9). */ #include #include #include #include #include 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; } static int write_file(const char *path, const char *content) { FILE *f = fopen(path, "wb"); if (!f) return -1; fputs(content, f); fclose(f); return 0; } /* First physical line of `path` (newline stripped) into `out`. */ static int first_line(const char *path, char *out, size_t outsz) { FILE *f = fopen(path, "rb"); if (!f) return -1; if (fgets(out, (int)outsz, f) == NULL) { fclose(f); out[0] = '\0'; } fclose(f); size_t n = strlen(out); if (n && out[n - 1] == '\n') out[n - 1] = '\0'; return 0; } struct row { const char *tag; /* scratch-file stem */ const char *path; /* dotted module path on the //ww:module-reset */ const char *leaf; /* the in-file `package ;` clause */ const char *body; /* body after the clause (zero module-tagged decls) */ const char *want; /* expected emitted `package ;` leaf */ }; /* Each shape lacks any module-tagged decl, so wwi_emit's decl-scan misses * and the leaf must come from the parse-stamped N_FILE identity. */ static const struct row rows[] = { { "empty", "emptymod", "emptymod", "", "emptymod" }, { "comment", "cmod", "cmod", "// only a comment\n", "cmod" }, { "nested", "a.b.c", "c", "", "c" }, }; #define NROW ((int)(sizeof rows / sizeof rows[0])) #define RET 42 static int run_row(const char *bin, const char *dir, const struct row *r) { char prod[1024], cswwi[1024], wwwwi[1024], css[1024], wws[1024]; char root[1024], rcss[1024], rwws[1024], unit[8192], cmd[16384]; snprintf(prod, sizeof prod, "%s/%s.prod.ww", dir, r->tag); snprintf(cswwi, sizeof cswwi, "%s/%s.cs.wwi", dir, r->tag); snprintf(wwwwi, sizeof wwwwi, "%s/%s.ww.wwi", dir, r->tag); snprintf(css, sizeof css, "%s/%s.cs.s", dir, r->tag); snprintf(wws, sizeof wws, "%s/%s.ww.s", dir, r->tag); snprintf(root, sizeof root, "%s/%s.root.ww", dir, r->tag); snprintf(rcss, sizeof rcss, "%s/%s.rcs.s", dir, r->tag); snprintf(rwws, sizeof rwws, "%s/%s.rww.s", dir, r->tag); /* the producer unit — exactly the driver's sep_emit_body shape. */ snprintf(unit, sizeof unit, "//ww:module-reset %s\n" "package %s;\n" "%s", r->path, r->leaf, r->body); if (write_file(prod, unit) != 0) { fprintf(stderr, "wwileaf[%s]: cannot write producer unit\n", r->tag); return 1; } /* Leg a — both stages emit the dep `.wwi`; the `package` leaf is the * module's real leaf, not the default "main". */ snprintf(cmd, sizeof cmd, "%s/w6c -c -I %s -o %s %s 2>/dev/null", bin, cswwi, css, prod); if (runwait(cmd) != 0) { fprintf(stderr, "wwileaf[%s] FAIL: w6c producer errored\n", r->tag); return 1; } snprintf(cmd, sizeof cmd, "%s/w6c_ww -c -I %s -o %s %s 2>/dev/null", bin, wwwwi, wws, prod); if (runwait(cmd) != 0) { fprintf(stderr, "wwileaf[%s] FAIL: w6c_ww producer errored\n", r->tag); return 1; } char want[256], line[512]; snprintf(want, sizeof want, "package %s;", r->want); if (first_line(cswwi, line, sizeof line) != 0 || strcmp(line, want) != 0) { fprintf(stderr, "wwileaf[%s] FAIL: .wwi package line '%s' want '%s' (BUG-C)\n", r->tag, line, want); return 1; } /* Leg b — the dep `.wwi` is byte-identical across stages (rule 10). */ if (files_eq(cswwi, wwwwi) != 0) { fprintf(stderr, "wwileaf[%s] FAIL: w6c vs w6c_ww .wwi differ (rule 10)\n", r->tag); return 1; } /* Leg c — a root importing the dep RESOLVES on both stages (pre-fix: * "package main does not match import path %s" REJECT). The dep `.wwi` * is prepended under //ww:module , exactly as the driver composes * an importer unit. */ { char *wwi = NULL; size_t wlen = 0; if (slurp(cswwi, &wwi, &wlen) < 0) { fprintf(stderr, "wwileaf[%s]: cannot read dep .wwi\n", r->tag); return 1; } snprintf(unit, sizeof unit, "//ww:module %s\n" "%s\n" "//ww:module-reset\n" "package main;\n" "import %s;\n" "export fn main() i32 = { return %d; };\n", r->path, wwi, r->path, RET); free(wwi); } if (write_file(root, unit) != 0) { fprintf(stderr, "wwileaf[%s]: cannot write importer unit\n", r->tag); return 1; } snprintf(cmd, sizeof cmd, "%s/w6c -c -o %s %s 2>/dev/null", bin, rcss, root); if (runwait(cmd) != 0) { fprintf(stderr, "wwileaf[%s] FAIL: w6c rejected the import (BUG-C)\n", r->tag); return 1; } snprintf(cmd, sizeof cmd, "%s/w6c_ww -c -o %s %s 2>/dev/null", bin, rwws, root); if (runwait(cmd) != 0) { fprintf(stderr, "wwileaf[%s] FAIL: w6c_ww rejected the import (BUG-C)\n", r->tag); return 1; } if (files_eq(rcss, rwws) != 0) { fprintf(stderr, "wwileaf[%s] FAIL: importer .s differ cs vs ww (rule 10)\n", r->tag); return 1; } return 0; } int main(void) { const char *bin = absbin(); if (!bin) return 1; char dir[] = "/tmp/wwileaf_XXXXXX"; if (mkdtemp(dir) == NULL) { fprintf(stderr, "wwileaf: mkdtemp failed\n"); return 1; } int fail = 0; for (int i = 0; i < NROW; i++) fail |= run_row(bin, dir, &rows[i]); char rm[2048]; snprintf(rm, sizeof rm, "rm -rf %s", dir); (void)runwait(rm); if (fail) return 1; printf("wwileaf: %d decl-less shapes — .wwi leaf derived, byte-id, " "import resolves cs==ww (#11)\n", NROW); return 0; }