ww package: reject non-function main declarations

This commit is contained in:
2026-08-21 15:13:09 +09:00
parent 2b7fafde0c
commit 2387d3e2d2
6 changed files with 520 additions and 3 deletions

View File

@@ -3885,6 +3885,35 @@ classify_init_decls(Checker *c, Node *file)
} }
} }
/* Pinned Go 1.26.5 types2 rejects a non-function package-scope `main`
* before declaring it, but only when the declared package name is `main`
* (resolver.go:90-110 declarePkgObj). WW's function entry ABI deliberately
* permits argument/result-bearing functions, so this is the independent
* declaration-kind rule: canonical identity, physical directory, path leaf,
* and root/action status are not inputs. Remove rejected declarations from
* later name installation just as the pinned resolver returns without
* declaring its object. */
static void
reject_nonfunction_main_decls(Checker *c, Node *file)
{
Node *prev = NULL;
for (Node *d = file->list; d; ) {
Node *next = d->next;
int invalid = top_decl_kind(d) && d->kind != N_FNDECL
&& d->str != NULL && strcmp(d->str, "main") == 0
&& d->pkgname != NULL && strcmp(d->pkgname, "main") == 0;
if (invalid) {
err(c, d->pos, "cannot declare main - must be func");
if (prev == NULL)
file->list = next;
else
prev->next = next;
} else
prev = d;
d = next;
}
}
/* Import usage is a property of the file-local qualifier occurrence. Record /* Import usage is a property of the file-local qualifier occurrence. Record
* qualified syntax before resolving declaration bodies so import diagnostics * qualified syntax before resolving declaration bodies so import diagnostics
* retain production Go's source order without making a failed bare lookup a * retain production Go's source order without making a failed bare lookup a
@@ -4049,6 +4078,7 @@ check_file(Checker *c, Node *file)
file->list = usenode; file->list = usenode;
} }
} }
reject_nonfunction_main_decls(c, file);
mark_import_uses(c, file); mark_import_uses(c, file);
check_import_redeclarations(c, file); check_import_redeclarations(c, file);
check_import_usage_and_collisions(c, file); check_import_usage_and_collisions(c, file);

View File

@@ -7771,6 +7771,181 @@ ordinary non-list `[no matches]` plus accounting pinned.
No persisted-byte contract changed: build workdir format remains `18`, test No persisted-byte contract changed: build workdir format remains `18`, test
workdir format remains `19`, and semantic storage format remains `3`. workdir format remains `19`, and semantic storage format remains `3`.
### 11.38 Implemented declared-`main` function-kind semantics
A package whose **declared package name** is `main` now rejects every
package-scope non-function declaration named `main`. `let`, `const`, `def`, and
`type` forms receive `cannot declare main - must be func` from either compiler
checker and are not installed in package scope. The rule is deliberately
narrower than Go's complete source signature rule: WW retains its established
C/Hare-style program-entry ABI, including supported argument- and
result-bearing function declarations.
#### Pinned Go evidence and fact classification
The sole authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- `types2.(*Checker).declarePkgObj` tests both identifier spelling `main` and
`check.pkg.name == "main"`, emits `cannot declare main - must be func`, and
returns without declaring the object
([`cmd/compile/internal/types2/resolver.go`, lines 90110](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L90-L110)).
The public checker implements the same condition and return
([`go/types/resolver.go`, method `(*Checker).declarePkgObj`, lines 103124](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/resolver.go#L103-L124)).
- Official type-checker testdata rejects constant, type, and variable
declarations named `main` in package `main`
([`internal/types/testdata/check/decls5.go`, lines 510](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/decls5.go#L5-L10)).
The fixed-bug test also rejects `var main = func() {}`: a variable containing
a function is still not a function declaration
([`test/fixedbugs/issue21256.go`, lines 19](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/fixedbugs/issue21256.go#L1-L9)).
Those conditions, diagnostics, early returns, and testdata assertions are
**behavior directly implemented or asserted by pinned Go**. That the semantic
owner is package declaration checking; that a rejected object does not become
the entry binding; and that declared package name rather than canonical path,
path leaf, physical directory, or command selection owns the rule are
**behavior derived from the pinned implementation**.
Pinned Go separately requires a function `main` in package `main` to have no
arguments or results
([`cmd/compile/internal/types2/resolver.go`, method
`(*Checker).collectObjects`, lines 416444](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L416-L444)),
asserted by official `mainsig.go`
([lines 713](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/mainsig.go#L7-L13)).
That is also **behavior directly implemented or asserted by pinned Go**, but it
does not honestly apply to WW's source ABI. Both baseline WW stages accepted
`fn main(x: i32) void` and `fn main() i32`, produced byte-identical
executables, and ran them successfully; WW's own self-hosted command tools use
`main(argc: i32, argv: **u8) i32`. Those observations are **directly measured
WW behavior**. Preserving those function forms while applying the independent
declaration-kind requirement is **behavior derived from the pinned
implementation within WW's applicable model boundary**.
#### Fresh four-axis audit and direct pre-fix measurements
The bounded audit examined all four permanent axes before this package slice
was selected:
- **Go-like build:** pinned `(*ErrorReporter).errorUnresolved` gives missing
`main.main` a dedicated error
([`cmd/link/internal/ld/errors.go`, lines 2967](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/errors.go#L29-L67)),
asserted by `TestUndefinedRelocErrors`
([`cmd/link/internal/ld/ld_test.go`, lines 1945](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/ld_test.go#L19-L45))
and official `testdata/issue10978/main.go` lines 527. Both WW stages
rejected a selected `main` package with no entry, emitted empty stdout and
the same 50 stderr bytes (SHA-256
`9ed4d7684412c6d2e615041902072c81e9e09acb3970246d89a2c8bdddd2fcfa`),
and published nothing. This applicable control was aligned.
- **Go-like test:** pinned `isTestFunc` and `checkTestFunc` define and reject a
wrong test function shape
([`cmd/go/internal/load/test.go`, lines 555579 and 775787](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L555-L579)),
with the official wrong-signature script anchor at
[`cmd/go/testdata/script/test_main.txt`, lines 1113 and 3040](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_main.txt#L11-L13).
Both WW stages rejected `@test fn bad(x: i32) void` before execution, emitted
the same semantic diagnostic, no accounting, and final `FAIL\n` stdout
(SHA-256
`4f8e9e45f8a9e1843b81eaf3bdf52a6b778d415d23bf985774a9d34a43f69bd5`).
This applicable control was aligned.
- **Go-like package:** baseline Cstage accepted `let main`, `const main`, and
`def main`, published mode-0755 executables, and those executables exited 139
with empty output. The `let`/`const` executable SHA-256 was
`edd3bad62be69701a373aa0567972bfb976690b0332b8c117891125254fc85b5`;
the `def` executable SHA-256 was
`9e56d6710395b287e18e85187eee86d846927c6f3ea91216858c6026f38ebdff`.
Cstage `type main` and every WWstage non-function form instead reached the
linker's missing-`main` failure. Neither stage emitted the pinned package
diagnostic. Both stages accepted a directory command package containing
`let main` plus a valid internal test, ran it, and reported package `ok` with
byte-identical 178-byte stdout (SHA-256
`a6e165fb558be62932e217ccd1d3175348f7490489bca8e9828872e28c01236f`).
This was the selected difference.
- **Go-like import:** pinned `unusedImports` and `errorUnusedPkg` reject a
nonblank unused alias
([`cmd/compile/internal/types2/resolver.go`, lines 706740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740)),
asserted by official `importdecl0a.go` lines 927. Both WW stages rejected
unused `import spare audit.dep;`, emitted empty stdout, and reported
`"audit.dep" imported as spare and not used`. This applicable control was
aligned.
The WW command results, output lengths, hashes, exit statuses, and runtime
signals are **directly measured WW behavior**. The linked source and testdata
facts are **behavior directly implemented or asserted by pinned Go**. Selecting
the declaration-kind rule while excluding the incompatible function-signature
rule is **behavior derived from the pinned implementation**.
As an identity control, both stages built `package utility; export let main:
i32 = 7` as byte-identical 924-byte archives (SHA-256
`10382e7812229d73c4acefdf8988a13372b6eb7a2981559b3adade524ed5a929`).
That is **directly measured WW behavior** and pins the required non-effect for
non-`main` declared packages.
#### Ownership, final behavior, and preserved boundaries
`reject_nonfunction_main_decls` in `cmd/wcc/check.c` and its self-hosted twin
`rejectnonfunctionmaindecls` in `selfhost/cmd/wcc/check.ww` are the semantic
owners. They run after parsing but before qualified-use discovery and package
name installation. Each walks selected top-level declarations, tests the
declaration-carried `pkgname`, reports the pinned diagnostic, and removes only
the rejected node from subsequent package-scope checking. This mirrors the
pinned resolver's return-before-declare behavior. No driver mode, entry flag,
canonical action key, directory classification, or linker-symbol heuristic is
consulted.
Direct post-fix calls to `w6c` and `w6c_ww` on the same invalid source now exit
1 with empty stdout, no assembly output, and byte-identical 102-byte stderr
(SHA-256
`39001ed88e2ab8b7675fcc51b4b794cf8ebc2a803e1f05de45d7d0ba1cd98a38`)
ending in `cannot declare main - must be func`. Directory builds of all four
forms fail through `ww: w6c failed for ...`, never reach `w6a` or `w6l`, publish
no output, and give byte-identical Cstage/WWstage diagnostics when the owned
output path is the same. Directory tests emit only the command-owned final
`FAIL\n` on stdout, report build failure on stderr, and emit no test body,
accounting, or package `ok` result.
Loading and Go-platform source eligibility are unchanged. Production and test
source selection still determines which declarations reach the checker; an
excluded source has no effect. Declared package name remains independent from
canonical dotted identity, aliases, path leaf, filename, physical directory,
requested root, output name, linker order, and artifact/storage locator. A
dependency physically and canonically ending in `main` but declared `utility`
continues to export `main`, bind through its declared qualifier, and produce
stage-byte-identical `.unit.ww`, `.wwi`, assembly, object, archive, and command
executable bytes. Valid `main(argc, argv) i32` and `main() i32` commands remain
accepted and byte-identical between stages.
Graph construction and action identities are unchanged for valid programs. An
invalid selected command or command-test variant reaches its normal compiler
action and fails there; assembler, archiver, linker, runtime, generated test
execution, and publication do not become alternative semantic owners. An
ordinary import of a declared-`main` package is still rejected earlier by the
loader as `ww: package PATH is a program, not an importable package`, even when
that command also contains the malformed declaration. This preserves import
diagnostic precedence and the toolchain-owned external-test exception.
Cold rejection creates no output or retained scratch. Warm rejection after a
successful command preserves the complete committed owner unit, interface,
assembly, object, archive, init unit/assembly/object, tool vouchers, workdir
stamp, and public executable byte for byte. It installs no staged generation;
exact source restoration reuses the committed action and reproduces the prior
binary. There is no test-result cache and no new reuse key. Producer failure,
rollback, existing-output preservation, concurrent action isolation,
interruption, process cleanup, and transaction cleanup continue through their
existing owners; the checker adds no process, descriptor, mutable global state,
or cleanup path. No active `.new`, `.install`, `.wwtxn.*`, adjacent rejection
scratch, test child, or capture survives the tested failure boundaries.
The WW-native `nonfunction_main_declarations_reject` observer proves all four
non-function kinds, exact direct-compiler stage parity, cold build rejection,
directory-test nonexecution, declared-name/dotted-import/physical-leaf
separation, supported entry ABI preservation, valid artifact-byte parity,
command-import precedence, complete warm work/publication rollback, restored
reuse, and residue absence. Existing interruption and concurrent-transaction
observers remain the owners of those unchanged mechanisms; this declaration
check introduces no independently interruptible or shared state.
No valid persisted-byte contract changed. Build workdir format remains `18`,
test workdir format remains `19`, and semantic storage format remains `3`.
## 12. Candidate architectures and hard-gate decision ## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins. Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -307,9 +307,17 @@ ImportPath = ident { "." ident } .
ownership, symbols, artifacts, storage, or diagnostics. Repeated occurrences ownership, symbols, artifacts, storage, or diagnostics. Repeated occurrences
of the same exact identity remain valid and deduplicate normally. of the same exact identity remain valid and deduplicate normally.
- An executable package is one declared `package main` and containing a - An executable package is one declared `package main` and containing a
`fn main`; path and directory spelling do not classify commands. An ordinary `fn main`; path and directory spelling do not classify commands. Within a
import of a package declared `main` is rejected, except for the toolchain's package declared `main`, a package-scope declaration named `main` must be a
colocated external-test wiring. function: `let`, `const`, `def`, and `type` forms reject as
`cannot declare main - must be func` and do not enter package scope. The
restriction depends only on the declared package name. A package with any
other declared name may use or export `main` regardless of its dotted import
path, path leaf, physical directory, or selection role. WW retains its
established program-entry ABI, so a function `main` may carry WW's supported
arguments and result; this rule does not adopt Go's source signature. An
ordinary import of a package declared `main` is rejected, except for the
toolchain's colocated external-test wiring.
- For `ww build`, an explicit `-o` names an output directory when ordinary - For `ww build`, an explicit `-o` names an output directory when ordinary
`stat` reports an existing directory (following symlinks) or its spelling `stat` reports an existing directory (following symlinks) or its spelling
ends in `/`. This classification is independent of whether one or many ends in `/`. This classification is independent of whether one or many
@@ -414,6 +422,13 @@ called as `init()`, selected as `pkg.init`, exported, or used by another kind
of declaration. Package-variable initialization completes before these of declaration. Package-variable initialization completes before these
functions run. functions run.
In a package whose declared name is `main`, only a function declaration may
claim the package-scope name `main`. A rejected non-function declaration is not
installed and cannot satisfy the executable entry. This is independent of
canonical import identity and physical location. The accepted function shape
continues to use WW's entry ABI, including its supported argument and result
forms.
### 5.2 `let` ### 5.2 `let`
``` ```
@@ -697,6 +712,12 @@ under test binds to the augmented white-box action when it exists; affected
transitive importers are copied and rewired so ordinary and augmented package transitive importers are copied and rewired so ordinary and augmented package
state do not coexist in the linked closure. state do not coexist in the linked closure.
A non-function package-scope `main` in production or same-package test source
of a package declared `main` is a package-checker failure. Generated test-main
ownership does not hide or replace it: the product is not linked or executed,
no test accounting or package `ok` result is emitted, and ordinary build/test
failure presentation and rollback apply.
Those variants are action distinctions over exact package representatives, not Those variants are action distinctions over exact package representatives, not
new ordinary package identities for case-fold comparison. Production, new ordinary package identities for case-fold comparison. Production,
same-package test, external test, and recompiled copies of one exact canonical same-package test, external test, and recompiled copies of one exact canonical

View File

@@ -268,6 +268,21 @@ check rather than part of `ww build`. Assembly-only `-S` still selects command
actions and rejects a no-command directory request, but it performs no actions and rejects a no-command directory request, but it performs no
install-only destination validation or output-directory creation. install-only destination validation or output-directory creation.
Package declaration checking also precedes test execution. In a package whose
declared name is `main`, `let main`, `const main`, `def main`, and `type main`
are rejected by both compiler stages as `cannot declare main - must be func`
and are not installed into package scope. The generated directory-test main is
an independent tool-owned action and cannot mask the malformed production or
same-package declaration: the product emits no test body output, accounting,
or `ok` result. This condition keys only on the source-declared package name;
a package with another declared name may export `main` even when its dotted
path or physical leaf is `main`, and WW's supported argument/result-bearing
function entries remain valid. The native
`nonfunction_main_declarations_reject` observer covers all four declaration
kinds, direct compiler ownership, build/test diagnostics, command-import
precedence, declared-name/import identity, valid artifact bytes, cold cleanup,
and warm work/output rollback and reuse across both stages.
The retained executable is byte-identical to the temporary runnable and has The retained executable is byte-identical to the temporary runnable and has
executable mode `0777` filtered by the caller's umask, but it is never the path executable mode `0777` filtered by the caller's umask, but it is never the path
executed by the coordinator. Compile-only publication participates in the executed by the coordinator. Compile-only publication participates in the

View File

@@ -7696,6 +7696,36 @@ fn classifyinitdecls(c: *checker, file: *syntax.node) void = {
}; };
}; };
// Pinned Go 1.26.5 types2 rejects a non-function package-scope `main`
// before declaring it, but only when the declared package name is `main`
// (resolver.go:90-110 declarePkgObj). WW's function entry ABI deliberately
// permits argument/result-bearing functions, so this is the independent
// declaration-kind rule: canonical identity, physical directory, path leaf,
// and root/action status are not inputs. Remove rejected declarations from
// later name installation just as the pinned resolver returns without
// declaring its object. Twin of cmd/wcc/check.c.
fn rejectnonfunctionmaindecls(c: *checker, file: *syntax.node) void = {
let prev: *syntax.node = nil;
let d: *syntax.node = file.list;
for (d != nil) {
let next: *syntax.node = d.next;
let invalid: bool = topdeclkind(d)
&& d.kind != syntax.nkind.N_FNDECL
&& syntax.streq(d.str, "main")
&& syntax.streq(d.pkgname, "main");
if (invalid) {
importdiagprefix(d);
cerr("cannot declare main - must be func\n");
c.errs += 1;
if (prev == nil) { file.list = next; }
else { prev.next = next; };
} else {
prev = d;
};
d = next;
};
};
fn initstripcast(n: *syntax.node) *syntax.node = { fn initstripcast(n: *syntax.node) *syntax.node = {
for (n != nil && n.kind == syntax.nkind.N_CAST) { n = n.lhs; }; for (n != nil && n.kind == syntax.nkind.N_CAST) { n = n.lhs; };
return n; return n;
@@ -8578,6 +8608,7 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
file.list = usenode; file.list = usenode;
}; };
}; };
rejectnonfunctionmaindecls(c, file);
markimportuses(c, file); markimportuses(c, file);
checkimportredeclarations(c, file); checkimportredeclarations(c, file);
checkimportusageandcollisions(c, file); checkimportusageandcollisions(c, file);

View File

@@ -8716,6 +8716,251 @@ fn cwdwritedata(dir: str, label: str) void = {
clean(root); 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 // Invalid @test attribute shapes reject at build with stable text on
// BOTH frontends (the -T synth checker owns them; the fixture corpus // BOTH frontends (the -T synth checker owns them; the fixture corpus
// cannot reach -T, so these rows live here). Fragments only — the // cannot reach -T, so these rows live here). Fragments only — the