test: port the sep import/namespace observers to ww; retire 989_{coloimport,depmain,declns,slttypepref}

sepimport_test.ww re-hosts the #98 colo srcd-shadow `ww test` gate,
the #99 imported-dep bare-main mangle count, the #23/#30 modfn
coexist byte-id (incl. the cross-layout vbu order pin), and the
#58/#50 scopelookuptype mod-preference rows over the checked-in and
synthesized module trees. slttypepref's ww_ww-absent skip gate is
dropped: the Make target declares both drivers (mangle_test
precedent).
This commit is contained in:
2026-08-08 14:07:39 +09:00
parent cd49966539
commit 7c84c52db4
6 changed files with 329 additions and 1076 deletions

View File

@@ -362,7 +362,7 @@ BYTEID_WW_TARGETS = $(BYTEID_WW_TESTS:%=wwtest/%)
# gates (module trees, sepwork artifact layout, composed //ww:module
# units, link rejects), NOT byteid suites: they run under
# test-compiler beside the surviving residual carriers.
SEP_WW_TESTS = test/sep/sepbuild_test.ww
SEP_WW_TESTS = test/sep/sepbuild_test.ww test/sep/sepimport_test.ww
SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%)
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
test/wcc/991_w6a_ww.c \

328
test/sep/sepimport_test.ww Normal file
View File

