ww package: reject non-function main declarations
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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 90–110](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 103–124](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 5–10](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 1–9](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 416–444](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L416-L444)),
|
||||||
|
asserted by official `mainsig.go`
|
||||||
|
([lines 7–13](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 29–67](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 19–45](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/ld_test.go#L19-L45))
|
||||||
|
and official `testdata/issue10978/main.go` lines 5–27. 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 555–579 and 775–787](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 11–13 and 30–40](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 706–740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740)),
|
||||||
|
asserted by official `importdecl0a.go` lines 9–27. 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.
|
||||||
|
|||||||
27
docs/spec.md
27
docs/spec.md
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user