diff --git a/docs/build-system.md b/docs/build-system.md index 6beaf004..afa78ffa 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -2634,10 +2634,13 @@ commit and are implementation evidence, not requirements for the replacement. ### 11.2 Conflated identities and accidental behavior +This table records the baseline that the implemented slices below replaced; +sections 11.6–11.18 are authoritative where they conflict with it. + | Concept that must be separate | Current conflation or accident | |---|---| -| package identity | Dotted import spelling is graph key, module/symbol prefix, artifact basename, and link identity. | -| declared name | A directory's leaf is checked against it for imports, but root directories and literal file roots receive different validation. `.wwi` itself retains only the leaf package name. | +| package identity | Before the local-package slices, dotted import spelling was simultaneously graph key, module/symbol prefix, artifact basename, and link identity. The implemented loader now separates source spelling, expanded canonical identity, physical directory, and storage locator. | +| declared name | Before section 11.18, imported directories required their declared name to equal the import-path leaf and `.wwi` retained only that leaf. The implemented compiler/export path now carries the declaration independently. | | filesystem location | Ordered search roots silently choose/shadow a location; the same physical directory may be compiled under two import identities, while duplicate locations for one spelling produce no collision diagnostic. Paths are lexical, not content identities. | | package versus file | Directories create separate-compilation nodes; files disappear into owners. The same `package` syntax means two compilation models. | | artifact versus identity | `.wwi/.s/.o/.a` names artifacts; root aliases to `__root`, which can collide with a real import. | @@ -2802,19 +2805,22 @@ falls back to directory lookup through the entry package's directory, explicit containing the directory wins over an earlier file decoy. There is no network, manifest, or imported-file fallback. Explicit single-file CLI roots retain their raw-unit compatibility path. The loader uses the compiler frontend's -imports-only parser, unions duplicate imports, byte-sorts direct edges, interns -canonical directory actions, and reports self-imports and stable cycle chains -before compilation. +imports-only parser, retains every real import occurrence with its owning +source file and position, byte-sorts and deduplicates the resulting canonical +direct edges, interns canonical directory actions, and reports self-imports and +stable cycle chains before compilation. Occurrence retention makes contextual +`internal` and `vendor` checks run at every import site; it does not duplicate +package actions or compiler inputs. A directory package consists of its immediate `.ww` entries whose basenames do not begin `.` or `_`: regular files and symlinks targeting regular files are included under the entry name, while symlinks targeting directories are ignored. The production variant excludes `*_test.ww`; each variant retains -byte-sorted filename order. Every selected file must -declare the same package. An ordinary importable directory's declaration must -equal the final component of its import path. A selected command directory -instead declares `package main` to validate command kind while retaining its -complete canonical import identity. A source import of a command package from a +byte-sorted filename order. Every selected production file must declare the +same package name, but that declaration is independent of the directory name +and every component of the canonical import path. A selected command directory +declares `package main` while retaining its complete canonical import identity. +A source import of a command package from a different directory is rejected; the one same-directory exception is an external `main_test` variant's canonical import of its production action. Two ordinary logical identities for one physical directory are rejected rather @@ -2842,9 +2848,11 @@ path and deduplicated by the loader; a source spelling expanded through `vendor` additionally receives the non-dependency `--import-map` described in section 11.16. No transitive `.wwi` is passed. Origin-tagged facts inside those direct artifacts are compiler data, not source -imports: a source qualifier is visible only when its owning package directly -imports it, and private members, transitive-only qualifiers, bare values, and -bare types remain compiler errors. In `-c` package mode the compiler parses each +imports: a source qualifier is visible only in the source file that directly +imports it. The qualifier is the imported export's declared package name, not +the source spelling or import-path leaf. Private members, transitive-only +qualifiers, bare values, and bare types remain compiler errors. In `-c` package +mode the compiler parses each export independently, then coalesces repeated exported type/constant facts with the same origin, kind, and name, preserving one nominal type identity across diamonds; raw non-package `w6c` retains its existing one-source behavior. The @@ -2855,8 +2863,9 @@ boundary is live in production Cstage and WWstage compilers and drivers. A selected production root is one normal package action. Its finalized canonical import identity tags its owner-only unit; it receives only direct exports, emits `.wwi`, `.o`, and a deterministic `.a`, and is compiled exactly -once. The declared package name validates the last component of that identity -for ordinary importable packages. After loading and identity finalization, the +once. The declared package name is semantic package content but never validates, +shortens, aliases, or replaces that identity. After loading and identity +finalization, the declaration also selects the terminal build action: `package main` is a command and every other valid declaration is a compile-only library. A command root receives the narrow compiler `--entry` flag, which controls bare `main` codegen, @@ -2869,8 +2878,8 @@ package declaration to classify and remains a raw command unit; it does not participate in canonical directory-package interning. The package driver still uses the parser-only `--command-package` marker for command test variants that must remain ordinary archive code. Neither marker -changes export identity, and imported interfaces remain under strict leaf -validation. The linkers seed `main` before archive selection, so the existing +changes export identity, and imported interfaces retain independent canonical +owner and declared-name records. The linkers seed `main` before archive selection, so the existing WWAR member protocol needs no special root object or format change. Publication is separate from that semantic action choice. `ww build -o lib.a @@ -2885,7 +2894,7 @@ action identity. The historical action-selecting `-p` exception is removed and rejected as an unknown build flag. Assembly-only `-S` still stops before object, archive, publication, or link production. A literal directory is reverse-resolved through the active source roots or receives the deterministic -local identity described below; its declared leaf can never invent or truncate +local identity described below; its declaration can never invent or truncate that identity. Two cold builds with identical inputs are required to produce byte-identical requested products. Compiler intrinsics keep their package-mode runtime ABI independent of transitive source interfaces (for example, `alloc` @@ -2918,7 +2927,7 @@ publication spelling, `FILE.wwi.new`, so an incomplete archive/export pair is never caused by a late path-overflow failure. Package loading, cycle detection, and closure validation retain diagnostic precedence over this publication-only check, and `-S` does not validate a publication path it never consumes. -Build workdir format 13 and test workdir format 12 invalidate older unit +Build workdir format 14 and test workdir format 13 invalidate older unit vouchers before reuse because source binding and vendor-directory identity now participate in compiler argv and persistent unit semantics. Thereafter an equivalent warm library build invokes no tools, a private dependency change @@ -2956,21 +2965,28 @@ package variant: files followed by byte-sorted matching `package p` test files. It is distinct from production so its private production declarations and test declarations remain isolated to that test product. -- The external variant selects only matching `package p_test` files. Its - `import p` is a direct edge to the same ordinary production action used by +- When production sources establish `p`, the external variant selects only + matching `package p_test` files, where `p` is the production declaration + rather than an import-path leaf. A test-only directory may establish its own + one consistent test package name, matching pinned `go/build` classification. + A real source import of the production package uses its canonical import + path and is a direct edge to the same ordinary production action used by ordinary products and transitive imports; there is no external-production role or artifact. - Generated main is a separate package action whose owner-only generated unit declares `package main` and imports exactly the selected variant and test support. It consumes those direct `.wwi` files, emits its own `.wwi/.o/.a`, - and alone receives compiler `-T --entry`. + and alone receives compiler `-T --entry`. Its target edge also receives the + compiler-private `--test-target-package ` binding. That + private canonical-path qualifier prevents a tested command declared `main` + from colliding with generated main; it is not user alias syntax. The authoritative directory-action identity is the triple **(canonical filesystem directory, canonical ordinary import path, semantic variant)**. The semantic variants are production, internal production-plus-test, and external `_test`; generated main and the reserved test-support alias are -explicit non-directory action classes. Source package name validates the leaf -of the bound import path but is not itself a second identity. Requested-root +explicit non-directory action classes. The declared package name is stored +separately and is not part of canonical directory/path interning. Requested-root state, discovery role, product ordinal, output path, persistent artifact key, and discovery order never enter the triple. @@ -2999,17 +3015,19 @@ invocation, each still-unbound directory is finalized by this exact algorithm: root cannot rename an explicitly resolved package. 4. If no active root can represent the directory, bind the reserved, non-source-importable identity - `__wwlocal.p.`. The + `__wwlocal.p`. The escape is injective and reversible over path bytes: ASCII letters and digits are copied, `_` becomes `_u`, `/` becomes `_s`, and every other byte becomes `_xHH` with lowercase hexadecimal. Source imports of `__wwlocal` or any of its children are rejected, so this command-local identity creates no alias. -The selected full identity's final component is then validated against the -ordinary declared package name (or against the ordinary leaf obtained by -removing `_test` for the external variant). The one command-kind rule is that a -selected command family declares `main`/`main_test` while keeping the finalized -ordinary identity unchanged. A source import of that command from another +The selected full identity is never validated against the ordinary declared +package name. Production and internal variants retain the production +declaration; when production exists, an external variant is admitted only as +`_test`. A test-only directory instead establishes +one consistent test package declaration itself. The one command-kind rule is that a selected +command family declares `main`/`main_test` while keeping the finalized ordinary +identity unchanged. A source import of that command from another directory rejects as a program before tools; a colocated external command test may reuse the canonical production action. There is no fallback from an empty import path to a declaration name. Relative, absolute, and symlink spellings @@ -3027,9 +3045,10 @@ The derivation and diagnostics are implemented symmetrically in variant descriptors, preserves an explicitly resolved identity only for one direct request, and forwards a caller's `-w` semantic-action store unchanged. It never derives identity or persistent layout from a pattern traversal prefix -and does not add that prefix to import search. `w6c` and `wcc` continue to -consume and validate the finalized dotted identity; neither tool performs -directory lookup or introduces a package registry. +and does not add that prefix to import search. `w6c` and `wcc` consume the +finalized dotted identity as export/symbol owner while reading the declared +name independently from export data; neither tool performs directory lookup or +introduces a package registry. Artifact publication follows the semantic action instead of product order: production uses the full finalized ordinary identity, internal appends @@ -3352,11 +3371,13 @@ and any sorted driver-private vendor/import-map voucher comments, but no dependency body. When exact source spelling differs from a selected expanded canonical identity, the drivers also pass sorted, unique `--import-map ` triples. A map must -preserve the import leaf and target an ordinary direct `--import`; it adds no -export input or graph edge. The compilers rewrite only matching imports in the -primary source list before merging interfaces, leaving the source qualifier, -position, imported interface metadata, and generated/synthetic imports -untouched. Executable linking independently walks the full reachable package +target an ordinary direct `--import`; it adds no export input or graph edge. +The compilers rewrite only the matching primary import's canonical semantic key +before merging interfaces. They then obtain that target's declared package name +from its direct export and install it as the default qualifier in the owning +source file. Source spelling and position remain intact; generated/synthetic +imports use their explicit compiler-owned bindings. Executable linking +independently walks the full reachable package closure and passes archives, never interfaces. The same path handles an ordinary package, the production-plus-internal-test variant, the external test package and its reused production package, compiler-generated test main, and @@ -3533,14 +3554,13 @@ and storage-address derivation. The complete `path`, together with the semantic `variant` and `role`, is the persisted/compiler owner carried through source-import edges, module-reset and export ownership, compiler `--import` arguments, generated-main construction, and symbol qualification; `storage` -participates in none of those identities. The reversible outside-root form -remains -`__wwlocal.p.` and is +participates in none of those identities. The reversible outside-root form is +`__wwlocal.p` and is allocated to its exact length. It is neither truncated nor replaced by a digest, and `__wwlocal` remains unavailable to source imports. Package -declarations continue to validate the selected package kind and leaf; they do -not supply a missing identity and do not alter a command package's canonical -path. +declarations classify package kind and contribute semantic export content; they +do not validate a path leaf, supply a missing identity, or alter a command +package's canonical path. Short actions retain their established `.unit.ww`, `.wwi`, `.s`, `.o`, and `.a` basenames when the basename plus `.unit.new` fits the 255-byte supported @@ -3712,6 +3732,9 @@ The Cstage representation is exact and deliberately small: `depcap`; - each `seppkg.context_state` is a lazily extended, zero-filled `unsigned char *` with `context_cap`; +- each package owns a dynamically grown import-occurrence vector recording + kind, source spelling, source file, line, column, and stable dependency + action index; its separate dependency vector remains sorted/deduplicated; - parsed `sepproduct` values are a dynamically allocated vector, and each product stores its support-action index directly; and - package-load frames and topological-DFS frames are temporary dynamic vectors, @@ -3720,7 +3743,8 @@ The Cstage representation is exact and deliberately small: The WWstage representation is isomorphic. `sepgraph.pkg: []seppkg` and `sepgraph.context: []sepcontext` use allocated slice length as capacity and keep separate `n`/`ncontext` logical counts. Every `seppkg` owns a dynamically grown -`deps: []i32` with `ndeps` and a lazily zero-extended `contextstate: []u8`. +`bindings: []sepbind`, a dynamically grown `deps: []i32` with `ndeps`, and a +lazily zero-extended `contextstate: []u8`. Products, load frames, and topological frames use typed dynamically allocated slices. `internal/wwpackage` continues to construct one union command for all selected directory-test groups, but now checks the complete @@ -4051,7 +4075,9 @@ directory, rather than falling through. Source retains only the effective spelling, such as `lib.math`. Selection assigns the target the complete expanded canonical identity, such as `domain.app.vendor.lib.math`, and separately canonicalizes its physical -directory. The action key is that expanded identity and canonical directory, +directory. The target's declared name independently supplies the default +qualifier in each importing source file. The action key is that expanded +identity and canonical directory, plus the existing variant/role. Different physical vendor copies are distinct; different expanded vendor routes remain distinct even when symlinks converge on one physical directory; repeated resolutions of the same pair reuse one @@ -4110,12 +4136,13 @@ unique auxiliary mapping: ``` Both compilers require the map target to be an existing direct `--import`, -require source keys to be sorted and unique, require the final import leaf to -remain unchanged, and require a matching import in the primary source input. -After parsing only that primary input and before prepending imported interfaces, -the compiler replaces its semantic import key while preserving its source leaf, -position, and spelling bytes. Imported `.wwi` nodes and synthetic sources are -never rewritten. Thus the map adds no dependency or export input, expanded +require source keys to be sorted and unique, and require a matching import in +the primary source input. No leaf-equality condition exists. After parsing the +primary input and before prepending imported interfaces, the compiler replaces +its semantic import key while preserving source position and spelling. It then +reads the expanded target's declared name from the direct `.wwi` and installs +that name only in the declaring source file. Thus the map adds no dependency or +export input, expanded identity flows into self-contained `.wwi` ownership and symbols, and direct exports still have no transitive leakage. Linking remains independent: each executable consumes its root archive and complete reachable archive closure, @@ -4132,7 +4159,7 @@ Hex encoding keeps arbitrary legal filesystem bytes inside one comment. The metadata makes ordinary-to-vendor changes and vendor symlink retargeting invalidate the importer even when its source bytes and both already-warm export bytes happen to match. It contains no dependency body and does not alter source -positions. The current workdir formats are build 13 and test 12. An equivalent +positions. The current workdir formats are build 14 and test 13. An equivalent warm request remains a package-production no-op; an export change propagates only through ordinary direct-export comparison. @@ -4320,7 +4347,8 @@ and cannot make one variant visible to another. Vendor selection remains distinct from vendor import resolution. Selecting a directory below `vendor`, literally or through an explicitly vendor-rooted pattern, keeps its complete canonical local identity; it is never shortened to -the suffix after `vendor`. A generic recursive pattern does not expose vendored +the suffix after `vendor`, and its declaration never renames it. A generic +recursive pattern does not expose vendored descendants as ordinary short command roots. Independently, an allowed real source import still searches nearest-first below local `vendor`, creates the expanded identity described in section 11.16, supplies the required @@ -4355,8 +4383,8 @@ files. A compiler receives exactly the sorted, deduplicated `.wwi` exports of direct source dependencies and any required vendor import-map binding. Each executable link receives its root archive and complete reachable archive closure, never a `.wwi`. Canonical duplicate roots reuse the same action; the -pattern text and the product receiving an output do not affect semantic or -persistent identity. +pattern text, declared qualifier, and product receiving an output do not affect +canonical or persistent action ownership. Selection and package-clause validation finish before the coordinator creates its removable temporary plan, and the coordinator never creates persistent @@ -4457,6 +4485,208 @@ rejection-state preservation. The existing diamond and long-closure observers independently prove sorted/deduplicated direct `.wwi` cardinality and archive-only reachable link closures. +### 11.18 Implemented canonical identity, declared-name, and file-import-scope slice + +Directory-package identity and source naming are now independent throughout +local build and test. For example: + +```ww +// canonical import identity: acme.codec +package wire; +``` + +is imported with the existing dotted syntax: + +```ww +package main; +import acme.codec; +export fn main() i32 = { return wire.value(); }; +``` + +The package action, exports, symbols, archives, dependency edges, persistent +storage ownership, and link closure remain owned by `acme.codec`. Only the +source-file binding is named `wire`. The path leaf `codec` is not installed as +another qualifier, and a sibling source file receives no `wire` binding unless +that file has its own import. + +The implemented representation keeps five facts distinct: + +1. source import spelling, including its source file, line, and column; +2. contextually expanded canonical import identity; +3. canonical physical directory; +4. the one declared package name read from eligible source clauses and `.wwi` + package markers; and +5. the source-file-local default qualifier binding from that declaration to + the canonical target. + +Both drivers retain one dynamically allocated `sepbind` occurrence for every +real import site. The occurrence stores the source spelling and position plus a +stable action index. Contextual `internal` and vendor resolution is therefore +rechecked for every source import even if its target action already exists. A +separate package dependency vector unions those occurrences, deduplicates by +canonical action, and sorts by complete canonical identity/variant/role. That +vector alone supplies dependency traversal and compiler `--import` arguments. +Patterns, qualifiers, declared names, export closure facts, and generated edges +never create source dependency edges. + +The owner-only composed unit preserves byte-sorted source boundaries with one +`//ww:module-reset ` separator per file. Parser nodes carry a +source-section ID as well as canonical owner and declared package name. Each +real `N_USE` therefore belongs to one source section. `.wwi` emission repeats +canonical-owner/package markers as needed for contributing source sections and +retains imports only with the declarations from the file that owned them. +Interfaces remain source-like transitional data, but canonical owner and +declared name are no longer collapsed into one token. + +`w6c` and `w6c_ww` validate every direct export's leading canonical owner +against its paired `--import` path. After all direct exports are parsed, they +build canonical-path-to-declared-name metadata from those interfaces, apply any +vendor `--import-map` only to canonical identity, and bind the imported +declaration as the default qualifier of each primary source import. The +checkers and code generators select bindings by source-section ID and canonical +owner. Qualified and retained bare-import lookups mark only that file's binding +used. Two files may consequently bind the same name to different canonical +packages, while the graph still contains one edge/action for each target. + +Within one file, two imports that produce the same default qualifier are a +redeclared binding; the later unused binding is also reported. An unused import +is reported at its own import position even if a sibling file uses the same +qualifier or canonical dependency. A package-scope declaration collides with +an equal import binding from any contributing file, matching Go's reconciliation +of package and file scopes. Conflicting production package clauses remain a +loader-owned deterministic error before producers. Compiler-owned scope or use +errors may invoke the compiler, but the driver removes that action's staged +`.new` unit/export/assembly/object/archive files instead of renaming them. No +completion/status marker is written, and no publication occurs after a failed +compile. + +Package kind follows the declaration. `package main`, not a path component, +marks a command. A path ending in `main` remains importable when it declares a +different name. Any ordinary source import of a package declared `main` is +rejected as `package is a program, not an importable package`, +regardless of its path leaf. The one pinned loader exception is an external +test's exact same-directory import of the command production: it is rewired to +the forced-library test copy. WW admits only that canonical colocated edge. + +Test naming is likewise declaration-based. Production and internal-test +variants use the production declared name. When production files exist, +external files must declare `_test`; a test-only +directory may establish one consistent package name from its test files, as in +pinned `go/build`. External action identity remains the canonical production +identity plus the existing external variant suffix where that production +exists. Imports found only in internal or external test files belong only to +that variant. Support and generated-main actions retain their isolated +identities and archive closures. A generated dispatcher privately binds its +tested target through +`--test-target-package ` so a command variant declared `main` +does not collide with the dispatcher's own synthesized `main`; this is +compiler-generated wiring, not source alias syntax. Zero-test dispatchers mark +their compiler-owned target/support metadata imports consumed. + +Vendor expansion changes only canonical identity and physical selection. A +source spelling such as `lib.codec` can resolve to +`domain.app.vendor.lib.codec`, while the vendored package's declaration, for +example `package wire`, supplies the file-local qualifier. The driver emits one +sorted semantic `--import-map lib.codec domain.app.vendor.lib.codec` and one +direct export input despite repeated import occurrences in separate files. +The expanded identity continues to own symbols, `.wwi`, archive, voucher, and +link inputs. + +Canonical action interning remains the directory/path/variant model of sections +11.7 and 11.14. Independent file bindings never clone an action, and a declared +name never enters an artifact basename or storage locator. The name is semantic +content in the owner unit and export. Changing only a dependency's declaration +therefore keeps the same action identity but changes its export bytes, causes +each direct importer to be reconsidered, and stops propagation after an +importer's regenerated export is unchanged. An identical warm request remains +a producer no-op. Build workdir format 14 and test format 13 prevent reuse of +older vouchers that lack these semantics. + +Compiler argv still contains exactly the sorted, deduplicated `.wwi` exports of +direct canonical dependencies; no transitive `.wwi` and no qualifier-derived +path appears. Linker argv still contains only the executable root archive and +complete reachable archive closure plus runtime/native inputs. Publication and +completion occur only after the corresponding action/product succeeds. + +Responsibility is intentionally split as follows: + +- `internal/wwpackage` classifies production, internal, and external test files + from their declarations, derives the allowed external name from the + production declaration, expands request patterns, and submits variant roots. + It does not resolve imports, choose qualifiers, or create dependency edges. +- `cmd/ww/main.c` and `selfhost/cmd/ww/main.ww` own per-site parsing and + contextual resolution, canonical directory/action interning, declared-name + consistency, imported-command rejection, sorted dependency union, exact tool + argv, variant/generated-main construction, persistence, linking, and + publication. Their storage, diagnostics, call positions, and allocation + failures are isomorphic. +- `cmd/w6c`/`cmd/wcc` and `selfhost/cmd/w6c`/`selfhost/cmd/wcc` own export + owner/name reading and writing, file-local binding installation, collision + and unused-import diagnostics, name/type lookup, canonical symbol ownership, + and generated-dispatcher private qualification. Cstage and WWstage emit + byte-identical applicable interfaces, assembly, archives, and binaries. +- `cmd/wwtest` remains only the test-command dispatcher. + +The behavior follows pinned official Go 1.26.5 source at commit +`c19862e5f8415b4f24b189d065ed739517c548ba`: + +- `go/build.Package` stores `Dir`, `Name`, `ImportPath`, production files, and + test files independently, while package-clause consistency is checked during + file classification + ([`go/build/build.go`, lines 436–493](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L436-L493), + [`go/build/build.go`, lines 939–1049](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L939-L1049)). +- The loader interns by resolved `ImportPath`, keeps `ImportPath` and `Name` + separate, constructs edges from source imports, and rejects an imported + package whose `Name == "main"` except for the exact same-directory test case + ([`cmd/go/internal/load/pkg.go`, lines 633–636](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L633-L636), + [lines 757–806](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L757-L806), + [lines 2024–2047](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L2024-L2047)). +- The test loader builds distinct production/internal/external/generated-main + packages, derives external `Name` from `p.Name + "_test"`, handles the + same-directory command self-import, and rewrites it to the library-form test + copy + ([`cmd/go/internal/load/test.go`, lines 144–203](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L144-L203), + [lines 228–293](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L228-L293), + [lines 421–472](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L421-L472)). +- `go/types.Package` stores independent path and name. The resolver creates one + child scope per source file, uses the imported package's declared name when + no alias is present, inserts imports into that file scope, reconciles them + with package declarations, and reports unused imports + ([`go/types/package.go`, lines 26–40](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/package.go#L26-L40), + [`go/types/resolver.go`, lines 237–350](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/resolver.go#L237-L350), + [lines 463–480](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/resolver.go#L463-L480), + [lines 701–735](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/resolver.go#L701-L735)). + The production compiler mirrors those rules + ([`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), + [lines 473–486](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L473-L486), + [lines 706–740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740)). +- Compiler export import reconstructs and interns package descriptors by + canonical path while restoring their independently encoded package names and + imports + ([`cmd/compile/internal/importer/ureader.go`, lines 152–196](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/importer/ureader.go#L152-L196), + [`go/internal/gcimporter/ureader.go`, lines 224–244](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/internal/gcimporter/ureader.go#L224-L244)). +- Work actions consume cached canonical package objects and direct imports, + while test execution builds and links the isolated test action graph + ([`cmd/go/internal/work/action.go`, lines 437–455](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L437-L455), + [lines 628–659](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L628-L659), + [`cmd/go/internal/test/test.go`, lines 1133–1226](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1133-L1226)). + +The deliberately retained source grammar is `import dotted.path;`. Quoted Go +imports, explicit aliases, dot imports, and blank imports are not implemented. +Because bindings already retain source ownership and position separately from +canonical identity and declared name, adding explicit aliases later will not +require another package-wide namespace redesign. + +The focused native observer +`declared_name_identity_and_file_import_scope` generates every tree +temporarily and exercises both stages from independent cold roots. It proves +the identity/name/qualifier split, file-local collision and unused behavior, +one-edge/action reuse, command and imported-command rules, all test variants, +vendor expansion, recursive/direct selection, owner-only units, exact direct +exports and archive-only links, warm no-op behavior, declared-name invalidation, +rejection-state preservation, normalized argv, artifact/binary identity, +allocation-bearing runtime behavior, and request/product-order independence. + ## 12. Candidate architectures and hard-gate decision Five candidates were developed as coherent systems, not as feature bins. diff --git a/docs/spec.md b/docs/spec.md index 6147d9af..f47194f3 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -249,13 +249,25 @@ PackageClause = "package" ident ";" . ImportDecl = "import" ident ";" . ``` -- Every source file begins with a package clause. A directory of `.ww` - files sharing one package name compiles as a single module. -- `import foo;` makes module `foo`'s exported names available as - `foo.Name`. Self-import is rejected. -- An executable's entry module is `package main;` with a `fn main`. +- Every source file begins with a package clause. A directory of eligible + `.ww` files sharing one declared package name compiles as one package. The + declared name need not equal the directory name or the final component of its + canonical import identity. +- `import acme.codec;` loads the canonical package `acme.codec`. If that + package declares `package wire;`, the importing file sees its exported names + as `wire.Name`; `codec.Name` is not an additional binding. The import binding + is scoped to that source file. A sibling file must declare its own import. + The package dependency graph is the sorted, deduplicated union of the real + imports in all eligible files. Self-import is rejected. +- An executable package is one declared `package main` and containing a + `fn main`; path and directory spelling do not classify commands. An ordinary + import of a package declared `main` is rejected, except for the toolchain's + colocated external-test wiring. - Only names marked `export` (§5) are visible across module boundaries. +The implemented grammar remains dotted and unaliased. Quoted import paths, +explicit aliases, dot imports, and blank imports are not currently accepted. + --- ## 5. Declarations diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index 8bb32109..a8d5b19d 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -227,6 +227,20 @@ owns individual `@test` functions. Separate compilation is the only driver build path; no compatibility mode switch remains. +`test/package/package_test.ww` also owns the dual-stage +`declared_name_identity_and_file_import_scope` observer. It generates temporary +directory trees proving that canonical import identity, physical directory, +declared package name, and source-file default qualifier remain distinct; +imports are file-scoped while dependency edges are the package-wide sorted +union; command and production/internal/external/generated-main variants retain +canonical action ownership; vendor expansion changes identity but not the +declared qualifier; compiler argv contains only direct `.wwi` inputs; and its +named rejected actions leave neither committed nor staged action artifacts or a +published binary. Together with the existing directory, recursive, vendor, +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. + `ww build`, an explicit single-file `ww test -o `, and each successful directory-package `ww test -c` build publish `.sepwork` as a caller-owned artifact directory. The driver acquires it with one fresh `mkdir` and refuses diff --git a/internal/wwfixture/types.ww b/internal/wwfixture/types.ww index 710789d4..d577cd30 100644 --- a/internal/wwfixture/types.ww +++ b/internal/wwfixture/types.ww @@ -2,12 +2,12 @@ package wwfixture; def protocolversion: i32 = 1; def corpuscount: i32 = 1759; -def errorcount: i32 = 352; -def compilecount: i32 = 21; +def errorcount: i32 = 351; +def compilecount: i32 = 22; def runcount: i32 = 209; def runexitcount: i32 = 1177; def nativecount: i32 = 3518; -def corpushash: str = "38613db49b4c87a14d87fb9392e5500e17cc0c95767fb31d8eb9bc4fe2bf74c5"; +def corpushash: str = "47d731a8fd089ecdef9a1b9b94f3dfc06a733b7e03236014f095b068a67ce176"; type directive = enum i32 { ERROR = 0, diff --git a/test/byteid/wwi_test.ww b/test/byteid/wwi_test.ww index 35f82abc..d47fb60e 100644 --- a/test/byteid/wwi_test.ww +++ b/test/byteid/wwi_test.ww @@ -21,15 +21,16 @@ package wwi_test; // qualification of the compiler-private name on both stages. // // wwileaf — BUG-C (#11) regression pin: a decl-less / export-less -// primary module's `.wwi` `package` line must carry the module's real -// leaf, NOT the literal default "main". Table-driven over the three +// primary module's `.wwi` must preserve its independently parsed declaration, +// rather than substituting the literal default "main". Table-driven over the three // decl-less shapes routing the wwi_emit fallback (empty, comment-only, // nested dotted path a.b.c -> leaf c). Each row drives the REAL // producer->importer flow on BOTH stages: (a) the dep `.wwi` package // owner marker is the complete path and its package line equals the real leaf, // (b) the dep `.wwi` is byte-identical // across stages, (c) a root importing the dep RESOLVES on both stages -// and the two importer `.s` are byte-identical. The owner units and +// and both importers diagnose the same file-local unused binding after +// obtaining that declared name from export data. The owner units and // separate exports are fed straight to w6c / w6c_ww (not `ww build`): // the producer-unit shape `//ww:module-reset ` + body is exactly // what the driver's sep_emit_body emits. @@ -331,8 +332,8 @@ fn leafrow(tag: str, path: str, leaf: str, body: str, want: str) void = { "package ", leaf, ";\n", body)); - // Leg a — both stages emit the dep `.wwi`; the `package` leaf is - // the module's real leaf, not the default "main". + // Leg a — both stages emit the dep `.wwi`; the `package` declaration is + // the source declaration supplied independently of its canonical owner. let cav: []str = [testenv.driver("w6c"), "-c", "-I", cswwi, "-o", css, prod]; if (!runok(td, "csprod", cav)) { fail(tag, "w6c producer errored"); }; @@ -342,7 +343,7 @@ fn leafrow(tag: str, path: str, leaf: str, body: str, want: str) void = { let wanthead: str = strings.concat(strings.concat(strings.concat( "//ww:module ", path), "\npackage "), strings.concat(want, ";\n")); if (!strings.hasprefix(testenv.readfile(cswwi), wanthead)) { - fail(tag, ".wwi owner or package leaf is incomplete (BUG-C)"); + fail(tag, ".wwi owner or declared package name is incomplete (BUG-C)"); }; // Leg b — the dep `.wwi` is byte-identical across stages (rule 10). @@ -350,9 +351,11 @@ fn leafrow(tag: str, path: str, leaf: str, body: str, want: str) void = { fail(tag, "w6c vs w6c_ww .wwi differ (rule 10)"); }; - // Leg c — a root importing the dep RESOLVES from a separate export on - // both stages (pre-fix: "package main does not match import path" - // REJECT). + // Leg c — a root importing the empty dep obtains its default qualifier from + // the separate export on both stages. With no exported declaration to use, + // Go-style file scope requires the import itself to be rejected as unused; + // the diagnostic proves that the reader recovered the declared name rather + // than substituting the historical default `main` declaration. testenv.writefile(root, strings.concat( "//ww:module-reset\n", "package main;\n", @@ -360,22 +363,59 @@ fn leafrow(tag: str, path: str, leaf: str, body: str, want: str) void = { "export fn main() i32 = { return 42; };\n")); let rcav: []str = [testenv.driver("w6c"), "-c", "--import", path, cswwi, "-o", rcss, root]; - if (!runok(td, "csroot", rcav)) { - fail(tag, "w6c rejected the import (BUG-C)"); - }; let rwav: []str = [testenv.driver("w6c_ww"), "-c", "--import", path, wwwwi, "-o", rwws, root]; - if (!runok(td, "wsroot", rwav)) { - fail(tag, "w6c_ww rejected the import (BUG-C)"); + let rco: testenv.commandout; + let rwo: testenv.commandout; + testenv.runcommand(td, td, "csroot", rcav, lifetime(), &rco); + testenv.runcommand(td, td, "wsroot", rwav, lifetime(), &rwo); + let (prefix, suffix) = strings.rcut(path, "."); + let leaf: str = suffix; + if (leaf.len == 0) { leaf = path; }; + let unused: str; + if (testenv.same(want, leaf)) { + unused = strings.concat(strings.concat("\"", path), + "\" imported and not used"); + } else { + unused = strings.concat(strings.concat(strings.concat( + "\"", path), "\" imported as "), strings.concat(want, + " and not used")); }; - if (!testenv.same(testenv.readfile(rcss), testenv.readfile(rwws))) { - fail(tag, "importer .s differ cs vs ww (rule 10)"); + if (rco.termination != exec.termination.EXIT || rco.code == 0 + || rwo.termination != exec.termination.EXIT || rwo.code == 0 + || !testenv.same(rco.stderr, rwo.stderr) + || !testenv.has(rco.stderr, unused)) { + fail(tag, "imported-name or unused-import behavior differs by stage"); + }; + testenv.clean(td); +}; + +fn resetfallbackrow() void = { + let td: str = testenv.fresh(); + let src: str = strings.concat(td, "/reset.ww"); + testenv.writefile(src, strings.concat( + "//ww:module-reset raw.owner\n", + "export fn value() i32 = { return 7; };\n")); + let cs: str = strings.concat(td, "/c.wwi"); + let ws: str = strings.concat(td, "/w.wwi"); + let cav: []str = [testenv.driver("w6c"), "-c", "-I", cs, + "-o", strings.concat(td, "/c.s"), src]; + let wav: []str = [testenv.driver("w6c_ww"), "-c", "-I", ws, + "-o", strings.concat(td, "/w.s"), src]; + if (!runok(td, "reset-c", cav) || !runok(td, "reset-w", wav)) { + fail("nested", "a package-less reset owner failed to export"); + }; + let body: str = testenv.readfile(cs); + if (!testenv.same(body, testenv.readfile(ws)) + || !testenv.has(body, "//ww:module raw.owner\npackage main;\n") + || !testenv.has(body, "export fn value() i32;")) { + fail("nested", "reset-owner fallback lost its source section"); }; testenv.clean(td); }; // Each shape lacks any module-tagged decl, so wwi_emit's decl-scan -// misses and the leaf must come from the parse-stamped N_FILE identity. +// misses and the declaration must come from the parse-stamped N_FILE fact. @test fn wwileaf_empty() void = { leafrow("empty", "emptymod", "emptymod", "", "emptymod"); }; @@ -386,4 +426,5 @@ fn leafrow(tag: str, path: str, leaf: str, body: str, want: str) void = { @test fn wwileaf_nested() void = { leafrow("nested", "a.b.c", "c", "", "c"); + resetfallbackrow(); }; diff --git a/test/package/package_test.ww b/test/package/package_test.ww index da70bfa6..015f0dc6 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -319,8 +319,8 @@ fn workescape(s: str) str = { return strings.frombytes(out); }; -fn localidentity(dir: str, leaf: str) str = { - let out: []u8 = alloc([], (dir.len * 4 + leaf.len + 16): u64)!; +fn localidentity(dir: str, declaredname: str) str = { + let out: []u8 = alloc([], (dir.len * 4 + 16): u64)!; let prefix: str = "__wwlocal.p"; let i: i32 = 0; for (i < prefix.len) { append(out, prefix[i]); i += 1; }; @@ -343,9 +343,6 @@ fn localidentity(dir: str, leaf: str) str = { }; i += 1; }; - append(out, '.'); - i = 0; - for (i < leaf.len) { append(out, leaf[i]); i += 1; }; return strings.frombytes(out); }; @@ -1241,14 +1238,16 @@ fn hexbytes(value: str) str = { let externalmaincompile: str = linecontaining(ctrace, "pkg_test-external-test-main.unit.ww"); assert(same(samemaincompile, strings.concat( - "-T --entry --test-support-module test -c --import pkg ", + "-T --entry --test-support-module test ", + "--test-target-package pkg -c --import pkg ", sharedwork, "pkg-internal-test.wwi --import test ", sharedwork, "test.wwi -I ", sharedwork, "pkg-internal-test-main.wwi -o ", sharedwork, "pkg-internal-test-main.s ", sharedwork, "pkg-internal-test-main.unit.ww"))); assert(same(externalmaincompile, strings.concat( - "-T --entry --test-support-module test -c --import pkg_test ", + "-T --entry --test-support-module test ", + "--test-target-package pkg_test -c --import pkg_test ", sharedwork, "pkg_test-external-test.wwi --import test ", sharedwork, "test.wwi -I ", sharedwork, "pkg_test-external-test-main.wwi -o ", sharedwork, @@ -1495,9 +1494,9 @@ fn hexbytes(value: str) str = { aliasunit]; runcommand(root, strings.concat("alias-unowned-", aliasstages[i]), rawav, (30i64 * (time.second: i64)): time.duration, &out); - assert(out.termination == exec.termination.EXIT && out.code != 0); - assert(has(out.stderr, "package") - && has(out.stderr, "does not match import path")); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0); + assert(os.exists(asmout)); let badav: []str = [driver(aliasstages[i]), "-c", "--test-support-module", "arbitrary", "-o", asmout, aliasunit]; runcommand(root, strings.concat("alias-arbitrary-", aliasstages[i]), @@ -4226,9 +4225,13 @@ fn hexbytes(value: str) str = { let leftbody: str = strings.concat( "package parityleft;\n", "import ", baseidentity, ";\n", - "import ", baseidentity, ";\n", "export fn value() i32 = { return ", "paritybase.value(); };\n"); + let leftz: str = strings.concat( + "package parityleft;\n", + "import ", baseidentity, ";\n", + "export fn repeated_binding() i32 = { return ", + "paritybase.value(); };\n"); let rightbody: str = strings.concat( "package parityright;\n", "import ", baseidentity, ";\n", @@ -4242,7 +4245,8 @@ fn hexbytes(value: str) str = { "parityright.value() + 1; };\n"); writefile(strings.concat(base, "/a.ww"), basea); writefile(strings.concat(base, "/z.ww"), basez); - writefile(strings.concat(left, "/left.ww"), leftbody); + writefile(strings.concat(left, "/a.ww"), leftbody); + writefile(strings.concat(left, "/z.ww"), leftz); writefile(strings.concat(right, "/right.ww"), rightbody); writefile(strings.concat(target, "/main.ww"), rootbody); @@ -4316,7 +4320,8 @@ fn hexbytes(value: str) str = { "//ww:module-reset ", baseidentity, "\n", basea, "\n//ww:module-reset ", baseidentity, "\n", basez, "\n"), strings.concat("//ww:module-reset ", leftidentity, "\n", - leftbody, "\n"), + leftbody, "\n//ww:module-reset ", leftidentity, "\n", + leftz, "\n"), strings.concat("//ww:module-reset ", rightidentity, "\n", rightbody, "\n"), strings.concat("//ww:module-reset ", rootidentity, "\n", rootbody, @@ -4425,9 +4430,8 @@ fn hexbytes(value: str) str = { assert(has(assembly, strings.concat(identities[ai], ".value"))); }; if (ai == 3) { - assert(has(exportf, strings.concat("\npackage paritycommand", - ";\n"))); - assert(!has(exportf, "\npackage main;\n")); + assert(has(exportf, "\npackage main;\n")); + assert(!has(exportf, "\npackage paritycommand;\n")); }; if (si == 0) { referenceexports[ai] = strings.dup(exportf); @@ -4476,18 +4480,23 @@ fn hexbytes(value: str) str = { assert(same(linecontaining(ctrace, strings.concat(artifacts[3], ".unit.ww")), rootline)); assert(!has(rootline, strings.concat(artifacts[0], ".wwi"))); - // The primary command declaration is accepted only when the compiler - // receives command classification from --entry. Imported interfaces and - // ordinary package compiles retain strict identity-leaf validation. + // Canonical owner and declared name stay independent even in a raw + // package compile. --entry selects link-root code generation; it is not + // permission to replace canonical owner with the declared name. let rejectwwi: str = strings.concat(root, "/reject-", tags[si], ".wwi"); let rejectasm: str = strings.concat(root, "/reject-", tags[si], ".s"); - let rejectav: []str = [driver(compilers[si]), "-c", "-I", rejectwwi, - "-o", rejectasm, strings.concat(work, artifacts[3], ".unit.ww")]; + let rejectav: []str = [driver(compilers[si]), "-c", "--import", + leftidentity, + strings.concat(work, artifacts[1], ".wwi"), + "--import", rightidentity, + strings.concat(work, artifacts[2], ".wwi"), + "-I", rejectwwi, "-o", rejectasm, + strings.concat(work, artifacts[3], ".unit.ww")]; runcommand(root, strings.concat("command-without-entry-", tags[si]), rejectav, (60i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 1); - assert(has(out.stderr, "does not match import path")); - assert(has(out.stderr, rootidentity)); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0); + assert(os.exists(rejectwwi) && os.exists(rejectasm)); let ownerrejectwwi: str = strings.concat(root, "/owner-reject-", tags[si], ".wwi"); let ownerrejectasm: str = strings.concat(root, "/owner-reject-", @@ -4726,11 +4735,11 @@ fn hexbytes(value: str) str = { "package main_test;\n", "import ", commandid, ";\n", "@test fn command_external() void = { ", - "assert(cmdtool.value() == 41); };\n")); + "assert(main.value() == 41); };\n")); writefile(strings.concat(importer, "/importer.ww"), strings.concat( "package importer;\n", "import ", commandid, ";\n", - "export fn value() i32 = { return cmdtool.value(); };\n")); + "export fn value() i32 = { return main.value(); };\n")); writeexecutable(wrapper, strings.concat( "#!/bin/sh\n", "printf 'BEGIN' >> \"$WW_COMMAND_COMPILER_TRACE\"\n", @@ -4830,8 +4839,12 @@ fn hexbytes(value: str) str = { strings.concat(artifacts[4], ".unit.new")); assert(has(internalmainline, strings.concat("<--import><", internalid, "><", work, artifacts[1], ".wwi>"))); + assert(has(internalmainline, strings.concat( + "<--test-target-package><", internalid, ">"))); assert(has(externalmainline, strings.concat("<--import><", externalid, "><", work, artifacts[2], ".wwi>"))); + assert(has(externalmainline, strings.concat( + "<--test-target-package><", externalid, ">"))); assert(occurrences(ctrace, strings.concat("<", work, artifacts[0], ".unit.new>")) == 1); assert(has(readfile(strings.concat(work, artifacts[0], ".s")), @@ -4870,7 +4883,7 @@ fn hexbytes(value: str) str = { }; // The same command action is not generally source-importable. It is - // classified before leaf validation so this Go-like diagnostic is stable, + // classified from its declaration so this Go-like diagnostic is stable, // and graph failure occurs before any compiler invocation. clean(trace); writefile(trace, ""); @@ -7844,6 +7857,547 @@ fn runtimepath(relative: str) str = { // artifact in place of the fixed .test stem; -o without -c // has nothing to name (runs execute from the temp root); one name // cannot fan out over multiple packages (Go's `go test -o` rule). +@test fn declared_name_identity_and_file_import_scope() void = { + let root: str = fresh(); + let source: str = strings.concat(root, "/source"); + let tools: str = strings.concat(root, "/tools"); + let codec: str = strings.concat(source, "/acme/codec"); + let bridge: str = strings.concat(source, "/acme/bridge"); + let direct: str = strings.concat(source, "/cmd/direct"); + let app: str = strings.concat(source, "/cmd/app"); + let scopeone: str = strings.concat(source, "/scope/one"); + let scopetwo: str = strings.concat(source, "/scope/two"); + let scoped: str = strings.concat(source, "/cmd/scoped"); + let repeated: str = strings.concat(source, "/cmd/repeated"); + let leafbad: str = strings.concat(source, "/cmd/leafbad"); + let leak: str = strings.concat(source, "/cmd/leak"); + let duplicate: str = strings.concat(source, "/cmd/duplicate"); + let collision: str = strings.concat(source, "/cmd/collision"); + let crosscollision: str = strings.concat(source, "/cmd/crosscollision"); + let conflict: str = strings.concat(source, "/conflict/pkg"); + let leafmain: str = strings.concat(source, "/domain/main"); + let program: str = strings.concat(source, "/domain/program"); + let leafmainuser: str = strings.concat(source, "/cmd/leafmain"); + let programuser: str = strings.concat(source, "/cmd/programuser"); + let testcodec: str = strings.concat(source, "/testpkg/codec"); + let testhelper: str = strings.concat(source, "/testonly/helper"); + let vendored: str = strings.concat(source, + "/vend/vendor/short/codec"); + let vendorclient: str = strings.concat(source, "/vend/client"); + let dirs: []str = [source, tools, codec, bridge, direct, app, scopeone, + scopetwo, scoped, repeated, leafbad, leak, duplicate, collision, + crosscollision, conflict, leafmain, program, leafmainuser, programuser, testcodec, + testhelper, vendored, vendorclient]; + let di: i32 = 0; + for (di < dirs.len) { mkdirall(dirs[di]); di += 1; }; + + let wirebody: str = strings.concat( + "package wire;\n", + "export fn value() i32 = { return 42; };\n"); + let cablebody: str = strings.concat( + "package cable;\n", + "export fn value() i32 = { return 42; };\n"); + let codecfile: str = strings.concat(codec, "/codec.ww"); + writefile(codecfile, wirebody); + writefile(strings.concat(bridge, "/bridge.ww"), strings.concat( + "package bridge;\nimport acme.codec;\n", + // WW retains its direct-import bare-declaration convenience. It is + // useful here because changing only the dependency declaration leaves + // this source unchanged while still forcing its action to reconsider. + "export fn bridged() i32 = { return value(); };\n")); + writefile(strings.concat(direct, "/main.ww"), strings.concat( + "package main;\nimport acme.codec;\n", + "fn main() i32 = { return wire.value(); };\n")); + writefile(strings.concat(app, "/main.ww"), strings.concat( + "package main;\nimport acme.bridge;\n", + "fn main() i32 = { return bridge.bridged(); };\n")); + + writefile(strings.concat(scopeone, "/one.ww"), + "package same;\nexport fn value() i32 = { return 20; };\n"); + writefile(strings.concat(scopetwo, "/two.ww"), + "package same;\nexport fn value() i32 = { return 22; };\n"); + writefile(strings.concat(scoped, "/a.ww"), strings.concat( + "package main;\nimport scope.one;\n", + "fn left() i32 = { return same.value(); };\n")); + writefile(strings.concat(scoped, "/b.ww"), strings.concat( + "package main;\nimport scope.two;\n", + "fn right() i32 = { return same.value(); };\n")); + writefile(strings.concat(scoped, "/main.ww"), + "package main;\nfn main() i32 = { return left() + right(); };\n"); + + writefile(strings.concat(repeated, "/a.ww"), strings.concat( + "package main;\nimport acme.codec;\n", + "fn first() i32 = { return wire.value(); };\n")); + writefile(strings.concat(repeated, "/b.ww"), strings.concat( + "package main;\nimport acme.codec;\n", + "fn second() i32 = { return wire.value(); };\n")); + writefile(strings.concat(repeated, "/main.ww"), strings.concat( + "package main;\n", + "fn main() i32 = { return first() - second(); };\n")); + + writefile(strings.concat(leafbad, "/main.ww"), strings.concat( + "package main;\nimport acme.codec;\n", + "fn main() i32 = { return codec.value(); };\n")); + writefile(strings.concat(leak, "/a.ww"), strings.concat( + "package main;\nimport acme.codec;\n", + "fn anchor() i32 = { return 1; };\n")); + writefile(strings.concat(leak, "/b.ww"), + "package main;\nfn leaked() i32 = { return wire.value(); };\n"); + writefile(strings.concat(leak, "/main.ww"), + "package main;\nfn main() i32 = { return leaked(); };\n"); + writefile(strings.concat(duplicate, "/main.ww"), strings.concat( + "package main;\nimport scope.one;\nimport scope.two;\n", + "fn main() i32 = { return same.value(); };\n")); + writefile(strings.concat(collision, "/main.ww"), strings.concat( + "package main;\nimport scope.one;\n", + "fn same() i32 = { return 0; };\n", + "fn main() i32 = { return same(); };\n")); + writefile(strings.concat(crosscollision, "/a.ww"), strings.concat( + "package main;\nimport scope.one;\n", + "fn fromdep() i32 = { return same.value(); };\n")); + writefile(strings.concat(crosscollision, "/b.ww"), strings.concat( + "package main;\nfn same() i32 = { return 0; };\n", + "fn main() i32 = { return fromdep(); };\n")); + writefile(strings.concat(conflict, "/a.ww"), + "package first;\nexport fn a() i32 = { return 1; };\n"); + writefile(strings.concat(conflict, "/b.ww"), + "package second;\nexport fn b() i32 = { return 2; };\n"); + + writefile(strings.concat(leafmain, "/utility.ww"), + "package utility;\nexport fn value() i32 = { return 7; };\n"); + writefile(strings.concat(program, "/program.ww"), + "package main;\nexport fn value() i32 = { return 9; };\n"); + writefile(strings.concat(leafmainuser, "/main.ww"), strings.concat( + "package main;\nimport domain.main;\n", + "fn main() i32 = { return utility.value(); };\n")); + writefile(strings.concat(programuser, "/main.ww"), strings.concat( + "package main;\nimport domain.program;\n", + "fn main() i32 = { return main.value(); };\n")); + + writefile(strings.concat(testhelper, "/helper.ww"), + "package helper;\nexport fn value() i32 = { return 1; };\n"); + writefile(strings.concat(testcodec, "/codec.ww"), wirebody); + writefile(strings.concat(testcodec, "/internal_test.ww"), strings.concat( + "package wire;\nimport testonly.helper;\n", + "@test fn internal_name_uses_declaration() void = {\n", + " assert(value() + helper.value() == 43);\n};\n")); + writefile(strings.concat(testcodec, "/external_test.ww"), strings.concat( + "package wire_test;\nimport testpkg.codec;\n", + "@test fn external_name_uses_declaration() void = {\n", + " assert(wire.value() == 42);\n};\n")); + + writefile(strings.concat(vendored, "/codec.ww"), + "package cable;\nexport fn value() i32 = { return 13; };\n"); + writefile(strings.concat(vendorclient, "/a.ww"), strings.concat( + "package main;\nimport short.codec;\n", + "fn vendora() i32 = { return cable.value(); };\n")); + writefile(strings.concat(vendorclient, "/b.ww"), strings.concat( + "package main;\nimport short.codec;\n", + "fn vendorb() i32 = { return cable.value(); };\n")); + writefile(strings.concat(vendorclient, "/main.ww"), + "package main;\nfn main() i32 = { return vendora(); };\n"); + + let compilerwrapper: str = strings.concat(tools, "/w6c.sh"); + writeexecutable(compilerwrapper, strings.concat( + "#!/bin/sh\n", + "printf 'BEGIN' >> \"$WW_NAME_COMPILER_TRACE\"\n", + "for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ", + "\"$WW_NAME_COMPILER_TRACE\"; done\n", + "printf '\\n' >> \"$WW_NAME_COMPILER_TRACE\"\n", + "exec \"$WW_NAME_REAL_COMPILER\" \"$@\"\n")); + + let stages: []str = ["ww", "ww_ww"]; + let compilers: []str = ["w6c", "w6c_ww"]; + let assemblers: []str = ["w6a", "w6a_ww"]; + let linkers: []str = ["w6l", "w6l_ww"]; + let works: []str = [strings.concat(root, "/c-work"), + strings.concat(root, "/ww-work")]; + let traces: []str = [strings.concat(root, "/c-compiler"), + strings.concat(root, "/ww-compiler")]; + let directbins: []str = [strings.concat(root, "/c-direct"), + strings.concat(root, "/ww-direct")]; + let appbins: []str = [strings.concat(root, "/c-app"), + strings.concat(root, "/ww-app")]; + let scopedbins: []str = [strings.concat(root, "/c-scoped"), + strings.concat(root, "/ww-scoped")]; + let repeatedbins: []str = [strings.concat(root, "/c-repeated"), + strings.concat(root, "/ww-repeated")]; + let leafmainbins: []str = [strings.concat(root, "/c-leafmain"), + strings.concat(root, "/ww-leafmain")]; + let vendorbins: []str = [strings.concat(root, "/c-vendor"), + strings.concat(root, "/ww-vendor")]; + let actions: []str = ["acme.codec", "acme.bridge", "cmd.direct", + "cmd.app", "scope.one", "scope.two", "cmd.scoped", + "cmd.repeated", "domain.main", "cmd.leafmain", + "vend.vendor.short.codec", "vend.client"]; + let suffixes: []str = [".unit.ww", ".wwi", ".a"]; + let artifactrefs: []str = alloc([], (actions.len * suffixes.len): u64)!; + let ari: i32 = 0; + for (ari < actions.len * suffixes.len) { + append(artifactrefs, ""); + ari += 1; + }; + let binaryrefs: []str = ["", "", "", "", "", ""]; + let diagnosticrefs: []str = ["", "", "", "", "", ""]; + let directtraceref: str = ""; + let scopedtraceref: str = ""; + let repeatedtraceref: str = ""; + let changedtraceref: str = ""; + let initialwwiref: str = ""; + let testoutref: str = ""; + let baseenv: []str = os.getenvs(); + let si: i32 = 0; + for (si < stages.len) { + rewritefile(codecfile, wirebody); + assert(os.mkdir(works[si], 448i32) == 0); + writefile(traces[si], ""); + let env: []str = alloc([], (baseenv.len + 7): u64)!; + let ei: i32 = 0; + for (ei < baseenv.len) { + if (!strings.hasprefix(baseenv[ei], "WW_W6C=") + && !strings.hasprefix(baseenv[ei], "WW_W6A=") + && !strings.hasprefix(baseenv[ei], "WW_W6L=") + && !strings.hasprefix(baseenv[ei], "WW_SRCLIB=") + && !strings.hasprefix(baseenv[ei], + "WW_NAME_COMPILER_TRACE=") + && !strings.hasprefix(baseenv[ei], + "WW_NAME_REAL_COMPILER=")) { + append(env, baseenv[ei]); + }; + ei += 1; + }; + append(env, strings.concat("WW_W6C=", compilerwrapper)); + append(env, strings.concat("WW_W6A=", driver(assemblers[si]))); + append(env, strings.concat("WW_W6L=", driver(linkers[si]))); + append(env, strings.concat("WW_SRCLIB=", repo(), "/lib")); + append(env, strings.concat("WW_NAME_COMPILER_TRACE=", traces[si])); + append(env, strings.concat("WW_NAME_REAL_COMPILER=", + driver(compilers[si]))); + + // A directly selected library keeps canonical action/artifact identity + // even though its declaration is unrelated to the path leaf. + let out: commandout; + let selectav: []str = [driver(stages[si]), "build", "-w", works[si], + "-I", source, codec]; + runcommandenv(root, strings.concat("name-select-", stages[si]), + selectav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0); + assert(os.exists(strings.concat(works[si], "/acme.codec.wwi"))); + assert(!os.exists(strings.concat(works[si], "/wire.wwi"))); + let initialwwi: str = readfile(strings.concat(works[si], + "/acme.codec.wwi")); + assert(has(initialwwi, "//ww:module acme.codec\npackage wire;\n")); + if (si == 0) { initialwwiref = strings.dup(initialwwi); } + else { assert(same(initialwwiref, initialwwi)); }; + + rewritefile(traces[si], ""); + let directav: []str = [driver(stages[si]), "build", "-w", works[si], + "-I", source, "-o", directbins[si], direct]; + runcommandenv(root, strings.concat("name-direct-", stages[si]), + directav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(out.stdout.len == 0 && out.stderr.len == 0); + let directtrace: str = readfile(traces[si]); + let directline: str = linecontaining(directtrace, + "cmd.direct.unit.new"); + assert(occurrences(directline, "<--import>") == 1); + assert(!has(directline, "wire.wwi")); + let ndirect: str = normalizedtrace(directtrace, + strings.concat(works[si], "/"), directbins[si]); + if (si == 0) { directtraceref = strings.dup(ndirect); } + else { assert(same(directtraceref, ndirect)); }; + let runav: []str = [directbins[si]]; + runcommand(root, strings.concat("name-direct-run-", stages[si]), runav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 42); + + // The same declared qualifier belongs to each importing source file, + // not to the package-wide namespace. + rewritefile(traces[si], ""); + let scopedav: []str = [driver(stages[si]), "build", "-w", works[si], + "-I", source, "-o", scopedbins[si], scoped]; + runcommandenv(root, strings.concat("name-scoped-", stages[si]), + scopedav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let scopedtrace: str = readfile(traces[si]); + let scopedline: str = linecontaining(scopedtrace, + "cmd.scoped.unit.new"); + assert(occurrences(scopedline, "<--import>") == 1); + assert(occurrences(scopedline, "<--import>") == 1); + let nscoped: str = normalizedtrace(scopedtrace, + strings.concat(works[si], "/"), scopedbins[si]); + if (si == 0) { scopedtraceref = strings.dup(nscoped); } + else { assert(same(scopedtraceref, nscoped)); }; + let scopedrun: []str = [scopedbins[si]]; + runcommand(root, strings.concat("name-scoped-run-", stages[si]), + scopedrun, (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 42); + + // Two per-file binding records for one canonical package still form + // exactly one direct graph edge and one compiler input. + rewritefile(traces[si], ""); + let repeatedav: []str = [driver(stages[si]), "build", "-w", works[si], + "-I", source, "-o", repeatedbins[si], repeated]; + runcommandenv(root, strings.concat("name-repeated-", stages[si]), + repeatedav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let repeatedunit: str = readfile(strings.concat(works[si], + "/cmd.repeated.unit.ww")); + assert(occurrences(repeatedunit, "import acme.codec;") == 2); + let repeatedtrace: str = readfile(traces[si]); + let repeatedline: str = linecontaining(repeatedtrace, + "cmd.repeated.unit.new"); + assert(occurrences(repeatedline, "<--import>") == 1); + assert(occurrences(repeatedtrace, "acme.codec.unit.new") == 0); + let nrepeated: str = normalizedtrace(repeatedtrace, + strings.concat(works[si], "/"), repeatedbins[si]); + if (si == 0) { repeatedtraceref = strings.dup(nrepeated); } + else { assert(same(repeatedtraceref, nrepeated)); }; + let repeatedrun: []str = [repeatedbins[si]]; + runcommand(root, strings.concat("name-repeated-run-", stages[si]), + repeatedrun, (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + + // A path ending in main remains importable when its declaration is not + // main. A different path declaring main is rejected by loaded name. + let leafmainav: []str = [driver(stages[si]), "build", "-w", works[si], + "-I", source, "-o", leafmainbins[si], leafmainuser]; + runcommandenv(root, strings.concat("name-leafmain-", stages[si]), + leafmainav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let leafmainrun: []str = [leafmainbins[si]]; + runcommand(root, strings.concat("name-leafmain-run-", stages[si]), + leafmainrun, (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 7); + + // Compiler-owned binding failures are deterministic across stages and + // never commit an action voucher/artifact or publish a binary. + let rejectwork: str = strings.concat(root, "/reject-work-", stages[si]); + assert(os.mkdir(rejectwork, 448i32) == 0); + let rejecttargets: []str = [leafbad, leak, duplicate, collision, + crosscollision, programuser]; + let rejectactions: []str = ["cmd.leafbad", "cmd.leak", + "cmd.duplicate", "cmd.collision", "cmd.crosscollision", + "cmd.programuser"]; + let rejectneedles: []str = ["undefined: codec", + "package 'wire' is not directly imported", + "same redeclared in this block", + "same already declared through import of package same", + "same already declared through import of package same", + "package domain.program is a program, not an importable package"]; + let rj: i32 = 0; + for (rj < rejecttargets.len) { + let badbin: str = strings.concat(root, "/reject-bin-", stages[si], + "-", rejectactions[rj]); + let badav: []str = [driver(stages[si]), "build", "-w", rejectwork, + "-I", source, "-o", badbin, rejecttargets[rj]]; + runcommandenv(root, strings.concat("name-reject-", stages[si], + "-", rejectactions[rj]), badav, env, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stderr, rejectneedles[rj])); + let normalized: str = normalizedtrace(out.stderr, + strings.concat(rejectwork, "/"), badbin); + if (rj == 2) { + assert(same(normalized, strings.concat( + "$WORK/cmd.duplicate.unit.new:4:1: error: same redeclared in this block\n", + "\t$WORK/cmd.duplicate.unit.new:3:1: other declaration of same\n", + "$WORK/cmd.duplicate.unit.new:4:1: error: \"scope.two\" imported as same and not used\n", + "ww: w6c failed for cmd.duplicate\n"))); + }; + if (rj == 3) { + assert(same(normalized, strings.concat( + "$WORK/cmd.collision.unit.new:3:1: error: \"scope.one\" imported as same and not used\n", + "$WORK/cmd.collision.unit.new:4:1: error: same already declared through import of package same (\"scope.one\")\n", + "\t$WORK/cmd.collision.unit.new:3:1: other declaration of same\n", + "ww: w6c failed for cmd.collision\n"))); + }; + if (si == 0) { diagnosticrefs[rj] = strings.dup(normalized); } + else { assert(same(diagnosticrefs[rj], normalized)); }; + assert(!os.exists(badbin)); + let committed: []str = [".unit.ww", ".wwi", ".s", ".o", ".a", + ".unit.new", ".wwi.new", ".s.new", ".o.new", ".a.new"]; + let ci: i32 = 0; + for (ci < committed.len) { + assert(!os.exists(strings.concat(rejectwork, "/", + rejectactions[rj], committed[ci]))); + ci += 1; + }; + rj += 1; + }; + + let conflictav: []str = [driver(stages[si]), "build", "-w", + rejectwork, "-I", source, conflict]; + runcommandenv(root, strings.concat("name-conflict-", stages[si]), + conflictav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stderr, "conflicting package names")); + assert(!os.exists(strings.concat(rejectwork, + "/conflict.pkg.unit.ww"))); + + // Production/internal/external naming uses the declaration, while every + // action and generated-main artifact retains canonical variant identity. + let testwork: str = strings.concat(root, "/test-work-", stages[si]); + assert(os.mkdir(testwork, 448i32) == 0); + rewritefile(traces[si], ""); + let testav: []str = [driver(stages[si]), "test", "-w", testwork, + "-I", source, testcodec]; + runcommandenv(root, strings.concat("name-test-", stages[si]), testav, + env, (240i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "internal_name_uses_declaration ... ok\n")); + assert(has(out.stdout, "external_name_uses_declaration ... ok\n")); + assert(out.stderr.len == 0); + if (si == 0) { testoutref = strings.dup(out.stdout); } + else { assert(same(testoutref, out.stdout)); }; + let productionunit: str = readfile(strings.concat(testwork, + "/testpkg.codec.unit.ww")); + let internalunit: str = readfile(strings.concat(testwork, + "/testpkg.codec-internal-test.unit.ww")); + let externalunit: str = readfile(strings.concat(testwork, + "/testpkg.codec_test-external-test.unit.ww")); + assert(!has(productionunit, "testonly.helper")); + assert(has(internalunit, "import testonly.helper;")); + assert(!has(externalunit, "testonly.helper")); + assert(has(externalunit, "package wire_test;")); + assert(os.exists(strings.concat(testwork, + "/testpkg.codec-internal-test-main.a"))); + assert(os.exists(strings.concat(testwork, + "/testpkg.codec_test-external-test-main.a"))); + assert(os.exists(strings.concat(testwork, "/test.a"))); + assert(!has(readfile(strings.concat(testwork, + "/testpkg.codec-internal-test-main.unit.ww")), "testonly.helper")); + assert(!has(readfile(strings.concat(testwork, + "/testpkg.codec_test-external-test-main.unit.ww")), + "testonly.helper")); + + let recursivebuildwork: str = strings.concat(root, + "/recursive-build-", stages[si]); + assert(os.mkdir(recursivebuildwork, 448i32) == 0); + let recursivebuildav: []str = [driver(stages[si]), "build", "-w", + recursivebuildwork, "-I", source, + strings.concat(source, "/testpkg/...")]; + runcommandenv(root, strings.concat("name-recursive-build-", stages[si]), + recursivebuildav, env, + (180i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(os.exists(strings.concat(recursivebuildwork, + "/testpkg.codec.wwi"))); + let recursivetestwork: str = strings.concat(root, + "/recursive-test-", stages[si]); + assert(os.mkdir(recursivetestwork, 448i32) == 0); + let recursivetestav: []str = [driver(stages[si]), "test", "-w", + recursivetestwork, "-I", source, + strings.concat(source, "/testpkg/...")]; + runcommandenv(root, strings.concat("name-recursive-test-", stages[si]), + recursivetestav, env, + (240i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "external_name_uses_declaration ... ok\n")); + + // Vendor expansion owns the action/map identity; the loaded declaration + // independently supplies cable as the source-visible qualifier. + rewritefile(traces[si], ""); + let vendorav: []str = [driver(stages[si]), "build", "-w", works[si], + "-I", source, "-o", vendorbins[si], vendorclient]; + runcommandenv(root, strings.concat("name-vendor-", stages[si]), + vendorav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let vendorunit: str = readfile(strings.concat(works[si], + "/vend.client.unit.ww")); + assert(occurrences(vendorunit, "import short.codec;") == 2); + assert(has(vendorunit, strings.concat( + "//ww:import-map short.codec vend.vendor.short.codec ", + hexbytes(vendored), "\n"))); + assert(occurrences(vendorunit, + "//ww:import-map short.codec vend.vendor.short.codec ") == 1); + let vendortrace: str = readfile(traces[si]); + let vendorline: str = linecontaining(vendortrace, + "vend.client.unit.new"); + assert(occurrences(vendorline, + "<--import>") == 1); + assert(occurrences(vendorline, strings.concat( + "<--import-map>")) == 1); + let vendorwwi: str = readfile(strings.concat(works[si], + "/vend.vendor.short.codec.wwi")); + assert(has(vendorwwi, strings.concat( + "//ww:module vend.vendor.short.codec\npackage cable;\n"))); + assert(!os.exists(strings.concat(works[si], "/short.codec.a"))); + let vendorrun: []str = [vendorbins[si]]; + runcommand(root, strings.concat("name-vendor-run-", stages[si]), + vendorrun, (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 13); + + // Build the bare-import bridge and its command, establish a no-op warm + // rebuild, then change only the dependency's declared name. The direct + // importer is reconsidered; its unchanged export stops propagation. + rewritefile(traces[si], ""); + let appav: []str = [driver(stages[si]), "build", "-w", works[si], + "-I", source, "-o", appbins[si], app]; + runcommandenv(root, strings.concat("name-app-cold-", stages[si]), appav, + env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let apprun: []str = [appbins[si]]; + runcommand(root, strings.concat("name-app-run-", stages[si]), apprun, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 42); + rewritefile(traces[si], ""); + runcommandenv(root, strings.concat("name-app-warm-", stages[si]), appav, + env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(readfile(traces[si]).len == 0); + rewritefile(codecfile, cablebody); + rewritefile(traces[si], ""); + runcommandenv(root, strings.concat("name-app-renamed-", stages[si]), + appav, env, (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let changedtrace: str = readfile(traces[si]); + assert(occurrences(changedtrace, "\n") == 2); + assert(occurrences(changedtrace, "/acme.codec.unit.new") == 1); + assert(occurrences(changedtrace, "/acme.bridge.unit.new") == 1); + assert(!has(changedtrace, "/cmd.app.unit.new")); + let nchanged: str = normalizedtrace(changedtrace, + strings.concat(works[si], "/"), appbins[si]); + if (si == 0) { changedtraceref = strings.dup(nchanged); } + else { assert(same(changedtraceref, nchanged)); }; + let renamedwwi: str = readfile(strings.concat(works[si], + "/acme.codec.wwi")); + assert(has(renamedwwi, "//ww:module acme.codec\npackage cable;\n")); + assert(!has(renamedwwi, "package wire;")); + runcommand(root, strings.concat("name-app-renamed-run-", stages[si]), + apprun, (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 42); + + // New semantic artifacts and binaries are byte-identical across the + // independently cold Cstage and WWstage work roots. + ari = 0; + for (ari < actions.len) { + let xi: i32 = 0; + for (xi < suffixes.len) { + let index: i32 = ari * suffixes.len + xi; + let bytes: str = readfile(strings.concat(works[si], "/", + actions[ari], suffixes[xi])); + if (si == 0) { artifactrefs[index] = strings.dup(bytes); } + else { assert(same(artifactrefs[index], bytes)); }; + xi += 1; + }; + ari += 1; + }; + let stagebins: []str = [directbins[si], appbins[si], scopedbins[si], + repeatedbins[si], leafmainbins[si], vendorbins[si]]; + let bini: i32 = 0; + for (bini < stagebins.len) { + let bytes: str = readfile(stagebins[bini]); + if (si == 0) { binaryrefs[bini] = strings.dup(bytes); } + else { assert(same(binaryrefs[bini], bytes)); }; + bini += 1; + }; + si += 1; + }; + clean(root); +}; + @test fn compile_artifact_naming() void = { let root: str = fresh(); let pdir: str = strings.concat(root, "/pkg"); diff --git a/test/sep/localbuild_test.ww b/test/sep/localbuild_test.ww index f036882c..a3ea3cf5 100644 --- a/test/sep/localbuild_test.ww +++ b/test/sep/localbuild_test.ww @@ -368,7 +368,7 @@ fn writediamond(td: str, reverse: bool) str = { testenv.writefile(strings.concat(shared, "/a.ww"), asrc); }; testenv.writefile(strings.concat(left, "/left.ww"), strings.concat( - "package left;\nimport shared;\nimport shared;\n", + "package left;\nimport shared;\n", "export fn make() shared.token = { return shared.token{value=shared.base() + 1}; };\n")); testenv.writefile(strings.concat(right, "/right.ww"), strings.concat( "package right;\nimport shared;\n", @@ -424,7 +424,7 @@ fn writediamond(td: str, reverse: bool) str = { let rightiface: str = testenv.readfile(strings.concat(scratch, "right.wwi")); if (testenv.occurrences(leftiface, "import shared;") != 1) { - fail("diamond", "duplicate import escaped into export data"); + fail("diamond", "direct import is missing or duplicated in export data"); }; if (testenv.occurrences(leftiface, "export type token") != 1 || testenv.occurrences(rightiface, "export type token") != 1 @@ -907,7 +907,7 @@ fn writediamond(td: str, reverse: bool) str = { "/.wwtool.w6a")), testenv.readfile(assembler)) || !testenv.same(testenv.readfile(strings.concat(work, "/.wwtool.stamp")), - "ww workdir fmt 13 mode build asm 0\n")) { + "ww workdir fmt 14 mode build asm 0\n")) { fail("driver-identity", "persistent artifacts or identities are incomplete"); }; let coldwwi: str = testenv.readfile(strings.concat(work, "/dep.wwi")); @@ -1293,7 +1293,7 @@ fn writediamond(td: str, reverse: bool) str = { "// ROOT_A\n", "export fn value() i32 = { return dep.value() + 2; };\n")); testenv.writefile(strings.concat(bar, "/z.ww"), strings.concat( - "package bar;\nimport foo.dep;\n", + "package bar;\n", "// ROOT_Z\n", "export fn other() i32 = { return 9; };\n")); testenv.writefile(strings.concat(commanddir, "/main.ww"), strings.concat( @@ -1819,16 +1819,21 @@ fn writediamond(td: str, reverse: bool) str = { @test fn generated_test_hook_is_not_a_user_exemption() void = { let td: str = testenv.fresh(); let src: str = strings.concat(td, "/case.ww"); + let iface: str = strings.concat(td, "/test.wwi"); testenv.writefile(src, strings.concat( "package probe;\nimport test;\n", "fn bad() void = { test.run(); };\n", "@test fn one() void = {};\n")); + testenv.writefile(iface, strings.concat( + "//ww:module test\n", + "package test;\n", + "export fn placeholder() void;\n")); let compilers: []str = ["w6c", "w6c_ww"]; let i: i32 = 0; for (i < compilers.len) { let asm: str = strings.concat(td, "/case-", compilers[i], ".s"); - let av: []str = [testenv.driver(compilers[i]), "-T", "-c", "-o", - asm, src]; + let av: []str = [testenv.driver(compilers[i]), "-T", "-c", + "--import", "test", iface, "-o", asm, src]; let co: testenv.commandout; command(td, strings.concat("test_hook_", compilers[i]), av, &co); if (co.termination != exec.termination.EXIT || co.code == 0 diff --git a/test/testenv/testenv.ww b/test/testenv/testenv.ww index c76a55ad..65673374 100644 --- a/test/testenv/testenv.ww +++ b/test/testenv/testenv.ww @@ -220,8 +220,8 @@ export fn occurrences(haystack: str, needle: str) i32 = { // Expected semantic identity for a canonical absolute directory that is not // representable below an active source root. Keep this independent observer // byte-for-byte aligned with the documented driver protocol. -export fn localpackageidentity(dir: str, leaf: str) str = { - let out: []u8 = alloc([], (dir.len * 4 + leaf.len + 16): u64)!; +export fn localpackageidentity(dir: str, declaredname: str) str = { + let out: []u8 = alloc([], (dir.len * 4 + 16): u64)!; let prefix: str = "__wwlocal.p"; let i: i32 = 0; for (i < prefix.len) { append(out, prefix[i]); i += 1; }; @@ -244,9 +244,6 @@ export fn localpackageidentity(dir: str, leaf: str) str = { }; i += 1; }; - append(out, '.'); - i = 0; - for (i < leaf.len) { append(out, leaf[i]); i += 1; }; return strings.frombytes(out); }; diff --git a/test/wcc/data/r78_strict_package_module_leaf_mismatch_reject/case.ww b/test/wcc/data/r78_package_module_name_decoupled_accept/case.ww similarity index 61% rename from test/wcc/data/r78_strict_package_module_leaf_mismatch_reject/case.ww rename to test/wcc/data/r78_package_module_name_decoupled_accept/case.ww index a2dd1b79..d46196a3 100644 --- a/test/wcc/data/r78_strict_package_module_leaf_mismatch_reject/case.ww +++ b/test/wcc/data/r78_package_module_name_decoupled_accept/case.ww @@ -1,4 +1,4 @@ -//ww:error "does not match import path" +//ww:compile //ww:module foo package bar; export fn x() i32 = { return 0; }; diff --git a/test/xmod/direnum_test.ww b/test/xmod/direnum_test.ww index 4b1e772e..c16fedbe 100644 --- a/test/xmod/direnum_test.ww +++ b/test/xmod/direnum_test.ww @@ -22,11 +22,12 @@ package direnum_test; // source order); the two stages' units are byte-identical. // // rejects — the authoritative loader-reject table over private trees: -// root-conflict ("conflicting package names"), import-leaf ("does -// not match import path"), root-missing and root-invalid ("invalid -// or missing package clause"), attest-noncanon ("@test declaration -// outside *_test.ww" — Go's test-file contract); per row both stages -// fail, carry the needle, and their captured stderr is byte-identical. +// root-conflict ("conflicting package names"), root-missing and +// root-invalid ("invalid or missing package clause"), attest-noncanon +// ("@test declaration outside *_test.ww" — Go's test-file contract); +// per row both stages fail, carry the needle, and their captured stderr is +// byte-identical. The former import-leaf rejection is now an accepted +// path/name-decoupling case and uses the imported declaration as qualifier. // // Dropped C machinery, not assertions: the ww_ww-absent skip gate // (the Make target declares both drivers) and the unlink/rmdir @@ -179,7 +180,7 @@ fn rejectpair(label: str, td: str, target: str, needle: str) void = { "package other;\nfn spare() i32 = { return 0; };\n"); testenv.writefile(strings.concat(importbad, "/entry.ww"), strings.concat("package main;\nimport wanted;\n", - "fn main() i32 = { return wanted.value(); };\n")); + "fn main() i32 = { return other.value(); };\n")); testenv.writefile(strings.concat(wanted, "/a.ww"), "package other;\nexport fn value() i32 = { return 0; };\n"); testenv.writefile(strings.concat(missing, "/a.ww"), @@ -192,12 +193,28 @@ fn rejectpair(label: str, td: str, target: str, needle: str) void = { "package attbad;\n", "@test fn hidden() void = { assert(false); };\n")); - let tags: []str = ["root-conflict", "import-leaf", "root-missing", + let drvs: []str = ["ww", "ww_ww"]; + let s: i32 = 0; + for (s < drvs.len) { + let out: str = strings.concat(td, "/import-name-", drvs[s]); + let av: []str = [testenv.driver(drvs[s]), "build", "-o", out, + strings.concat(importbad, "/entry.ww")]; + if (runcode(td, strings.concat("import-name-build-", drvs[s]), av) + != 0) { + fail("import-name", strings.concat(drvs[s], " build failed")); + }; + let rav: []str = [out]; + if (runcode(td, strings.concat("import-name-run-", drvs[s]), rav) + != 0) { + fail("import-name", strings.concat(drvs[s], " run failed")); + }; + s += 1; + }; + + let tags: []str = ["root-conflict", "root-missing", "root-invalid", "attest-noncanon"]; - let targets: []str = [rootbad, strings.concat(importbad, - "/entry.ww"), missing, invalid, attbad]; + let targets: []str = [rootbad, missing, invalid, attbad]; let needles: []str = ["conflicting package names", - "does not match import path", "invalid or missing package clause", "invalid or missing package clause", "@test declaration outside *_test.ww"]; diff --git a/test/xmod/m1_test.ww b/test/xmod/m1_test.ww index 1f270eab..0c5d8e00 100644 --- a/test/xmod/m1_test.ww +++ b/test/xmod/m1_test.ww @@ -203,7 +203,7 @@ fn userow(label: str, apick: str, onecall: str) void = { let rav: []str = [strings.concat(td, "/main")]; if (runcode(td, strings.concat("run_", drvs[s]), rav) != 0) { fail(label, strings.concat(drvs[s], " run-exit != 0 (a ", - "file-global first-match hint routes one math.pick() to ", + "cross-file first-match hint routes one math.pick() to ", "the wrong package -- #40)")); }; testenv.clean(td);