diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index 70cc9ed4..5c08d6a1 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -3245,13 +3245,17 @@ check_file(Checker *c, Node *file) * already emits the qualified call. wwstage twin in check.ww. */ if (c->is_test) { int present = 0; + /* The synthetic runner belongs to file->sourceid. An import in an + * earlier module-reset section neither supplies nor uses its binding. */ for (Node *u = file->list; u; u = u->next) { if (c->test_target != NULL && u->kind == N_USE - && !u->imported && u->usepath + && !u->imported && u->sourceid == file->sourceid + && u->usepath && (strcmp(u->usepath, c->test_target) == 0 || strcmp(u->usepath, c->test_module) == 0)) u->used = 1; if (u->kind == N_USE && !u->imported + && u->sourceid == file->sourceid && u->usepath && strcmp(u->usepath, c->test_module) == 0) { present = 1; } diff --git a/docs/build-system.md b/docs/build-system.md index afa78ffa..9f3a9f95 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -4581,7 +4581,10 @@ tested target through `--test-target-package ` so a command variant declared `main` does not collide with the dispatcher's own synthesized `main`; this is compiler-generated wiring, not source alias syntax. Zero-test dispatchers mark -their compiler-owned target/support metadata imports consumed. +their compiler-owned target/support metadata imports consumed. That private +binding is installed or marked consumed only in the generated dispatcher's +source section; an import from an earlier test-file section neither supplies +nor satisfies it. Vendor expansion changes only canonical identity and physical selection. A source spelling such as `lib.codec` can resolve to diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index c9aebcb8..c94c2529 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -7655,15 +7655,18 @@ fn checkfile(c: *checker, file: *syntax.node) void = { // cstage cmd/wcc/check.c. if (c.istest != 0) { let present: bool = false; + // The synthetic runner belongs to file.sourceid. An import in an + // earlier module-reset section neither supplies nor uses its binding. let su: *syntax.node = file.list; for (su != nil) { if (c.testtarget.len > 0 && su.kind == syntax.nkind.N_USE - && su.imported == 0 + && su.imported == 0 && su.sourceid == file.sourceid && (syntax.streq(su.usepath, c.testtarget) || syntax.streq(su.usepath, c.testmodule))) { su.used = 1i32; }; if (su.kind == syntax.nkind.N_USE && su.imported == 0 + && su.sourceid == file.sourceid && syntax.streq(su.usepath, c.testmodule)) { present = true; }; diff --git a/test/byteid/freenoop_test.ww b/test/byteid/freenoop_test.ww index f9faa5bc..e3b4ecb6 100644 --- a/test/byteid/freenoop_test.ww +++ b/test/byteid/freenoop_test.ww @@ -134,7 +134,6 @@ fn nooprow(label: str, src: str) void = { " return 0;\n", "};\n")); nooprow("free_alloc_roundtrip", strings.concat( - "import rt;\n", "export fn main() i32 = {\n", " let p: *i64 = alloc(11i64)!;\n", " free(p);\n", diff --git a/test/byteid/libbyteid_test.ww b/test/byteid/libbyteid_test.ww index 6ca92636..6cc4e03f 100644 --- a/test/byteid/libbyteid_test.ww +++ b/test/byteid/libbyteid_test.ww @@ -52,6 +52,7 @@ type ent = struct { cite: str, // task #59 entry for non-ID modes sentinel: str, // probe-only: proof the module body landed moddir: str, // probe-only: completeness key + direct: bool, // build moddir itself; used when it exports no source name }; // Row constructors; the roster appends their results directly (#34 @@ -59,7 +60,7 @@ type ent = struct { fn fx(f: str) ent = { let e: ent; e.fixture = f; e.probe = ""; e.inc = ""; e.mode = MID; - e.cite = ""; e.sentinel = ""; e.moddir = ""; + e.cite = ""; e.sentinel = ""; e.moddir = ""; e.direct = false; return e; }; @@ -82,10 +83,10 @@ fn pri(p: str, inc: str, sentinel: str, moddir: str) ent = { return e; }; -// Dir-mode entry: byte-id the module's COMPOSED test package (every -// *_test.ww under moddir, module-reset separators, byte-lex order — -// the coordinator's pkgcombined shape) through both driver stages. -// Needed once a package's test files share a helpers file and stop +// Dir-mode entry: byte-id the module's composed test package (every +// *_test.ww under moddir, module-reset separators, byte-lex order) through +// both driver stages. The separators preserve each source file's import +// scope. Needed once a package's test files share a helpers file and stop // being standalone single-file roots (the B3 strings split). fn dx(moddir: str) ent = { let e: ent = fx(""); @@ -93,7 +94,17 @@ fn dx(moddir: str) ent = { return e; }; -// The 60-unit roster. Graduation history lives in git (the retired C +// Direct-directory entry for packages with no exported source name. Such a +// package cannot have a valid nonblank import probe until blank imports exist. +fn dr(moddir: str, sentinel: str) ent = { + let e: ent = fx(""); + e.moddir = moddir; + e.sentinel = sentinel; + e.direct = true; + return e; +}; + +// The 56-unit roster. Graduation history lives in git (the retired C // carrier's table comments); cites are kept only where a non-ID pin // would need them. fn corpus() []ent = { @@ -120,7 +131,7 @@ fn corpus() []ent = { append(es, fx("lib/math/random/random_test.ww")); append(es, fx("lib/memio/memio_test.ww")); append(es, fx("lib/os/os_test.ww")); - append(es, pr("package main;\nimport os.exec;\nfn main() i32 = { return 0; };\n", + append(es, pr("package main;\nimport os.exec;\nfn main() i32 = { return exec.termination.EXIT as i32; };\n", "package exec;", "lib/os/exec")); append(es, fx("lib/regex/regex_test.ww")); append(es, fx("lib/strconv/ftos_test.ww")); @@ -136,26 +147,25 @@ fn corpus() []ent = { append(es, fx("lib/bufio/stream_test.ww")); append(es, fx("lib/bufio/scanner_test.ww")); append(es, fx("lib/fmt/fmt_test.ww")); - append(es, pr("package main;\nimport sort;\nfn main() i32 = { return 0; };\n", + append(es, pr("package main;\nimport sort;\nfn use(v: sort.cmpfunc) void = {};\nfn main() i32 = { return 0; };\n", "package sort;", "lib/sort")); - append(es, pr("package main;\nimport path;\nfn main() i32 = { return 0; };\n", + append(es, pr("package main;\nimport path;\nfn use(v: path.buffer) void = {};\nfn main() i32 = { return 0; };\n", "package path;", "lib/path")); - append(es, pr("package main;\nimport endian;\nfn main() i32 = { return 0; };\n", + append(es, pr("package main;\nimport endian;\nfn main() i32 = { let b: [2]u8; return endian.begetu16(b[0:2]): i32; };\n", "package endian;", "lib/endian")); - append(es, pr("package main;\nimport net;\nfn main() i32 = { return 0; };\n", + append(es, pr("package main;\nimport net;\nfn use(v: net.sockaddrin) void = {};\nfn main() i32 = { return 0; };\n", "package net;", "lib/net")); - append(es, pr("package main;\nimport hash;\nfn main() i32 = { return 0; };\n", + append(es, pr("package main;\nimport hash;\nfn use(v: hash.hash) void = {};\nfn main() i32 = { return 0; };\n", "package hash;", "lib/hash")); // fnv lives below the driver's default root, so its canonical // directory is found from the explicit parent import root. - append(es, pri("package main;\nimport fnv;\nfn main() i32 = { return 0; };\n", + append(es, pri("package main;\nimport fnv;\nfn main() i32 = { let b: []u8; let _ = fnv.fnv1a(b); return 0; };\n", "lib/hash", "package fnv;", "lib/hash/fnv")); // Sentinel is a fn, not `package math;` — lib/math is also package // math, so the package clause alone would not prove the right body. - append(es, pr("package main;\nimport crypto.math;\nfn main() i32 = { return 0; };\n", + append(es, pr("package main;\nimport crypto.math;\nfn main() i32 = { return math.rotl32(0u32, 0): i32; };\n", "fn rotl32", "lib/crypto/math")); - append(es, pr("package main;\nimport c.libc;\nfn main() i32 = { return 0; };\n", - "package libc;", "lib/c/libc")); + append(es, dr("lib/c/libc", "package libc;")); // root-only, ZERO-dep build -S: the one driver-leg edge every // import probe misses (dep-count-0 unit composition). Folded in // from the retired 815/940/951 driver-parity carriers. No @@ -245,6 +255,28 @@ fn catasm(sepdir: str) str = { return out; }; +fn hasunit(sepdir: str) bool = { + if (!testenv.isdir(sepdir)) { return false; }; + let names: []str = testenv.listdir(sepdir); + let i: i32 = 0; + for (i < names.len) { + if (strings.hassuffix(names[i], ".unit.ww")) { return true; }; + i += 1; + }; + return false; +}; + +fn hasasm(sepdir: str) bool = { + if (!testenv.isdir(sepdir)) { return false; }; + let names: []str = testenv.listdir(sepdir); + let i: i32 = 0; + for (i < names.len) { + if (strings.hassuffix(names[i], ".s")) { return true; }; + i += 1; + }; + return false; +}; + fn buildstage(td: str, name: str, drv: str, sub: str, incs: []str, stem: str, base: str) bool = { let av: []str = []; @@ -280,10 +312,10 @@ fn checkone(e: *ent) void = { e.fixture))); } else { if (e.probe.len != 0) { testenv.writefile(strings.concat(td, "/", base), e.probe); + } else { if (e.direct) { + base = strings.concat(testenv.repo(), "/", e.moddir); } else { - // dir-mode: compose every *_test.ww under moddir exactly - // as pkgcombined does (external group: test sources only, - // module-reset separators; testenv.listdir sorts). + // Dir-mode keeps every source in its own module-reset section. base = "combined.ww"; let srcdir: str = strings.concat(testenv.repo(), "/", e.moddir); @@ -301,7 +333,7 @@ fn checkone(e: *ent) void = { ni += 1; }; testenv.writefile(strings.concat(td, "/", base), body); - }; }; + }; }; }; // Most packages resolve from the driver's canonical lib/ root. // Nested packages whose tests use a bare leaf import carry an @@ -317,8 +349,9 @@ fn checkone(e: *ent) void = { // lib/test); import probes carry their own fn main(), which -T // loud-rejects (910), so they `build -S`. let sub: str = "test"; - if (e.probe.len != 0) { sub = "build"; }; - let stem: str = stripext(base); + if (e.probe.len != 0 || e.direct) { sub = "build"; }; + let stem: str = "direct"; + if (!e.direct) { stem = stripext(base); }; let stemc: str = strings.concat(td, "/c_", stem); let stemw: str = strings.concat(td, "/w_", stem); @@ -327,10 +360,14 @@ fn checkone(e: *ent) void = { let we: bool = buildstage(td, "wwstage", testenv.driver("ww_ww"), sub, incs, stemw, base); - // the cstage build resolves the units; its __root.unit.ww proves - // resolution ran. WWREJECT still requires cstage to compile. - if (!ce || !testenv.exists(strings.concat(stemc, - ".sepwork/__root.unit.ww"))) { + // The cstage build resolves the units. A source probe owns __root; a + // directly selected package retains its canonical import identity. + // WWREJECT still requires cstage to compile. + let resolvedunit: str = strings.concat(stemc, + ".sepwork/__root.unit.ww"); + let resolved: bool = testenv.exists(resolvedunit); + if (e.direct) { resolved = hasunit(strings.concat(stemc, ".sepwork")); }; + if (!ce || !resolved) { fail(label, "cstage produced no resolved unit"); }; @@ -367,10 +404,15 @@ fn checkone(e: *ent) void = { }; if (!we) { fail(label, "wwstage rejected"); }; - let cs: str = catasm(strings.concat(stemc, ".sepwork")); - let ws: str = catasm(strings.concat(stemw, ".sepwork")); - // two empty concats compare equal — that green covers nothing - if (cs.len == 0 || ws.len == 0) { + let csep: str = strings.concat(stemc, ".sepwork"); + let wsep: str = strings.concat(stemw, ".sepwork"); + let cs: str = catasm(csep); + let ws: str = catasm(wsep); + // Two missing assembly sets compare equal and cover nothing. A directly + // selected declaration-only package legitimately emits an empty .s file; + // its resolved owner unit and sentinel above prove the source was compiled. + if (!hasasm(csep) || !hasasm(wsep) + || (!e.direct && (cs.len == 0 || ws.len == 0))) { fail(label, "empty .s concat"); }; let idsame: bool = testenv.same(cs, ws);