@@ -0,0 +1,328 @@
package sepimport_test;
// Import/namespace-resolution observers on the sep drivers, both
// stages. Ports of the retired native carriers
// test/wcc/989_coloimport_sep.c, 989_depmain_sep.c, 989_declns_sep.c
// and 989_slttypepref_run.c; every assertion preserved.
//
// coloimport (#98) — a co-located `_test` entry must not shadow the
// dir-package it imports under `ww test`: the two-pass locate_import
// walk resolves `import widget` to the DIR-package on a later -I
// entry, not the same-named sibling FILE on the srcd entry. Exit 0
// both stages (pre-fix w6c rejects `package widget does not match
// import path gadget`), widget.{unit.ww,wwi} registered in .sepwork,
// and every .s/.wwi byte-id cs vs ww (rule 10).
//
// depmain (#99) — an IMPORTED dir-package's non-exported `fn main`
// mangles on its import path (aa.bb.main / cc.main) instead of
// emitting a bare `TEXT main` that collides with the root entry;
// EXACTLY one column-0 `TEXT main,` across the whole sep build.
//
// declns (#23/#30 modfn leg) — a value-namespace `fn aa` coexists
// with an imported MODULE aa (exit 6 = aa() + aa.helper()); the _vbu
// layout flips decl order and must stay byte-identical on
// {aa.s,aa.wwi,__root.s} (silent order-dependence in the SK_USE/value
// promote is the regression); __root.s carries BOTH
// `CALL aa.helper(SB)` and `CALL main.aa(SB)`.
//
// slttypepref (#58/#50 c1) — scopelookuptype must prefer the current
// module's SK_TYPE when a param shadows a type leaf two modules both
// export: xb's `size(invalid)` resolves xb's !i64 (exit 8), never
// xa's !i8, on both stages, shadow and noshadow rows. The
// two-DIRECTORY tree is load-bearing (a single-file twin risks a
// different scope-install order — E3-C1 retarget note in the
// carrier); each (row,driver) rebuilds a fresh tree.
//
// Dropped C machinery, not assertions: the ww_ww-absent skip gate
// (the Make target declares both drivers) and the per-path
// unlink/rmdir accounting (testenv.clean asserts the removal).
import os;
import os.exec;
import strings;
import testenv;
import time;
fn fail(label: str, why: str) void = {
let m: str = strings.concat("sepimport FAIL: ", label, " -- ", why,
"\n");
os.write(2, m.ptr, m.len: u64);
assert(false);
};
fn tmo() time.duration = {
return (240i64 * (time.second: i64)): time.duration;
};
// -1 encodes an abnormal (non-EXIT) termination, never a valid code.
fn runcode(dir: str, name: str, argv: []str) i32 = {
let co: testenv.commandout;
testenv.runcommand(dir, dir, name, argv, tmo(), &co);
if (co.termination != exec.termination.EXIT) { return -1; };
return co.code;
};
fn cmpsepwork(label: str, csdir: str, wwdir: str) void = {
if (!testenv.isdir(csdir)) { fail(label, "no cs sepwork"); };
let names: []str = testenv.listdir(csdir);
let seen: i32 = 0;
let i: i32 = 0;
for (i < names.len) {
if (strings.hassuffix(names[i], ".s")
|| strings.hassuffix(names[i], ".wwi")) {
seen += 1;
if (!testenv.same(
testenv.readfile(strings.concat(csdir, "/", names[i])),
testenv.readfile(strings.concat(wwdir, "/", names[i])))) {
fail(label, strings.concat("cs!=ww for ", names[i],
" (rule 10)"));
};
};
i += 1;
};
// an existing-but-empty sepwork would pass the loop vacuously
if (seen == 0) { fail(label, "no .s/.wwi in cs sepwork"); };
};
// ---- coloimport (#98) --------------------------------------------------
@test fn coloimport() void = {
let td: str = testenv.fresh();
let inc: str = strings.concat(testenv.repo(), "/test/wcc/data/colo98");
let entry: str = strings.concat(inc, "/widget/widgettest.ww");
let drvs: []str = ["ww", "ww_ww"];
let tags: []str = ["cs", "ww"];
let i: i32 = 0;
for (i < 2) {
let prog: str = strings.concat(td, "/widget.", tags[i], ".bin");
let av: []str = [testenv.driver(drvs[i]), "test", "-I", inc,
"-o", prog, entry];
if (runcode(td, strings.concat("colo_", tags[i]), av) != 0) {
fail("coloimport", strings.concat("`ww test` exit != 0 on ",
drvs[i], " (import folded to the sibling file?)"));
};
i += 1;
};
let csdir: str = strings.concat(td, "/widget.cs.bin.sepwork");
let wwdir: str = strings.concat(td, "/widget.ww.bin.sepwork");
if (!testenv.exists(strings.concat(csdir, "/widget.unit.ww"))) {
fail("coloimport", strings.concat("widget.unit.ww missing (import ",
"folded to the sibling file, not the dir-package)"));
};
if (!testenv.exists(strings.concat(csdir, "/widget.wwi"))) {
fail("coloimport", "widget.wwi missing");
};
cmpsepwork("coloimport", csdir, wwdir);
testenv.clean(td);
};
// ---- depmain (#99) -----------------------------------------------------
// column-0 anchored `TEXT main,` labels across every .s under `dir`;
// the leading '\n' prepend also counts a file-leading label.
fn countbaremain(dir: str) i32 = {
let names: []str = testenv.listdir(dir);
let n: i32 = 0;
let i: i32 = 0;
for (i < names.len) {
if (strings.hassuffix(names[i], ".s")) {
let body: str = strings.concat("\n",
testenv.readfile(strings.concat(dir, "/", names[i])));
n += testenv.occurrences(body, "\nTEXT main,");
};
i += 1;
};
return n;
};
fn depmainrow(label: str, entry: str, want: i32, deps: str,
mangled: str) void = {
let td: str = testenv.fresh();
let src: str = strings.concat(testenv.repo(), "/", entry);
let drvs: []str = ["ww", "ww_ww"];
let tags: []str = ["cs", "ww"];
let i: i32 = 0;
for (i < 2) {
let prog: str = strings.concat(td, "/", label, ".", tags[i],
".bin");
let av: []str = [testenv.driver(drvs[i]), "build", "-o", prog,
src];
if (runcode(td, strings.concat("build_", tags[i]), av) != 0) {
fail(label, strings.concat(drvs[i], " build failed (bare-main ",
"collision -> w6l duplicate symbol?)"));
};
let rav: []str = [prog];
if (runcode(td, strings.concat("run_", tags[i]), rav) != want) {
fail(label, strings.concat(drvs[i],
" run-exit != the dep main's return"));
};
i += 1;
};
let csdir: str = strings.concat(td, "/", label, ".cs.bin.sepwork");
let wwdir: str = strings.concat(td, "/", label, ".ww.bin.sepwork");
if (!testenv.has(testenv.readfile(strings.concat(csdir, "/", deps)),
mangled)) {
fail(label, strings.concat(deps, " lacks `", mangled,
"` (dep main not mangled)"));
};
if (countbaremain(csdir) != 1) {
fail(label, strings.concat("bare `TEXT main,` count != 1 ",
"(only the root entry stays bare)"));
};
cmpsepwork(label, csdir, wwdir);
testenv.clean(td);
};
@test fn depmain_aabb() void = {
depmainrow("aabb", "test/wcc/data/depmain99/main.ww", 7,
"aa.bb.s", "TEXT aa.bb.main,");
};
@test fn depmain_cc() void = {
depmainrow("cc", "test/wcc/data/depmain99/main_cc.ww", 9,
"cc.s", "TEXT cc.main,");
};
// ---- declns modfn coexist (#23/#30) ------------------------------------
@test fn declns() void = {
let td: str = testenv.fresh();
let lays: []str = ["modfn_coexist", "modfn_coexist_vbu"];
let labs: []str = ["coexist", "coexist_vbu"];
let drvs: []str = ["ww", "ww_ww"];
let tags: []str = ["cs", "ww"];
let l: i32 = 0;
for (l < 2) {
let s: i32 = 0;
for (s < 2) {
let prog: str = strings.concat(td, "/", labs[l], ".",
tags[s]);
let src: str = strings.concat(testenv.repo(),
"/test/wcc/data/", lays[l], "/main.ww");
let av: []str = [testenv.driver(drvs[s]), "build", "-o",
prog, src];
if (runcode(td, strings.concat("b_", labs[l], "_", tags[s]),
av) != 0) {
fail(labs[l], strings.concat(drvs[s], " build failed"));
};
let rav: []str = [prog];
if (runcode(td, strings.concat("r_", labs[l], "_", tags[s]),
rav) != 6) {
fail(labs[l], strings.concat(drvs[s],
" exit != 6 (coexistence mis-resolved)"));
};
s += 1;
};
l += 1;
};
// the root emits no .wwi post-#69, so its suffix set omits .wwi
let parts: []str = ["aa.s", "aa.wwi", "aa.unit.ww", "__root.s",
"__root.unit.ww"];
// cs==ww (rule 10) per layout over the fixed 5-part table
let l2: i32 = 0;
for (l2 < 2) {
let p: i32 = 0;
for (p < parts.len) {
if (!testenv.same(
testenv.readfile(strings.concat(td, "/", labs[l2],
".cs.sepwork/", parts[p])),
testenv.readfile(strings.concat(td, "/", labs[l2],
".ww.sepwork/", parts[p])))) {
fail(labs[l2], strings.concat("cs!=ww for ", parts[p],
" (rule 10)"));
};
p += 1;
};
l2 += 1;
};
// cross-LAYOUT byte-id, same stage: decl-order independence of the
// SK_USE/value promote. `.unit.ww` embeds the differently ORDERED
// source, so the codegen claim is tested on .s/.wwi only.
let oparts: []str = ["aa.s", "aa.wwi", "__root.s"];
let t: i32 = 0;
for (t < 2) {
let p: i32 = 0;
for (p < oparts.len) {
if (!testenv.same(
testenv.readfile(strings.concat(td, "/coexist.",
tags[t], ".sepwork/", oparts[p])),
testenv.readfile(strings.concat(td, "/coexist_vbu.",
tags[t], ".sepwork/", oparts[p])))) {
fail("declns", strings.concat("vbu!=non-vbu for ",
oparts[p], " (", tags[t],
") -- silent decl-order dependence"));
};
p += 1;
};
t += 1;
};
// non-vacuity: the two namespaces resolve to DISTINCT symbols
let t2: i32 = 0;
for (t2 < 2) {
let rs: str = testenv.readfile(strings.concat(td, "/coexist.",
tags[t2], ".sepwork/__root.s"));
if (!testenv.has(rs, "CALL\taa.helper(SB)")) {
fail("declns", strings.concat("__root.s (", tags[t2],
") lacks module-qualified `CALL aa.helper(SB)`"));
};
if (!testenv.has(rs, "CALL\tmain.aa(SB)")) {
fail("declns", strings.concat("__root.s (", tags[t2],
") lacks local-fn `CALL main.aa(SB)`"));
};
t2 += 1;
};
testenv.clean(td);
};
// ---- slttypepref (#58/#50 c1) ------------------------------------------
// fresh two-directory tree per (row,driver); relative build with the
// tree as cwd (canonical dir-package build, binary lands in cwd).
fn sltbuild(drv: str, param: str) i32 = {
let dir: str = testenv.fresh();
assert(os.mkdir(strings.concat(dir, "/xa"), 493) == 0);
assert(os.mkdir(strings.concat(dir, "/xb"), 493) == 0);
testenv.writefile(strings.concat(dir, "/xa/xa.ww"), strings.concat(
"package xa;\n",
"export type invalid = !i8;\n"));
testenv.writefile(strings.concat(dir, "/xb/xb.ww"), strings.concat(
"package xb;\n",
"import xa;\n",
"export type invalid = !i64;\n",
"export fn f(", param,
": i32) i64 = { return size(invalid): i64; };\n"));
testenv.writefile(strings.concat(dir, "/root.ww"), strings.concat(
"package main;\n",
"import xb;\n",
"import xa;\n",
"fn main() int = { return xb.f(0): int; };\n"));
let bav: []str = [testenv.driver(drv), "build", "root.ww"];
let brc: i32 = runcode(dir, "build", bav);
if (brc != 0) {
testenv.clean(dir);
return -1;
};
let rav: []str = [strings.concat(dir, "/root")];
let got: i32 = runcode(dir, "run", rav);
testenv.clean(dir);
return got;
};
fn sltrow(label: str, param: str, want: i32) void = {
let gc: i32 = sltbuild("ww", param);
if (gc != want) { fail(label, "cstage exit != want (size of the wrong module's type)"); };
let gw: i32 = sltbuild("ww_ww", param);
if (gw != gc) {
fail(label, strings.concat("cs != ww exit (scopelookuptype ",
"mod-preference divergence -- #58/#50 c1)"));
};
if (gw != want) { fail(label, "wwstage exit != want"); };
};
@test fn slt_shadow() void = { sltrow("shadow", "invalid", 8); };
@test fn slt_noshadow() void = { sltrow("noshadow", "x", 8); };

