compiler: implement blank package name semantics

This commit is contained in:
2026-08-23 15:33:59 +09:00
parent e67b8e1bc4
commit 0b24b11d65
14 changed files with 2862 additions and 112 deletions

View File

@@ -6982,8 +6982,11 @@ test-runtime gap:
with official anchors in
[`internal/types/testdata/check/blank.go`](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/blank.go#L1-L5)
and [`test/blank1.go`](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/blank1.go#L1-L10).
Direct production, imported, and test-only probes were rejected identically
by both WW stages. This candidate was aligned.
Direct production, imported, and test-only probes were rejected by both WW
stages, but only through the loader's generic `invalid or missing package
clause`; the direct compilers also produced different syntax-recovery
streams. Equal rejection was not semantic alignment. The gap was different
and is completed in section 11.56.
- **Import:** pinned `unusedImports` requires every nonblank import binding to
be used
([`cmd/compile/internal/types2/resolver.go`, lines 706740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740)); official
@@ -10354,11 +10357,11 @@ a raw source and does not trigger the non-directory test-source omission in
- **directly measured WW behavior** — direct `SIGTERM` of either driver while a
selected warm build is blocked in compilation is an inherited lifecycle
non-effect, not part of the classifier change. Both stages terminate with
shell status 143, reap the complete owned process group, and preserve the
prior public output and committed semantic bytes, but the existing directory
machinery leaves three request-private `.new` files. That independently
verified cleanup gap remains open and this slice does not describe it as
fixed.
shell status 143 and preserve the prior public output and committed semantic
bytes, but leave the directly spawned compiler alive and exactly three
fixed-name `.new` files. A later persistent request rejects the existing
`.unit.new`. That independently verified supervision/cleanup gap remains open
and this slice does not describe it as fixed.
- **directly measured WW behavior** — Cstage and WWstage agree byte-for-byte
on status, stdout, stderr, diagnostics, public output, and every semantic
artifact for selected success and failure rows. Complete
@@ -10852,6 +10855,319 @@ every other package name remain ordinary.
remains `3`; no action descriptor, cache/result record, transaction marker,
manifest, database, or lock is added.
### 11.56 Implemented blank declared package-name checking
The exact declared name in `package _;` is now valid package-clause syntax and
an invalid package name. Loaders retain the clause, its imports, and an
otherwise coherent source family long enough to construct the applicable
ordinary action; the compiler checker then reports exactly
`invalid package name _` at the underscore token and continues checking that
source. The underscore is accepted only in this package-name grammar slot. It
does not become an ordinary identifier, import alias, qualifier, canonical
package name, or successful exported identity.
#### Pinned authority, tests, and applicability
The sole authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- **behavior directly implemented or asserted by pinned Go** — the compiler
scanner admits `_` to identifier scanning, dispatches it through the name
path, and returns it as a name token; the parser accepts and stores that token
in the package-name position
([`cmd/compile/internal/syntax/scanner.go`, lines 88107](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/scanner.go#L88-L107),
[368394](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/scanner.go#L368-L394),
and
[437439](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/scanner.go#L437-L439),
[`parser.go`, lines 397420](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/parser.go#L397-L420)
and
[27512763](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/parser.go#L2751-L2763)).
The types2 checker rejects the retained node as `invalid package name _`
and continues file initialization
([`cmd/compile/internal/types2/check.go`, lines 336355](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/check.go#L336-L355));
syntax errors prevent types2 from running
([`cmd/compile/internal/noder/noder.go`, lines 4577](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/noder/noder.go#L45-L77),
[`irgen.go`, lines 2399](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/noder/irgen.go#L23-L99)).
- **behavior directly implemented or asserted by pinned Go** — `go/build`
reads `_` without declaration-error parsing, classifies production and test
roles, and records imports before cmd/go builds package actions
([`go/build/read.go`, lines 5556](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/read.go#L55-L56),
[187198](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/read.go#L187-L198),
and
[265340](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/read.go#L265-L340),
[`go/build/build.go`, lines 9311039](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L931-L1039)).
Named files use one synthetic package loader
([`cmd/go/internal/load/pkg.go`, lines 32443315](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L3244-L3315));
test synthesis augments production with internal-test files rather than
treating them as unrelated packages
([`cmd/go/internal/load/test.go`, lines 175226](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L175-L226)).
- **behavior directly implemented or asserted by pinned Go** —
[`test/blank1.go`, lines 131](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/blank1.go#L1-L31)
asserts the blank-name error plus later checker errors, directly proving
continued checking.
[`internal/types/testdata/check/blank.go`, lines 15](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/blank.go#L1-L5)
separately asserts only the blank-name error. Official role controls
[`build_test_only.txt`, lines 118](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_test_only.txt#L1-L18)
and
[`build_no_go.txt`, lines 130](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_no_go.txt#L1-L30)
anchor the surrounding test-only selection rules. The pinned repository has
no official cmd/go test matrix for named, multiple, mixed,
test-only, artifact, rollback, concurrency, or interruption forms of
`package _`; those are WW-native proofs, not attributed to an absent Go
script.
- **behavior directly implemented or asserted by pinned Go** — after primary
syntax succeeds, types2 validates the imported package object's name before
consulting a local alias. Name `_` reports
`could not import PATH (invalid package name: "_")` at the import path;
an empty name instead quotes the actual empty value as
`invalid package name: ""`. The resolver
installs and caches a path-leaf-named fake package, marks the occurrence
used, and continues checking
([`cmd/compile/internal/types2/resolver.go`, lines 125180 and 248335](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L125-L180)).
The public checker twin implements the same rule
([`go/types/resolver.go`, lines 157190 and 263350](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/resolver.go#L157-L190)).
- No official pinned test directly supplies an imported `Package` whose name
is `_`. The two primary-source blank-name tests above do not assert this
importer-result validation. Its imported-interface matrix is therefore
WW-native proof grounded in the pinned resolver implementation, not a claim
about absent official testdata.
- **behavior derived from the pinned implementation** — the diagnostic owns
the underscore position, normally line 1 column 9, and belongs after source
loading, graph construction, complete-file syntax, and dependency-eligible
producer ordering. WW has the same explicit package clause, blank token,
syntax/check split, package families, imports, and package actions, so the
rule applies without adding modules, manifests, registries, generalized
imports, or a new identity model.
Before this slice, **directly measured WW behavior** was a generic positioned
`invalid or missing package clause` from both public stages before any
producer. Direct Cstage `w6c` emitted four package-clause recovery diagnostics
while WWstage `w6c_ww` emitted three. A visible named `_test.ww` ordinary build
was rejected at its header instead of being validated and omitted. These facts
were a real package/build/test/import difference despite stage-equal public
rejection. After the primary-source parser/checker repair and before the
imported-interface completion, **directly measured WW behavior** also accepted
an owner-matched hand-authored `.wwi` declaring `package _;`: default,
explicit, and blank controls accepted and published usable semantic facts, and
the explicit alias control produced successful byte-identical Cstage/WWstage
assembly and interface output. Ordinary producers could not create that
metadata, but direct `--import` and caller-owned persistent interfaces made the
gap externally observable.
#### Semantic ownership
Primary package-slot syntax and marker positions remain owned by the C/WW
parser twins `cmd/wcc/parse.c` and `lib/ww/syntax/parse.ww`; primary and
imported blank-name checker diagnostics and fake-package resolution are owned
by `cmd/wcc/check.c` and `selfhost/cmd/wcc/check.ww`. Imported export-data
materialization, four-state package metadata, primary-rooted reachability,
owner filtering, recovery qualifier assignment, and delayed concatenation are
owned by `cmd/w6c/main.c` and `selfhost/cmd/w6c/main.ww`. Exact import-path
token positions are private `N_USE` state owned by `cmd/wcc/ww.h`,
`lib/ww/syntax/ast.ww`, both parser twins, and
`lib/ww/syntax/decl.ww`.
The public drivers and package coordinator already supply canonical direct
interface arguments and inherit the validation without edits. No loader,
assembler, archiver, linker, runtime, package-identity, or import-syntax owner
changes in this completion. This complete ownership replaces the earlier
transitional assumption that the slice had only four syntax/checker owners.
#### Four-axis behavior and phase order
- **Build:** an eligible blank production source passes the loader, contributes
its ordinary package action and import closure, and fails in checker entry.
A visible literal named `_test.ww` still follows section 11.51: after its
header and contiguous imports are valid, ordinary build omits it before body
parsing or action construction, including when the declared name is `_`.
`ww run` retains its earlier MainOnly boundary: a blank root is not `main`,
so a successfully loaded root is rejected before any producer or compiler.
- **Test:** an all-blank production, internal-test, or honest test-only family
reaches its applicable test-package compiler action and fails there. A valid
production `p` plus blank test `_`, or blank production `_` plus unrelated
`p`/`p_test`, remains a coordinator family mismatch before tools. Existing
raw/directory `FAIL` placement, compile-only empty stdout, and directory
`-S` CLI-shape diagnostics are unchanged; no failed blank product runs.
- **Package:** `_` is retained only as a declared source-family observation.
It is not canonical identity. In an all-blank selected action, the checker
emits one positioned BlankPkgName diagnostic per retained source marker in
deterministic source-section order and continues later checking. Mixed
declared names retain loader conflict precedence.
- **Import:** a valid contiguous import in a blank source is a normal source
occurrence and graph edge. Missing or invalid recursive dependencies can
therefore fail before the parent checker. An ordinary blank source provider
cannot publish a successful interface or archive. A reached hand-authored or
corrupted `.wwi` that declares `_` is nevertheless parsed and defensively
rejected by the consuming compiler; an unused interface is inert. Default,
explicit, and blank aliases cannot mask the invalid provider name.
Visibility, vendor, cycle, and initialization rules do not change.
The complete observable phase order is: source eligibility and loader-visible
header/family checks; recursive import loading; MainOnly rejection for `run`;
eligible dependency producers for build/test or a run root that passed
MainOnly; the parent compiler's complete-file syntax; BlankPkgName checking;
reached imported-package validation; later checker diagnostics; then existing
driver/coordinator failure trailers.
Consequently malformed contiguous imports precede graph construction, a
missing dependency can suppress both a later body syntax error and
BlankPkgName, and full-file syntax suppresses checker diagnostics. A blank run
root never starts even valid dependency producers because MainOnly is earlier.
#### Test graph, actions, and identity boundaries
A production-only blank package under `ww test` has one ordinary production
compiler action. Production `_` plus a same-package `_test.ww` also declared
`_` forms one internal-test family: its augmented action owns both source sets,
and `sep_recompile_for_test` substitutes it throughout the product closure, so
the separate production node is not independently compiled for that product.
One compiler invocation reports one BlankPkgName per retained marker. A
test-only blank `_test.ww` forms one test-only internal action and reports once.
A blank production plus the actually related external name `__test` passes
family classification, but its blank production dependency fails before the
external action, generated main, link, or runtime can complete.
Direct raw files retain `__root.*`; dotted directories and providers retain
their canonical dotted package/import/action/artifact identities; production,
internal, external, recompiled, support, and generated-main actions retain
their existing distinctions. Physical paths, parents, source spellings, and
symlink targets remain loader or diagnostic observations. No successful
ordinary producer `.wwi` can advertise `_`; a supplied or corrupted `.wwi`
that does is invalid input, not an alternate symbol, publication, or
persistence identity.
#### Imported `.wwi` validation, reachability, and recovery
The rules in this subsection are **behavior derived from the pinned
implementation** for WW's supported source-like export-data channel. They do
not claim an official imported-blank fixture that does not exist.
Every sorted `--import CANONICAL FILE.wwi` is first read, owner-checked, and
syntax-parsed into its own standalone AST. Interface read, owner, syntax, and
structural import-map errors keep their existing precedence. The compiler then
syntax-parses the primary input. Any primary syntax error returns before
imported-package semantic validation, so it is never accompanied by the
broken-import diagnostic.
After successful primary parsing and import-map application, compiler test
mode materializes any required support `N_USE` before resolution. The node has
the primary owner/source ID, is marked used, uses visible name `test` or the
collision-safe reserved `__wwtest`, and is positioned at the generated primary
root because no source path token exists. An existing equivalent primary
occurrence prevents duplication. When no support interface is supplied, the
node retains the established raw external-support fallback; a supplied
blank-named support interface is validated like every source import. Reserved
`__wwtest` preserves its compiler-selected visible spelling after valid
resolution but cannot skip provider-name validation. Existing
`--test-target-package` roots already require a primary occurrence and add no
second node.
A metadata-only pass classifies each represented canonical package as valid,
missing, conflicting, or invalid; invalid means its one nonconflicting real
declared name is exactly `_`. Compiler-private placeholder packages remain
valid recovery metadata. Reachability is seeded only by canonical primary and
compiler-required `N_USE.usepath` occurrences, then reaches a fixed point over
standalone interface lists. An imported use contributes an edge only when its
owning canonical package is already reached and valid. Interface containers,
arbitrary embedded origins, and invalid, missing, conflicting, or unreachable
owners are never roots or traversal sources.
Before binding or concatenation, each standalone list is filtered to nodes
whose canonical owner is both reached and valid. Invalid-owner, unreachable,
and ownerless hand-authored facts are discarded. Thus an unused invalid
interface is wholly inert even when it contains an embedded valid-origin
section that imports the invalid path: it emits no diagnostic, installs no
declaration or scope, contributes no output or serialized fact, and leaves an
otherwise valid primary byte-equivalent to the no-interface control. If the
primary independently reaches that valid origin, its retained import can then
reach and diagnose the invalid provider. Binding runs on those filtered lists
and the primary list before the lists are concatenated, so unreachable
internal uses cannot manufacture missing, conflict, or invalid effects.
Every retained occurrence resolving to an invalid provider is marked used and
records declared provider name `_`. A nonblank occurrence receives a recovery
package binding using its explicit alias or, by default, the final component of
the canonical dotted path; a blank occurrence installs no visible binding.
The fake package has an empty scope. Qualified value, call, and type gateways
therefore recover as the error type without missing-member,
unknown-type/export, or calling-nonfunction cascades, while a lexically closer
value binding still shadows the recovery qualifier normally.
At checker entry, immediately after primary BlankPkgName diagnostics, the
first retained invalid occurrence of each canonical path emits exactly
`could not import PATH (invalid package name: "_")`; later occurrences of the
same path are deduplicated, while distinct paths diagnose in retained
occurrence order. Independent checker diagnostics continue afterward.
Deduplication uses canonical dotted path because WW has no Go source-directory
import-key component. Default, explicit, and blank alias forms all position
this diagnostic at the path's first identifier, never at an explicit alias.
The existing alias-or-path first-spec position remains unchanged for every
other binding diagnostic. Full parsing, imports-only parsing, and named-source
header parsing all retain both position families.
Public persistent build and test actions consume the same supported interface
channel. If a reached caller-owned committed `.wwi` is corrupted to declare
`package _;`, the consumer compiler fails after primary syntax and resolution;
that action's assembler and downstream archive, link, retention, or runtime do
not complete. A package-action failure prevents generated main; a generated-main
action that is itself the consumer performs the same validation before its own
assembly. Raw, production, internal, external, test-only, generated-main, `-c`,
and applicable `-S` consumer actions use the same rule. A same-named `.ww` file
remains an import decoy rather than a provider. An
unreferenced corrupt interface remains inert and does not invalidate or alter
the consumer. A failing direct compiler returns status 1 with empty stdout;
public test presentation retains its existing running `FAIL` and
package-trailer rules, while compile-only and assembly-only forms retain empty
stdout.
#### Artifacts, rollback, concurrency, parity, and formats
The failed primary blank action and a consumer rejecting a reached blank-named
interface emit neither compiler assembly nor `.wwi`, so their downstream
assembler, archiver, linker, test harness, and user runtime do not run. Valid
dependencies or test support that precede either failure may execute their
ordinary producers, but request rollback removes every request-owned stage and
commits no failed generation, public product, retained test binary, unit,
interface, assembly, object, archive, executable, stamp, or status. Direct
named `-o`/`-I` outputs, public outputs, retained tests, and committed semantic
bytes remain byte-identical. Restoring the exact valid source or `.wwi` bytes
uses the existing content-identity reuse path; invalid bytes never publish a
replacement consumer generation. Reached interface bytes already participate
through the existing compiler input and invalidation rules; semantic validation
adds no graph, action, or persistence key. A build-omitted named `_test.ww`
contributes no action or invalidation key.
Blank-package state, imported metadata, reachability sets, deduplication, and
fake bindings are compiler/checker-process-local. Independent concurrent
requests cannot share diagnostics, graph state, staging, or cleanup. Normal
failure is waited and rolled back through the existing transaction owner and
leaves no anonymous descriptor, `.new`, `.old`, `.install`, `.wwtxn.*`,
capture, result, request scratch, or child.
Direct `w6c` and `w6c_ww`, and public `ww` and `ww_ww`, agree on status,
stdout, exact path-token positions and diagnostic order, fake recovery,
output absence, prior-byte preservation, and every comparable semantic
dependency artifact. Producer provenance remains the established intentional
stage difference.
No signal-supervision behavior changed. Direct external `SIGTERM` during a
blocked persistent compilation still preserves prior committed/public bytes
but can leave the spawned compiler and fixed-name `.new` staging that poisons a
later request. That independently verified gap remains open and is not claimed
fixed by normal BlankPkgName rollback.
No serialized representation changed. The full parser changes only the
in-memory position of a blank package marker to the underscore token; outer
file/header/import-only markers and valid package markers keep their former
positions. Each twin's private in-memory `Node` gains only
`usepathfile/usepathline/usepathcol`; AST enum values, AST printing, `.wwi`
schema, build workdir format `18`, test workdir format `19`, and semantic
storage format `3` remain unchanged. No cache, result record, manifest, action
descriptor, transaction marker, database, or lock is added. This closes one
coherent semantic slice across all four axes;
it does not complete the remaining suffix-first run front, multiple named
source packages, shared test-process state and failure topology, RE2-compatible
flat `-run`, finite special-source handling, or external-driver interruption
recovery.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.