ww package: reject non-function main declarations
This commit is contained in:
@@ -8716,6 +8716,251 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
clean(root);
|
||||
};
|
||||
|
||||
// A package declared `main` may reserve the entry spelling only for a function.
|
||||
// This is the declaration-kind half of pinned Go's main rule; WW deliberately
|
||||
// retains its C/Hare-style argument/result entry ABI. The checker owns the
|
||||
// reject before assembly/linking, directory tests reject before execution, and
|
||||
// declared package name remains independent from dotted/physical identity.
|
||||
@test fn nonfunction_main_declarations_reject() void = {
|
||||
let root: str = fresh();
|
||||
let shapes: []str = [
|
||||
"let main: i32 = 0;\n",
|
||||
"const main: i32 = 0;\n",
|
||||
"def main: i32 = 0;\n",
|
||||
"type main = i32;\n",
|
||||
];
|
||||
let labels: []str = ["let", "const", "def", "type"];
|
||||
let expected: str = "cannot declare main - must be func";
|
||||
let i: i32 = 0;
|
||||
for (i < shapes.len) {
|
||||
let dir: str = strings.concat(root, "/invalid-", labels[i]);
|
||||
assert(os.mkdir(dir, 448i32) == 0);
|
||||
let src: str = strings.concat(dir, "/main.ww");
|
||||
writefile(src, strings.concat("package main;\n", shapes[i]));
|
||||
let output: str = strings.concat(root, "/invalid-output-", labels[i]);
|
||||
let avc: []str = [driver("ww"), "build", "-o", output, dir];
|
||||
let avw: []str = [driver("ww_ww"), "build", "-o", output, dir];
|
||||
let outc: commandout;
|
||||
let outw: commandout;
|
||||
runcommand(root, strings.concat("invalid-main-c-", labels[i]), avc,
|
||||
(30i64 * (time.second: i64)): time.duration, &outc);
|
||||
runcommand(root, strings.concat("invalid-main-ww-", labels[i]), avw,
|
||||
(30i64 * (time.second: i64)): time.duration, &outw);
|
||||
expectexit(&outc, 1);
|
||||
expectexit(&outw, 1);
|
||||
assert(outc.stdout.len == 0 && outw.stdout.len == 0);
|
||||
assert(same(primarydiagnostic(outc.stderr), expected));
|
||||
assert(same(primarydiagnostic(outw.stderr), expected));
|
||||
assert(same(outc.stderr, outw.stderr));
|
||||
assert(has(outc.stderr, "ww: w6c failed for "));
|
||||
assert(!has(outc.stderr, "w6a") && !has(outc.stderr, "w6l"));
|
||||
assert(!os.exists(output));
|
||||
assert(!os.exists(strings.concat(output, ".new")));
|
||||
assert(!os.exists(strings.concat(output, ".install")));
|
||||
assert(!os.exists(strings.concat(output, ".sepwork")));
|
||||
assert(!directoryhasfragment(root, ".wwtxn."));
|
||||
i += 1;
|
||||
};
|
||||
|
||||
// Direct compiler calls pin the true owner and byte-identical frontend
|
||||
// diagnostic independently from driver-generated unit paths.
|
||||
let directsrc: str = strings.concat(root, "/invalid-let/main.ww");
|
||||
let directc: str = strings.concat(root, "/direct-c.s");
|
||||
let directw: str = strings.concat(root, "/direct-ww.s");
|
||||
let dcav: []str = [driver("w6c"), "-c", "--entry", "-o", directc,
|
||||
directsrc];
|
||||
let dwav: []str = [driver("w6c_ww"), "-c", "--entry", "-o", directw,
|
||||
directsrc];
|
||||
let directco: commandout;
|
||||
let directwo: commandout;
|
||||
runcommand(root, "invalid-main-direct-c", dcav,
|
||||
(30i64 * (time.second: i64)): time.duration, &directco);
|
||||
runcommand(root, "invalid-main-direct-ww", dwav,
|
||||
(30i64 * (time.second: i64)): time.duration, &directwo);
|
||||
expectexit(&directco, 1);
|
||||
expectexit(&directwo, 1);
|
||||
assert(directco.stdout.len == 0 && directwo.stdout.len == 0);
|
||||
assert(same(directco.stderr, directwo.stderr));
|
||||
assert(same(primarydiagnostic(directco.stderr), expected));
|
||||
assert(!os.exists(directc) && !os.exists(directw));
|
||||
|
||||
// The same malformed production package must not become a passing test
|
||||
// package merely because the dispatcher owns a separate synthetic entry.
|
||||
let testdir: str = strings.concat(root, "/invalid-test");
|
||||
assert(os.mkdir(testdir, 448i32) == 0);
|
||||
writefile(strings.concat(testdir, "/main.ww"),
|
||||
"package main;\nlet main: i32 = 0;\n");
|
||||
writefile(strings.concat(testdir, "/main_test.ww"),
|
||||
"package main;\n@test fn must_not_run() void = { abort(\"ran\"); };\n");
|
||||
let testdiags: []str = ["", ""];
|
||||
let stages: []str = ["ww", "ww_ww"];
|
||||
let tags: []str = ["c", "ww"];
|
||||
i = 0;
|
||||
for (i < stages.len) {
|
||||
let av: []str = [driver(stages[i]), "test", testdir];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("invalid-main-test-", tags[i]), av,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(same(out.stdout, "FAIL\n"));
|
||||
assert(!has(out.stdout, "must_not_run") && !has(out.stdout, " passed,"));
|
||||
assert(!has(out.stderr, "must_not_run") && !has(out.stderr, "ok "));
|
||||
assert(has(out.stderr, expected));
|
||||
assert(has(out.stderr, strings.concat("FAIL ", testdir)));
|
||||
testdiags[i] = strings.dup(primarydiagnostic(out.stderr));
|
||||
i += 1;
|
||||
};
|
||||
assert(same(testdiags[0], expected) && same(testdiags[0], testdiags[1]));
|
||||
|
||||
// A warm semantic failure preserves every committed action byte, tool
|
||||
// voucher, and public binary, then exact source restoration reuses them.
|
||||
let warmdiags: []str = ["", ""];
|
||||
i = 0;
|
||||
for (i < stages.len) {
|
||||
let warmdir: str = strings.concat(root, "/warm-", tags[i]);
|
||||
let work: str = strings.concat(root, "/warm-work-", tags[i]);
|
||||
let output: str = strings.concat(root, "/warm-output-", tags[i]);
|
||||
assert(os.mkdir(warmdir, 448i32) == 0);
|
||||
assert(os.mkdir(work, 448i32) == 0);
|
||||
let source: str = strings.concat(warmdir, "/main.ww");
|
||||
let valid: str = "package main;\nfn main() i32 = { return 0; };\n";
|
||||
writefile(source, valid);
|
||||
let av: []str = [driver(stages[i]), "build", "-w", work,
|
||||
"-o", output, warmdir];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("invalid-main-warm-cold-", tags[i]), av,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
let action: str = localidentity(warmdir, "main");
|
||||
let suffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a",
|
||||
".init.unit.ww", ".init.s", ".init.o"];
|
||||
let refs: []str = alloc([], suffixes.len: u64)!;
|
||||
let j: i32 = 0;
|
||||
for (j < suffixes.len) {
|
||||
append(refs, strings.dup(readfile(strings.concat(strings.concat(work,
|
||||
"/"), strings.concat(action, suffixes[j])))));
|
||||
j += 1;
|
||||
};
|
||||
let toolpaths: []str = ["/.wwtool.ww", "/.wwtool.w6c",
|
||||
"/.wwtool.w6a", "/.wwtool.stamp"];
|
||||
let toolrefs: []str = alloc([], toolpaths.len: u64)!;
|
||||
j = 0;
|
||||
for (j < toolpaths.len) {
|
||||
append(toolrefs, strings.dup(readfile(strings.concat(work,
|
||||
toolpaths[j]))));
|
||||
j += 1;
|
||||
};
|
||||
let binref: str = strings.dup(readfile(output));
|
||||
rewritefile(source, "package main;\nlet main: i32 = 0;\n");
|
||||
runcommand(root, strings.concat("invalid-main-warm-reject-", tags[i]),
|
||||
av, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && same(primarydiagnostic(out.stderr), expected));
|
||||
warmdiags[i] = strings.dup(primarydiagnostic(out.stderr));
|
||||
j = 0;
|
||||
for (j < suffixes.len) {
|
||||
assert(same(refs[j], readfile(strings.concat(strings.concat(work,
|
||||
"/"), strings.concat(action, suffixes[j])))));
|
||||
j += 1;
|
||||
};
|
||||
j = 0;
|
||||
for (j < toolpaths.len) {
|
||||
assert(same(toolrefs[j], readfile(strings.concat(work,
|
||||
toolpaths[j]))));
|
||||
j += 1;
|
||||
};
|
||||
assert(same(binref, readfile(output)));
|
||||
assert(!directoryhasnew(work));
|
||||
assert(!os.exists(strings.concat(output, ".new")));
|
||||
assert(!os.exists(strings.concat(output, ".install")));
|
||||
assert(!os.exists(strings.concat(output, ".sepwork")));
|
||||
assert(!directoryhasfragment(root, ".wwtxn."));
|
||||
rewritefile(source, valid);
|
||||
runcommand(root, strings.concat("invalid-main-warm-restored-", tags[i]),
|
||||
av, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
assert(same(binref, readfile(output)) && !directoryhasnew(work));
|
||||
i += 1;
|
||||
};
|
||||
assert(same(warmdiags[0], expected) && same(warmdiags[0], warmdiags[1]));
|
||||
|
||||
// The declared package name, never the dotted path leaf or physical
|
||||
// directory, owns the rule. A non-main package may export `main`, and a
|
||||
// command may import it while retaining WW's argument/result entry ABI.
|
||||
let tree: str = strings.concat(root, "/tree");
|
||||
let dep: str = strings.concat(tree, "/domain/main");
|
||||
let app: str = strings.concat(tree, "/app");
|
||||
mkdirall(dep);
|
||||
assert(os.mkdir(app, 448i32) == 0);
|
||||
writefile(strings.concat(dep, "/dep.ww"),
|
||||
"package utility;\nexport let main: i32 = 7;\n");
|
||||
writefile(strings.concat(app, "/main.ww"), strings.concat(
|
||||
"package main;\nimport domain.main;\n",
|
||||
"fn main(argc: i32, argv: **u8) i32 = { return utility.main - 7; };\n"));
|
||||
let binaries: []str = [strings.concat(root, "/identity-c"),
|
||||
strings.concat(root, "/identity-ww")];
|
||||
i = 0;
|
||||
for (i < stages.len) {
|
||||
let av: []str = [driver(stages[i]), "build", "-I", tree,
|
||||
"-o", binaries[i], app];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("main-name-boundary-", tags[i]), av,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
let runav: []str = [binaries[i], "one", "two"];
|
||||
runcommand(root, strings.concat("main-name-boundary-run-", tags[i]),
|
||||
runav, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
i += 1;
|
||||
};
|
||||
assert(same(readfile(binaries[0]), readfile(binaries[1])));
|
||||
let artifacts: []str = ["domain.main.unit.ww", "domain.main.wwi",
|
||||
"domain.main.s", "domain.main.o", "domain.main.a"];
|
||||
i = 0;
|
||||
for (i < artifacts.len) {
|
||||
assert(same(readfile(strings.concat(strings.concat(binaries[0],
|
||||
".sepwork/"), artifacts[i])),
|
||||
readfile(strings.concat(strings.concat(binaries[1], ".sepwork/"),
|
||||
artifacts[i]))));
|
||||
i += 1;
|
||||
};
|
||||
|
||||
// Importing any declared-main package is still the loader-owned error and
|
||||
// retains precedence over the declaration checker, even when that command
|
||||
// also contains the malformed declaration.
|
||||
let badcmd: str = strings.concat(tree, "/badcmd");
|
||||
let client: str = strings.concat(tree, "/client");
|
||||
assert(os.mkdir(badcmd, 448i32) == 0);
|
||||
assert(os.mkdir(client, 448i32) == 0);
|
||||
writefile(strings.concat(badcmd, "/main.ww"),
|
||||
"package main;\nlet main: i32 = 0;\n");
|
||||
writefile(strings.concat(client, "/client.ww"), strings.concat(
|
||||
"package client;\nimport _ badcmd;\n",
|
||||
"export fn value() i32 = { return 7; };\n"));
|
||||
let importout: str = strings.concat(root, "/bad-import-output");
|
||||
let importdiag: str =
|
||||
"ww: package badcmd is a program, not an importable package\n";
|
||||
i = 0;
|
||||
for (i < stages.len) {
|
||||
let av: []str = [driver(stages[i]), "build", "-I", tree,
|
||||
"-o", importout, client];
|
||||
let out: commandout;
|
||||
runcommand(root, strings.concat("bad-main-import-", tags[i]), av,
|
||||
(30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, importdiag));
|
||||
assert(!has(out.stderr, expected));
|
||||
assert(!os.exists(importout) && !os.exists(strings.concat(importout,
|
||||
".sepwork")));
|
||||
i += 1;
|
||||
};
|
||||
clean(root);
|
||||
};
|
||||
|
||||
// Invalid @test attribute shapes reject at build with stable text on
|
||||
// BOTH frontends (the -T synth checker owns them; the fixture corpus
|
||||
// cannot reach -T, so these rows live here). Fragments only — the
|
||||
|
||||
Reference in New Issue
Block a user