View File

@@ -1,257 +0,0 @@
/*
* 989_coloimport_sep — #98 E3 flip-blocker: a co-located `_test` entry must
* not shadow the dir-package it imports under `ww test`.
*
* The driver builds the sep searchpath srcd-first (srcd = the entry file's
* own directory). When the entry is a co-located black-box test
* `lib/<mod>/<mod>test.ww`, srcd = `lib/<mod>`, so a transitive `import
* <mod>` (reached from another package in the closure) hits the sibling
* FILE `lib/<mod>/<mod>.ww` (is_dir=0) FIRST — treated as an intra-package
* split, folded INLINE into the importer's unit under that importer's
* module-reset → w6c rejects `package <mod> does not match import path
* <importer>`. locate_import / locateimport now walk the searchpath in
* TWO passes — pass 1 seeks a DIRECTORY on any entry, pass 2 (only if no
* directory matches anywhere) seeks a FILE — so the dir-package `<mod>/`
* on a later entry beats the same-named sibling FILE on the srcd entry,
* while a genuine leaf package with no directory still resolves in the
* file pass. This gate stands for the 7 lib-run tests
* (904/966/967/972/974/976/977) the bug blocked under sep.
*
* Fixture (test/wcc/data/colo98/, the exact srcd-shadow shape, minimised):
* widget/widget.ww package widget — the sibling primary file
* that shadows the dir.
* widget/widgettest.ww package widget_test, import gadget — the
* co-located test ENTRY;
* srcd = .../widget.
* gadget/gadget.ww package gadget, import widget — the NON-root
* importer compiled with -I,
* whose self-named `import
* widget` triggers the fold.
* `-I test/wcc/data/colo98` puts widget's parent on the fallback path,
* mirroring how `lib` holds the real `lib/<mod>/` dirs.
*
* Asserts (both driver stages, direct builds):
* 1. RUN-EXIT 0 — the @test resolves, builds, links, runs. Pre-fix the
* non-root importer's fold makes w6c reject → non-zero; this is the
* non-vacuity teeth (verified: revert the two-pass walker → reddens
* with `package widget does not match import path gadget`).
* 2. REGISTERED — widget.unit.ww + widget.wwi appear in .sepwork: the
* import resolved to the DIR-package, not the sibling file. Pre-fix
* widget is never registered (folds into the importer), so both are
* absent — the "right module tag" signal.
* 3. cs==ww (rule 10) — per-package .s/.wwi byte-identical across the
* cstage `ww` and wwstage `ww_ww` sep-drivers, proving the `locatein`
* port is symmetric with `locate_import_in`.
*
* Every intermediate is `-o`-redirected to /tmp for isolation. Models
* 989_septest_run conventions; 989 prefix per the sep-gate precedent.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <dirent.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;
}
/* cs==ww over a sep build's per-package output: every .s/.wwi the cstage
* driver produced in `csdir` must be byte-identical to the wwstage
* driver's same-named file in `wwdir`. Returns the count of mismatches. */
static int
cmp_sepwork(const char *csdir, const char *wwdir, const char *label)
{
DIR *d = opendir(csdir);
if (!d) {
fprintf(stderr, "coloimport FAIL: %s — no cs sepwork %s\n", label, csdir);
return 1;
}
int bad = 0, seen = 0;
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
const char *nm = ent->d_name;
size_t nl = strlen(nm);
int is_s = (nl > 2 && strcmp(nm + nl - 2, ".s") == 0);
int is_wwi = (nl > 4 && strcmp(nm + nl - 4, ".wwi") == 0);
if (!is_s && !is_wwi) continue;
seen++;
char a[2048], b[2048];
snprintf(a, sizeof a, "%s/%s", csdir, nm);
snprintf(b, sizeof b, "%s/%s", wwdir, nm);
if (files_eq(a, b) != 0) {
fprintf(stderr, "coloimport FAIL: %s — cs!=ww for %s (rule 10)\n",
label, nm);
bad++;
}
}
closedir(d);
if (seen == 0) {
fprintf(stderr, "coloimport FAIL: %s — no .s/.wwi in %s\n", label, csdir);
bad++;
}
return bad;
}
struct tcase {
const char *label;
const char *entry; /* the co-located `_test` build target */
const char *incdir; /* -I dir (parent that holds the dir-package) */
const char *regpkg; /* the dir-package that must register its own unit */
};
static struct tcase cases[] = {
/* the transitive self-named-import shadow — the exact lib-run shape. */
{ "widget", "test/wcc/data/colo98/widget/widgettest.ww",
"test/wcc/data/colo98", "widget" },
{ NULL, NULL, NULL, NULL },
};
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
char td[64], cmd[8192];
int fail = 0;
snprintf(td, sizeof td, "/tmp/wwcoloimport_%d", getpid());
if (mkdir(td, 0755) != 0) {
perror(td);
return 1;
}
for (int i = 0; cases[i].label; i++) {
struct tcase *t = &cases[i];
struct { const char *drv, *tag; int rc; }
stg[] = { { "ww", "cs", -1 }, { "ww_ww", "ww", -1 } };
for (int s = 0; s < 2; s++) {
char prog[1024];
snprintf(prog, sizeof prog, "%s/%s.%s.bin", td, t->label, stg[s].tag);
/* Each direct build produces the per-package .s/.wwi artifacts
* this gate inspects. */
snprintf(cmd, sizeof cmd,
"%s/%s test -I %s -o %s %s "
">/dev/null 2>&1",
bin, stg[s].drv, t->incdir, prog,
t->entry);
stg[s].rc = runwait(cmd);
}
/* 1. RUN-EXIT 0 on BOTH stages (pre-fix: w6c rejects the importer). */
if (stg[0].rc != 0 || stg[1].rc != 0) {
fprintf(stderr, "coloimport FAIL: %s exits cs=%d ww=%d (expected 0)\n",
t->label, stg[0].rc, stg[1].rc);
fail++;
continue;
}
char csdir[1024], wwdir[1024];
snprintf(csdir, sizeof csdir, "%s/%s.cs.bin.sepwork", td, t->label);
snprintf(wwdir, sizeof wwdir, "%s/%s.ww.bin.sepwork", td, t->label);
/* 2. REGISTERED: the dir-package owns its unit + interface (pre-fix
* it folds into the importer and these are absent). */
char u[2048], w[2048];
snprintf(u, sizeof u, "%s/%s.unit.ww", csdir, t->regpkg);
snprintf(w, sizeof w, "%s/%s.wwi", csdir, t->regpkg);
if (access(u, 0) != 0) {
fprintf(stderr, "coloimport FAIL: %s — %s missing (import folded "
"to sibling file, not the dir-package)\n", t->label, u);
fail++;
}
if (access(w, 0) != 0) {
fprintf(stderr, "coloimport FAIL: %s — %s missing\n", t->label, w);
fail++;
}
/* 3. cs==ww (rule 10): per-package .s/.wwi byte-identical. */
fail += cmp_sepwork(csdir, wwdir, t->label);
}
for (int i = 0; cases[i].label; i++) {
const char *tags[] = { "cs", "ww" };
for (int s = 0; s < 2; s++) {
char prog[1024], work[1100];
snprintf(prog, sizeof prog, "%s/%s.%s.bin", td,
cases[i].label, tags[s]);
if (unlink(prog) != 0 && errno != ENOENT) {
perror(prog);
fail++;
}
snprintf(work, sizeof work, "%s.sepwork", prog);
snprintf(cmd, sizeof cmd, "rm -rf %s", work);
if (runwait(cmd) != 0) {
fprintf(stderr, "coloimport: cleanup failed: %s\n", work);
fail++;
}
}
}
if (rmdir(td) != 0) {
perror(td);
fail++;
}
if (fail) {
fprintf(stderr, "coloimport: %d check(s) failed\n", fail);
return 1;
}
printf("coloimport: co-located `_test` entry resolves `import <mod>` to the "
"dir-package (not the sibling file) under `ww test` on both driver "
"stages + cs==ww per-pkg .s/.wwi\n");
return 0;
}

