ww imports: reject init bindings before recovery

This commit is contained in:
2026-08-22 04:27:23 +09:00
parent 4069eda942
commit 78c9eee82d
10 changed files with 789 additions and 15 deletions

View File

@@ -18336,3 +18336,356 @@ fn runtimepath(relative: str) str = {
&& !directoryhasnew(root));
clean(root);
};
// Go 1.26.5 resolves an import target, computes its effective file-local name,
// and rejects `init` before installing any binding or unused/duplicate/collision
// recovery state. The error belongs to the explicit alias token, or to the path
// token when the dependency's declared name supplies the effective binding.
@test fn effective_init_imports_never_enter_binding_recovery() void = {
let root: str = fresh();
let source: str = strings.concat(root, "/source");
let normal: str = strings.concat(source, "/dep/normal");
let named: str = strings.concat(source, "/dep/named");
let badbinding: str = strings.concat(source, "/dep/badbinding");
let imported: str = strings.concat(source, "/imported");
let explicit: str = strings.concat(source, "/explicit");
let implicit: str = strings.concat(source, "/implicit");
let collision: str = strings.concat(source, "/collision");
let missing: str = strings.concat(source, "/missing");
let control: str = strings.concat(source, "/control");
let warm: str = strings.concat(source, "/warm");
let prodcase: str = strings.concat(source, "/prodcase");
let samecase: str = strings.concat(source, "/samecase");
let externalcase: str = strings.concat(source, "/externalcase");
let onlycase: str = strings.concat(source, "/onlycase");
let dirs: []str = [normal, named, badbinding, imported, explicit, implicit,
collision, missing, control, warm, prodcase, samecase, externalcase,
onlycase];
let di: i32 = 0;
for (di < dirs.len) { mkdirall(dirs[di]); di += 1; };
writefile(strings.concat(normal, "/normal.ww"), strings.concat(
"package normal;\n",
"export fn value() i32 = { return 37; };\n"));
writefile(strings.concat(named, "/named.ww"), strings.concat(
"package init;\n",
"export fn value() i32 = { return 41; };\n"));
writefile(strings.concat(badbinding, "/badbinding.ww"), strings.concat(
"package badbinding;\n",
"import init dep.normal;\n",
"export fn value() i32 = { return 43; };\n"));
writefile(strings.concat(imported, "/main.ww"), strings.concat(
"package main;\n",
"import dep.badbinding;\n",
"fn main() i32 = { return badbinding.value(); };\n"));
writefile(strings.concat(explicit, "/main.ww"), strings.concat(
"package main;\n",
"import init dep.normal;\n",
"import init dep.normal;\n",
"fn main() i32 = { return init.value(); };\n"));
writefile(strings.concat(implicit, "/main.ww"), strings.concat(
"package main;\n",
"import dep.named;\n",
"fn main() i32 = { return init.value(); };\n"));
writefile(strings.concat(collision, "/main.ww"), strings.concat(
"package main;\n",
"import init dep.normal;\n",
"let init: i32 = 1;\n",
"fn main() i32 = { return 0; };\n"));
writefile(strings.concat(missing, "/main.ww"), strings.concat(
"package main;\n",
"import init dep.absent;\n",
"fn main() i32 = { return 0; };\n"));
writefile(strings.concat(control, "/main.ww"), strings.concat(
"package main;\n",
"import _ dep.named;\n",
"import stable dep.named;\n",
"fn main() i32 = { return stable.value(); };\n"));
let warmfile: str = strings.concat(warm, "/main.ww");
let warmvalid: str = strings.concat(
"package main;\n",
"import stable dep.normal;\n",
"fn main() i32 = { return stable.value(); };\n");
writefile(warmfile, warmvalid);
writefile(strings.concat(prodcase, "/prod.ww"), strings.concat(
"package prodcase;\n",
"import init dep.normal;\n",
"fn value() i32 = { return 1; };\n"));
writefile(strings.concat(prodcase, "/prod_test.ww"), strings.concat(
"package prodcase;\n",
"@test fn must_not_run() void = { abort(); };\n"));
writefile(strings.concat(samecase, "/prod.ww"),
"package samecase;\nfn value() i32 = { return 1; };\n");
writefile(strings.concat(samecase, "/same_test.ww"), strings.concat(
"package samecase;\n",
"import init dep.normal;\n",
"@test fn must_not_run() void = { abort(); };\n"));
writefile(strings.concat(externalcase, "/prod.ww"), strings.concat(
"package externalcase;\n",
"export fn value() i32 = { return 1; };\n"));
writefile(strings.concat(externalcase, "/external_test.ww"), strings.concat(
"package externalcase_test;\n",
"import init dep.normal;\n",
"@test fn must_not_run() void = { abort(); };\n"));
writefile(strings.concat(onlycase, "/only_test.ww"), strings.concat(
"package onlycase;\n",
"import init dep.normal;\n",
"@test fn must_not_run() void = { abort(); };\n"));
let core: str = "cannot import package as init - init must be a func";
let stages: []str = ["ww", "ww_ww"];
let tags: []str = ["c", "ww"];
let explicitdiag: str = "";
let implicitdiag: str = "";
let collisiondiag: str = "";
let missingdiag: str = "";
let importeddiag: str = "";
let controldata: []str = alloc([], 8u64)!;
let controlbin: str = "";
let suffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a",
".init.unit.ww", ".init.s", ".init.o"];
let si: i32 = 0;
for (si < stages.len) {
// The same rejection owns a dependency compiler action reached through
// an imported root. Request-wide rollback leaves no cold semantic state.
let importedwork: str = strings.concat(root, "/imported-work-", tags[si]);
let importedout: str = strings.concat(root, "/imported-output-", tags[si]);
mkdirall(importedwork);
let importedav: []str = [driver(stages[si]), "build", "-w",
importedwork, "-I", source, "-o", importedout, "imported"];
let importedresult: commandout;
runcommand(root, strings.concat("init-imported-", tags[si]), importedav,
(120i64 * (time.second: i64)): time.duration, &importedresult);
expectexit(&importedresult, 1);
assert(importedresult.stdout.len == 0
&& occurrences(importedresult.stderr, core) == 1
&& has(importedresult.stderr,
"/dep.badbinding.unit.new:3:8: error: cannot import package as init - init must be a func\n")
&& !has(importedresult.stderr, "imported as init and not used")
&& !os.exists(importedout)
&& !os.exists(strings.concat(importedout, ".new"))
&& !os.exists(strings.concat(importedout, ".sepwork"))
&& directoryisempty(importedwork));
let importednormalized: str = normalizedtrace(importedresult.stderr,
strings.concat(importedwork, "/"), importedout);
if (si == 0) { importeddiag = strings.dup(importednormalized); }
else { assert(same(importeddiag, importednormalized)); };
let work: str = strings.concat(root, "/reject-work-", tags[si]);
mkdirall(work);
let targets: []str = ["explicit", "implicit", "collision", "missing"];
let outputs: []str = [strings.concat(root, "/explicit-", tags[si]),
strings.concat(root, "/implicit-", tags[si]),
strings.concat(root, "/collision-", tags[si]),
strings.concat(root, "/missing-", tags[si])];
let ti: i32 = 0;
for (ti < targets.len) {
let av: []str = [driver(stages[si]), "build", "-w", work,
"-I", source, "-o", outputs[ti], targets[ti]];
let out: commandout;
runcommand(root, strings.concat("init-build-", tags[si], "-",
targets[ti]), av,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0 && !os.exists(outputs[ti])
&& !os.exists(strings.concat(outputs[ti], ".new"))
&& !os.exists(strings.concat(outputs[ti], ".sepwork")));
let normalized: str = normalizedtrace(out.stderr,
strings.concat(work, "/"), outputs[ti]);
if (ti == 0) {
assert(occurrences(out.stderr, core) == 2
&& has(out.stderr,
"/explicit.unit.new:3:8: error: cannot import package as init - init must be a func\n")
&& has(out.stderr,
"/explicit.unit.new:4:8: error: cannot import package as init - init must be a func\n")
&& has(out.stderr, "undefined: init")
&& !has(out.stderr, "imported as init and not used")
&& !has(out.stderr, "init redeclared in this block")
&& !has(out.stderr,
"already declared through import of package init"));
if (si == 0) { explicitdiag = strings.dup(normalized); }
else { assert(same(explicitdiag, normalized)); };
} else if (ti == 1) {
assert(occurrences(out.stderr, core) == 1
&& has(out.stderr,
"/implicit.unit.new:3:8: error: cannot import package as init - init must be a func\n")
&& has(out.stderr, "undefined: init")
&& !has(out.stderr, "imported as init and not used"));
if (si == 0) { implicitdiag = strings.dup(normalized); }
else { assert(same(implicitdiag, normalized)); };
} else if (ti == 2) {
assert(occurrences(out.stderr, core) == 1
&& has(out.stderr,
"/collision.unit.new:3:8: error: cannot import package as init - init must be a func\n")
&& has(out.stderr,
"cannot declare init - must be func")
&& !has(out.stderr, "imported as init and not used")
&& !has(out.stderr,
"already declared through import of package init"));
if (si == 0) { collisiondiag = strings.dup(normalized); }
else { assert(same(collisiondiag, normalized)); };
} else {
assert(occurrences(out.stderr, core) == 0
&& has(out.stderr,
":2:1: error: cannot find package dep.absent\n"));
if (si == 0) { missingdiag = strings.dup(normalized); }
else { assert(same(missingdiag, normalized)); };
};
assert(directoryisempty(work));
ti += 1;
};
// `_` never creates a binding, and a non-init explicit alias remains
// valid even when the loaded package declares its name as init.
let controlwork: str = strings.concat(root, "/control-work-", tags[si]);
let controlout: str = strings.concat(root, "/control-output-", tags[si]);
mkdirall(controlwork);
let controlav: []str = [driver(stages[si]), "build", "-w", controlwork,
"-I", source, "-o", controlout, "control"];
let out: commandout;
runcommand(root, strings.concat("init-control-", tags[si]), controlav,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0);
let runav: []str = [controlout];
runcommand(root, strings.concat("init-control-run-", tags[si]), runav,
time.second, &out);
expectexit(&out, 41);
let fi: i32 = 0;
for (fi < suffixes.len) {
let bytes: str = readfile(strings.concat(controlwork, "/control",
suffixes[fi]));
if (si == 0) { append(controldata, strings.dup(bytes)); }
else { assert(same(controldata[fi], bytes)); };
fi += 1;
};
if (si == 0) { controlbin = strings.dup(readfile(controlout)); }
else { assert(same(controlbin, readfile(controlout))); };
assert(!directoryhasnew(controlwork)
&& !directoryhasfragment(controlwork, ".wwtxn."));
si += 1;
};
// Production, same-package test, external-test, and test-only sources all
// use the same recovery rule before test-main construction or execution.
let testtargets: []str = ["prodcase", "samecase", "externalcase",
"onlycase"];
let testdiags: []str = ["", "", "", ""];
si = 0;
for (si < stages.len) {
let ti: i32 = 0;
for (ti < testtargets.len) {
let testwork: str = strings.concat(root, "/test-work-", tags[si], "-",
boundarypkgname(ti));
let testout: str = strings.concat(root, "/test-output-", tags[si], "-",
boundarypkgname(ti));
mkdirall(testwork);
let av: []str = [driver(stages[si]), "test", "-w", testwork,
"-I", source, "-o", testout, testtargets[ti]];
let out: commandout;
runcommand(root, strings.concat("init-test-", tags[si], "-",
boundarypkgname(ti)), av,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(same(out.stdout, "FAIL\n")
&& occurrences(out.stderr, core) == 1
&& has(out.stderr, strings.concat(":8: error: ", core, "\n"))
&& !has(out.stderr, "imported as init and not used")
&& !has(out.stderr, "init redeclared in this block")
&& !has(out.stderr,
"already declared through import of package init")
&& !has(out.stdout, "must_not_run")
&& !has(out.stdout, "discovered")
&& !has(out.stdout, "passed") && !has(out.stdout, "failed")
&& !os.exists(testout)
&& !os.exists(strings.concat(testout, ".new"))
&& !os.exists(strings.concat(testout, ".sepwork"))
&& directoryisempty(testwork));
let normalized: str = normalizedtrace(out.stderr,
strings.concat(testwork, "/"), testout);
if (si == 0) { testdiags[ti] = strings.dup(normalized); }
else { assert(same(testdiags[ti], normalized)); };
ti += 1;
};
si += 1;
};
// A warm semantic rejection preserves every committed action and public
// byte; exact source restoration returns to the same persisted generation.
let warmpaths: []str = ["/.wwtool.ww", "/.wwtool.w6c", "/.wwtool.w6a",
"/.wwtool.stamp", "/dep.normal.unit.ww", "/dep.normal.wwi",
"/dep.normal.s", "/dep.normal.o", "/dep.normal.a", "/warm.unit.ww",
"/warm.wwi", "/warm.s", "/warm.o", "/warm.a",
"/warm.init.unit.ww", "/warm.init.s", "/warm.init.o"];
let warmcross: []str = alloc([], warmpaths.len: u64)!;
let warmbincross: str = "";
si = 0;
for (si < stages.len) {
let work: str = strings.concat(root, "/warm-work-", tags[si]);
let output: str = strings.concat(root, "/warm-output-", tags[si]);
mkdirall(work);
let av: []str = [driver(stages[si]), "build", "-w", work,
"-I", source, "-o", output, "warm"];
let out: commandout;
runcommand(root, strings.concat("init-warm-cold-", tags[si]), av,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
let snapshots: []str = alloc([], warmpaths.len: u64)!;
let pi: i32 = 0;
for (pi < warmpaths.len) {
let bytes: str = strings.dup(readfile(strings.concat(work,
warmpaths[pi])));
append(snapshots, bytes);
if (si == 0) { append(warmcross, strings.dup(bytes)); }
else if (pi >= 4) { assert(same(warmcross[pi], bytes)); };
pi += 1;
};
let binbytes: str = strings.dup(readfile(output));
if (si == 0) { warmbincross = strings.dup(binbytes); }
else { assert(same(warmbincross, binbytes)); };
rewritefile(warmfile, strings.concat(
"package main;\n",
"import init dep.normal;\n",
"fn main() i32 = { return 0; };\n"));
runcommand(root, strings.concat("init-warm-reject-", tags[si]), av,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0 && occurrences(out.stderr, core) == 1
&& has(out.stderr,
"/warm.unit.new:3:8: error: cannot import package as init - init must be a func\n")
&& !has(out.stderr, "imported as init and not used")
&& same(binbytes, readfile(output))
&& !os.exists(strings.concat(output, ".new"))
&& !os.exists(strings.concat(output, ".sepwork")));
pi = 0;
for (pi < warmpaths.len) {
assert(same(snapshots[pi], readfile(strings.concat(work,
warmpaths[pi]))));
pi += 1;
};
assert(!directoryhasnew(work)
&& !directoryhasfragment(work, ".wwtxn.")
&& !directoryhasfragment(work, ".install"));
rewritefile(warmfile, warmvalid);
runcommand(root, strings.concat("init-warm-restored-", tags[si]), av,
(120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& same(binbytes, readfile(output)));
pi = 0;
for (pi < warmpaths.len) {
assert(same(snapshots[pi], readfile(strings.concat(work,
warmpaths[pi]))));
pi += 1;
};
si += 1;
};
assert(!directoryhasnew(root)
&& !directoryhasfragment(root, ".wwtxn.")
&& !directoryhasfragment(root, ".install"));
clean(root);
};