ww imports: require imports before declarations
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 397–428](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 2887–2923](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 5–30](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 29–67](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 19–45](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/ld_test.go#L19-L45),
|
||||
[`testdata/issue10978/main.go`, lines 5–27](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 219–345](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/testflag.go#L219-L345));
|
||||
official `test_flag.txt` asserts both placements
|
||||
([lines 1–4](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 538–549](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L538-L549)
|
||||
and [lines 939–967](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L939-L967)),
|
||||
asserted by `TestMultiplePackageImport`
|
||||
([`go/build/build_test.go`, lines 105–133](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.
|
||||
|
||||
11
docs/spec.md
11
docs/spec.md
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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); };
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(); };
|
||||
|
||||
Reference in New Issue
Block a user