View File

@@ -1,308 +0,0 @@
/*
* 989_declns_sep — #23/#30 declaration-namespace + module-fn coexistence
* under the directory-package model (task #83, M4 E2-C2c).
* The flat duplicate and builtin runtime rows now belong to r989_declns_*
* (with duplicate-type already owned by runww_dup_type_reject). This
* residual retains only the modfn separate-package artifact leg.
*
* Re-hosts every decl-namespace assertion that once lived ONLY in
* 910/997 onto a gate that survives the M4 flip (910/997 were deleted
* at the flip — they fed the retired 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` + 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).
*
* Every modfn intermediate is `-o`-redirected to /tmp for isolation; 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 <errno.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;
}
/*
* Duplicate-declaration and builtin-redeclaration runtime ownership moved to
* r989_declns_* fixtures. This wrapper retains only sep/package artifacts.
*/
/*
* modfn coexist — the two dir-package layouts. Build each via `ww build
* ` + 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 < 2; 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 -o %s %s 2>/dev/null",
bin, drv, prog, modfn_lays[l].fixture);
if (runwait(cmd) != 0) {
fprintf(stderr, "declns FAIL: %s build %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;
char td[64], cmd[256];
snprintf(td, sizeof td, "/tmp/wwdeclns_%d", getpid());
if (mkdir(td, 0755) != 0) {
perror(td);
return 1;
}
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);
const char *tags[] = { "cs", "ww" };
for (size_t l = 0; l < sizeof modfn_lays / sizeof modfn_lays[0]; l++) {
for (size_t s = 0; s < sizeof tags / sizeof tags[0]; s++) {
char prog[1024], work[1100];
snprintf(prog, sizeof prog, "%s/%s.%s", td,
modfn_lays[l].lay, tags[s]);
if (unlink(prog) != 0 && errno != ENOENT) {
perror(prog);
fail++;
}
snprintf(work, sizeof work, "%s.sepwork", prog);
snprintf(cmd, sizeof cmd, "rm -rf %s", work);
if (runwait(cmd) != 0) {
fprintf(stderr, "declns: cleanup failed: %s\n", work);
fail++;
}
}
}
if (rmdir(td) != 0) {
perror(td);
fail++;
}
if (fail) {
fprintf(stderr, "declns: %d check(s) failed\n", fail);
return 1;
}
printf("declns: modfn dir-pkg coexist→6 (cs==ww, "
"vbu byte-id to use-before-value), both stages (#23/#30)\n");
return 0;
}

View File

@@ -1,289 +0,0 @@
/*
* 989_depmain_sep — #99 E3 flip-blocker: an IMPORTED dir-package's non-
* exported `fn main` must mangle on its import path under `ww build`,
* not emit a bare `TEXT main` that collides with the root unit's entry.
*
* The bare-`main` carve-out (cgen mod_collect + cgfn label, both stages)
* keys on `leaf=="main" && imported==0`. Each package is its own w6c unit,
* composed with a path-carrying `//ww:module-reset <path>`
* (#57) that mangles decls but leaves imported==0 — so a dep unit's `fn
* main` was imported==0 too, the carve-out fired, and it emitted a bare
* `TEXT main` → `w6l: duplicate symbol main` against the root's real main.
*
* The fix adds a NON-ROOT gate `&& !sep_isdep` to the carve-out. sep_isdep
* is set from `wwiout != NULL`: the producer passes -I (the .wwi output) to
* DEP units ONLY — the root/link-entry unit's .wwi is stripped (#69) — so
* `wwiout==NULL` is the exact "this is the root/link-entry unit" oracle. A
* dep's main now mangles on its path; only the root's stays bare.
*
* Fixtures (test/wcc/data/depmain99/, srcd-relative dir-package resolution):
* main.ww package main, import aa.bb — root; calls bb.run() → 7.
* aa/bb/bb.ww package bb, `fn main`→7 + `export fn run` calls it — the
* DOTTED-path imported dep whose main must mangle aa.bb.main.
* main_cc.ww package main, import cc — root; calls cc.run() → 9.
* cc/cc.ww package cc, `fn main`→9 + `export fn run` — the SINGLE-
* component imported dep whose main must mangle cc.main.
*
* Asserts (both driver stages, direct builds):
* 1. SEP build+link+run exit == the dep's main's return — pre-fix the
* bare-main collision makes w6l reject. This is the non-vacuity teeth
* (verified: revert `&& !sep_isdep` → `w6l: duplicate symbol main`).
* 2. MANGLED — the dep's .s shows `TEXT <path>.main`; exactly ONE bare
* `TEXT main,` across the whole sep build (the root's __root.s).
* 3. cs==ww (rule 10) — per-package .s/.wwi byte-identical across the
* cstage `ww` and wwstage `ww_ww` sep-drivers.
*
* Every intermediate is `-o`-redirected to /tmp for isolation. Models 989_coloimport_sep
* conventions; 989 prefix per the sep-gate precedent.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <dirent.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -2;
}
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;
}
/* cs==ww over a sep build's per-package output: every .s/.wwi the cstage
* driver produced in `csdir` must be byte-identical to the wwstage driver's
* same-named file in `wwdir`. Returns the count of mismatches. */
static int
cmp_sepwork(const char *csdir, const char *wwdir, const char *label)
{
DIR *d = opendir(csdir);
if (!d) {
fprintf(stderr, "depmain FAIL: %s — no cs sepwork %s\n", label, csdir);
return 1;
}
int bad = 0, seen = 0;
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
const char *nm = ent->d_name;
size_t nl = strlen(nm);
int is_s = (nl > 2 && strcmp(nm + nl - 2, ".s") == 0);
int is_wwi = (nl > 4 && strcmp(nm + nl - 4, ".wwi") == 0);
if (!is_s && !is_wwi) continue;
seen++;
char a[2048], b[2048];
snprintf(a, sizeof a, "%s/%s", csdir, nm);
snprintf(b, sizeof b, "%s/%s", wwdir, nm);
if (files_eq(a, b) != 0) {
fprintf(stderr, "depmain FAIL: %s — cs!=ww for %s (rule 10)\n",
label, nm);
bad++;
}
}
closedir(d);
if (seen == 0) {
fprintf(stderr, "depmain FAIL: %s — no .s/.wwi in %s\n", label, csdir);
bad++;
}
return bad;
}
/* count_bare_main — number of bare `TEXT main,` labels across every .s in
* `dir`. Exactly 1 is correct (the root entry); the bug produces 2 (the
* dep's mis-bared main collides). */
static int
count_bare_main(const char *dir)
{
DIR *d = opendir(dir);
if (!d) return -1;
int n = 0;
struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
const char *nm = ent->d_name;
size_t nl = strlen(nm);
if (!(nl > 2 && strcmp(nm + nl - 2, ".s") == 0)) continue;
char p[2048];
snprintf(p, sizeof p, "%s/%s", dir, nm);
char *b = NULL;
size_t bn = 0;
if (slurp(p, &b, &bn) < 0) continue;
/* a bare label is `TEXT main,` at column 0 of any line. */
for (char *s = b; (s = strstr(s, "TEXT main,")) != NULL; s++)
if (s == b || s[-1] == '\n') n++;
free(b);
}
closedir(d);
return n;
}
/* file_has — 0 if `needle` occurs in `path`, 1 if absent, -1 on read err. */
static int
file_has(const char *path, const char *needle)
{
char *b = NULL;
size_t bn = 0;
if (slurp(path, &b, &bn) < 0) return -1;
int found = (strstr(b, needle) != NULL);
free(b);
return found ? 0 : 1;
}
struct tcase {
const char *label;
const char *entry; /* root build target */
int exit; /* the dep main's return, == the program exit */
const char *deps; /* the dep's .s basename in .sepwork */
const char *mangled; /* the mangled TEXT label the dep must emit */
};
static struct tcase cases[] = {
/* dotted-path import: dep main mangles on the full path. */
{ "aabb", "test/wcc/data/depmain99/main.ww", 7,
"aa.bb.s", "TEXT aa.bb.main," },
/* single-component import: dep main mangles on the leaf package. */
{ "cc", "test/wcc/data/depmain99/main_cc.ww", 9,
"cc.s", "TEXT cc.main," },
{ NULL, NULL, 0, NULL, NULL },
};
int
main(void)
{
const char *bin = absbin();
if (!bin) return 1;
char td[64], cmd[8192];
int fail = 0;
snprintf(td, sizeof td, "/tmp/wwdepmain_%d", getpid());
if (mkdir(td, 0755) != 0) {
perror(td);
return 1;
}
for (int i = 0; cases[i].label; i++) {
struct tcase *t = &cases[i];
struct { const char *drv, *tag; int rc; }
stg[] = { { "ww", "cs", -1 }, { "ww_ww", "ww", -1 } };
/* 1. SEP build+link+run exit == the dep main's return, both stages. */
for (int s = 0; s < 2; s++) {
char prog[1024];
snprintf(prog, sizeof prog, "%s/%s.%s.bin", td, t->label, stg[s].tag);
snprintf(cmd, sizeof cmd,
"%s/%s build -o %s %s "
">/dev/null 2>&1 && %s",
bin, stg[s].drv, prog, t->entry, prog);
stg[s].rc = runwait(cmd);
if (stg[s].rc != t->exit) {
fprintf(stderr, "depmain FAIL: %s sep %s exit=%d (expected %d)\n",
t->label, stg[s].tag, stg[s].rc, t->exit);
fail++;
}
}
char csdir[1024], wwdir[1024];
snprintf(csdir, sizeof csdir, "%s/%s.cs.bin.sepwork", td, t->label);
snprintf(wwdir, sizeof wwdir, "%s/%s.ww.bin.sepwork", td, t->label);
/* 2. MANGLED: the dep's .s emits `TEXT <path>.main`; exactly one bare
* `TEXT main,` across the whole sep build (the root's __root.s). */
char deps[2048];
snprintf(deps, sizeof deps, "%s/%s", csdir, t->deps);
if (file_has(deps, t->mangled) != 0) {
fprintf(stderr, "depmain FAIL: %s — %s lacks `%s` (dep main not "
"mangled)\n", t->label, deps, t->mangled);
fail++;
}
int nbare = count_bare_main(csdir);
if (nbare != 1) {
fprintf(stderr, "depmain FAIL: %s — %d bare `TEXT main,` in %s "
"(expected 1, the root entry)\n", t->label, nbare, csdir);
fail++;
}
/* 3. cs==ww (rule 10): per-package .s/.wwi byte-identical. */
fail += cmp_sepwork(csdir, wwdir, t->label);
}
for (int i = 0; cases[i].label; i++) {
const char *tags[] = { "cs", "ww" };
for (int s = 0; s < 2; s++) {
char prog[1024], work[1100];
snprintf(prog, sizeof prog, "%s/%s.%s.bin", td,
cases[i].label, tags[s]);
if (unlink(prog) != 0 && errno != ENOENT) {
perror(prog);
fail++;
}
snprintf(work, sizeof work, "%s.sepwork", prog);
snprintf(cmd, sizeof cmd, "rm -rf %s", work);
if (runwait(cmd) != 0) {
fprintf(stderr, "depmain: cleanup failed: %s\n", work);
fail++;
}
}
}
if (rmdir(td) != 0) {
perror(td);
fail++;
}
if (fail) {
fprintf(stderr, "depmain: %d check(s) failed\n", fail);
return 1;
}
printf("depmain: imported dir-package `fn main` mangles on its import path "
"under `ww build` (only the root entry stays bare) on both driver "
"stages + cs==ww per-pkg .s/.wwi\n");
return 0;
}

