cmd: compile packages from direct exports

This commit is contained in:
2026-08-12 01:12:50 +09:00
parent 9b970eb16a
commit 0ac0fd68ec
4 changed files with 265 additions and 108 deletions

View File

@@ -331,14 +331,10 @@ enumerate_dir_ww(const char *dirpath, char ***out_files)
* definer's qualified symbol (#53) equals the consumer's qualified * definer's qualified symbol (#53) equals the consumer's qualified
* reference (#40) and the sep `.o`s link. A dep is NEVER bare-embedded. * reference (#40) and the sep `.o`s link. A dep is NEVER bare-embedded.
* *
* The prepend is the TRANSITIVE closure of a package's deps (lead- * Only DIRECT dependency artifacts are prepended. A `.wwi` relocates the
* ratified, superseding rob-c3-spec §1.3 "direct deps"): a dep's public * recursively reachable public foreign type/const facts required by its own
* interface can name a transitive dep's type (os exposes time.instant), * API, retaining their origin modules without turning them into source
* so the consuming unit needs the whole closure for name RESOLUTION — * imports. Full transitive reachability remains a linker concern.
* direct-deps-only does not type-check. This mirrors harec reading the
* transitive `.td` closure. The checker still scopes qualifier lookup to
* each source package's direct N_USE declarations, so carrying those type
* facts does not make a transitive package source-visible.
*/ */
#define SEP_MAXPKG 256 #define SEP_MAXPKG 256
@@ -749,15 +745,6 @@ sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no,
return 0; return 0;
} }
static void
sep_mark_deps(struct sepgraph *g, int pi, char *inset)
{
for (int k = 0; k < g->pkg[pi].ndeps; k++) {
int di = g->pkg[pi].deps[k];
if (!inset[di]) { inset[di] = 1; sep_mark_deps(g, di, inset); }
}
}
/* Emit one of pi's own source files into the sep-unit under the /* Emit one of pi's own source files into the sep-unit under the
* //ww:module-reset primary boundary (so -c emits its decls, imported * //ww:module-reset primary boundary (so -c emits its decls, imported
* ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead of the * ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead of the
@@ -839,24 +826,21 @@ sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
return 0; return 0;
} }
/* Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s /* Compose pi's sep-unit at `unitf`: its byte-sorted DIRECT dependency
* (reverse-topo order, each tagged by its dotted path), then pi's own * `.wwi`s, each tagged by its dotted path, then pi's own body under
* body under //ww:module-reset. Directory membership comes only from the * //ww:module-reset. Compiler exports are self-contained for public type
* package node loaded before planning. */ * facts; the linker separately retains the reachable archive closure. */
static int static int
sep_compose_unit(struct sepgraph *g, int pi, const char *scratch, sep_compose_unit(struct sepgraph *g, int pi, const char *scratch,
const int *order, int norder, const char *searchpath, const char *unitf) const char *searchpath, const char *unitf)
{ {
FILE *u = fopen(unitf, "wb"); FILE *u = fopen(unitf, "wb");
if (u == NULL) { if (u == NULL) {
fprintf(stderr, "ww: cannot open %s\n", unitf); fprintf(stderr, "ww: cannot open %s\n", unitf);
return -1; return -1;
} }
char inset[SEP_MAXPKG] = {0}; for (int k = 0; k < g->pkg[pi].ndeps; k++) {
sep_mark_deps(g, pi, inset); int dj = g->pkg[pi].deps[k];
for (int oi = 0; oi < norder; oi++) {
int dj = order[oi];
if (dj == pi || !inset[dj]) continue;
char wwi[1024]; char wwi[1024];
sep_fname(g, dj, scratch, ".wwi", wwi, sizeof wwi); sep_fname(g, dj, scratch, ".wwi", wwi, sizeof wwi);
FILE *wf = fopen(wwi, "rb"); FILE *wf = fopen(wwi, "rb");
@@ -1043,7 +1027,7 @@ copy_file_atomic(const char *src, const char *dst)
static void static void
workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm) workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm)
{ {
snprintf(buf, bufsz, "ww workdir fmt 1 mode %s asm %d\n", snprintf(buf, bufsz, "ww workdir fmt 2 mode %s asm %d\n",
is_test ? "test" : "build", emit_asm); is_test ? "test" : "build", emit_asm);
} }
@@ -1222,7 +1206,7 @@ build_one_sep_impl(const char *src, int entry_is_dir,
const char *ca = warm ? anew : apath; const char *ca = warm ? anew : apath;
int needs_export = pi != root || root_package; int needs_export = pi != root || root_package;
int needs_archive = pi != root || root_package; int needs_archive = pi != root || root_package;
if (sep_compose_unit(g, pi, scratch, order, norder, srcdir, if (sep_compose_unit(g, pi, scratch, srcdir,
cu) < 0) { free(order); return 1; } cu) < 0) { free(order); return 1; }
if (warm && !stale_all && file_equal(unitnew, unitf) if (warm && !stale_all && file_equal(unitnew, unitf)
&& file_is_reg(asmf) && file_is_reg(asmf)

View File

@@ -2802,15 +2802,28 @@ the final component of its import path; two logical identities for one physical
directory are rejected rather than compiled twice. directory are rejected rather than compiled twice.
Packages compile serially in dependency-first postorder. The compiler emits the Packages compile serially in dependency-first postorder. The compiler emits the
existing deterministic `.wwi` interface for every importable package and only existing deterministic `.wwi` interface for every importable package. Its
exported declarations enter that interface. A source qualifier is visible only primary section contains that package's byte-sorted direct imports and exported
when its owning package directly imports it; private members and transitive-only declarations. The compiler then appends byte-sorted, origin-tagged sections for
qualifiers are compiler errors. For compatibility with public signatures that only the exported foreign type and constant facts recursively reachable from
name deeper types, composed compiler units still carry transitive interface type the primary public signatures. This makes each direct dependency interface
facts for internal resolution, but those facts do not create source-visible self-contained for the public type information its consumers need while
bare names, package qualifiers, or value bindings. Replacing that retaining the deeper declarations' original package identity. Checked fixed
source-like closure with a self-contained typed export encoding remains part of array dimensions are emitted as numeric type facts, so a public layout never
the later export-format work, not package-loader semantics. requires exposing the private constant spelling that produced its length.
A package compilation unit contains one `.wwi` for each byte-sorted **direct**
import and no separately injected transitive interface. Origin-tagged facts
inside those direct artifacts are compiler data, not source imports: a source
qualifier is visible only when its owning package directly imports it, and
private members, transitive-only qualifiers, bare values, and bare types remain
compiler errors. In `-c` package mode the compiler coalesces repeated exported
type/constant facts with the same origin, kind, and name, preserving one nominal
type identity across diamonds; raw non-package `w6c` retains its existing
duplicate behavior. The source-like `.wwi` syntax remains a transitional export
encoding pending the binary `.wwe` format described above, but the direct-input
ownership boundary is now live in production Cstage and WWstage compilers and
drivers.
An ordinary root is linked with the full reachable object closure into the An ordinary root is linked with the full reachable object closure into the
requested executable (legacy WW programs may use a package name other than requested executable (legacy WW programs may use a package name other than
@@ -2821,7 +2834,10 @@ identity (`ww build -p -I ROOT -o bar.a foo.bar` emits `foo.bar.*` symbols),
while a literal directory uses its declared leaf package. Package output while a literal directory uses its declared leaf package. Package output
requires a directory and `-p` cannot be combined with assembly-only `-S`. Two requires a directory and `-p` cannot be combined with assembly-only `-S`. Two
cold builds with identical inputs are required to produce byte-identical cold builds with identical inputs are required to produce byte-identical
requested products. requested products. Compiler intrinsics keep their package-mode runtime ABI
independent of transitive source interfaces (for example, `alloc` lowers to the
runtime allocator without requiring an `rt.wwi` compiler input), while the
linker still receives every reachable package archive plus the runtime archive.
## 12. Candidate architectures and hard-gate decision ## 12. Candidate architectures and hard-gate decision

View File

@@ -624,16 +624,12 @@ type lflags = struct {
// producer (writes this package's `.wwi` via -I). Reverse-topo order // producer (writes this package's `.wwi` via -I). Reverse-topo order
// guarantees a package's deps' `.wwi` exist before it compiles. // guarantees a package's deps' `.wwi` exist before it compiles.
// //
// The load-bearing rule (#56, rob-resolved): every dep is tagged by its // Every direct dep is tagged by its FULL DOTTED import path on prepend
// FULL DOTTED import path on prepend (`//ww:module <path>`), so the // (`//ww:module <path>`), so the definer's qualified symbol equals the
// definer's qualified symbol (#53) equals the consumer's qualified // consumer's qualified reference and the sep `.o`s link. A dependency's
// reference (#40) and the sep `.o`s link. The prepend is the TRANSITIVE // compiler-owned `.wwi` carries its reachable public foreign type facts;
// closure of a package's deps (lead-ratified): a dep's interface can // separately prepending transitive interfaces is neither necessary nor
// name a transitive dep's type, so the consuming unit needs the whole // allowed. The unit composition is byte-identical to the cstage driver.
// closure for resolution. Qualifier lookup remains scoped to the direct
// imports owned by each source package, so those type facts do not expose
// a transitive package. The unit composition is byte-identical to the cstage
// driver (rule 10) so w6c/w6c_ww emit identical `.s`.
def SEP_MAXPKG: i32 = 256; def SEP_MAXPKG: i32 = 256;
@@ -670,7 +666,8 @@ fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
return i; return i;
}; };
if (os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) { if (os.samefile(pathstr(g.pkg[i].entry), pathstr(entry))) {
cerr("ww: one package directory has identities "); cerr("ww: package directory "); cerr(pathstr(entry));
cerr(" has identities ");
if (g.pkg[i].path[0u64] == 0u8) { cerr("(root)"); } if (g.pkg[i].path[0u64] == 0u8) { cerr("(root)"); }
else { cerr(pathstr(g.pkg[i].path)); }; else { cerr(pathstr(g.pkg[i].path)); };
cerr(" and "); cerr(" and ");
@@ -1047,18 +1044,6 @@ fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32,
return 0; return 0;
}; };
fn sepmarkdeps(g: *sepgraph, pi: i32, inset: []u8) void = {
let k: i32 = 0;
for (k < g.pkg[pi].ndeps) {
let di: i32 = g.pkg[pi].deps[k];
if (inset[di] == 0u8) {
inset[di] = 1u8;
sepmarkdeps(g, di, inset);
};
k += 1;
};
};
// Emit one of pi's own source files into the sep-unit under the // Emit one of pi's own source files into the sep-unit under the
// //ww:module-reset primary boundary (so -c emits its decls, imported // //ww:module-reset primary boundary (so -c emits its decls, imported
// ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead); // ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead);
@@ -1165,50 +1150,41 @@ fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8,
return 0; return 0;
}; };
// Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s // Compose pi's sep-unit at `unitf`: its byte-sorted DIRECT dependency
// (reverse-topo order, each tagged by its dotted path), then pi's own // `.wwi`s, each tagged by its dotted path, then pi's own body under
// body under //ww:module-reset. Directory membership comes only from the // //ww:module-reset. Compiler exports are self-contained for public type
// package node loaded before planning. // facts; the linker separately retains the reachable archive closure.
fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8, order: []i32, fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8,
norder: i32, searchpath: *u8, unitf: *u8) i32 = { searchpath: *u8, unitf: *u8) i32 = {
let u: i32 = os.open(pathstr(unitf), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644 let u: i32 = os.open(pathstr(unitf), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
if (u < 0) { if (u < 0) {
cerr("ww: cannot open unit\n"); cerr("ww: cannot open unit\n");
return -1; return -1;
}; };
let inset: []u8 = alloc([], g.n: u64)!; let k: i32 = 0;
inset.len = g.n; for (k < g.pkg[pi].ndeps) {
let z: i32 = 0; let dj: i32 = g.pkg[pi].deps[k];
for (z < g.n) { inset[z] = 0u8; z += 1; }; let wwi: *u8 = sepfname(g, dj, scratch, ".wwi");
sepmarkdeps(g, pi, inset); let wb: *u8;
let oi: i32 = 0; let wn: u64;
for (oi < norder) { wb, wn = slurp(wwi);
let dj: i32 = order[oi]; if (wb == nil) {
if (dj != pi) { cerr("ww: missing wwi\n");
if (inset[dj] != 0u8) { os.close(u);
let wwi: *u8 = sepfname(g, dj, scratch, ".wwi"); return -1;
let wb: *u8;
let wn: u64;
wb, wn = slurp(wwi);
if (wb == nil) {
cerr("ww: missing wwi\n");
os.close(u);
return -1;
};
let dm: str = "//ww:module ";
if (!sepwriteall(u, dm.ptr, dm.len: u64)
|| !sepwriteall(u, g.pkg[dj].path,
cstrlen(g.pkg[dj].path))
|| !sepwriteall(u, "\n".ptr, 1u64)
|| !sepwriteall(u, wb, wn)
|| !sepwriteall(u, "\n".ptr, 1u64)) {
cerr("ww: cannot compose package unit\n");
os.close(u);
return -1;
};
};
}; };
oi += 1; let dm: str = "//ww:module ";
if (!sepwriteall(u, dm.ptr, dm.len: u64)
|| !sepwriteall(u, g.pkg[dj].path,
cstrlen(g.pkg[dj].path))
|| !sepwriteall(u, "\n".ptr, 1u64)
|| !sepwriteall(u, wb, wn)
|| !sepwriteall(u, "\n".ptr, 1u64)) {
cerr("ww: cannot compose package unit\n");
os.close(u);
return -1;
};
k += 1;
}; };
let bv: expctx; let bv: expctx;
bv.out = u; bv.out = u;
@@ -1429,14 +1405,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = {
fn workdirstamptext(istest: i32, emitasm: i32) str = { fn workdirstamptext(istest: i32, emitasm: i32) str = {
if (istest != 0) { if (istest != 0) {
if (emitasm != 0) { if (emitasm != 0) {
return "ww workdir fmt 1 mode test asm 1\n"; return "ww workdir fmt 2 mode test asm 1\n";
}; };
return "ww workdir fmt 1 mode test asm 0\n"; return "ww workdir fmt 2 mode test asm 0\n";
}; };
if (emitasm != 0) { if (emitasm != 0) {
return "ww workdir fmt 1 mode build asm 1\n"; return "ww workdir fmt 2 mode build asm 1\n";
}; };
return "ww workdir fmt 1 mode build asm 0\n"; return "ww workdir fmt 2 mode build asm 0\n";
}; };
fn stampmatches(path: *u8, want: str) bool = { fn stampmatches(path: *u8, want: str) bool = {
@@ -1699,7 +1675,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
}; };
let needsexport: bool = (pi != root) || rootpackage; let needsexport: bool = (pi != root) || rootpackage;
let needsarchive: bool = (pi != root) || rootpackage; let needsarchive: bool = (pi != root) || rootpackage;
if (sepcomposeunit(g, pi, scratch, order, norder, searchpath.ptr, cu) < 0) { if (sepcomposeunit(g, pi, scratch, searchpath.ptr, cu) < 0) {
return 1; return 1;
}; };
let fresh: bool = false; let fresh: bool = false;

View File

@@ -41,6 +41,17 @@ fn build(dir: str, driver: str, tag: str, target: str, out: str) i32 = {
return code(dir, strings.concat("build_", tag), av); return code(dir, strings.concat("build_", tag), av);
}; };
fn diagnosticbody(s: str) str = {
let p: i32 = testenv.pos(s, ": error: ");
// Driver-owned graph diagnostics have no source-location prefix. Compare
// those in full; strip only compiler paths/locations from source errors.
if (p < 0) { return s; };
let begin: i32 = p + 9;
let end: i32 = begin;
for (end < s.len && s[end] != '\n') { end += 1; };
return strings.sub(s, begin, end);
};
fn rejectstable(dir: str, label: str, target: str, needle: str) void = { fn rejectstable(dir: str, label: str, target: str, needle: str) void = {
let drivers: []str = ["ww", "ww_ww", "ww", "ww_ww"]; let drivers: []str = ["ww", "ww_ww", "ww", "ww_ww"];
let tags: []str = ["c1", "w1", "c2", "w2"]; let tags: []str = ["c1", "w1", "c2", "w2"];
@@ -74,6 +85,12 @@ fn rejectstable(dir: str, label: str, target: str, needle: str) void = {
if (!testenv.same(errors[1], errors[3])) { if (!testenv.same(errors[1], errors[3])) {
fail(label, "WW diagnostic changed across repeated builds"); fail(label, "WW diagnostic changed across repeated builds");
}; };
let cb: str = diagnosticbody(errors[0]);
let wb: str = diagnosticbody(errors[1]);
if (!testenv.same(cb, wb)) {
fail(label, strings.concat("C/WW diagnostic bodies differ: '", cb,
"' vs '", wb, "'"));
};
}; };
@test fn standalone_and_package_artifact() void = { @test fn standalone_and_package_artifact() void = {
@@ -136,6 +153,162 @@ fn rejectstable(dir: str, label: str, target: str, needle: str) void = {
testenv.clean(td); testenv.clean(td);
}; };
@test fn self_contained_direct_exports() void = {
let td: str = testenv.fresh();
let dimensions: str = strings.concat(td, "/dimensions");
let implementation: str = strings.concat(td, "/implementation");
let api: str = strings.concat(td, "/api");
let main: str = strings.concat(td, "/main");
mkdir(dimensions); mkdir(implementation); mkdir(api); mkdir(main);
let dimensionssrc: str = strings.concat(
"package dimensions;\n",
"export def WIDTH: i32 = 4;\n",
"export def UNUSED_WIDTH: i32 = 8;\n");
let implsrc: str = strings.concat(
"package implementation;\n",
"import dimensions;\n",
"def PRIVATE_WIDTH: i32 = 3;\n",
"export type Buffer = [PRIVATE_WIDTH]u8;\n",
"export type PublicBuffer = [WIDTH]u8;\n",
"export type CastBuffer = [(WIDTH: u64)]u8;\n",
"export type Detail = struct { code: i64, };\n",
"export type Member = struct { detail: Detail, payload: i64, };\n",
"export type Unused = struct { noise: i32, };\n",
"export def UNUSED: i32 = 99;\n",
"type private_type = struct { secret: i32, };\n",
"def private_value: i32 = 77;\n",
"export fn construct(v: i32) Member = { return Member{detail=Detail{code=(v: i64) + 1i64}, payload=(v: i64) + 2i64}; };\n",
"export fn unrelated() i32 = { return UNUSED; };\n",
"fn hidden() i32 = { return private_value; };\n");
let apisrc: str = strings.concat(
"package api;\n",
"import implementation;\n",
"export type Member = struct { local: i32, };\n",
"export fn produce(v: i32) implementation.Member = { return implementation.construct(v); };\n",
"export fn score(m: implementation.Member) i32 = { return (m.detail.code + m.payload): i32; };\n",
"export fn width_of(b: Buffer) i32 = { return len(b): i32; };\n",
"export fn public_width_of(b: implementation.PublicBuffer) i32 = { return len(b): i32; };\n",
"export fn cast_width_of(b: implementation.CastBuffer) i32 = { return len(b): i32; };\n");
let mainsrc: str = strings.concat(
"package main;\n",
"import api;\n",
"fn main() i32 = { let m = api.produce(19); let p: *i64 = alloc(api.score(m): i64)!; return (*p): i32; };\n");
testenv.writefile(strings.concat(dimensions, "/dimensions.ww"), dimensionssrc);
testenv.writefile(strings.concat(implementation, "/implementation.ww"),
implsrc);
testenv.writefile(strings.concat(api, "/api.ww"), apisrc);
testenv.writefile(strings.concat(main, "/main.ww"), mainsrc);
let outputs: []str = [strings.concat(td, "/direct-c1"),
strings.concat(td, "/direct-c2"), strings.concat(td, "/direct-w")];
let drivers: []str = ["ww", "ww", "ww_ww"];
let tags: []str = ["direct_c1", "direct_c2", "direct_w"];
let i: i32 = 0;
for (i < outputs.len) {
if (build(td, drivers[i], tags[i], main, outputs[i]) != 0) {
fail("self-contained", strings.concat(drivers[i], " build failed"));
};
let runav: []str = [outputs[i]];
if (code(td, strings.concat("run_", tags[i]), runav) != 41) {
fail("self-contained", "implementation archive was not linked");
};
i += 1;
};
if (!testenv.same(testenv.readfile(outputs[0]), testenv.readfile(outputs[1]))
|| !testenv.same(testenv.readfile(outputs[0]), testenv.readfile(outputs[2]))) {
fail("self-contained", "clean C/C/WW executable outputs differ");
};
let cwork: str = strings.concat(outputs[0], ".sepwork/");
let cwork2: str = strings.concat(outputs[1], ".sepwork/");
let wwork: str = strings.concat(outputs[2], ".sepwork/");
let apiiface: str = testenv.readfile(strings.concat(cwork, "api.wwi"));
if (!testenv.same(apiiface, testenv.readfile(strings.concat(cwork2,
"api.wwi"))) || !testenv.same(apiiface, testenv.readfile(strings.concat(
wwork, "api.wwi")))) {
fail("self-contained", "C/C/WW api exports differ");
};
if (!testenv.has(apiiface, "//ww:module implementation\n")
|| !testenv.has(apiiface, "export type Buffer = [3]u8;")
|| !testenv.has(apiiface, "export type PublicBuffer = [4]u8;")
|| !testenv.has(apiiface, "export type CastBuffer = [4]u8;")
|| !testenv.has(apiiface, "export type Detail = struct")
|| testenv.occurrences(apiiface, "export type Member = struct") != 2) {
fail("self-contained", "api export lacks the recursive public type facts");
};
if (testenv.has(apiiface, "//ww:module dimensions\n")
|| testenv.has(apiiface, "WIDTH")
|| testenv.has(apiiface, "export def UNUSED_WIDTH")
|| testenv.has(apiiface, "PRIVATE_WIDTH")
|| testenv.has(apiiface, "export type Unused")
|| testenv.has(apiiface, "export def UNUSED")
|| testenv.has(apiiface, "export fn construct")
|| testenv.has(apiiface, "unrelated")
|| testenv.has(apiiface, "private_type")
|| testenv.has(apiiface, "private_value")
|| testenv.has(apiiface, "hidden")) {
fail("self-contained", "api export leaked unrelated or private declarations");
};
let expectedunit: str = strings.concat("//ww:module api\n", apiiface,
"\n//ww:module-reset\n", mainsrc, "\n");
if (!testenv.same(expectedunit, testenv.readfile(strings.concat(cwork,
"__root.unit.ww"))) || !testenv.same(expectedunit,
testenv.readfile(strings.concat(wwork, "__root.unit.ww")))) {
fail("self-contained", "consumer compiler input was not direct-api-only");
};
if (!testenv.exists(strings.concat(cwork, "implementation.a"))
|| !testenv.exists(strings.concat(cwork, "dimensions.a"))) {
fail("self-contained", "reachable archives missing from link closure");
};
let packages: []str = [strings.concat(td, "/api-c1.a"),
strings.concat(td, "/api-c2.a"), strings.concat(td, "/api-w.a")];
i = 0;
for (i < packages.len) {
let pav: []str = [testenv.driver(drivers[i]), "build", "-p", "-I", td,
"-o", packages[i], api];
if (code(td, strings.concat("package_", tags[i]), pav) != 0) {
fail("self-contained", "ww build -p api failed");
};
i += 1;
};
if (!testenv.same(testenv.readfile(packages[0]), testenv.readfile(packages[1]))
|| !testenv.same(testenv.readfile(packages[0]), testenv.readfile(packages[2]))
|| !testenv.same(apiiface, testenv.readfile(strings.concat(packages[0],
".wwi"))) || !testenv.same(apiiface, testenv.readfile(strings.concat(
packages[1], ".wwi"))) || !testenv.same(apiiface,
testenv.readfile(strings.concat(packages[2], ".wwi")))) {
fail("self-contained", "deterministic package artifact/export mismatch");
};
testenv.writefile(strings.concat(td, "/deep-qualifier.ww"), strings.concat(
"package main;\nimport api;\n",
"fn main() i32 = { let m: implementation.Member; return 0; };\n"));
rejectstable(td, "deep-qualifier", strings.concat(td,
"/deep-qualifier.ww"), "unknown type 'implementation.Member'");
testenv.writefile(strings.concat(td, "/deep-value.ww"), strings.concat(
"package main;\nimport api;\n",
"fn main() i32 = { return construct(1); };\n"));
rejectstable(td, "deep-value", strings.concat(td, "/deep-value.ww"),
"undefined: construct");
testenv.writefile(strings.concat(td, "/deep-const.ww"), strings.concat(
"package main;\nimport api;\n",
"fn main() i32 = { return WIDTH; };\n"));
rejectstable(td, "deep-const", strings.concat(td, "/deep-const.ww"),
"undefined: WIDTH");
testenv.writefile(strings.concat(td, "/deep-type.ww"), strings.concat(
"package main;\nimport api;\n",
"fn main() i32 = { let d: Detail; return 0; };\n"));
rejectstable(td, "deep-type", strings.concat(td, "/deep-type.ww"),
"unknown type 'Detail'");
testenv.writefile(strings.concat(td, "/private-direct.ww"), strings.concat(
"package main;\nimport implementation;\n",
"fn main() i32 = { return implementation.hidden(); };\n"));
rejectstable(td, "private-direct", strings.concat(td,
"/private-direct.ww"), "has no exported declaration 'hidden'");
testenv.clean(td);
};
fn writediamond(td: str, reverse: bool) str = { fn writediamond(td: str, reverse: bool) str = {
let shared: str = strings.concat(td, "/shared"); let shared: str = strings.concat(td, "/shared");
let left: str = strings.concat(td, "/left"); let left: str = strings.concat(td, "/left");
@@ -159,13 +332,13 @@ fn writediamond(td: str, reverse: bool) str = {
}; };
testenv.writefile(strings.concat(left, "/left.ww"), strings.concat( testenv.writefile(strings.concat(left, "/left.ww"), strings.concat(
"package left;\nimport shared;\nimport shared;\n", "package left;\nimport shared;\nimport shared;\n",
"export fn value() i32 = { return shared.base() + 1; };\n")); "export fn make() shared.token = { return shared.token{value=shared.base() + 1}; };\n"));
testenv.writefile(strings.concat(right, "/right.ww"), strings.concat( testenv.writefile(strings.concat(right, "/right.ww"), strings.concat(
"package right;\nimport shared;\n", "package right;\nimport shared;\n",
"export fn value() i32 = { return shared.base() + 2; };\n")); "export fn value(t: shared.token) i32 = { return t.value + shared.base() + 2; };\n"));
testenv.writefile(strings.concat(main, "/main.ww"), strings.concat( testenv.writefile(strings.concat(main, "/main.ww"), strings.concat(
"package main;\nimport right;\nimport left;\n", "package main;\nimport right;\nimport left;\n",
"fn main() i32 = { return left.value() + right.value(); };\n")); "fn main() i32 = { return right.value(left.make()); };\n"));
return main; return main;
}; };
@@ -209,9 +382,17 @@ fn writediamond(td: str, reverse: bool) str = {
}; };
let leftiface: str = testenv.readfile(strings.concat(scratch, let leftiface: str = testenv.readfile(strings.concat(scratch,
"left.wwi")); "left.wwi"));
let rightiface: str = testenv.readfile(strings.concat(scratch,
"right.wwi"));
if (testenv.occurrences(leftiface, "import shared;") != 1) { if (testenv.occurrences(leftiface, "import shared;") != 1) {
fail("diamond", "duplicate import escaped into export data"); fail("diamond", "duplicate import escaped into export data");
}; };
if (testenv.occurrences(leftiface, "export type token") != 1
|| testenv.occurrences(rightiface, "export type token") != 1
|| testenv.occurrences(unit, "export type token") != 2
|| testenv.occurrences(unit, "//ww:module shared\n") != 2) {
fail("diamond", "origin fact closure did not merge deterministically");
};
i += 1; i += 1;
}; };
let shuffledout: str = strings.concat(td, "/diamond-c-shuffled"); let shuffledout: str = strings.concat(td, "/diamond-c-shuffled");