ww imports: require imports before declarations

This commit is contained in:
2026-08-21 19:20:52 +09:00
parent b03bbb1428
commit ee4573bf55
9 changed files with 730 additions and 8 deletions

View File

@@ -1361,6 +1361,10 @@ parseimports(Parser *p)
Node *head = NULL, *tail = NULL;
Node *packages = NULL, *packagetail = NULL;
int sawpackage = 0;
/* Go source files admit one import section before ordinary declarations.
* Keep recovery permissive, but diagnose the first import in each later
* section. Compiler-owned bundle markers begin a fresh source section. */
int previmport = 1;
while (p->cur.kind != TK_EOF) {
/* Compiler/driver bundle markers carry package identity out of
@@ -1368,6 +1372,7 @@ parseimports(Parser *p)
* the following package clause and imports. */
if (p->cur.kind == TK_MODPATH) {
sawpackage = 0;
previmport = 1;
p->sourceid++;
p->pathmod = p->cur.text;
p->curmod = p->cur.text;
@@ -1379,6 +1384,7 @@ parseimports(Parser *p)
if (p->cur.kind == TK_MODRESET) {
const char *rp = p->cur.text;
sawpackage = 0;
previmport = 1;
p->sourceid++;
advance(p);
p->pathmod = NULL;
@@ -1389,6 +1395,7 @@ parseimports(Parser *p)
}
if (p->cur.kind == TK_MODULE) {
Pos pp = p->cur.pos;
previmport = 1;
advance(p);
if (p->cur.kind != TK_IDENT) {
errorf(p->cur.pos, "invalid or missing package clause");
@@ -1426,6 +1433,12 @@ parseimports(Parser *p)
sawpackage = 1;
}
if (p->cur.kind == TK_USE) {
if (!previmport) {
errorf(p->cur.pos,
"imports must appear before other declarations");
p->errs++;
}
previmport = 1;
Node *d = parseuse(p);
d->module = p->curmod;
d->pkgname = p->curpkg;
@@ -1437,6 +1450,7 @@ parseimports(Parser *p)
tail = d;
continue;
}
previmport = 0;
if (p->cur.kind == TK_AT) {
skipimportattrs(p);
if (p->cur.kind == TK_EXPORT) advance(p);
@@ -1574,6 +1588,9 @@ parsefile(Parser *p)
Node *head = NULL, *tail = NULL;
Node *packages = NULL, *packagetail = NULL;
int sawpackage = 0;
/* Semantic twin of parseimports: full/direct parsing reports the same
* per-source import-section ordering error while retaining the AST. */
int previmport = 1;
while (p->cur.kind != TK_EOF) {
/* `package foo;` — directory-as-module declaration. Every
* primary section opens with one (`package main;` for an
@@ -1584,6 +1601,7 @@ parsefile(Parser *p)
if (p->cur.kind == TK_MODULE) {
Pos packagepos = p->cur.pos;
sawpackage = 1;
previmport = 1;
advance(p);
const char *name = expectident(p);
expect(p, TK_SEMI);
@@ -1611,6 +1629,7 @@ parsefile(Parser *p)
* root-only bare-`main` rule, #32). */
if (p->cur.kind == TK_MODPATH) {
sawpackage = 0;
previmport = 1;
p->sourceid++;
p->pathmod = p->cur.text;
p->curmod = p->cur.text;
@@ -1630,6 +1649,7 @@ parsefile(Parser *p)
* decls to bare — that usage is deliberate-only. */
if (p->cur.kind == TK_MODRESET) {
sawpackage = 0;
previmport = 1;
p->sourceid++;
/* #57: a path-carrying reset (sep primary body) mangles
* decls on the dotted path so definer == importer, but
@@ -1668,6 +1688,13 @@ parsefile(Parser *p)
p->errs++;
sawpackage = 1;
}
int thisimport = p->cur.kind == TK_USE;
if (thisimport && !previmport) {
errorf(p->cur.pos,
"imports must appear before other declarations");
p->errs++;
}
previmport = thisimport;
Node *attrs = parseattrs(p);
int exp = accept(p, TK_EXPORT);
Node *d = NULL;

View File

@@ -8246,6 +8246,187 @@ this presentation rule does not change.
No persisted-byte contract changed. Build workdir format remains `18`, test
workdir format remains `19`, and semantic storage format remains `3`.
### 11.41 Implemented source-file import-section ordering
Every eligible WW source now has one contiguous import section immediately
after its package clause. Once an ordinary top-level declaration begins, the
first `import` in a later section is rejected as
`imports must appear before other declarations`. The parser continues for
recovery: consecutive imports in that late section do not repeat the ordering
diagnostic, while another ordinary declaration followed by another import
starts a separately diagnosed late section.
This is a source-file syntax rule, not a new import form. Existing unquoted
dotted default, explicit-alias, and blank imports are unchanged. Existing
aggregate module/reset boundaries and each constituent package clause begin a
new source section. The boundary bookkeeping remains parser metadata rather
than package, import, graph, action, artifact, symbol, `.wwi`, publication, or
persistence identity.
#### Pinned Go evidence and fact classification
The sole authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- `syntax.(*parser).fileOrNil` states the source-file grammar as a package
clause, zero or more imports, and then zero or more top-level declarations.
Its recovery loop accepts a later import only to continue parsing and emits
exactly `imports must appear before other declarations` when the preceding
declaration was not an import
([`cmd/compile/internal/syntax/parser.go`, lines 397428](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/syntax/parser.go#L397-L428)).
- The public parser first consumes the initial import section, then applies the
same predecessor check while parsing the rest of the file
([`go/parser/parser.go`, method `(*parser).parseFile`, lines 28872923](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/parser/parser.go#L2887-L2923)).
- Official types testdata requires one diagnostic for a late import followed by
contiguous imports, then another diagnostic when an ordinary declaration
separates a second late section
([`internal/types/testdata/fixedbugs/issue43190.go`, lines 530](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/fixedbugs/issue43190.go#L5-L30)).
Those grammar branches, diagnostic text, error-recovery behavior, and testdata
assertions are **behavior directly implemented or asserted by pinned Go**.
That the state belongs to one source parser, resets at WW's existing aggregate
source boundaries, and must reject before import-graph construction is
**behavior derived from the pinned implementation**.
The rule honestly applies to WW's model because it orders declaration classes
WW already implements. It requires no quoted, grouped, dot, or generalized
import syntax; module or manifest identity; registry, lock, cache, database,
CAS, or network resolution; or source-level build expression.
#### Fresh four-axis audit and direct pre-fix measurements
The bounded audit examined all four permanent axes against the pinned checkout
before selecting this import difference:
- **Go-like build:** pinned linker method
`(*ErrorReporter).errorUnresolved` gives unresolved `main.main` a dedicated
error
([`cmd/link/internal/ld/errors.go`, lines 2967](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/errors.go#L29-L67)),
asserted by `TestUndefinedRelocErrors` and its source fixture
([`cmd/link/internal/ld/ld_test.go`, lines 1945](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/ld_test.go#L19-L45),
[`testdata/issue10978/main.go`, lines 527](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/testdata/issue10978/main.go#L5-L27)).
Both WW stages rejected a selected declared-`main` package without an entry,
using empty stdout and the same linker/driver diagnostics. This applicable
control was aligned.
- **Go-like test:** pinned `testFlags` explicitly permits known test flags
before and after the package list and implements the transition between
package operands and flags
([`cmd/go/internal/test/testflag.go`, lines 219345](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/testflag.go#L219-L345));
official `test_flag.txt` asserts both placements
([lines 14](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/test_flag.txt#L1-L4)).
In both WW stages, `-run selected` before or after a directory operand ran
exactly the same one of two registered tests and produced identical output.
This applicable control was aligned for WW's supported option set.
- **Go-like package:** pinned `MultiplePackageError` and directory scanning
reject two eligible declarations with different package names
([`go/build/build.go`, lines 538549](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L538-L549)
and [lines 939967](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L939-L967)),
asserted by `TestMultiplePackageImport`
([`go/build/build_test.go`, lines 105133](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build_test.go#L105-L133)).
Both WW stages rejected a `first`/`second` directory before tools with the
identical positioned diagnostic. This applicable control was aligned.
- **Go-like import:** a command source declared a helper, then imported
`audit.dep`, then used that package from `main`. Both WW stages exited 0,
emitted empty build output, produced byte-identical executables (SHA-256
`f28892147ab0ae283dff5ceea7114dbc81142ed48cb9088ee7fb8294a5ce44cd`),
and those executables exited 42. Corresponding owner unit, interface,
assembly, object, archive, and initializer bytes were stage-identical. A
same-package test source with the same late-import shape ran successfully in
both stages with identical 251-byte stdout (SHA-256
`3c42fa9840f485fb21b5b5318a13b89abfe29e94b530db00779262368f4fbeba`)
and empty stderr. This acceptance was the selected difference.
The WW statuses, streams, runtime exits, and artifact hashes are **directly
measured WW behavior**. The cited implementation and testdata facts are
**behavior directly implemented or asserted by pinned Go**. Applying their
per-file ordering state to WW's existing dotted declarations is **behavior
derived from the pinned implementation**.
#### Ownership and final four-axis behavior
`parseimports` and `parsefile` in `cmd/wcc/parse.c`, with their semantic twins
in `lib/ww/syntax/parse.ww`, are the only production owners. Each keeps one
parser-local `previmport` bit. A normal import following a non-import reports
the pinned diagnostic, then sets the bit so adjacent imports remain one
recovery section. Any ordinary declaration clears it. The existing
module-path, module-reset, and package-clause boundaries set it for a new
source section.
The imports-only pass is used by public driver loading and therefore rejects a
selected or imported late source before graph and producer construction. The
full parser independently gives direct `w6c`/`w6c_ww` input and aggregate units
the same rule. The only tracked compatibility fixture that deliberately put a
declaration before its import was reordered; it still proves file-scoped import
binding and declaration installation order with byte-identical Cstage/WWstage
artifacts, without asserting the rejected syntax.
- **Go-like build:** selected and imported late sources now fail during parser
loading, before compiler, assembler, archiver, linker, output planning side
effects, or runtime. Missing-target resolution does not replace the earlier
syntax error. Valid import-first commands still build, publish, and run.
- **Go-like test:** late imports in production, same-package test,
external-test, and test-only sources fail before variant actions, generated
main, test binary, runtime, accounting, or retained publication. The
directory command emits its existing attributable final `FAIL\n`. A valid
import-first test retains and runs normally.
- **Go-like package:** source eligibility and package-clause classification
remain earlier owners. Wrong-platform sources produce no ordering error;
selected package-name conflicts retain their coordinator diagnostic.
Declared names and command/test family classification are unchanged.
- **Go-like import:** a file can no longer introduce a qualifier or side-effect
edge after ordinary declarations. Valid imports retain their exact source
spelling, declared-name qualifier, file scope, contextual/vendor resolution,
canonical identity, visibility checks, cycle checks, and initialization
edges.
Direct post-fix `w6c` and `w6c_ww`, and public `ww build`/`ww_ww build`, reject
the measured source with empty stdout and byte-identical 149-byte stderr
(SHA-256
`791ac87ab0aa2c91228f863ae80a8815aa83edf78c4996c5f11191193e3e4240`).
The diagnostic points to the late import at line 7, column 1. Directory tests
emit byte-identical `FAIL\n` stdout (SHA-256
`4f8e9e45f8a9e1843b81eaf3bdf52a6b778d415d23bf985774a9d34a43f69bd5`)
and byte-identical 314-byte stderr (SHA-256
`82ec52b26eaff053f475ce0773b7aee902e734cd87dc100848aee3772063f5b1`),
with no test body or accounting. A direct three-import recovery probe emits
exactly two stage-identical ordering diagnostics: one for the first of two
contiguous late imports and one after the intervening declaration.
Loading and fixed-target filename selection otherwise do not change. An
excluded `_windows.ww` or `_windows_test.ww` file contributes no parse,
package, import, graph, action, artifact, diagnostic, or invalidation state.
For valid files, graph nodes, action dependencies and scheduling, compiler and
linker arguments, initialization, runtime behavior, result ordering, and
publication remain unchanged. A package canonically named `domain.dep` may
still declare `renamed`; its importer uses `renamed.Name`, and its unit/export
remain owned by `domain.dep`.
Cold rejection creates no work artifact, output, capture, or adjacent scratch.
A warm source reordered into the invalid form preserves the complete committed
unit/interface/assembly/object/archive/initializer generation, tool vouchers,
stamp, and public executable byte for byte. Exact restoration reuses the
committed producers and republishes the same executable. Because rejection
occurs before a producer or test child, producer failure, runtime failure,
signals, timeout, interruption, and process-group cleanup acquire no new path;
their existing owners remain authoritative. Concurrent valid and invalid
requests use independent parser state, workdirs, captures, and outputs. No
`.new`, `.install`, `.wwtxn.*`, cold scratch, test process, or capture residue
survives the observed failure boundaries.
The WW-native `imports_precede_other_top_level_declarations` observer proves
direct compiler parity, exact recovery-section counts, selected and imported
build rejection, syntax-before-resolution precedence, all directory test
source variants, wrong-platform exclusion, valid runtime behavior, declared
name versus canonical identity, cold/warm persistence and rollback, restored
reuse, concurrent isolation, diagnostic equality, retained executable equality,
and intermediate artifact-byte equality. The C parser unit and the existing
`sepimport` observer separately pin the imports-only AST recovery and valid
file-scoped binding regression.
Rejected source creates no persisted byte contract, while valid source bytes
are unchanged. Build workdir format remains `18`, test workdir format remains
`19`, and semantic storage format remains `3`.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -257,6 +257,17 @@ ImportPath = ident { "." ident } .
use the related `p_test`, and the actions remain separate even though one
canonical directory owns their test product. The declared name need not equal
the directory name or the final component of its canonical import identity.
- Each source file has one contiguous import section immediately after its
package clause. Once a non-import top-level declaration begins, a later
`import` is rejected as `imports must appear before other declarations`.
Parsing continues for recovery: consecutive imports in that late section
produce one ordering diagnostic, while another ordinary declaration followed
by another import starts a separately diagnosed late section. Existing
aggregate module/reset boundaries and constituent package clauses reset this
parser state per source; they do not relax the rule within a source or become
package/import identity.
A filename excluded by the target-selection rule below reaches no parser and
therefore cannot contribute an ordering diagnostic or import edge.
- Directory source eligibility uses Go 1.26.5 filename suffix semantics for
WW's fixed `linux/amd64` target. In the basename stem before the first dot, a
final `_test` token is ignored for platform matching. A final known OS or

View File

@@ -402,6 +402,19 @@ directory entry order. A recursive pattern omits a directory with no eligible
source, while an explicit wrong-target-only build rejects it as having no WW
package source. There is no source-level build-expression or user-tag mode.
Within every eligible production, same-package test, external-test, or
test-only source, the package clause is followed by one contiguous import
section and then ordinary top-level declarations. A later import is a parser
error, `imports must appear before other declarations`. The imports-only load
pass diagnoses the first import in each separated late section before graph or
producer construction; the full parser gives direct compiler input the same
result. Consequently an invalid test source creates no variant, generated main,
test binary, runtime process, accounting, retained output, or new persistent
generation. The directory command retains its existing attributable final
`FAIL\n`. Wrong-target files remain excluded before this rule and contribute no
diagnostic. This ordering state is per source and never package, import,
variant, action, artifact, publication, or persistence identity.
After that eligibility boundary and the coordinator's required package-clause
classification and production `@test` validation parses, the delegated loader
performs selected-basename Go 1.26.5 simple-fold preflight before its graph

View File

@@ -497,12 +497,16 @@ export fn parseimports(p: *parser) *node = {
let packages: *node = nil;
let packagetail: *node = nil;
let sawpackage: bool = false;
// One import section precedes ordinary declarations. Keep parsing for
// recovery, diagnosing the first import in each later source section.
let previmport: bool = true;
for (p.curkind != tkind.TK_EOF) {
// Compiler/driver bundle markers carry package identity out of
// band. They are not source declarations, so keep scanning for
// the following package clause and imports.
if (p.curkind == tkind.TK_MODPATH) {
sawpackage = false;
previmport = true;
p.sourceid += 1;
p.pathmod = p.curtext;
p.curmod = p.curtext;
@@ -514,6 +518,7 @@ export fn parseimports(p: *parser) *node = {
if (p.curkind == tkind.TK_MODRESET) {
let rp: str = p.curtext;
sawpackage = false;
previmport = true;
p.sourceid += 1;
advance(p);
p.pathmod = "";
@@ -526,6 +531,7 @@ export fn parseimports(p: *parser) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
previmport = true;
advance(p);
if (p.curkind != tkind.TK_IDENT) {
errmsg(p, "invalid or missing package clause");
@@ -563,6 +569,10 @@ export fn parseimports(p: *parser) *node = {
sawpackage = true;
};
if (p.curkind == tkind.TK_USE) {
if (!previmport) {
errmsg(p, "imports must appear before other declarations");
};
previmport = true;
let d: *node = parseuse(p);
d.nmod = p.curmod;
d.pkgname = p.curpkg;
@@ -571,6 +581,7 @@ export fn parseimports(p: *parser) *node = {
tail = d;
continue;
};
previmport = false;
if (p.curkind == tkind.TK_AT) {
skipimportattrs(p);
if (p.curkind == tkind.TK_EXPORT) { advance(p); };
@@ -618,6 +629,8 @@ export fn parsefile(p: *parser) *node = {
let packages: *node = nil;
let packagetail: *node = nil;
let sawpackage: i32 = 0;
// Full/direct parsing is the semantic twin of the imports-only pass.
let previmport: bool = true;
for (p.curkind != tkind.TK_EOF) {
// `package foo;` — directory-as-module declaration. Every
// primary section opens with one (`package main;` for an
@@ -630,6 +643,7 @@ export fn parsefile(p: *parser) *node = {
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
sawpackage = 1;
previmport = true;
advance(p);
let name: str;
expectident(p, &name);
@@ -658,6 +672,7 @@ export fn parsefile(p: *parser) *node = {
// root-only bare-`main` rule, #32).
if (p.curkind == tkind.TK_MODPATH) {
sawpackage = 0;
previmport = true;
p.sourceid += 1;
p.pathmod = p.curtext;
p.curmod = p.curtext;
@@ -677,6 +692,7 @@ export fn parsefile(p: *parser) *node = {
// decls to bare — that usage is deliberate-only.
if (p.curkind == tkind.TK_MODRESET) {
sawpackage = 0;
previmport = true;
p.sourceid += 1;
// #57: a path-carrying reset (sep primary body) mangles decls
// on the dotted path so definer == importer, but leaves
@@ -714,6 +730,11 @@ export fn parsefile(p: *parser) *node = {
errmsg(p, "missing package clause");
sawpackage = 1;
};
let thisimport: bool = p.curkind == tkind.TK_USE;
if (thisimport && !previmport) {
errmsg(p, "imports must appear before other declarations");
};
previmport = thisimport;
let attrs = parseattrs(p);
let exported: i32 = 0;
if (p.curkind == tkind.TK_EXPORT) { exported = 1; advance(p); };

View File

@@ -854,6 +854,452 @@ fn cwdwritedata(dir: str, label: str) void = {
clean(root);
};
// A source file has one contiguous import section immediately after its
// package clause. The parser may continue after a late import for recovery,
// but the imports-only loader and the full compiler parser both reject it.
// This observer keeps that source rule separate from dotted import identity.
@test fn imports_precede_other_top_level_declarations() void = {
let root: str = fresh();
let tree: str = strings.concat(root, "/tree");
let base: str = strings.concat(tree, "/domain/base");
let dep: str = strings.concat(tree, "/domain/dep");
let app: str = strings.concat(tree, "/domain/app");
let missinglate: str = strings.concat(tree, "/domain/missinglate");
let prodtest: str = strings.concat(tree, "/domain/prodtest");
let internaltest: str = strings.concat(tree, "/domain/internaltest");
let externaltest: str = strings.concat(tree, "/domain/externaltest");
let testonly: str = strings.concat(tree, "/domain/testonly");
let validtest: str = strings.concat(tree, "/domain/validtest");
mkdirall(base); mkdirall(dep); mkdirall(app); mkdirall(missinglate);
mkdirall(prodtest); mkdirall(internaltest); mkdirall(externaltest);
mkdirall(testonly); mkdirall(validtest);
writefile(strings.concat(base, "/base.ww"),
"package base;\nexport fn value() i32 = { return 40; };\n");
let depfile: str = strings.concat(dep, "/dep.ww");
let depvalid: str = strings.concat(
"package renamed;\nimport domain.base;\n",
"fn delta() i32 = { return 1; };\n",
"export fn value() i32 = { return base.value() + delta(); };\n");
let deplate: str = strings.concat(
"package renamed;\nfn delta() i32 = { return 1; };\n",
"import domain.base;\n",
"export fn value() i32 = { return base.value() + delta(); };\n");
writefile(depfile, depvalid);
let appfile: str = strings.concat(app, "/main.ww");
let appvalid: str = strings.concat(
"package main;\nimport domain.dep;\n",
"fn local() i32 = { return 1; };\n",
"fn main() i32 = { return renamed.value() + local(); };\n");
let applate: str = strings.concat(
"package main;\nfn local() i32 = { return 1; };\n",
"import domain.dep;\n",
"fn main() i32 = { return renamed.value() + local(); };\n");
writefile(appfile, appvalid);
// Platform exclusion precedes package/import parsing. Neither the wrong
// declared name nor this late missing import may affect linux/amd64.
writefile(strings.concat(app, "/ignored_windows.ww"), strings.concat(
"package wrong;\nfn ignored() void = {};\n",
"import missing.platform;\n"));
writefile(strings.concat(missinglate, "/main.ww"), strings.concat(
"package main;\nfn before() i32 = { return 0; };\n",
"import missing.pkg;\nfn main() i32 = { return before(); };\n"));
// Production, same-package test, external-test, and test-only sources all
// pass through the same per-source ordering rule before product actions.
writefile(strings.concat(prodtest, "/pkg.ww"), strings.concat(
"package prodtest;\nfn local() i32 = { return 1; };\n",
"import domain.dep;\n",
"export fn value() i32 = { return local() + renamed.value(); };\n"));
writefile(strings.concat(prodtest, "/pkg_test.ww"),
"package prodtest;\n@test fn must_not_run() void = { abort(\"ran\"); };\n");
writefile(strings.concat(internaltest, "/pkg.ww"),
"package internaltest;\nfn local() i32 = { return 1; };\n");
writefile(strings.concat(internaltest, "/pkg_test.ww"), strings.concat(
"package internaltest;\nfn testlocal() i32 = { return 1; };\n",
"import domain.dep;\n",
"@test fn must_not_run() void = { abort(\"ran\"); };\n"));
writefile(strings.concat(externaltest, "/pkg.ww"),
"package externaltest;\nexport fn local() i32 = { return 1; };\n");
writefile(strings.concat(externaltest, "/pkg_test.ww"), strings.concat(
"package externaltest_test;\nfn testlocal() i32 = { return 1; };\n",
"import domain.dep;\n",
"@test fn must_not_run() void = { abort(\"ran\"); };\n"));
writefile(strings.concat(testonly, "/only_test.ww"), strings.concat(
"package testonly;\nfn testlocal() i32 = { return 1; };\n",
"import domain.dep;\n",
"@test fn must_not_run() void = { abort(\"ran\"); };\n"));
writefile(strings.concat(validtest, "/pkg.ww"), strings.concat(
"package validtest;\nimport domain.dep;\n",
"fn local() i32 = { return renamed.value(); };\n"));
writefile(strings.concat(validtest, "/pkg_test.ww"), strings.concat(
"package validtest;\nimport domain.base;\n",
"@test fn import_first_runs() void = {",
" assert(local() + base.value() == 81); };\n"));
writefile(strings.concat(validtest, "/ignored_windows_test.ww"),
strings.concat("package wrong;\nfn ignored() void = {};\n",
"import missing.platform;\n"));
let expected: str = "imports must appear before other declarations";
let sections: str = strings.concat(root, "/sections.ww");
writefile(sections, strings.concat(
"package sections;\nfn first() void = {};\n",
"import one;\nimport two;\n",
"fn second() void = {};\nimport three;\n"));
let compilers: []str = ["w6c", "w6c_ww"];
let directdiag: str = "";
let out: commandout;
let i: i32 = 0;
for (i < compilers.len) {
let asmout: str = strings.concat(root, "/direct-", compilers[i], ".s");
let av: []str = [driver(compilers[i]), "-o", asmout, sections];
runcommand(root, strings.concat("import-order-direct-", compilers[i]),
av, (30i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0 && !os.exists(asmout));
assert(occurrences(out.stderr, expected) == 2);
assert(same(primarydiagnostic(out.stderr), expected));
if (i == 0) { directdiag = strings.dup(out.stderr); }
else { assert(same(directdiag, out.stderr)); };
i += 1;
};
let compilerwrapper: str = strings.concat(root, "/import-w6c.sh");
let assemblerwrapper: str = strings.concat(root, "/import-w6a.sh");
let linkerwrapper: str = strings.concat(root, "/import-w6l.sh");
writeexecutable(compilerwrapper, strings.concat(
"#!/bin/sh\nprintf 'compile\\n' >> \"$WW_IMPORT_CTRACE\"\n",
"exec \"$WW_IMPORT_REAL_C\" \"$@\"\n"));
writeexecutable(assemblerwrapper, strings.concat(
"#!/bin/sh\nprintf 'assemble\\n' >> \"$WW_IMPORT_ATRACE\"\n",
"exec \"$WW_IMPORT_REAL_A\" \"$@\"\n"));
writeexecutable(linkerwrapper, strings.concat(
"#!/bin/sh\nprintf 'link\\n' >> \"$WW_IMPORT_LTRACE\"\n",
"exec \"$WW_IMPORT_REAL_L\" \"$@\"\n"));
let stages: []str = ["ww", "ww_ww"];
let assemblers: []str = ["w6a", "w6a_ww"];
let linkers: []str = ["w6l", "w6l_ww"];
let tags: []str = ["c", "ww"];
let works: []str = [strings.concat(root, "/work-c"),
strings.concat(root, "/work-ww")];
let outputs: []str = [strings.concat(root, "/app-c"),
strings.concat(root, "/app-ww")];
let testworks: []str = [strings.concat(root, "/test-work-c"),
strings.concat(root, "/test-work-ww")];
let testbins: []str = [strings.concat(root, "/valid-c.test"),
strings.concat(root, "/valid-ww.test")];
let baseenv: []str = os.getenvs();
let appdiag: str = "";
let depdiag: str = "";
let missingdiag: str = "";
let validtestout: str = "";
let testdiags: []str = ["", "", "", ""];
let artifactrefs: []str = alloc([], 64u64)!;
let testartifactrefs: []str = alloc([], 64u64)!;
let appbinref: str = "";
let testbinref: str = "";
let actions: []str = ["domain.base", "domain.dep", "domain.app"];
let suffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a"];
let initsuffixes: []str = [".init.unit.ww", ".init.s", ".init.o"];
let testactions: []str = ["domain.base", "domain.dep",
"domain.validtest-internal-test", "domain.validtest-test-main"];
let testtargets: []str = ["domain.prodtest", "domain.internaltest",
"domain.externaltest", "domain.testonly"];
i = 0;
for (i < stages.len) {
mkdirall(works[i]); mkdirall(testworks[i]);
let ctrace: str = strings.concat(root, "/compile-", tags[i]);
let atrace: str = strings.concat(root, "/assemble-", tags[i]);
let ltrace: str = strings.concat(root, "/link-", tags[i]);
writefile(ctrace, ""); writefile(atrace, ""); writefile(ltrace, "");
let env: []str = alloc([], (baseenv.len + 10): 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_IMPORT_")) {
append(env, baseenv[ei]);
};
ei += 1;
};
append(env, strings.concat("WW_W6C=", compilerwrapper));
append(env, strings.concat("WW_W6A=", assemblerwrapper));
append(env, strings.concat("WW_W6L=", linkerwrapper));
append(env, strings.concat("WW_IMPORT_CTRACE=", ctrace));
append(env, strings.concat("WW_IMPORT_ATRACE=", atrace));
append(env, strings.concat("WW_IMPORT_LTRACE=", ltrace));
append(env, strings.concat("WW_IMPORT_REAL_C=", driver(compilers[i])));
append(env, strings.concat("WW_IMPORT_REAL_A=", driver(assemblers[i])));
append(env, strings.concat("WW_IMPORT_REAL_L=", driver(linkers[i])));
let buildav: []str = [driver(stages[i]), "build", "-I", tree,
"-w", works[i], "-o", outputs[i], "domain.app"];
runcommandenv(root, strings.concat("import-order-cold-", tags[i]),
buildav, env, (90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0);
assert(readfile(ctrace).len != 0 && readfile(atrace).len != 0
&& readfile(ltrace).len != 0);
let built: str = strings.dup(readfile(outputs[i]));
if (i == 0) { appbinref = strings.dup(built); }
else { assert(same(appbinref, built)); };
let runav: []str = [outputs[i]];
runcommand(root, strings.concat("import-order-run-", tags[i]), runav,
time.second, &out);
expectexit(&out, 42);
assert(out.stdout.len == 0 && out.stderr.len == 0);
assert(has(readfile(strings.concat(works[i],
"/domain.dep.unit.ww")),
"//ww:module-reset domain.dep\npackage renamed;"));
// Save and compare every valid semantic output affected by this graph.
let before: []str = alloc([], 32u64)!;
let refi: i32 = 0;
let ai: i32 = 0;
for (ai < actions.len) {
let si: i32 = 0;
for (si < suffixes.len) {
let bytes: str = strings.dup(readfile(strings.concat(works[i],
"/", actions[ai], suffixes[si])));
append(before, bytes);
if (i == 0) { append(artifactrefs, strings.dup(bytes)); }
else { assert(same(artifactrefs[refi], bytes)); };
refi += 1; si += 1;
};
ai += 1;
};
let ii: i32 = 0;
for (ii < initsuffixes.len) {
let bytes: str = strings.dup(readfile(strings.concat(works[i],
"/domain.app", initsuffixes[ii])));
append(before, bytes);
if (i == 0) { append(artifactrefs, strings.dup(bytes)); }
else { assert(same(artifactrefs[refi], bytes)); };
refi += 1; ii += 1;
};
// A selected source becoming late rejects before every producer and
// preserves the complete committed generation and public executable.
rewritefile(appfile, applate);
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
runcommandenv(root, strings.concat("import-order-app-late-", tags[i]),
buildav, env, (90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0 && same(primarydiagnostic(out.stderr), expected));
assert(readfile(ctrace).len == 0 && readfile(atrace).len == 0
&& readfile(ltrace).len == 0 && same(built, readfile(outputs[i])));
if (i == 0) { appdiag = strings.dup(out.stderr); }
else { assert(same(appdiag, out.stderr)); };
refi = 0; ai = 0;
for (ai < actions.len) {
let si: i32 = 0;
for (si < suffixes.len) {
assert(same(before[refi], readfile(strings.concat(works[i],
"/", actions[ai], suffixes[si]))));
refi += 1; si += 1;
};
ai += 1;
};
ii = 0;
for (ii < initsuffixes.len) {
assert(same(before[refi], readfile(strings.concat(works[i],
"/domain.app", initsuffixes[ii]))));
refi += 1; ii += 1;
};
assert(!directoryhasnew(works[i])
&& !os.exists(strings.concat(outputs[i], ".new"))
&& !os.exists(strings.concat(outputs[i], ".install"))
&& !directoryhasfragment(works[i], ".wwtxn."));
// Exact restoration reuses the committed producer bytes.
rewritefile(appfile, appvalid);
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
runcommandenv(root, strings.concat("import-order-app-restored-", tags[i]),
buildav, env, (90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
&& same(built, readfile(outputs[i])));
// The same rule applies while loading an imported package; the root's
// prior bytes and all graph identities remain unchanged.
rewritefile(depfile, deplate);
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
runcommandenv(root, strings.concat("import-order-dep-late-", tags[i]),
buildav, env, (90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0 && same(primarydiagnostic(out.stderr), expected)
&& has(out.stderr, "/domain/dep/dep.ww:"));
assert(readfile(ctrace).len == 0 && readfile(atrace).len == 0
&& readfile(ltrace).len == 0 && same(built, readfile(outputs[i])));
if (i == 0) { depdiag = strings.dup(out.stderr); }
else { assert(same(depdiag, out.stderr)); };
rewritefile(depfile, depvalid);
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
runcommandenv(root, strings.concat("import-order-dep-restored-", tags[i]),
buildav, env, (90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
&& same(built, readfile(outputs[i])));
// Syntax ordering precedes import resolution, output creation, and cold
// scratch even when the late import target does not exist.
let misswork: str = strings.concat(root, "/missing-work-", tags[i]);
let missout: str = strings.concat(root, "/missing-output-", tags[i]);
mkdirall(misswork);
let missav: []str = [driver(stages[i]), "build", "-I", tree,
"-w", misswork, "-o", missout, "domain.missinglate"];
rewritefile(ctrace, ""); rewritefile(atrace, ""); rewritefile(ltrace, "");
runcommandenv(root, strings.concat("import-order-missing-", tags[i]),
missav, env, (60i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(out.stdout.len == 0 && same(primarydiagnostic(out.stderr), expected)
&& !has(out.stderr, "cannot find package"));
assert(readfile(ctrace).len == 0 && readfile(atrace).len == 0
&& readfile(ltrace).len == 0 && directoryisempty(misswork)
&& !os.exists(missout) && !os.exists(strings.concat(missout, ".new"))
&& !os.exists(strings.concat(missout, ".sepwork")));
if (i == 0) { missingdiag = strings.dup(out.stderr); }
else { assert(same(missingdiag, out.stderr)); };
let ti: i32 = 0;
for (ti < testtargets.len) {
let twork: str = strings.concat(root, "/invalid-test-work-",
tags[i], "-", boundarypkgname(ti));
let tbin: str = strings.concat(root, "/invalid-test-bin-",
tags[i], "-", boundarypkgname(ti));
mkdirall(twork);
let tav: []str = [driver(stages[i]), "test", "-I", tree,
"-w", twork, "-o", tbin, testtargets[ti]];
rewritefile(ctrace, ""); rewritefile(atrace, "");
rewritefile(ltrace, "");
runcommandenv(root, strings.concat("import-order-test-", tags[i],
"-", boundarypkgname(ti)), tav, env,
(90i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 1);
assert(same(out.stdout, "FAIL\n")
&& same(primarydiagnostic(out.stderr), expected));
assert(!has(out.stdout, "must_not_run")
&& !has(out.stdout, " discovered,")
&& !has(out.stderr, "ok "));
assert(readfile(ctrace).len == 0 && readfile(atrace).len == 0
&& readfile(ltrace).len == 0 && directoryisempty(twork));
assert(!os.exists(tbin) && !os.exists(strings.concat(tbin, ".new"))
&& !os.exists(strings.concat(tbin, ".install"))
&& !os.exists(strings.concat(tbin, ".sepwork")));
if (i == 0) { testdiags[ti] = strings.dup(out.stderr); }
else { assert(same(testdiags[ti], out.stderr)); };
ti += 1;
};
// A valid import-first test still compiles to the same retained binary
// and runs with the same bytes on both stages. Its excluded wrong-target
// late import remains completely absent from loading.
let validav: []str = [driver(stages[i]), "test", "-c", "-I", tree,
"-w", testworks[i], "-o", testbins[i], "domain.validtest"];
runcommandenv(root, strings.concat("import-order-valid-test-", tags[i]),
validav, env, (120i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stdout.len == 0 && out.stderr.len == 0);
let testbytes: str = strings.dup(readfile(testbins[i]));
if (i == 0) { testbinref = strings.dup(testbytes); }
else { assert(same(testbinref, testbytes)); };
let testrunav: []str = [testbins[i]];
runcommand(root, strings.concat("import-order-valid-test-run-", tags[i]),
testrunav, (30i64 * (time.second: i64)): time.duration, &out);
expectexit(&out, 0);
assert(out.stderr.len == 0
&& has(out.stdout, "import_first_runs ... ok\n"));
if (i == 0) { validtestout = strings.dup(out.stdout); }
else { assert(same(validtestout, out.stdout)); };
refi = 0; ai = 0;
for (ai < testactions.len) {
let si: i32 = 0;
for (si < suffixes.len) {
let bytes: str = readfile(strings.concat(testworks[i], "/",
testactions[ai], suffixes[si]));
if (i == 0) { append(testartifactrefs, strings.dup(bytes)); }
else { assert(same(testartifactrefs[refi], bytes)); };
refi += 1; si += 1;
};
ai += 1;
};
ii = 0;
for (ii < initsuffixes.len) {
let bytes: str = readfile(strings.concat(testworks[i],
"/domain.validtest-test-main", initsuffixes[ii]));
if (i == 0) { append(testartifactrefs, strings.dup(bytes)); }
else { assert(same(testartifactrefs[refi], bytes)); };
refi += 1; ii += 1;
};
assert(!directoryhasnew(works[i]) && !directoryhasnew(testworks[i])
&& !directoryhasfragment(works[i], ".wwtxn.")
&& !directoryhasfragment(testworks[i], ".wwtxn."));
i += 1;
};
// Parser state is request-local. A valid Cstage build and an invalid
// WWstage build can overlap without changing either result or residue.
let pcwork: str = strings.concat(root, "/parallel-c-work");
let pwwork: str = strings.concat(root, "/parallel-ww-work");
let pcout: str = strings.concat(root, "/parallel-c-output");
let pwout: str = strings.concat(root, "/parallel-ww-output");
mkdirall(pcwork); mkdirall(pwwork);
let pcav: []str = [driver("ww"), "build", "-I", tree, "-w", pcwork,
"-o", pcout, "domain.app"];
let pwav: []str = [driver("ww_ww"), "build", "-I", tree, "-w", pwwork,
"-o", pwout, "domain.missinglate"];
let pc: exec.command;
pc.path = pcav[0]; pc.argv = pcav; pc.env = os.getenvs(); pc.dir = repo();
pc.stdoutpath = strings.concat(root, "/parallel-c.stdout");
pc.stderrpath = strings.concat(root, "/parallel-c.stderr");
pc.deadline = time.add(time.now(time.clock.monotonic),
(120i64 * (time.second: i64)): time.duration);
pc.grace = (100i64 * (time.millisecond: i64)): time.duration;
let pw: exec.command;
pw.path = pwav[0]; pw.argv = pwav; pw.env = os.getenvs(); pw.dir = repo();
pw.stdoutpath = strings.concat(root, "/parallel-ww.stdout");
pw.stderrpath = strings.concat(root, "/parallel-ww.stderr");
pw.deadline = time.add(time.now(time.clock.monotonic),
(120i64 * (time.second: i64)): time.duration);
pw.grace = (100i64 * (time.millisecond: i64)): time.duration;
let pcp: exec.process;
let pwp: exec.process;
exec.start(&pcp, &pc); exec.start(&pwp, &pw);
let pcdone: bool = false;
let pwdone: bool = false;
for (!pcdone || !pwdone) {
if (!pcdone) { pcdone = exec.poll(&pcp); };
if (!pwdone) { pwdone = exec.poll(&pwp); };
if (!pcdone || !pwdone) {
time.sleep(time.millisecond, time.clock.monotonic);
};
};
assert(pcp.result.errno == 0 && pcp.result.cleanuperrno == 0
&& pcp.result.termination == exec.termination.EXIT
&& pcp.result.code == 0);
assert(pwp.result.errno == 0 && pwp.result.cleanuperrno == 0
&& pwp.result.termination == exec.termination.EXIT
&& pwp.result.code == 1);
assert(readfile(pc.stdoutpath).len == 0 && readfile(pc.stderrpath).len == 0);
assert(readfile(pw.stdoutpath).len == 0
&& same(primarydiagnostic(readfile(pw.stderrpath)), expected));
assert(same(appbinref, readfile(pcout)) && !os.exists(pwout)
&& directoryisempty(pwwork) && !directoryhasnew(pcwork)
&& !directoryhasfragment(pcwork, ".wwtxn."));
let pcrun: []str = [pcout];
runcommand(root, "import-order-parallel-run", pcrun, time.second, &out);
expectexit(&out, 42);
assert(!directoryhasfragment(root, ".wwtxn.")
&& !directoryhasfragment(root, ".install")
&& !directoryhasnew(root));
clean(root);
};
@test fn list_mode_with_no_matches_emits_no_sentinel() void = {
let root: str = fresh();
let alpha: str = strings.concat(root, "/alpha");

View File

@@ -20,8 +20,9 @@ package sepimport_test;
//
// declns (#23/#30 modfn leg) — a distinct local `fn localaa` and the
// file-scoped imported qualifier aa both resolve (exit 6 = localaa() +
// aa.helper()); the _vbu layout flips declaration/import order and must stay
// byte-identical on {aa.s,aa.wwi,__root.s}; __root.s carries both calls.
// aa.helper()); the _vbu layout reorders the valid declarations after its
// import and must stay byte-identical on {aa.s,aa.wwi,__root.s}; __root.s
// carries both calls.
//
// slttypepref (#58/#50 c1) — scopelookuptype must prefer the current
// module's SK_TYPE when a param shadows a type leaf two modules both

View File

@@ -249,9 +249,9 @@ main(void)
const char *src =
"package main;\n"
"import zed;\n"
"import alpha;\n"
"fn f() void = { let s: str = \"import fake;\"; };\n"
"/* import hidden; */\n"
"import alpha;\n";
"/* import hidden; */\n";
int errs;
const char *pkg;
char *got = imports_to_str(src, &errs, &pkg);
@@ -266,6 +266,28 @@ main(void)
free((void *)pkg);
free(got);
}
{
const char *src =
"package main;\n"
"fn first() void = {};\n"
"import alpha;\n"
"import beta;\n"
"fn second() void = {};\n"
"import gamma;\n";
int errs;
const char *pkg;
char *got = imports_to_str(src, &errs, &pkg);
if (errs != 2 || got == NULL
|| strstr(got, "(use \"alpha\"") == NULL
|| strstr(got, "(use \"beta\"") == NULL
|| strstr(got, "(use \"gamma\"") == NULL) {
fputs("late import sections were not diagnosed and retained\n",
stderr);
fail++;
}
free((void *)pkg);
free(got);
}
{
int errs;
const char *pkg;

View File

@@ -1,7 +1,7 @@
// Declaration-before-import twin of ../modfn_coexist/main.ww. The distinct
// local name follows Go's import/package-scope collision rule while retaining
// the order-independence check for installing a real import binding.
// Reordered-source twin of ../modfn_coexist/main.ww. The distinct local name
// follows Go's import/package-scope collision rule while retaining the
// declaration/import installation-order check for a real file-scoped binding.
package main;
fn localaa() i32 = { return 1; };
import aa;
fn localaa() i32 = { return 1; };
export fn main() i32 = { return localaa() + aa.helper(); };