View File

@@ -1,221 +0,0 @@
/*
* 989_slttypepref_run (#58/#50, c1) — scopelookuptype must prefer the
* current module's SK_TYPE when a value binding shadows a type leaf that
* two modules both export.
*
* THE BUG (wwstage only, cat-A silent wrong-binary under rc=0): the
* aliassym fallback (check.ww ~908) composes scopelookupprefer-then-
* scopelookuptype; lib/ww/sym.ww scopelookuptype had NO mod parameter and
* returned the first (newest-installed) SK_TYPE in the bucket chain.
* scopedefineinmodule PREPENDS, so chain-first = last-registered. When a
* param/local shadows the type leaf (scopelookupprefer lands on the value,
* not the SK_TYPE) and two modules export the same type leaf, wwstage
* silently resolved whichever module installed last — install-order
* dependent — while cstage passes c->cur_mod to scope_lookup_type
* (sym.c:131) and resolves the current module's type deterministically.
* THE FIX: give scopelookuptype a `mod` param and prefer the curmod-
* matching SK_TYPE (Pass-1 mod-match, Pass-2 chain-first fallback);
* the sole caller passes c.curmod. Mirror of cstage scope_lookup_type.
*
* row | shape | exit (cs==ww)
* ---------+-----------------------------------------------+--------------
* shadow | xb.f param `invalid` shadows type `invalid`; | 8 (was ww 1)
* | xb !i64, xa !i8; size(invalid) in xb.f |
* noshadow | param renamed (no shadow), aliassym not hit | 8 (control)
*
* The shadow row was RED pre-c1 (ww resolved xa's !i8 -> exit 1, the wrong
* module's type under rc=0). The noshadow control pins the non-shadow path
* (scopelookupprefer lands on the SK_TYPE directly) so the fix can't
* perturb it.
*
* E3-C1 retarget: the single-file multi-package source the original used
* is the amalgamator shape, deleted with the flip (ww is now strictly
* dir=module). Re-expressed as a dir-package tree: xb/ and xa/ both export
* `type invalid`, and xb IMPORTS xa so both `invalid` leaves are in scope
* during xb's sep-compile — the exact two-module same-leaf collision the
* scopelookuptype mod-preference fix governs. A regression flips to xa's
* !i8 -> exit 1.
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.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;
}
/* Only xb's param name varies between rows; xa and the root are shared.
* `xb_param` is the parameter spelling: "invalid" shadows the type leaf,
* "x" does not. */
struct row { const char *label; const char *xb_param; int want_exit; };
static const struct row rows[] = {
{ "shadow", "invalid", 8 },
{ "noshadow", "x", 8 },
};
static int
write_file(const char *path, const char *content)
{
FILE *f = fopen(path, "wb");
if (!f) return -1;
if (fputs(content, f) == EOF) { fclose(f); return -1; }
return fclose(f);
}
static int
run_build(const char *driver, const struct row *r, int *cleanup_failed)
{
char dir[] = "/tmp/sltp_XXXXXX";
char sub[128], path[256], xbsrc[512], cmd[1024];
int brc = -1, got = -2, cleanup_fail = 0;
int have_xa = 0, have_xb = 0;
*cleanup_failed = 0;
if (mkdtemp(dir) == NULL) return -2;
snprintf(path, sizeof path, "%s/xa", dir);
if (mkdir(path, 0755) != 0) goto out;
have_xa = 1;
snprintf(path, sizeof path, "%s/xb", dir);
if (mkdir(path, 0755) != 0) goto out;
have_xb = 1;
snprintf(path, sizeof path, "%s/xa/xa.ww", dir);
if (write_file(path, "package xa;\nexport type invalid = !i8;\n"))
goto out;
snprintf(sub, sizeof sub, "%s/xb/xb.ww", dir);
snprintf(xbsrc, sizeof xbsrc,
"package xb;\n"
"import xa;\n"
"export type invalid = !i64;\n"
"export fn f(%s: i32) i64 = { return size(invalid): i64; };\n",
r->xb_param);
if (write_file(sub, xbsrc)) goto out;
snprintf(path, sizeof path, "%s/root.ww", dir);
if (write_file(path,
"package main;\n"
"import xb;\n"
"import xa;\n"
"fn main() int = { return xb.f(0): int; };\n"))
goto out;
/* canonical dir-package build: cd into the package root, build the
* root unit; the binary lands in cwd (#22 dir=module). */
snprintf(cmd, sizeof cmd, "cd %s && %s build root.ww 2>/dev/null",
dir, driver);
brc = runwait(cmd);
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/root", dir);
got = -1;
if (brc == 0) got = runwait(outbin);
out:
snprintf(cmd, sizeof cmd, "rm -rf -- '%s/root.sepwork'", dir);
if (runwait(cmd) != 0) cleanup_fail = 1;
snprintf(path, sizeof path, "%s/root", dir);
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
snprintf(path, sizeof path, "%s/root.ww", dir);
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
if (have_xb) {
snprintf(path, sizeof path, "%s/xb/xb.ww", dir);
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
snprintf(path, sizeof path, "%s/xb", dir);
if (rmdir(path) != 0 && errno != ENOENT) cleanup_fail = 1;
}
if (have_xa) {
snprintf(path, sizeof path, "%s/xa/xa.ww", dir);
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
snprintf(path, sizeof path, "%s/xa", dir);
if (rmdir(path) != 0 && errno != ENOENT) cleanup_fail = 1;
}
if (rmdir(dir) != 0) cleanup_fail = 1;
*cleanup_failed = cleanup_fail;
return brc == 0 ? got : -1;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[1024], wdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int have_ww = (access(wdrv, X_OK) == 0);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
total++;
int cleanup_failed = 0;
int gc = run_build(cdrv, &rows[i], &cleanup_failed);
if (cleanup_failed) {
fprintf(stderr,
"slttypepref[cstage][%s]: temporary cleanup failed\n",
rows[i].label);
fail++;
}
if (gc < 0) {
fprintf(stderr, "slttypepref[cstage][%s]: build/run failed "
"(got %d)\n", rows[i].label, gc);
fail++;
continue;
}
if (gc != rows[i].want_exit) {
fprintf(stderr, "slttypepref[cstage][%s]: exit=%d want=%d\n",
rows[i].label, gc, rows[i].want_exit);
fail++;
}
if (!have_ww) {
fprintf(stderr, "slttypepref: skip wwstage (no %s)\n", wdrv);
continue;
}
cleanup_failed = 0;
int gw = run_build(wdrv, &rows[i], &cleanup_failed);
if (cleanup_failed) {
fprintf(stderr,
"slttypepref[wwstage][%s]: temporary cleanup failed\n",
rows[i].label);
fail++;
}
if (gw != gc) {
fprintf(stderr, "slttypepref[%s]: cs=%d != ww=%d "
"(scopelookuptype mod-preference divergence — #58/#50 c1)\n",
rows[i].label, gc, gw);
fail++;
}
if (gw != rows[i].want_exit) {
fprintf(stderr, "slttypepref[wwstage][%s]: exit=%d want=%d\n",
rows[i].label, gw, rows[i].want_exit);
fail++;
}
}
if (fail) {
fprintf(stderr, "slttypepref_run: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("slttypepref_run: %d/%d ok\n", total, total);
return 0;
}