ww imports: reject init bindings before recovery
This commit is contained in:
@@ -2907,6 +2907,25 @@ decl_mod(Node *file, Node *d)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
invalid_init_import(Node *u)
|
||||
{
|
||||
return u != NULL && u->kind == N_USE && !u->useblank
|
||||
&& u->str != NULL && strcmp(u->str, "init") == 0;
|
||||
}
|
||||
|
||||
static Pos
|
||||
import_binding_pos(Node *u)
|
||||
{
|
||||
Pos p = u->pos;
|
||||
if (u->usefile != NULL) {
|
||||
p.file = u->usefile;
|
||||
p.line = u->useline;
|
||||
p.col = u->usecol;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
* use_path — map a source-file default qualifier (the imported package's
|
||||
* declared name) to the full canonical import path it binds, for
|
||||
@@ -2932,7 +2951,7 @@ find_use_path(Node *file, const char *curmod, int source, const char *alias,
|
||||
if (strcmp(alias, leaf) == 0) return curmod;
|
||||
}
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->str == NULL
|
||||
if (u->kind != N_USE || u->str == NULL || invalid_init_import(u)
|
||||
|| u->sourceid != source || strcmp(u->str, alias) != 0)
|
||||
continue;
|
||||
const char *p = u->usepath ? u->usepath : u->str;
|
||||
@@ -3960,6 +3979,19 @@ check_import_alt(Node *d, const char *name)
|
||||
d->pos.file ? d->pos.file : "?", d->pos.line, d->pos.col, name);
|
||||
}
|
||||
|
||||
/* Pinned Go 1.26.5 types2 rejects an effective import binding named init and
|
||||
* immediately continues before creating its PkgName. Diagnose every resolved
|
||||
* occurrence at the first import-spec token, then keep it out of all binding
|
||||
* recovery below while retaining the real loader-owned dependency edge. */
|
||||
static void
|
||||
reject_init_imports(Checker *c, Node *file)
|
||||
{
|
||||
for (Node *u = file->list; u; u = u->next)
|
||||
if (invalid_init_import(u))
|
||||
err(c, import_binding_pos(u),
|
||||
"cannot import package as init - init must be a func");
|
||||
}
|
||||
|
||||
/* Go's default import binding lives in the importing file's scope. Reject
|
||||
* only another binding in that same source section; equal names in sibling
|
||||
* files are independent even though their canonical edges are package-wide. */
|
||||
@@ -3968,11 +4000,11 @@ check_import_redeclarations(Checker *c, Node *file)
|
||||
{
|
||||
if (!c->sep_mode) return;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->useblank
|
||||
if (u->kind != N_USE || u->useblank || invalid_init_import(u)
|
||||
|| u->imported || u->str == NULL)
|
||||
continue;
|
||||
for (Node *v = file->list; v != u; v = v->next) {
|
||||
if (v->kind != N_USE || v->useblank
|
||||
if (v->kind != N_USE || v->useblank || invalid_init_import(v)
|
||||
|| v->imported || v->str == NULL
|
||||
|| v->sourceid != u->sourceid)
|
||||
continue;
|
||||
@@ -3994,7 +4026,7 @@ check_import_usage_and_collisions(Checker *c, Node *file)
|
||||
{
|
||||
if (!c->sep_mode) return;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->useblank
|
||||
if (u->kind != N_USE || u->useblank || invalid_init_import(u)
|
||||
|| u->imported || u->used || u->str == NULL)
|
||||
continue;
|
||||
const char *path = u->usesource ? u->usesource
|
||||
@@ -4011,7 +4043,7 @@ check_import_usage_and_collisions(Checker *c, Node *file)
|
||||
if (d->imported || !top_decl_kind(d) || d->str == NULL)
|
||||
continue;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->useblank
|
||||
if (u->kind != N_USE || u->useblank || invalid_init_import(u)
|
||||
|| u->imported || u->str == NULL
|
||||
|| strcmp(d->str, u->str) != 0)
|
||||
continue;
|
||||
@@ -4078,6 +4110,7 @@ check_file(Checker *c, Node *file)
|
||||
file->list = usenode;
|
||||
}
|
||||
}
|
||||
reject_init_imports(c, file);
|
||||
reject_nonfunction_main_decls(c, file);
|
||||
mark_import_uses(c, file);
|
||||
check_import_redeclarations(c, file);
|
||||
@@ -4106,10 +4139,8 @@ check_file(Checker *c, Node *file)
|
||||
"'%s' cannot import itself", owner);
|
||||
if (d->useblank)
|
||||
continue;
|
||||
if (d->str != NULL && strcmp(d->str, "init") == 0) {
|
||||
err(c, d->pos, "cannot import package as init - init must be a func");
|
||||
if (invalid_init_import(d))
|
||||
continue;
|
||||
}
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
if (prev != NULL) {
|
||||
/* Self-import: the driver concatenates the
|
||||
|
||||
@@ -1323,6 +1323,12 @@ parseuse(Parser *p)
|
||||
Pos pp = p->cur.pos;
|
||||
expect(p, TK_USE);
|
||||
Node *n = newnode(p->a, N_USE, pp);
|
||||
/* Keep n->pos at the import keyword for structural diagnostics. Go's
|
||||
* import declaration position is the first spec token: the explicit alias
|
||||
* when present, otherwise the path. */
|
||||
n->usefile = p->cur.pos.file;
|
||||
n->useline = p->cur.pos.line;
|
||||
n->usecol = p->cur.pos.col;
|
||||
const char *alias = NULL;
|
||||
const char *first;
|
||||
if (p->cur.kind == TK_UNDER) {
|
||||
|
||||
@@ -349,6 +349,10 @@ struct Node {
|
||||
const char *usepath; /* N_USE: canonical, vendor-expanded identity;
|
||||
* initially equal to `usesource`. */
|
||||
const char *usealias; /* N_USE: explicit file-local alias, or NULL. */
|
||||
const char *usefile; /* N_USE: first import-spec token position;
|
||||
* alias when explicit, path otherwise. */
|
||||
int useline;
|
||||
int usecol;
|
||||
const char *usepkgname; /* N_USE: imported declared package name,
|
||||
* independent of the visible binding in `str`. */
|
||||
int useblank; /* N_USE: `_` spelling; no source binding. */
|
||||
|
||||
@@ -9033,6 +9033,293 @@ the valid persisted-byte contract is unchanged. Build workdir format remains
|
||||
`18`, test workdir format remains `19`, and semantic storage format remains
|
||||
`3`. No test-result cache is introduced.
|
||||
|
||||
### 11.46 Implemented effective-`init` import-binding recovery
|
||||
|
||||
An import whose effective file-local qualifier is `init` is rejected as
|
||||
`cannot import package as init - init must be a func`. The rule covers an
|
||||
explicit `init` alias and an unaliased dependency declared `package init`.
|
||||
Every resolved rejected occurrence reports the core error at the first
|
||||
import-spec token: the alias token when explicit, otherwise the first path
|
||||
token. The occurrence creates no qualifier and takes no part in unused,
|
||||
duplicate-binding, or declaration/import-collision recovery. A later
|
||||
`init.Name` therefore recovers independently as undefined. Resolution retains
|
||||
precedence, so a missing target fails without an additional effective-`init`
|
||||
diagnostic.
|
||||
|
||||
Only the file-local binding is rejected. The resolved source occurrence and
|
||||
its exact dotted dependency remain loader and graph provenance. Neither the
|
||||
effective qualifier nor the dependency's declared package name becomes
|
||||
canonical package, import, graph, action, symbol, `.wwi`, artifact,
|
||||
publication, or persistence identity.
|
||||
|
||||
The paragraphs above state the normative implementation contract and are
|
||||
**behavior derived from the pinned implementation**. The post-change results
|
||||
recorded in the proof subsection below are **directly measured WW behavior**.
|
||||
|
||||
#### Pinned evidence and fact classification
|
||||
|
||||
The sole authority is official Go 1.26.5 at
|
||||
`c19862e5f8415b4f24b189d065ed739517c548ba`:
|
||||
|
||||
- `(*Checker).collectObjects` resolves an import at lines 248–263, selects the
|
||||
explicit alias or imported declared name at lines 264–274, rejects effective
|
||||
name `init` and immediately continues at lines 275–278, before explicit
|
||||
import recording, `PkgName` construction, used-import tracking, or file-scope
|
||||
installation at lines 280–335 in
|
||||
[`cmd/compile/internal/types2/resolver.go`, lines 223–335](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L223-L335);
|
||||
- `(*Checker).unusedImports` examines only admitted imports at
|
||||
[`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),
|
||||
while package/file collision reconciliation is at lines 472–489 of that
|
||||
file;
|
||||
- the independent public checker implements the same effective-name rejection
|
||||
and immediate continuation in
|
||||
[`go/types/resolver.go`, lines 261–315, especially 290–293](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/resolver.go#L261-L315);
|
||||
- compiler-syntax `(*parser).importDecl` assigns the declaration position to
|
||||
the first import-spec token in
|
||||
[`cmd/compile/internal/syntax/parser.go`, lines 543–574](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/parser.go#L543-L574),
|
||||
with node-position ownership in
|
||||
[`cmd/compile/internal/syntax/nodes.go`, lines 10–31 and 56–64](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/nodes.go#L10-L64);
|
||||
- public `ast.ImportSpec.Pos` independently chooses the alias position when
|
||||
present and the path position otherwise in
|
||||
[`go/ast/ast.go`, lines 908–915 and 939–946](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/ast/ast.go#L908-L946);
|
||||
- compiler diagnostics are stably position-sorted in
|
||||
[`cmd/compile/internal/base/print.go`, lines 70–92](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/base/print.go#L70-L92); and
|
||||
- package loading and action construction remain separate from file binding in
|
||||
[`cmd/go/internal/load/pkg.go`, lines 757–805](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L757-L805)
|
||||
and
|
||||
[`cmd/go/internal/load/pkg.go`, lines 2024–2047](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L2024-L2047),
|
||||
[`cmd/go/internal/work/action.go`, lines 647–658](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L647-L658),
|
||||
and test-variant construction in
|
||||
[`cmd/go/internal/load/test.go`, lines 175–240](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L175-L240).
|
||||
|
||||
Official assertions are
|
||||
[`internal/types/testdata/check/importdecl0/importdecl0a.go`, lines 9–17](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/importdecl0/importdecl0a.go#L9-L17),
|
||||
which expects only the core error for an explicit `init` alias;
|
||||
[`test/fixedbugs/issue4517d.go`, lines 7–9](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/fixedbugs/issue4517d.go#L7-L9),
|
||||
which covers the explicit spelling; and
|
||||
[`test/fixedbugs/issue43962.dir/a.go`, lines 1–5](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/fixedbugs/issue43962.dir/a.go#L1-L5)
|
||||
with
|
||||
[`b.go`, lines 1–7](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/fixedbugs/issue43962.dir/b.go#L1-L7),
|
||||
which covers an imported package declared `init`.
|
||||
|
||||
Those resolver, syntax-position, loader/action branches and official
|
||||
assertions are **behavior directly implemented or asserted by pinned Go**.
|
||||
The immediate continuation proves that the rejected occurrence contributes no
|
||||
unused, duplicate-binding, or declaration/import-collision recovery; every
|
||||
rejected occurrence reports independently; a later selector sees no package
|
||||
binding; and missing resolution wins before effective-name checking. The
|
||||
separate loader/action path proves that rejecting the qualifier does not erase
|
||||
the resolved dependency occurrence or change canonical identity. Those
|
||||
conclusions are **behavior derived from the pinned implementation**. They
|
||||
honestly apply to WW's local, unquoted dotted-import, manifest-free model
|
||||
without adding modules, manifests, registries, lock files, caches, databases,
|
||||
CAS, network resolution, quoted/grouped/dot/general imports, or a source-level
|
||||
build language.
|
||||
|
||||
#### Fresh four-axis audit and direct pre-fix measurements
|
||||
|
||||
The fresh audit also reconfirmed two applicable but unselected differences:
|
||||
multiple named source files remain different across build, test, package, and
|
||||
import construction; shared test-process state and ordinary-abort behavior
|
||||
remain different across test runtime and package/imported mutable state. They
|
||||
remain genuine future candidates and were not reclassified as aligned or
|
||||
inapplicable. Effective-`init` recovery was selected because the pinned
|
||||
resolver supplies one exact, bounded semantic owner whose complete binding and
|
||||
lifecycle effects can be closed without redefining either broader gap.
|
||||
|
||||
Before this slice, the following observations were **directly measured WW
|
||||
behavior**. Every Cstage/WWstage pair had identical status, stdout bytes,
|
||||
stderr bytes, and artifact state:
|
||||
|
||||
| Input | Status and stdout | Pre-fix stderr SHA-256 | Pre-fix recovery |
|
||||
|---|---|---|---|
|
||||
| explicit unused `init` alias | 1; empty | `5248382591e88562ba9c3c648af47523f617be0dcaf16586dc46a2517f76fe58` | bogus unused, then core |
|
||||
| explicit alias used by `init.value()` | 1; empty | `a3189e9aeaf56d5cb8aa63aa1a4de07d5176af531555a5834315298e4e9105a8` | core, then undefined |
|
||||
| implicit declared-name `init`, unused | 1; empty | `c77b3cf3b052727d90662c9b33ba85c10cba133d25b1d8b41dd53e32e1ab4978` | bogus unused, then core |
|
||||
| implicit qualifier used by `init.value()` | 1; empty | `cf0cf83a83077736497cd54a801762912f97ced4684d6c878257edbc79958340` | core, then undefined |
|
||||
| two rejected occurrences plus use | 1; empty | `9124e49408a710e1ed49a0b39b46b0967b9fe4c5f8bc67b21205d174bcfaebee` | bogus redeclaration/alternate and unused; two cores; undefined |
|
||||
| rejected import plus top-level `let init` | 1; empty | `b5e48cfab10e95d581d64d59e28409c63c7f450fa09c8d2bd11d2bb1bf487c08` | bogus unused/collision/alternate plus correct declaration/core errors |
|
||||
| missing target under alias `init` | 1; empty | `5c0fd415bb8bc8791bb87cf7211f69eea06b87ea63f264f5b8b526cbd8ac1fd9` | only missing-package error; aligned precedence |
|
||||
| same-package test, unused alias | 1; exact `FAIL\n` | `9b40aac1b160743787b4782a967350b9c97a4bf414ea0c05ea9b926c85403f9e` | bogus unused, then core |
|
||||
| external test, later use | 1; exact `FAIL\n` | `e3fcb66e8fe4eabaf2bbac0b510c370dd904fca90daab334ad4cd9b82ac914f3` | core, then undefined |
|
||||
|
||||
Representative pre-fix output placed both the bogus unused diagnostic and the
|
||||
core error at generated-unit column 1, the `import` keyword:
|
||||
|
||||
```text
|
||||
...unit.new:3:1: error: "pkg.normal" imported as init and not used
|
||||
...unit.new:3:1: error: cannot import package as init - init must be a func
|
||||
```
|
||||
|
||||
Repeated imports additionally reported `init redeclared in this block`; a
|
||||
top-level declaration additionally reported
|
||||
`init already declared through import of package init`. These were checker
|
||||
recovery artifacts forbidden by the pinned immediate continuation. Pinned
|
||||
position ownership maps WW's `import init ...` spelling to the alias at source
|
||||
column 8.
|
||||
|
||||
Aligned controls succeeded in both stages with empty streams: blank
|
||||
`import _ pkg.normal;` emitted a stage-byte-identical artifact with SHA-256
|
||||
`20847ada6923ab0bfd1dff6a3c387e0c5adace74152b84e895bc420a2e4134e9`,
|
||||
and used `import stable pkg.normal;` emitted a stage-byte-identical artifact
|
||||
with SHA-256
|
||||
`1a8fefbc12d5ea4a2bca938db6fbcdb66f3a14bbc0f7371756e4b5862ed8935c`.
|
||||
Each rejected cold case created no new public artifact. A compiler wrapper
|
||||
recorded exactly two calls per stage—dependency compilation followed by root
|
||||
compilation with its canonical `.wwi`—and failure left the traced workdirs
|
||||
empty with no `.new`, `.install`, or `.wwtxn.*` residue. Changing the warm
|
||||
valid qualifier to `init` preserved the complete committed work-file manifest
|
||||
and public product byte for byte in both stages, with no active transaction
|
||||
residue.
|
||||
|
||||
The direct pre-fix four-axis result was:
|
||||
|
||||
- **Go-like build:** different diagnostic recovery and position; loading,
|
||||
graph, producer order, failure, cold no-publication, warm rollback, and
|
||||
cleanup were aligned;
|
||||
- **Go-like test:** different for same-package and external-test sources for
|
||||
the same checker reason; variant selection and final `FAIL\n` accounting
|
||||
were aligned;
|
||||
- **Go-like package:** different because a rejected qualifier spuriously
|
||||
participated in package-declaration collision recovery; canonical package
|
||||
identity was aligned; and
|
||||
- **Go-like import:** the core rejection and missing-import precedence were
|
||||
aligned, while binding admission, recovery exclusions, and diagnostic
|
||||
position were different.
|
||||
|
||||
#### Ownership and complete four-axis contract
|
||||
|
||||
The semantic owners are the transient import-spec position plus checker-local
|
||||
file-binding recovery:
|
||||
|
||||
- Cstage: `N_USE` in `cmd/wcc/ww.h:319-375`, import parsing in
|
||||
`cmd/wcc/parse.c:1321-1359`, and binding/recovery in
|
||||
`cmd/wcc/check.c:2911-2972,3987-4144`;
|
||||
- WWstage: the `N_USE` twin in `lib/ww/syntax/ast.ww:108-158`, import parsing
|
||||
in `lib/ww/syntax/decl.ww:9-50`, and binding/recovery in
|
||||
`selfhost/cmd/wcc/check.ww:178-221,418-434,7531-7675,8601-8650`.
|
||||
|
||||
Both parsers retain the existing import-keyword node position for unrelated
|
||||
structural diagnostics and record a transient first-spec-token position. Both
|
||||
checkers identify a nonblank resolved occurrence whose effective qualifier is
|
||||
exactly `init`, emit the core diagnostic in source order before qualifier-use,
|
||||
duplicate, unused, collision, or installation recovery, exclude it from every
|
||||
such table, and silently omit the rejected qualifier from file scope. The AST
|
||||
occurrence itself remains intact.
|
||||
|
||||
Declared-name binding in `cmd/w6c/main.c:315-357` and
|
||||
`selfhost/cmd/w6c/main.ww:410-458`, driver graph construction in
|
||||
`cmd/ww/main.c:3396-3425,3630-3689` and its self-hosted twin, and all canonical
|
||||
dotted identity rules remain unchanged.
|
||||
|
||||
- **Go-like build:** a resolved invalid root or dependency reaches the normal
|
||||
dependency action, then the owning compiler action rejects once per
|
||||
occurrence at the spec token without qualifier-recovery noise. Producer
|
||||
failure still prevents new publication and preserves a prior committed
|
||||
generation.
|
||||
- **Go-like test:** the same checker rule applies after existing filename and
|
||||
role selection to production-test, same-package-test, external-test, and
|
||||
test-only sources. Failed products keep command-owned `FAIL\n` accounting and
|
||||
are not installed; test runtime and process topology do not change.
|
||||
- **Go-like package:** rejected qualifiers do not collide with package-scope
|
||||
declarations. An imported declared name `init` remains the effective name
|
||||
tested for an implicit import, but declared name, source role, package
|
||||
identity, and package declaration semantics do not change.
|
||||
- **Go-like import:** explicit and implicit effective-`init` bindings reject at
|
||||
the alias/path spec token; every occurrence rejects; later `init.Name` is
|
||||
undefined; missing resolution keeps precedence; and rejected bindings
|
||||
create no unused, duplicate, or declaration-collision recovery.
|
||||
|
||||
#### Loading, lifecycle, parity, and formats
|
||||
|
||||
Filename/platform/test-role eligibility and selected-source UTF-8, BOM, NUL,
|
||||
package-clause, and import-order validation remain earlier and unchanged.
|
||||
Excluded and wrong-target files remain non-inputs. Existing dependencies load
|
||||
normally; a missing dependency fails before checker binding recovery. Exact
|
||||
canonical paths continue to own package nodes, sorted/deduplicated graph edges,
|
||||
action keys, symbols, `.wwi` files, artifacts, publication, and invalidation.
|
||||
Dependency compilation still precedes the rejecting root action; no action
|
||||
ordering, scheduling, transaction, concurrency, or package-product topology
|
||||
changes.
|
||||
|
||||
Only transient parser/checker state changes. No valid compiler output,
|
||||
assembler, archiver, linker, initialization, runtime, test-runtime, or
|
||||
publication path changes. Invalid root input cannot complete its assembly,
|
||||
object, archive, link, retained-test, install, or runtime phases. Dependency or
|
||||
root producer failures, runtime failures, publication-only failures, and
|
||||
cleanup failures keep their existing owners and classifications.
|
||||
|
||||
Cold rejection creates no new public or retained root product and commits no
|
||||
partial unit, `.wwi`, object, archive, executable, tool record, stamp, or
|
||||
result. Warm rejection preserves the previous complete committed generation
|
||||
and public product. Dependency reuse and ordinary source invalidation remain
|
||||
unchanged, and a rejected generation never replaces root state. The rule adds
|
||||
no process, signal, wait, timeout, cancellation, shared state, or lock, so
|
||||
established request isolation, interruption rollback, descendant cleanup, and
|
||||
concurrent valid/invalid request behavior remain unaffected. Failure leaves no
|
||||
active `.new`, `.install`, `.wwtxn.*`, adjacent `.sepwork`, capture, scratch,
|
||||
or tool-stage transaction residue; the fixed recipe-owned `out/bootstrap` tree
|
||||
is not transaction residue.
|
||||
|
||||
The WW-native `effective_init_imports_never_enter_binding_recovery` observer
|
||||
owns explicit and implicit bindings; unused, used, repeated,
|
||||
declaration-collision, selector, and missing-target recovery; valid blank and
|
||||
explicit-alias controls; ordinary and imported builds; applicable
|
||||
production/same/external/test-only source roles; fixed-path stage
|
||||
status/stdout/stderr parity; invalid binding rejection inside a dependency
|
||||
reached through an imported root; cold empty-workdir rollback; warm
|
||||
preservation; valid artifact-byte parity; and residue cleanup. Concrete
|
||||
post-change statuses, diagnostic bytes and hashes, artifact hashes, and ordered
|
||||
full-gate results are recorded only after focused and full validation; they are
|
||||
not inferred from the implementation.
|
||||
|
||||
No persisted-byte contract changes. Import-spec coordinates and recovery
|
||||
tables are transient compiler state. Build workdir format remains `18`, test
|
||||
workdir format remains `19`, semantic storage format remains `3`, and no test
|
||||
result cache is introduced.
|
||||
|
||||
#### Direct post-fix proof and ordered validation
|
||||
|
||||
The following results are **directly measured WW behavior**. Both rebuilt
|
||||
stages reject explicit and implicit effective-`init` bindings at spec-token
|
||||
column 8. Every occurrence produces one core error; rejected bindings produce
|
||||
no unused, duplicate-binding, declaration/import-collision, or alternate-
|
||||
location recovery. Independent invalid declarations and later undefined
|
||||
selectors remain diagnosed, while missing-package resolution retains
|
||||
precedence.
|
||||
|
||||
A fixed-path explicit-unused probe produced identical Cstage and WWstage
|
||||
results: status 1, empty stdout, and 131-byte stderr with SHA-256
|
||||
`615a06b827a41ce214b9e76049b402be94680a14ba5e3416455d7a628185c7d5`.
|
||||
The valid blank-plus-explicit-alias control ran with status 41 and had identical
|
||||
unit, `.wwi`, assembly, object, archive, generated-init, and executable bytes
|
||||
across stages. Its 4,317-byte executable had SHA-256
|
||||
`64a48e8b18f035b8598fbe39feedc5a0e6df45d256305cb0f32e8b27a0471b92`.
|
||||
|
||||
The WW-native observer passed for ordinary roots, a rejecting imported
|
||||
dependency, production-test, same-package-test, external-test, and test-only
|
||||
sources. It directly proved normalized diagnostic parity, cold empty-workdir
|
||||
rollback and no publication, warm preservation and exact reuse of every
|
||||
committed semantic file and public product, valid artifact-byte parity, and no
|
||||
active transaction residue. `out/bin/test_300_check` passed 73/73 checks.
|
||||
|
||||
All ordered full gates then passed serially in the required order:
|
||||
|
||||
1. `make -j4 JOBS=4 test`
|
||||
2. `make -j4 JOBS=4 test-commit`
|
||||
3. `make -j4 JOBS=4 test-byteid`
|
||||
4. `make -j1 JOBS=1 test-bootstrap`
|
||||
5. `make -j1 JOBS=1 test-platform`
|
||||
6. `make -j1 JOBS=1 test-all`
|
||||
|
||||
The first byte-identity invocation encountered one transient `roster` timeout
|
||||
under four-way load. The exact isolated row passed, and the unchanged third
|
||||
gate was rerun successfully: 161 language files and 1,421 data fixtures were
|
||||
byte-identical with zero pinned divergences. Bootstrap reached the ww2/ww3/ww4
|
||||
fixed point and round-tripped all five WWstage tools byte-identically; the
|
||||
platform gate produced a byte-identical dynamic-link result. No production
|
||||
code changed after the successful ordered sequence began.
|
||||
|
||||
## 12. Candidate architectures and hard-gate decision
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
15
docs/spec.md
15
docs/spec.md
@@ -328,6 +328,21 @@ ImportPath = ident { "." ident } .
|
||||
Neither form exposes an imported declaration as a bare `Name`; ordinary
|
||||
unqualified lookup remains limited to lexical, builtin, and same-package
|
||||
declarations.
|
||||
- An import whose effective file-local qualifier is `init` is invalid. This
|
||||
includes both `import init acme.codec;` and an unaliased import whose target
|
||||
declares `package init;`. Each resolved occurrence is rejected at its first
|
||||
import-spec token (the explicit alias when present, otherwise the first path
|
||||
token) as `cannot import package as init - init must be a func`. The rejected
|
||||
qualifier is never installed and does not participate in unused-import,
|
||||
duplicate-binding, or declaration/import-collision recovery; consequently a
|
||||
later `init.Name` independently reports an undefined `init`. Repeated invalid
|
||||
occurrences each report the core error. Import resolution retains
|
||||
precedence, so a missing target fails as missing without an additional
|
||||
effective-`init` error. A resolved rejected occurrence remains source and
|
||||
graph provenance for its exact dotted target: only its file-local qualifier
|
||||
binding is absent. Canonical package, import, graph, action, artifact,
|
||||
symbol, `.wwi`, publication, and persistence identity never derives from the
|
||||
rejected qualifier or from the target's declared name.
|
||||
- `import _ acme.codec;` is a blank side-effect import. The lone `_` creates no
|
||||
qualifier, exposes no bare declaration, and is never diagnosed as unused.
|
||||
It is nevertheless a real import occurrence: resolution and all missing,
|
||||
|
||||
@@ -568,6 +568,39 @@ exact-argv, command, and persistent-workdir observers, the package suite proves
|
||||
archive-only link argv and exact warm/rejection-state behavior without
|
||||
duplicating those broader mechanisms in this observer.
|
||||
|
||||
The focused dual-stage `effective_init_imports_never_enter_binding_recovery`
|
||||
observer is
|
||||
the acceptance owner for imports whose effective file-local qualifier is
|
||||
`init`. Its validation matrix is required to cover an explicit alias and an
|
||||
implicit qualifier obtained from `package init`; unused and used occurrences;
|
||||
repeated rejected occurrences; a package-scope declaration named `init`;
|
||||
later `init.Name` recovery; missing-target precedence; and valid blank and
|
||||
non-`init` explicit-alias controls. It must exercise ordinary root and imported
|
||||
builds plus production-test, same-package-test, external-test, and test-only
|
||||
source roles where applicable. Each resolved rejected occurrence must produce
|
||||
one core diagnostic at the alias/path spec token and no unused, duplicate, or
|
||||
declaration-collision noise from that binding; a later selector remains an
|
||||
independent undefined-name error. The resolved dotted occurrence must remain
|
||||
in dependency graph and action provenance while the rejected qualifier stays
|
||||
absent from file scope.
|
||||
|
||||
That observer's lifecycle proof requires fixed-path Cstage/WWstage status,
|
||||
stdout, and stderr parity; cold absence of public and retained products; warm
|
||||
preservation of the complete committed work-file manifest and public product;
|
||||
imported-dependency rejection and cold empty-workdir rollback; valid control
|
||||
artifact-byte parity; and absence of active `.new`, `.install`, `.wwtxn.*`,
|
||||
adjacent `.sepwork`, capture, scratch, and tool-stage transaction residue.
|
||||
Producer/runtime failure, request concurrency, interruption, descendant
|
||||
cleanup, and test-process topology retain their established owners because
|
||||
the selected checker rejection adds no producer, runtime, process, signal,
|
||||
timeout, shared-state, or publication boundary. The observer passed in both
|
||||
stages, including imported-dependency rejection, all four test-source roles,
|
||||
cold and warm rollback, normalized diagnostic parity, valid artifact-byte
|
||||
parity, and residue checks. The complete ordered `test`, `test-commit`,
|
||||
`test-byteid`, `test-bootstrap`, `test-platform`, and `test-all` gates also
|
||||
passed. Build workdir format remains 18, test workdir format remains 19,
|
||||
semantic storage remains 3, and no test-result cache is introduced.
|
||||
|
||||
The same package owner contains the focused
|
||||
`directory_test_execution_working_directory` observer. It constructs
|
||||
independent temporary directories from an unrelated caller cwd and compares
|
||||
|
||||
@@ -130,6 +130,9 @@ export type node = struct {
|
||||
usesource: str, // N_USE: immutable dotted source spelling
|
||||
usepath: str, // N_USE: canonical vendor-expanded identity
|
||||
usealias: str, // N_USE: explicit file-local alias, or empty
|
||||
usefile: str, // N_USE: first spec token (alias, otherwise path)
|
||||
useline: i32,
|
||||
usecol: i32,
|
||||
usepkgname: str,// N_USE: imported declared package name
|
||||
useblank: i32, // N_USE: `_` spelling; no source binding
|
||||
pkgname: str, // declared package name; independent of canonical nmod
|
||||
@@ -150,7 +153,7 @@ export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
|
||||
// fval cast-init: 990's wwdump TK_FLOAT diff requires this file
|
||||
// to tokenise identically through C and ww (lex.ww:382 has the
|
||||
// same workaround for the cstage %g-formats vs ww-skips divergence).
|
||||
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, packed=0, type_=nil, tsuffix="", nmod="", usesource="", usepath="", usealias="", usepkgname="", useblank=0, pkgname="", sourceid=0, used=0, initfn=0, initsynthetic=0, runtimeinit=0, initorder=0u64, linksym="", refdecl=nil, initmark=0u64, imported=0})!;
|
||||
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, packed=0, type_=nil, tsuffix="", nmod="", usesource="", usepath="", usealias="", usefile="", useline=0, usecol=0, usepkgname="", useblank=0, pkgname="", sourceid=0, used=0, initfn=0, initsynthetic=0, runtimeinit=0, initorder=0u64, linksym="", refdecl=nil, initmark=0u64, imported=0})!;
|
||||
return n;
|
||||
};
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ fn parseuse(p: *parser) *node = {
|
||||
advance(p);
|
||||
let n: *node = newnode(nkind.N_USE, pf, pl, pc);
|
||||
n.nmod = p.curmod;
|
||||
// Keep the node position at the import keyword for structural diagnostics.
|
||||
// The binding error belongs to the first spec token, matching Go's alias-or-
|
||||
// path ImportDecl position.
|
||||
n.usefile = p.curfile;
|
||||
n.useline = p.curline;
|
||||
n.usecol = p.curcol;
|
||||
let alias: str;
|
||||
let first: str;
|
||||
if (p.curkind == tkind.TK_UNDER) {
|
||||
|
||||
@@ -175,6 +175,11 @@ fn declmod(file: *syntax.node, d: *syntax.node) str = {
|
||||
return empty;
|
||||
};
|
||||
|
||||
fn invalidinitimport(u: *syntax.node) bool = {
|
||||
return u != nil && u.kind == syntax.nkind.N_USE && u.useblank == 0
|
||||
&& syntax.streq(u.str, "init");
|
||||
};
|
||||
|
||||
// Map a source-file qualifier to canonical identity. Looking up the marker for
|
||||
// a possible DOT must not itself count as usage; only qualified resolution
|
||||
// marks the owning occurrence.
|
||||
@@ -192,6 +197,7 @@ fn findusepath(file: *syntax.node, modtag: str, source: i32, alias: str,
|
||||
let u: *syntax.node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && u.useblank == 0
|
||||
&& !invalidinitimport(u)
|
||||
&& u.sourceid == source) {
|
||||
if (syntax.streq(u.str, alias)) {
|
||||
let um: str = declmod(file, u);
|
||||
@@ -425,12 +431,7 @@ fn installdecl(c: *checker, file: *syntax.node, d: *syntax.node) void = {
|
||||
cerr("' cannot import itself\n"); c.errs += 1i32;
|
||||
};
|
||||
if (d.useblank != 0) { return; };
|
||||
if (syntax.streq(nm, "init")) {
|
||||
importdiagprefix(d);
|
||||
cerr("cannot import package as init - init must be a func\n");
|
||||
c.errs += 1;
|
||||
return;
|
||||
};
|
||||
if (invalidinitimport(d)) { return; };
|
||||
// #30 value-before-use: a same-leaf VALUE/type decl is already
|
||||
// installed (source order placed `fn aa` before `import aa`).
|
||||
// Promote it in place with use_alias instead of installing a
|
||||
@@ -7527,6 +7528,20 @@ fn importdiagprefix(n: *syntax.node) void = {
|
||||
cerr(strconv.i32tos(n.col, strconv.base.DEC)); cerr(": error: ");
|
||||
};
|
||||
|
||||
fn importbindingdiagprefix(n: *syntax.node) void = {
|
||||
let file: str = n.usefile;
|
||||
let line: i32 = n.useline;
|
||||
let col: i32 = n.usecol;
|
||||
if (file.len == 0) {
|
||||
file = n.file;
|
||||
line = n.line;
|
||||
col = n.col;
|
||||
};
|
||||
cerr(file); cerr(":");
|
||||
cerr(strconv.i32tos(line, strconv.base.DEC)); cerr(":");
|
||||
cerr(strconv.i32tos(col, strconv.base.DEC)); cerr(": error: ");
|
||||
};
|
||||
|
||||
fn importdiagalt(n: *syntax.node, name: str) void = {
|
||||
cerr("\t"); cerr(n.file); cerr(":");
|
||||
cerr(strconv.i32tos(n.line, strconv.base.DEC)); cerr(":");
|
||||
@@ -7534,6 +7549,22 @@ fn importdiagalt(n: *syntax.node, name: str) void = {
|
||||
cerr(": other declaration of "); cerr(name); cerr("\n");
|
||||
};
|
||||
|
||||
// Pinned Go 1.26.5 types2 rejects an effective import binding named init and
|
||||
// immediately continues before creating its PkgName. Diagnose every resolved
|
||||
// occurrence at the first import-spec token, while retaining its loader-owned
|
||||
// dependency edge and excluding it from binding recovery below.
|
||||
fn rejectinitimports(c: *checker, file: *syntax.node) void = {
|
||||
let u: *syntax.node = file.list;
|
||||
for (u != nil) {
|
||||
if (invalidinitimport(u)) {
|
||||
importbindingdiagprefix(u);
|
||||
cerr("cannot import package as init - init must be a func\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
u = u.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn topdeclkind(d: *syntax.node) bool = {
|
||||
return d != nil && (d.kind == syntax.nkind.N_TYPEDECL
|
||||
|| d.kind == syntax.nkind.N_DEF || d.kind == syntax.nkind.N_FNDECL
|
||||
@@ -7581,10 +7612,12 @@ fn checkimportredeclarations(c: *checker, file: *syntax.node) void = {
|
||||
let u: *syntax.node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && u.useblank == 0
|
||||
&& !invalidinitimport(u)
|
||||
&& u.imported == 0) {
|
||||
let v: *syntax.node = file.list;
|
||||
for (v != u) {
|
||||
if (v.kind == syntax.nkind.N_USE && v.useblank == 0
|
||||
&& !invalidinitimport(v)
|
||||
&& v.imported == 0
|
||||
&& v.sourceid == u.sourceid && syntax.streq(v.str, u.str)) {
|
||||
importdiagprefix(u); cerr(u.str);
|
||||
@@ -7605,6 +7638,7 @@ fn checkimportusageandcollisions(c: *checker, file: *syntax.node) void = {
|
||||
let u: *syntax.node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && u.useblank == 0
|
||||
&& !invalidinitimport(u)
|
||||
&& u.imported == 0 && u.used == 0) {
|
||||
let path: str = u.usesource;
|
||||
if (path.len == 0) { path = u.usepath; };
|
||||
@@ -7629,6 +7663,7 @@ fn checkimportusageandcollisions(c: *checker, file: *syntax.node) void = {
|
||||
u = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE && u.useblank == 0
|
||||
&& !invalidinitimport(u)
|
||||
&& u.imported == 0
|
||||
&& syntax.streq(d.str, u.str)) {
|
||||
let path: str = u.usesource;
|
||||
@@ -8608,6 +8643,7 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
|
||||
file.list = usenode;
|
||||
};
|
||||
};
|
||||
rejectinitimports(c, file);
|
||||
rejectnonfunctionmaindecls(c, file);
|
||||
markimportuses(c, file);
|
||||
checkimportredeclarations(c, 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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user