ww imports: reject init bindings before recovery

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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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. */

View File

@@ -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 248263, selects the
explicit alias or imported declared name at lines 264274, rejects effective
name `init` and immediately continues at lines 275278, before explicit
import recording, `PkgName` construction, used-import tracking, or file-scope
installation at lines 280335 in
[`cmd/compile/internal/types2/resolver.go`, lines 223335](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 706740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740),
while package/file collision reconciliation is at lines 472489 of that
file;
- the independent public checker implements the same effective-name rejection
and immediate continuation in
[`go/types/resolver.go`, lines 261315, especially 290293](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 543574](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 1031 and 5664](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 908915 and 939946](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 7092](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 757805](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L757-L805)
and
[`cmd/go/internal/load/pkg.go`, lines 20242047](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L2024-L2047),
[`cmd/go/internal/work/action.go`, lines 647658](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 175240](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 917](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 79](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 15](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/fixedbugs/issue43962.dir/a.go#L1-L5)
with
[`b.go`, lines 17](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.

View File

@@ -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,

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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) {

View File

@@ -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);

View File

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