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

@@ -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");