cmd: compile packages from direct exports
This commit is contained in:
@@ -331,14 +331,10 @@ enumerate_dir_ww(const char *dirpath, char ***out_files)
|
||||
* definer's qualified symbol (#53) equals the consumer's qualified
|
||||
* 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-
|
||||
* ratified, superseding rob-c3-spec §1.3 "direct deps"): a dep's public
|
||||
* interface can name a transitive dep's type (os exposes time.instant),
|
||||
* so the consuming unit needs the whole closure for name RESOLUTION —
|
||||
* 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.
|
||||
* Only DIRECT dependency artifacts are prepended. A `.wwi` relocates the
|
||||
* recursively reachable public foreign type/const facts required by its own
|
||||
* API, retaining their origin modules without turning them into source
|
||||
* imports. Full transitive reachability remains a linker concern.
|
||||
*/
|
||||
#define SEP_MAXPKG 256
|
||||
|
||||
@@ -749,15 +745,6 @@ sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no,
|
||||
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
|
||||
* //ww:module-reset primary boundary (so -c emits its decls, imported
|
||||
* ==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;
|
||||
}
|
||||
|
||||
/* Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s
|
||||
* (reverse-topo order, each tagged by its dotted path), then pi's own
|
||||
* body under //ww:module-reset. Directory membership comes only from the
|
||||
* package node loaded before planning. */
|
||||
/* Compose pi's sep-unit at `unitf`: its byte-sorted DIRECT dependency
|
||||
* `.wwi`s, each tagged by its dotted path, then pi's own body under
|
||||
* //ww:module-reset. Compiler exports are self-contained for public type
|
||||
* facts; the linker separately retains the reachable archive closure. */
|
||||
static int
|
||||
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");
|
||||
if (u == NULL) {
|
||||
fprintf(stderr, "ww: cannot open %s\n", unitf);
|
||||
return -1;
|
||||
}
|
||||
char inset[SEP_MAXPKG] = {0};
|
||||
sep_mark_deps(g, pi, inset);
|
||||
for (int oi = 0; oi < norder; oi++) {
|
||||
int dj = order[oi];
|
||||
if (dj == pi || !inset[dj]) continue;
|
||||
for (int k = 0; k < g->pkg[pi].ndeps; k++) {
|
||||
int dj = g->pkg[pi].deps[k];
|
||||
char wwi[1024];
|
||||
sep_fname(g, dj, scratch, ".wwi", wwi, sizeof wwi);
|
||||
FILE *wf = fopen(wwi, "rb");
|
||||
@@ -1043,7 +1027,7 @@ copy_file_atomic(const char *src, const char *dst)
|
||||
static void
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1222,7 +1206,7 @@ build_one_sep_impl(const char *src, int entry_is_dir,
|
||||
const char *ca = warm ? anew : apath;
|
||||
int needs_export = 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; }
|
||||
if (warm && !stale_all && file_equal(unitnew, unitf)
|
||||
&& file_is_reg(asmf)
|
||||
|
||||
@@ -2802,15 +2802,28 @@ the final component of its import path; two logical identities for one physical
|
||||
directory are rejected rather than compiled twice.
|
||||
|
||||
Packages compile serially in dependency-first postorder. The compiler emits the
|
||||
existing deterministic `.wwi` interface for every importable package and only
|
||||
exported declarations enter that interface. A source qualifier is visible only
|
||||
when its owning package directly imports it; private members and transitive-only
|
||||
qualifiers are compiler errors. For compatibility with public signatures that
|
||||
name deeper types, composed compiler units still carry transitive interface type
|
||||
facts for internal resolution, but those facts do not create source-visible
|
||||
bare names, package qualifiers, or value bindings. Replacing that
|
||||
source-like closure with a self-contained typed export encoding remains part of
|
||||
the later export-format work, not package-loader semantics.
|
||||
existing deterministic `.wwi` interface for every importable package. Its
|
||||
primary section contains that package's byte-sorted direct imports and exported
|
||||
declarations. The compiler then appends byte-sorted, origin-tagged sections for
|
||||
only the exported foreign type and constant facts recursively reachable from
|
||||
the primary public signatures. This makes each direct dependency interface
|
||||
self-contained for the public type information its consumers need while
|
||||
retaining the deeper declarations' original package identity. Checked fixed
|
||||
array dimensions are emitted as numeric type facts, so a public layout never
|
||||
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
|
||||
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
|
||||
requires a directory and `-p` cannot be combined with assembly-only `-S`. Two
|
||||
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
|
||||
|
||||
|
||||
@@ -624,16 +624,12 @@ type lflags = struct {
|
||||
// producer (writes this package's `.wwi` via -I). Reverse-topo order
|
||||
// guarantees a package's deps' `.wwi` exist before it compiles.
|
||||
//
|
||||
// The load-bearing rule (#56, rob-resolved): every dep is tagged by its
|
||||
// FULL DOTTED import path on prepend (`//ww:module <path>`), so the
|
||||
// definer's qualified symbol (#53) equals the consumer's qualified
|
||||
// reference (#40) and the sep `.o`s link. The prepend is the TRANSITIVE
|
||||
// closure of a package's deps (lead-ratified): a dep's interface can
|
||||
// name a transitive dep's type, so the consuming unit needs the whole
|
||||
// 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`.
|
||||
// Every direct dep is tagged by its FULL DOTTED import path on prepend
|
||||
// (`//ww:module <path>`), so the definer's qualified symbol equals the
|
||||
// consumer's qualified reference and the sep `.o`s link. A dependency's
|
||||
// compiler-owned `.wwi` carries its reachable public foreign type facts;
|
||||
// separately prepending transitive interfaces is neither necessary nor
|
||||
// allowed. The unit composition is byte-identical to the cstage driver.
|
||||
|
||||
def SEP_MAXPKG: i32 = 256;
|
||||
|
||||
@@ -670,7 +666,8 @@ fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
||||
return i;
|
||||
};
|
||||
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)"); }
|
||||
else { cerr(pathstr(g.pkg[i].path)); };
|
||||
cerr(" and ");
|
||||
@@ -1047,18 +1044,6 @@ fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32,
|
||||
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
|
||||
// //ww:module-reset primary boundary (so -c emits its decls, imported
|
||||
// ==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;
|
||||
};
|
||||
|
||||
// Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s
|
||||
// (reverse-topo order, each tagged by its dotted path), then pi's own
|
||||
// body under //ww:module-reset. Directory membership comes only from the
|
||||
// package node loaded before planning.
|
||||
fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8, order: []i32,
|
||||
norder: i32, searchpath: *u8, unitf: *u8) i32 = {
|
||||
// Compose pi's sep-unit at `unitf`: its byte-sorted DIRECT dependency
|
||||
// `.wwi`s, each tagged by its dotted path, then pi's own body under
|
||||
// //ww:module-reset. Compiler exports are self-contained for public type
|
||||
// facts; the linker separately retains the reachable archive closure.
|
||||
fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8,
|
||||
searchpath: *u8, unitf: *u8) i32 = {
|
||||
let u: i32 = os.open(pathstr(unitf), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (u < 0) {
|
||||
cerr("ww: cannot open unit\n");
|
||||
return -1;
|
||||
};
|
||||
let inset: []u8 = alloc([], g.n: u64)!;
|
||||
inset.len = g.n;
|
||||
let z: i32 = 0;
|
||||
for (z < g.n) { inset[z] = 0u8; z += 1; };
|
||||
sepmarkdeps(g, pi, inset);
|
||||
let oi: i32 = 0;
|
||||
for (oi < norder) {
|
||||
let dj: i32 = order[oi];
|
||||
if (dj != pi) {
|
||||
if (inset[dj] != 0u8) {
|
||||
let wwi: *u8 = sepfname(g, dj, scratch, ".wwi");
|
||||
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;
|
||||
};
|
||||
};
|
||||
let k: i32 = 0;
|
||||
for (k < g.pkg[pi].ndeps) {
|
||||
let dj: i32 = g.pkg[pi].deps[k];
|
||||
let wwi: *u8 = sepfname(g, dj, scratch, ".wwi");
|
||||
let wb: *u8;
|
||||
let wn: u64;
|
||||
wb, wn = slurp(wwi);
|
||||
if (wb == nil) {
|
||||
cerr("ww: missing wwi\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;
|
||||
bv.out = u;
|
||||
@@ -1429,14 +1405,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = {
|
||||
fn workdirstamptext(istest: i32, emitasm: i32) str = {
|
||||
if (istest != 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) {
|
||||
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 = {
|
||||
@@ -1699,7 +1675,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
|
||||
};
|
||||
let needsexport: 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;
|
||||
};
|
||||
let fresh: bool = false;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
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 = {
|
||||
let drivers: []str = ["ww", "ww_ww", "ww", "ww_ww"];
|
||||
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])) {
|
||||
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 = {
|
||||
@@ -136,6 +153,162 @@ fn rejectstable(dir: str, label: str, target: str, needle: str) void = {
|
||||
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 = {
|
||||
let shared: str = strings.concat(td, "/shared");
|
||||
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(
|
||||
"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(
|
||||
"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(
|
||||
"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;
|
||||
};
|
||||
|
||||
@@ -209,9 +382,17 @@ fn writediamond(td: str, reverse: bool) str = {
|
||||
};
|
||||
let leftiface: str = testenv.readfile(strings.concat(scratch,
|
||||
"left.wwi"));
|
||||
let rightiface: str = testenv.readfile(strings.concat(scratch,
|
||||
"right.wwi"));
|
||||
if (testenv.occurrences(leftiface, "import shared;") != 1) {
|
||||
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;
|
||||
};
|
||||
let shuffledout: str = strings.concat(td, "/diamond-c-shuffled");
|
||||
|
||||
Reference in New Issue
Block a user