fix: reject bare import bindings

This commit is contained in:
2026-08-22 15:17:10 +09:00
parent 91fce80b37
commit 89f519d5cb
6 changed files with 1490 additions and 38 deletions

View File

@@ -9545,6 +9545,221 @@ No format bump. Build workdir format remains `18`, test workdir format remains
`19`, semantic storage format remains `3`, and no test-result cache is
introduced.
### 11.48 Implemented selector-only effective import bindings
Pinned Go's applicable semantic rule is that an imported package's effective
name denotes a package-name object, not a value or type, and that object may be
used only to qualify a selector. A bare value occurrence is rejected as
`use of package BINDING not in selector`; a bare type occurrence is rejected as
`BINDING (package name) is not a type`. A bare occurrence does not mark the
import used, while a selector does. These rules and diagnostics are **behavior
directly implemented or asserted by pinned Go**.
The rule honestly applies within WW's local, dotted-import, manifest-free
model. WW already resolves every ordinary import to an effective default or
explicit file-local qualifier and already represents qualified value and type
selectors. Treating that existing qualifier as a package-name object closes a
checker hole without adding quoted, grouped, dot, generalized, network, module,
manifest, registry, or source-expression import machinery. Applying Go's
package-name-object rule to WW's already representable import binding is
**behavior derived from the pinned implementation**.
#### Pinned evidence and fact classification
The sole semantic authority is official Go 1.26.5 at
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- [`ident` in `cmd/compile/internal/types2/typexpr.go`, lines
1860](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/typexpr.go#L18-L60)
resolves an identifier to its object and, in a type context, rejects an
object that is not a type name as `NAME (KIND) is not a type`;
- [the package-name value branch in that file, lines
8992](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/typexpr.go#L89-L92)
rejects a package name outside a selector as
`use of package NAME not in selector`;
- [`objectKind` in `cmd/compile/internal/types2/object.go`, lines
675680](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/object.go#L675-L680)
names a `PkgName` object's kind `package name`;
- [selector checking in `cmd/compile/internal/types2/call.go`, lines
672735](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/call.go#L672-L735),
especially lines 681691, admits a package name in the selector position and
marks that exact package-name object used;
- [import declaration in `cmd/compile/internal/types2/resolver.go`, lines
248340](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L248-L340)
creates the `PkgName` in file scope, and
[`unusedImports`, lines
706740](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L740)
diagnoses each nonblank package-name object not marked by a selector;
- [compiler diagnostic sorting in `cmd/compile/internal/base/print.go`, lines
7092](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/base/print.go#L70-L92)
preserves stable source-position ordering; and
- [binary-expression checking in `cmd/compile/internal/types2/expr.go`, lines
788802](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/expr.go#L788-L802)
checks both operands before returning for either invalid result, so two bare
package-name operands retain two independently owned diagnostics.
Those source branches are **behavior directly implemented or asserted by
pinned Go**. Official assertions are
[`test/fixedbugs/issue11361.go`, lines
711](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/fixedbugs/issue11361.go#L7-L11),
which pairs an unused import with its bare package-value error;
[`src/internal/types/testdata/check/builtins0.go`, lines
613617](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/builtins0.go#L613-L617),
which rejects a package name passed as a value; and
[`src/internal/types/testdata/check/decls1.go`, lines
4573](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/decls1.go#L45-L73),
especially line 66, which asserts `math (package name) is not a type`.
From those pinned mechanisms it follows that a bare-only named import receives
both its unused-import diagnostic and its context-specific package-name
diagnostic; a legal selector removes only the unused diagnostic and never
excuses a distinct bare occurrence; an explicit alias supplies the displayed
name; and a sibling file without its own binding follows ordinary name lookup.
Those conclusions are **behavior derived from the pinned implementation**.
#### Fresh four-axis audit and direct pre-fix measurements
The fresh audit also confirmed multiple named source operands, explicit
`*_test.ww` build operands, shared top-level test-process state and fatal-abort
behavior, Go's regular-expression `-run` matching, and lexical shadowing of import
bindings as applicable but unselected differences. Ordinary directory
production selection was aligned. Grouped, quoted, and dot imports remain
inapplicable to WW's deliberately narrower syntax. Those classifications are
audit conclusions, not claims that this slice closes the unselected behavior.
Before this slice, the following observations were **directly measured WW
behavior**. With a dependency declared `package wire` and exporting a value and
type, both stages accepted legal `wire.Value` and `wire.Number` controls and
produced byte-identical 8,201-byte executables with SHA-256
`7fb0e95229aa22714d880070388ee5169697543076536267a8c709429cce0fcb`;
both ran with status 41. A bare-only value caused only the generated-unit
unused-import diagnostic in each stage. Once a legal selector marked the import
used, Cstage let a separate bare value reach the linker as
`undefined reference to 'wire'`, while WWstage let it reach code generation as
`cgident: unresolvable identifier (rule 7)`. An explicit alias reproduced the
same stage split using that alias. Reversing occurrence order or adding a
second bare occurrence did not restore checker ownership.
A bare type plus a legal selector was reported by both stages as
`unknown type 'wire'`, rather than the pinned package-name diagnostic. With no
selector, Cstage also retained the unused-import error, while WWstage emitted
only the unknown-type error. A sibling source that did not import `wire` let a
bare value reach the linker or code generator rather than following ordinary
undefined-name checking. Blank-import controls did follow ordinary undefined
lookup, missing targets retained missing-package precedence, and the valid
qualified/blank control retained artifact-byte and runtime parity. All cold
failed work directories examined were empty and no output was published.
The same checker escape was measured in same-package, external-test, honest
test-only, production-test, and imported-dependency paths: the test coordinator
reported `FAIL`, but Cstage failed in the linker while WWstage failed in code
generation. These status, stream, artifact, and cleanup results are **directly
measured WW behavior** from the pre-change Cstage/WWstage probes; they are not
attributed to Go.
The directly measured pre-fix result across the permanent axes was therefore:
- **Go-like build:** legal selector controls loaded, compiled, linked,
published, and ran byte-identically, but a bare package name escaped semantic
checking and failed at different backend phases;
- **Go-like test:** every applicable generated test source role reached the
ordinary package failure result, but its checker diagnostic and failure phase
differed between stages;
- **Go-like package:** effective qualifiers were already source-local during
legal selector resolution, but sibling and bare lookup could escape that
source-local package-name boundary; and
- **Go-like import:** unused accounting recognized some selector uses, but a
bare import binding was not consistently classified as a non-value,
non-type package-name object.
#### Ownership and complete four-axis contract
The true semantic owner is identifier and type-name resolution in the Cstage
and WWstage semantic checkers. The checker must distinguish a package-name
object belonging to the current source from ordinary lexical or package-scope
objects before generic undefined, type, code-generation, or link recovery. A
selector remains the only construct that consumes such an object and marks its
import used. A bare type name must not accidentally mark the import used. Both
implementations must produce the same normalized diagnostics at the same source
positions and retain ordinary undefined or unknown-type behavior when the
current file has no binding.
- **Go-like build:** every selected source is rejected during semantic checking
before compiler output, assembly, archive, link, or installation when it
contains a bare package-name object. Legal selectors retain the existing
build graph, actions, artifact bytes, runtime result, and publication rules.
- **Go-like test:** production, same-package, external-test, and honest
test-only sources use the same checker rule. Rejection precedes test-process
construction and execution; discovery, filters, descriptors, result
accounting, fatal/skip behavior, timeouts, and process cleanup do not change.
- **Go-like package:** the binding remains owned by exactly its source file.
Declared package names, canonical package and variant identity, selected
source membership, exported declarations, initialization, and symbol naming
do not change.
- **Go-like import:** default and explicit named bindings become consistently
selector-only package-name objects. Selectors alone satisfy unused-import
accounting. Blank imports create no binding, rejected effective `init`
imports install none, and missing-target resolution keeps precedence.
#### Lifecycle, parity, proof, and formats
Loading and source eligibility, filename ordering, package-clause validation,
canonical dotted resolution, contextual local/vendor expansion, source-role
classification, and graph-edge construction remain unchanged. The diagnostic
is a property of an already resolved file-local binding; physical directories
remain loader and presentation metadata and never acquire package, import,
graph, action, artifact, symbol, `.wwi`, publication, or persistence identity.
Semantic rejection occurs within the compiler action before code generation,
so no assembler, archiver, linker, test runtime, or installer may run for the
invalid action.
Diagnostic source order and precedence must remain stable: missing-package,
invalid effective-`init`, blank-no-binding, import collision, and ordinary
undefined-name paths keep their established owners; a valid binding receives
the exact package-name diagnostic and, unless a selector separately used it,
its independent unused-import diagnostic. Repeated invalid bare occurrences
are diagnosed independently.
Cold rejection must publish no executable, archive, interface, retained test
binary, or partial semantic action. Warm rejection must preserve the previous
public product and complete committed work generation byte-for-byte. A later
valid request must recover through existing invalidation and reuse rules.
Neither rejected source text nor its physical directory may create a new
identity or persistence key. Valid controls must retain comparable Cstage and
WWstage diagnostic and artifact-byte parity.
The invalid consumer's compiler action starts and rejects during semantic
checking, before its code-generation or downstream producer boundary. Already
valid dependency producers may also have completed before that rejection.
Assembler, archiver, linker, installer, test-runtime, and runtime-failure paths
for the invalid consumer are therefore unreachable. Existing producer/runtime
failure, rollback, publication transactions, parallel-product isolation,
concurrent-request locking, cancellation, interruption escalation, child
ownership, and descendant cleanup remain unchanged for other actions. Rejection
must remove request-owned stages and leave no active `.new`, `.install`,
`.wwtxn.*`, adjacent `.sepwork`, capture, result, scratch, or tool-stage
transaction residue.
The WW-native `bare_import_bindings_require_selectors` observer is required to
prove exact cross-stage status and normalized diagnostic parity for bare value
and type contexts, explicit aliases, selector order, repeated occurrences,
unused accounting, sibling-file isolation, blank/`init`/missing controls,
ordinary and imported builds, builtin-spelled package callees (`len`, `size`,
and `align`), nested value/type contexts, and every applicable test source role.
In particular, parser-shaped `size`/`align` type arguments must keep type
checking after the callee is classified as a package-name object and must never
escape through generic internal-expression recovery. The observer must also
prove valid artifact-byte/runtime parity, cold empty rollback, warm prior state
preservation, invalidation and recovery, and residue cleanup. Broader
producer/runtime failure, concurrency, interruption, and descendant-process
behavior retain their existing owners because this slice adds no such
boundary. These are proof requirements; validation and final post-change byte
measurements are recorded only after they are run.
No format bump. Build workdir format remains `18`, test workdir format remains
`19`, semantic storage format remains `3`, and no test-result cache is
introduced.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.