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.

View File

@@ -323,11 +323,25 @@ ImportPath = ident { "." ident } .
package declares `package wire;`, the importing file sees its exported names
as `wire.Name`; `codec.Name` is not an additional binding. An explicit alias
replaces only that visible qualifier: `import stable acme.codec;` exposes
`stable.Name`, not `wire.Name` or `codec.Name`. Both kinds of binding are
scoped to that source file. A sibling file must declare its own import.
`stable.Name`, not `wire.Name` or `codec.Name`. Each effective default or
explicit named binding is a file-local package-name object usable only as
the left qualifier of a selector. A bare occurrence in a value context is
rejected as `use of package BINDING not in selector`; a bare occurrence in
a type context is rejected as `BINDING (package name) is not a type`. Such a
rejected bare occurrence does not count as use of the import for
unused-import accounting. A legal selector does count as use, but does not
excuse any separate bare occurrence. Both kinds of named binding are scoped
to that source file. A sibling file must declare its own import; without one,
its otherwise equal spelling follows ordinary undefined-name or unknown-type
lookup rather than package-name diagnostics. Builtin spelling does not alter
the object: an import bound as `len`, `size`, `align`, or another builtin name
remains a selector-only package-name object.
Neither form exposes an imported declaration as a bare `Name`; ordinary
unqualified lookup remains limited to lexical, builtin, and same-package
declarations.
declarations. A blank import creates no package-name object, an effective
`init` import is rejected before installing one, and a missing target fails
during import resolution, so none of those cases acquires the bare-package
diagnostics or satisfies a named import's unused accounting.
- An import whose effective file-local qualifier is `init` is invalid. This
includes both `import init acme.codec;` and an unaliased import whose target
declares `package init;`. Each resolved occurrence is rejected at its first

View File

@@ -583,6 +583,35 @@ exact-argv, command, and persistent-workdir observers, the package suite proves
archive-only link argv and exact warm/rejection-state behavior without
duplicating those broader mechanisms in this observer.
The focused dual-stage `bare_import_bindings_require_selectors` observer is the
acceptance owner for the rule that an effective default or explicit import
binding is a file-local package-name object, not a value or type. Its generated
matrix must prove exact `use of package BINDING not in selector` value
diagnostics and `BINDING (package name) is not a type` type diagnostics; an
unused diagnostic for every named binding that has only bare occurrences; and
the absence of that unused diagnostic once a legal selector independently uses
the binding. It must cover occurrence order, repeated bare occurrences,
explicit aliases, builtin-spelled bindings including `len`, `size`, and `align`,
qualified value and type controls, nested value and type contexts, sibling-file
isolation, blank imports, rejected `init` bindings, and missing-target
precedence. Type-shaped arguments parsed for `size` and `align` must retain type
checking when either spelling is instead an import binding; neither stage may
fall into an internal expression-kind diagnostic.
The same observer must exercise ordinary root and imported builds plus
production, same-package test, external-test, and honest test-only source
roles. Cstage and WWstage must agree on status and normalized stdout/stderr,
including diagnostic order and source position, and valid controls must produce
byte-identical comparable artifacts and equal runtime results. Its lifecycle
matrix must include cold rejection with no public or retained product, warm
preservation of every prior committed work-file and public byte, imported
dependency failure, invalidation followed by rejection, subsequent valid
recovery, and exact transaction-residue checks. Legal selector controls retain
ordinary graph/action, compilation, linking, publication, persistence, and
reuse behavior. Producer/runtime failure, concurrency, interruption, process
topology, and descendant cleanup remain with their established observers
because this checker-owned rejection creates no producer or runtime boundary.
The focused dual-stage `effective_init_imports_never_enter_binding_recovery`
observer is
the acceptance owner for imports whose effective file-local qualifier is