test: prove case-fold collision preflight

This commit is contained in:
2026-08-15 16:11:01 +09:00
parent 265be3f796
commit 6974bec878
4 changed files with 1037 additions and 13 deletions

View File

@@ -311,11 +311,21 @@ and matches its final single `linux` token, exactly as Go does.
Selection is additive, not replacement-based: every matching file belongs to
the package. The production variant then excludes `*_test.ww`; internal and
external test classification uses only the already platform-selected test
files. An excluded file creates no source occurrence, import, dependency edge,
package/action/variant identity, compiler input, export, archive member, link
input, artifact, status, or persistence dependency. Adding or editing one is a
producer no-op. Adding, removing, or editing a selected file changes the owning
unit normally.
files. After those decisions, distinct selected basenames in one canonical
directory MUST NOT be equal under Go 1.26.5's Unicode simple-fold comparison.
The check spans the production, internal-test, and external-test selections of
one `ww test` product without merging those source units. An ordinary
`ww build` sees production names only. Exact basename reuse by another action
view of that directory is not a collision.
An excluded file creates no source occurrence, collision, import, dependency
edge, package/action/variant identity, compiler input, export, archive member,
link input, artifact, status, or persistence dependency. Adding or editing one
is a producer no-op. Adding, removing, or editing a selected noncolliding file
changes the owning unit normally. This applicability is deliberately narrower
than Go's `Package.AllFiles`: WW omits wrong-platform and `*_test.ww` names
from an ordinary build because those files are not loaded in WW's
fixed-target, manifest-free source model.
WW implements no source-level build expressions, user tags, target descriptor,
`UseAllFiles` escape, `+tag` replacement scheme, or manifest-defined selector.
@@ -5588,6 +5598,154 @@ source-level build expressions, quoted/grouped/dot imports, and targets other
than the separately specified fixed `linux/amd64` filename selection. None is
used to define canonical package or directory-product identity.
### 11.23 Implemented case-fold collision preflight
This slice pins the collision semantics to Go 1.26.5, tag commit
`c19862e5f8415b4f24b189d065ed739517c548ba`. The official command loader uses
an exact import cache at `src/cmd/go/internal/load/pkg.go:633-636,757-775`,
performs contextual and vendor resolution before cache lookup at
`:840-911,974-1005`, owns one command-global folded import table at `:1725`,
and rejects a second fold-equivalent import spelling at `:1950-1959`. Its
selected-name collision is over `Package.AllFiles` (`:149-194`) and is applied
at `:1991-2000`. `src/cmd/go/internal/str/str.go:32-89` defines the pinned
`ToFold` and `FoldDup` algorithms; the direct, transitive, and filename
expectations are in
`src/cmd/go/testdata/script/list_case_collision.txt:1-41`. Filename discovery
and package/test classification are ordered by
`src/go/build/build.go:859-914,1005-1036,1076-1082,1450-1469`.
Before this slice, a Linux case-sensitive filesystem let distinct directories
such as `domain.Foo` and `domain.foo`, expanded vendor identities, and selected
files such as `File.ww` and `file.ww` build as unrelated packages or sources.
Both Cstage and WWstage did so byte-identically. A symlink making the two import
spellings reach one directory happened to trip the older exact
directory/identity check, but that host-physical consequence was neither the Go
diagnostic nor the required package-graph rule.
#### Exact identity and request-only folded keys
Canonical package identity remains the exact, case-sensitive effective dotted
identity. It is still the key used by action interning, source import maps,
`.wwi` ownership, mangled symbols, storage digests, archives, diagnostics, and
link closure. Default bindings, explicit aliases, blank imports, declared
package names, path leaves, artifacts, and physical directories do not replace
it. Exact repeated occurrences therefore continue to form one canonical edge
and one package action.
The loader graph additionally owns a checked, dynamically grown, request-only
table from a simple-fold key to the first exact package representative. Every
ordinary effective identity registers after contextual local/vendor expansion
and before exact action reuse or physical-directory conflict checks. Exact
reuse succeeds. A distinct spelling with the same key rejects as
`ww: case-insensitive import collision: "A" and "a"`; WW byte-sorts the two
exact spellings so root, request, import, and discovery order cannot select a
different diagnostic. It never stores, interns, looks up, or publishes the
folded spelling as package identity.
WW dotted import components are ASCII by construction: the C lexer/parser
accept them through `cmd/wcc/lex.c:53-63` and
`cmd/wcc/parse.c:1318-1353`, and the self-hosted syntax path mirrors that in
`lib/ww/syntax/lex.ww` and `lib/ww/syntax/decl.ww`. Context-derived identities
are revalidated, and arbitrary local filesystem bytes are escaped into ASCII.
The general Unicode fold routine is nevertheless shared with filename
preflight; for package identities its observable domain reduces exactly to
ASCII case folding.
Vendor imports register the fully expanded canonical identity, not the short
source spelling. Thus `lib.Foo` and `lib.foo` reached below one vendor owner
collide as, for example, `domain.app.vendor.lib.Foo` and
`domain.app.vendor.lib.foo`. Reaching one physical directory through two such
identities changes neither ownership nor the diagnostic. The physical
directory remains a resolution fact, never a substitute identity.
Production, same-package test (`ptest`), external test (`pxtest`), and
product-scoped recompiled actions share their one exact ordinary package
representative and do not re-register as different packages. External
compiler identity may still carry `_test`; that action path is not the folded
package key. Copy-on-write clones copy exact identity and do not register.
Generated main has no ordinary import identity and does not register. Ordinary
toolchain package `test` registers like any real package; only the reserved
synthetic `__wwtest` role stays outside the ordinary representative table.
#### Selected source basenames
Directory enumeration first excludes leading-dot, leading-underscore,
wrong-platform, and variant-ineligible files, validates the selected regular
sources, and byte-sorts their names. Before delegation, the coordinator performs
its required package-clause classification and parses selected production files
to reject `@test` declarations outside `*_test.ww`. The delegated driver then
registers each selected basename in a second request-only table scoped by
canonical physical directory. Repeated views of the exact same selected
basename across `p`, `ptest`, or `pxtest` are accepted. Distinct fold-equivalent
basenames reject as
`ww: case-insensitive file name collision: "File.ww" and "file.ww"` before the
driver's graph-import scan or any producer. The preflight is not an earlier
replacement for the coordinator-owned source validation parse.
One `ww test` request shares that directory scope across production,
same-package test, external test, same-only, external-only, and mixed test-only
actions while preserving their separate compilation units. This catches a
collision crossing classifications, such as production `X_TEST.ww` and test
`x_test.ww`. An ordinary `ww build` excludes `*_test.ww` before registration.
Hidden, underscore-prefixed, and wrong-platform files never register and
therefore create no collision or invalidation.
This last ordering is WW's explicit applicability boundary rather than a claim
that every upstream `AllFiles` member is selected here. Go includes test files
and some ignored Go files in `AllFiles`, so its ordinary build can diagnose a
broader set. WW intentionally follows its existing fixed-target source
eligibility and build/test isolation: files it does not load have no graph or
persistence effect.
Filesystem basenames are arbitrary non-NUL bytes, so their fold keys reproduce
the pinned Unicode 15.0 `unicode.SimpleFold` minimum-cycle behavior without
locale or normalization. Each malformed UTF-8 byte contributes one U+FFFD to
the temporary key, as Go string ranging does; diagnostics preserve the exact
original byte and quote it as `\xNN`. Printable Unicode remains UTF-8, other
nonprinting runes use Go-style `\u` or `\U` escapes, and composed/decomposed
Unicode spellings are not normalized.
#### Tool, artifact, transaction, and persistence ownership
The coordinator owns initial eligibility and production/test classification;
the delegated driver loader owns both fold checks. The language parser still
owns the exact import occurrence and qualifier. The driver owns per-site self,
internal, vendor, and imported-main structural legality, while the compiler
checker owns file-local binding, use, and visibility. The export writer/reader
owns exact canonical `.wwi` data. Compiler, assembler, archiver, and linker
protocols did not change. Successful neighboring units, `.wwi`, assembly,
objects, archives, generated mains, binaries, and exact tool argv therefore
remain byte-identical in Cstage and WWstage.
All root and reachable dependency loading, fold registration, and final exact
identity binding finish before scratch acquisition, support or generated-main
producer work, compilation, assembly, archive construction, or linking. A
collision invokes none of those tools and creates no unit, `.wwi`, assembly,
object, archive, main, binary, result, status, voucher, stamp, or `.new` stage.
Request-wide publication remains transactional: committed sibling and
dependency bytes survive a newly introduced collision, and removing the
colliding source restores precise warm reuse.
The fold tables live only for one command and are freed at graph teardown.
Entries become live only after every owned string and vector allocation
succeeds, so allocation failure cannot publish a partial table or make cleanup
traverse uninitialized entries. Successful action/unit/storage content did not
change; build workdir format stays `18`, test workdir format stays `19`, and
semantic storage stays `3`. A format bump would only discard valid exact-key
artifacts and is therefore not used.
Native proof extends `package_graph_diagnostics_are_stable`,
`platform_filename_source_selection`, and
`vendor_directory_import_resolution`, with command-global allocation failure
retained in `allocation_failure_is_command_global`. The matrix covers direct,
transitive, reversed, recursive, same-directory, and vendor-expanded imports;
exact-repeat acceptance; reversed vendor import and product order;
production/internal/external/test-only filenames; reversed creation and
direct/recursive collision-diagnostic parity; cross-classification, Unicode,
invalid UTF-8, ignored files, and absence of normalization; zero-tool
rejection; multi-product publication isolation; cold/warm add-remove reuse;
exact artifact preservation; and Cstage/WWstage diagnostic and byte identity.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.