cmd: resolve source imports only as directories

Separate import lookup from raw CLI target compatibility in both production drivers. Package scans and test-support edges now create directory nodes only, while unit composition emits only the owning package's sorted sources after direct export data. Document the Go 1.26.5 ownership evidence and retained single-file root boundary.
This commit is contained in:
2026-08-12 17:12:49 +09:00
parent c7d9dc92de
commit 0af13ea501
3 changed files with 272 additions and 339 deletions

View File

@@ -178,65 +178,73 @@ import_path_form(const char *name, char *out, size_t outsz)
out[i] = '\0'; out[i] = '\0';
} }
/* Symmetric with wwstage locatein for byte-id driver output (rule 10). /* An import path names one directory package. There is deliberately no
* The legacy <dir>/<name>/<name>.ww form was dropped in task #22 — * <dir>/<path>.ww branch here: literal or searched single-file roots are a
* directory-as-module enumeration replaces it, mirroring * CLI compatibility concern handled by locate_module, never an import edge. */
* ref/hare/hare/module/srcs.ha (Hare has no fallback matching
* `foo/foo.ha`; a module IS the directory). */
static int static int
locate_import_in(const char *dir, const char *path_form, char *out, locate_import_in(const char *dir, const char *path_form, char *out,
size_t outsz, int *is_dir, int want_dir) size_t outsz)
{ {
struct stat st; struct stat st;
if (want_dir) { snprintf(out, outsz, "%s/%s", dir, path_form);
snprintf(out, outsz, "%s/%s", dir, path_form); if (stat(out, &st) == 0 && S_ISDIR(st.st_mode))
if (stat(out, &st) == 0 && S_ISDIR(st.st_mode)) {
*is_dir = 1;
return 1;
}
return 0;
}
snprintf(out, outsz, "%s/%s.ww", dir, path_form);
if (access(out, 0) == 0) {
*is_dir = 0;
return 1; return 1;
return 0;
}
/* Walk every ordered root for <root>/<path>/ only. A decoy
* <earlier-root>/<path>.ww is neither a match nor a shadow: source imports
* always create canonical directory-package nodes. */
static int
locate_import(const char *dirs, const char *path_form, char *out,
size_t outsz)
{
const char *p = dirs;
while (*p) {
const char *e = strchr(p, ':');
size_t n = e ? (size_t)(e - p) : strlen(p);
if (n > 0 && n < outsz) {
char dir[1024];
if (n >= sizeof dir) n = sizeof dir - 1;
memcpy(dir, p, n);
dir[n] = '\0';
if (locate_import_in(dir, path_form, out, outsz))
return 1;
}
if (!e) break;
p = e + 1;
} }
return 0; return 0;
} }
/* #98: "a module IS the directory" — a directory-package on ANY entry /* CLI target compatibility: directory packages still win globally, then a
* wins over a same-named sibling FILE on an EARLIER entry. The driver * bare target may resolve to <root>/<path>.ww. This function is never used
* builds the searchpath srcd-first; a co-located `lib/<mod>/<x>_test.ww` * while loading a source import. */
* entry makes srcd = lib/<mod>, so a self-named `import <mod>` would
* else file-hit the sibling lib/<mod>/<mod>.ww and fold it
* inline under the wrong module-reset → "package <mod> does not match
* import path <importer>". Two passes — directories first, files only
* if no directory matches anywhere — let lib/<mod>/ resolve as the dir
* while a genuine leaf package with no directory (e.g. lib/encoding/hex
* imported bare as `hex`, reachable only via its file in srcd) still
* resolves in the file pass. Latent: a dir-package now beats an
* earlier-entry same-named sibling FILE — loud-failing, none in the
* corpus; tracked as #101. */
static int static int
locate_import(const char *dirs, const char *path_form, char *out, locate_module(const char *dirs, const char *path_form, char *out,
size_t outsz, int *is_dir) size_t outsz, int *is_dir)
{ {
for (int want_dir = 1; want_dir >= 0; want_dir--) { if (locate_import(dirs, path_form, out, outsz)) {
const char *p = dirs; *is_dir = 1;
while (*p) { return 1;
const char *e = strchr(p, ':'); }
size_t n = e ? (size_t)(e - p) : strlen(p); const char *p = dirs;
if (n > 0 && n < outsz) { while (*p) {
char dir[1024]; const char *e = strchr(p, ':');
if (n >= sizeof dir) n = sizeof dir - 1; size_t n = e ? (size_t)(e - p) : strlen(p);
memcpy(dir, p, n); if (n > 0 && n < outsz) {
dir[n] = '\0'; char dir[1024];
if (locate_import_in(dir, path_form, out, if (n >= sizeof dir) n = sizeof dir - 1;
outsz, is_dir, want_dir)) return 1; memcpy(dir, p, n);
dir[n] = '\0';
snprintf(out, outsz, "%s/%s.ww", dir, path_form);
if (access(out, 0) == 0) {
*is_dir = 0;
return 1;
} }
if (!e) break;
p = e + 1;
} }
if (!e) break;
p = e + 1;
} }
return 0; return 0;
} }
@@ -521,7 +529,7 @@ struct seppkg {
int failed; /* discovery/compile failure reaches this action */ int failed; /* discovery/compile failure reaches this action */
int test_support; /* compiler-generated -T support package */ int test_support; /* compiler-generated -T support package */
int loaded; /* directory membership/name loaded exactly once */ int loaded; /* directory membership/name loaded exactly once */
int emit_context; /* resolution context used to compose file imports */ int emit_context; /* first verified resolution context */
unsigned char context_state[SEP_MAXCONTEXT]; /* 0 new, 1 active, 2 checked */ unsigned char context_state[SEP_MAXCONTEXT]; /* 0 new, 1 active, 2 checked */
struct ImportSet bindings; /* first context's canonical import bindings */ struct ImportSet bindings; /* first context's canonical import bindings */
int deps[SEP_MAXPKG]; /* direct-dep indices into sepgraph.pkg */ int deps[SEP_MAXPKG]; /* direct-dep indices into sepgraph.pkg */
@@ -882,8 +890,8 @@ sep_external_production_import(const struct seppkg *pkg, const char *path)
} }
/* Canonical bindings make a shared package independent of which selected /* Canonical bindings make a shared package independent of which selected
* root reaches it first. Directory, folded-file, and inline bindings are all * root reaches it first. Directory bindings and the raw-file inline
* part of the package action's source meaning. */ * compatibility binding are part of the package action's source meaning. */
static int static int
sep_binding_add(struct ImportSet *bindings, char kind, const char *name, sep_binding_add(struct ImportSet *bindings, char kind, const char *name,
const char *target) const char *target)
@@ -1019,28 +1027,27 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
break; break;
} }
char ipath[1024]; char ipath[1024];
int is_dir = 0;
int external_production = sep_external_production_import( int external_production = sep_external_production_import(
&g->pkg[pi], name); &g->pkg[pi], name);
int located = 0; int located = 0;
if (external_production) { if (external_production) {
snprintf(ipath, sizeof ipath, "%s", g->pkg[pi].entry); snprintf(ipath, sizeof ipath, "%s", g->pkg[pi].entry);
is_dir = 1;
located = 1; located = 1;
} else { } else {
located = locate_import(searchpath, path_form, ipath, located = locate_import(searchpath, path_form, ipath,
sizeof ipath, &is_dir); sizeof ipath);
} }
if (!located) { if (!located) {
const char *dot = strrchr(name, '.'); const char *dot = strrchr(name, '.');
const char *leaf = dot ? dot + 1 : name; const char *leaf = dot ? dot + 1 : name;
int inline_package = 0; int inline_package = 0;
for (Node *package = imports->body; package; if (!g->pkg[pi].is_dir)
package = package->next) for (Node *package = imports->body; package;
if (strcmp(package->module, leaf) == 0) { package = package->next)
inline_package = 1; if (strcmp(package->module, leaf) == 0) {
break; inline_package = 1;
} break;
}
if (inline_package) { if (inline_package) {
if (sep_binding_add(bindings, 'I', name, NULL) < 0) if (sep_binding_add(bindings, 'I', name, NULL) < 0)
rc = -1; rc = -1;
@@ -1050,7 +1057,7 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
rc = -1; rc = -1;
break; break;
} }
if (is_dir) { {
char *canon = realpath(ipath, NULL); char *canon = realpath(ipath, NULL);
if (canon == NULL) { if (canon == NULL) {
errorf(u->pos, "cannot canonicalize package '%s'", name); errorf(u->pos, "cannot canonicalize package '%s'", name);
@@ -1105,20 +1112,6 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
if (g->pkg[pi].ndeps >= SEP_MAXPKG) { rc = -1; break; } if (g->pkg[pi].ndeps >= SEP_MAXPKG) { rc = -1; break; }
g->pkg[pi].deps[g->pkg[pi].ndeps++] = di; g->pkg[pi].deps[g->pkg[pi].ndeps++] = di;
} }
} else {
char *canon = realpath(ipath, NULL);
if (canon == NULL
|| sep_binding_add(bindings, 'F', name, canon) < 0) {
free(canon);
rc = -1;
break;
}
free(canon);
if (sep_scan_file(g, pi, ipath, searchpath, filevisit,
bindings, 0) < 0) {
rc = -1;
break;
}
} }
} }
free(uses); free(uses);
@@ -1322,65 +1315,18 @@ sep_validate_module_closure(struct sepgraph *g, const int *order, int n,
} }
/* Emit one of pi's own source files into the sep-unit under the /* Emit one of pi's own source files into the sep-unit under the
* //ww:module-reset primary boundary (so -c emits its decls, imported * //ww:module-reset primary boundary. Imports were already resolved to
* ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead of the * directory-package edges and their direct `.wwi` files were prepended;
* body); FILE imports fold in (intra-package split). */ * no source outside pi's sorted owned-source set may enter this unit. */
static int static int
sep_emit_body(FILE *out, const char *path, struct ImportSet *visited, sep_emit_body(FILE *out, const char *path, const char *modpath)
const char *searchpath, const char *modpath, const struct seppkg *pkg)
{ {
if (import_seen(visited, path)) return 0;
import_add(visited, path);
char *buf; char *buf;
u64 len; u64 len;
if (sep_slurp(path, &buf, &len) < 0) { if (sep_slurp(path, &buf, &len) < 0) {
fprintf(stderr, "ww: cannot read %s\n", path); fprintf(stderr, "ww: cannot read %s\n", path);
return -1; return -1;
} }
Arena *a = newarena();
Lex l;
Parser p;
lexinit(&l, a, path, buf, len);
parserinit(&p, a, &l);
Node *imports = parseimports(&p);
if (l.errs || p.errs) {
freearena(a);
free(buf);
return -1;
}
int nuse = 0;
for (Node *u = imports->list; u; u = u->next)
if (u->kind == N_USE) nuse++;
Node **uses = nuse ? malloc((size_t)nuse * sizeof *uses) : NULL;
if (nuse && uses == NULL) {
freearena(a);
free(buf);
return -1;
}
int ui = 0;
for (Node *u = imports->list; u; u = u->next)
if (u->kind == N_USE) uses[ui++] = u;
if (nuse > 1) qsort(uses, (size_t)nuse, sizeof *uses, use_node_cmp);
for (int i = 0; i < nuse; i++) {
Node *u = uses[i];
const char *name = u->usepath ? u->usepath : u->str;
if (sep_external_production_import(pkg, name))
continue;
char path_form[1024];
import_path_form(name, path_form, sizeof path_form);
char ipath[1024];
int is_dir = 0;
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
&is_dir))
continue;
if (!is_dir && sep_emit_body(out, ipath, visited, searchpath,
modpath, pkg) < 0) {
free(uses);
freearena(a);
free(buf);
return -1;
}
}
/* #57: tag the primary body by its full dotted import path so the /* #57: tag the primary body by its full dotted import path so the
* definer mangles == the importer reference; a root build (path "") * definer mangles == the importer reference; a root build (path "")
* stays a bare reset (keeps bare main). */ * stays a bare reset (keeps bare main). */
@@ -1394,8 +1340,6 @@ sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
if (fwrite(buf, 1, (size_t)len, out) != (size_t)len if (fwrite(buf, 1, (size_t)len, out) != (size_t)len
|| fputc('\n', out) == EOF) || fputc('\n', out) == EOF)
bad = 1; bad = 1;
free(uses);
freearena(a);
free(buf); free(buf);
if (bad) { if (bad) {
fprintf(stderr, "ww: cannot write package unit\n"); fprintf(stderr, "ww: cannot write package unit\n");
@@ -1415,8 +1359,6 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *scratch,
if (g->pkg[pi].emit_context < 0 if (g->pkg[pi].emit_context < 0
|| g->pkg[pi].emit_context >= g->ncontext) || g->pkg[pi].emit_context >= g->ncontext)
return -1; return -1;
const char *searchpath =
g->context[g->pkg[pi].emit_context].searchpath;
FILE *u = fopen(unitf, "wb"); FILE *u = fopen(unitf, "wb");
if (u == NULL) { if (u == NULL) {
fprintf(stderr, "ww: cannot open %s\n", unitf); fprintf(stderr, "ww: cannot open %s\n", unitf);
@@ -1444,18 +1386,14 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *scratch,
return -1; return -1;
} }
} }
struct ImportSet bodyvisit = {0};
int bodyrc = 0; int bodyrc = 0;
if (g->pkg[pi].is_dir) { if (g->pkg[pi].is_dir) {
for (int i = 0; i < g->pkg[pi].nsources && bodyrc == 0; i++) for (int i = 0; i < g->pkg[pi].nsources && bodyrc == 0; i++)
bodyrc = sep_emit_body(u, g->pkg[pi].sources[i], &bodyvisit, bodyrc = sep_emit_body(u, g->pkg[pi].sources[i],
searchpath, g->pkg[pi].path, &g->pkg[pi]); g->pkg[pi].path);
} else { } else {
bodyrc = sep_emit_body(u, g->pkg[pi].entry, &bodyvisit, searchpath, bodyrc = sep_emit_body(u, g->pkg[pi].entry, g->pkg[pi].path);
g->pkg[pi].path, &g->pkg[pi]);
} }
for (int i = 0; i < bodyvisit.n; i++) free(bodyvisit.paths[i]);
free(bodyvisit.paths);
if (fclose(u) != 0) { if (fclose(u) != 0) {
fprintf(stderr, "ww: cannot close package unit %s\n", unitf); fprintf(stderr, "ww: cannot close package unit %s\n", unitf);
return -1; return -1;
@@ -1821,7 +1759,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
if (is_test) { if (is_test) {
char tpath[1024]; char tpath[1024];
int tdir = 0; int tdir = 0;
if (locate_import(toolsrcdir, "test", tpath, sizeof tpath, &tdir)) { if (locate_import(toolsrcdir, "test", tpath, sizeof tpath)) {
tdir = 1;
g->support_context = sep_context_for(g, toolsrcdir, NULL, g->support_context = sep_context_for(g, toolsrcdir, NULL,
toolsrcdir); toolsrcdir);
if (g->support_context < 0) return 1; if (g->support_context < 0) return 1;
@@ -1840,7 +1779,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
char userpath[1024]; char userpath[1024];
int userdir = 0; int userdir = 0;
if (locate_import(g->context[products[i].context].searchpath, if (locate_import(g->context[products[i].context].searchpath,
"test", userpath, sizeof userpath, &userdir)) { "test", userpath, sizeof userpath)) {
userdir = 1;
(void)userdir; (void)userdir;
char *uc = realpath(userpath, NULL); char *uc = realpath(userpath, NULL);
if (tc != NULL && uc != NULL if (tc != NULL && uc != NULL
@@ -2342,7 +2282,7 @@ resolve_module(const char *name, const char *incs, char *out, size_t outsz,
search_path(incs, sp, sizeof sp); search_path(incs, sp, sizeof sp);
char path_form[256]; char path_form[256];
import_path_form(name, path_form, sizeof path_form); import_path_form(name, path_form, sizeof path_form);
return locate_import(sp, path_form, out, outsz, is_dir); return locate_module(sp, path_form, out, outsz, is_dir);
} }
/* Returns the index past the last arg consumed for positionals (so callers /* Returns the index past the last arg consumed for positionals (so callers

View File

@@ -2792,13 +2792,16 @@ package main;
import lib.math; import lib.math;
``` ```
An import is translated from dots to path separators and resolved, with An import is translated from dots to path separators and resolved only as a
directory packages preferred, through the entry package's directory, explicit directory package through the entry package's directory, explicit `-I` roots in
`-I` roots in command order, and the toolchain source-library root. There is no command order, and the toolchain source-library root. A same-named `.ww` file is
network or manifest fallback. The loader uses the compiler frontend's neither a match nor a shadow for an import, so a later root containing the
imports-only parser, unions duplicate imports, byte-sorts direct edges, interns directory wins over an earlier file decoy. There is no network, manifest, or
resolved directories by filesystem identity, and reports self-imports and imported-file fallback. Explicit single-file CLI roots retain their raw-unit
stable cycle chains before compilation. compatibility path. The loader uses the compiler frontend's imports-only parser,
unions duplicate imports, byte-sorts direct edges, interns resolved directories
by filesystem identity, and reports self-imports and stable cycle chains before
compilation.
A directory package consists of its immediate regular non-symlink `.ww` files, A directory package consists of its immediate regular non-symlink `.ww` files,
excluding `*_test.ww`, in byte-sorted filename order. Every selected file must excluding `*_test.ww`, in byte-sorted filename order. Every selected file must
@@ -2896,10 +2899,10 @@ context from the local package slice: its directory, explicit `-I` roots in
command order, then the toolchain source root. Same and external variants of command order, then the toolchain source root. Same and external variants of
one directory share that context; unrelated directory roots never acquire one directory share that context; unrelated directory roots never acquire
lookup precedence from their request order. When multiple contexts reach one lookup precedence from their request order. When multiple contexts reach one
canonical production package, the loader verifies that every directory, canonical production package, the loader verifies that every directory import
folded-file, and inline import binding is identical before reusing its compile binding is identical before reusing its compile action. A different binding is
action. A different binding is a deterministic package-resolution failure for a deterministic package-resolution failure for the roots that reach it, rather
the roots that reach it, rather than a first-root-wins build. than a first-root-wins build.
A production action failure blocks exactly the roots that reach it. A A production action failure blocks exactly the roots that reach it. A
root-local compile or link failure does not suppress a successfully built root-local compile or link failure does not suppress a successfully built
@@ -3123,6 +3126,72 @@ WW adopts only the correctness boundary in its existing inspectable `cmp`-based
workdir. It does not add build IDs, hashes, a CAS, an action graph, a scheduler, workdir. It does not add build IDs, hashes, a CAS, an action graph, a scheduler,
or a manifest. or a manifest.
### 11.11 Implemented directory-only source-import slice
Cstage and WWstage now use a directory-only locator for every parsed source
import, including imports selected only by a package-test variant and the
compiler-generated test-support edge. The loader makes one ordered pass for
`<root>/<import-path>/`; it never probes `<root>/<import-path>.ww`. Unit
composition consequently writes only the owning package's byte-sorted source
files after its direct dependency interfaces and never recursively folds an
imported source body. Missing imports retain the importing source position and
the same stable diagnostic in both stages.
Root selection remains a separate compatibility boundary. A literal `.ww` CLI
target, or a bare CLI target found as `<root>/<name>.ww` after the global
directory search, can still create one raw single-file root. Its historical
inline package clauses may satisfy compiler-fixture bindings inside that raw
unit. Directory roots cannot use that exemption, and no filesystem source
import can reach it. This preserves low-level compiler fixtures without
weakening package-graph identity.
The self-hosted tools no longer depend on the removed behavior. `w6a/` and
`w6l/` are executable `package main` directories whose sorted source sets are
compiled once. The compiler backend is one `wcc/` directory package with a
narrow exported check/codegen façade; `w6c` and `wwdump` import that package
from its parent search root instead of importing its implementation files.
Legacy test fixtures were converted to directories, except for one intentional
compiler leaf-collision probe that now invokes `w6c` on an explicitly composed
raw unit. The Lisp example likewise imports a `lispcore/` directory package.
The focused native regression puts only `example/foo.ww` in an earlier import
root and a two-source `example/foo/` package in a later root. Both stages select
the directory, emit the exact sorted package-owned unit, consume its direct
dependency export, produce byte-identical deterministic `.wwi` and `.a`
artifacts, link and run a transitive archive closure, and repeat the resolution
through a real directory-package test. With only the file root present, both
stages reject the import with byte-identical package-attributed stderr. The
existing exact-tool observer remains the non-duplicated proof that each
canonical production action compiles once, compiler units contain only direct
`.wwi` inputs, and linker argument vectors contain the complete reachable `.a`
closure and no `.wwi` path.
This follows Go 1.26.5's concrete directory ownership. `ImportDir` is defined
as importing the package in a named directory; import lookup accepts directory
candidates, then reads and sorts that directory's immediate entries
([`go/build/build.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#521),
[`go/build/build.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#725),
[`go/build/build.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/go/build/build.go#859)).
Repeated loads reuse the canonical package object, and `PackageList` performs a
pointer-deduplicated postorder walk
([`cmd/go/internal/load/pkg.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/pkg.go#633),
[`cmd/go/internal/load/pkg.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/pkg.go#2776)).
Go's build action is interned by mode and package identity and receives only
the package's direct imports, while linking explicitly expands all transitive
link dependencies
([`cmd/go/internal/work/action.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#437),
[`cmd/go/internal/work/action.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#646),
[`cmd/go/internal/work/action.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/work/action.go#1034)).
Its compiler export writer finalizes self-contained public data in sorted index
order, which is why direct compiler artifacts suffice
([`cmd/compile/internal/noder/unified.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/compile/internal/noder/unified.go#463)).
Finally, Go constructs distinct ordinary, internal-test, external-test, and
generated-main package variants through the same import loader
([`cmd/go/internal/load/test.go`](https://go.googlesource.com/go/+/refs/tags/go1.26.5/src/cmd/go/internal/load/test.go#85)).
WW adopts these package-ownership boundaries without adding modules, manifests,
network lookup, a generalized action graph, or a cache protocol.
## 12. Candidate architectures and hard-gate decision ## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins. Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -284,12 +284,9 @@ fn importpathform(name: *u8, namelen: u64) *u8 = {
return buf.ptr; return buf.ptr;
}; };
// Try <dir>/<path>/ as a directory (wantdir != 0), else <dir>/<path>.ww // Try <dir>/<path>/ as a directory (wantdir != 0), else
// as a file. Sets *isdir on hit. Symmetric with cstage locate_import_in // <dir>/<path>.ww as a CLI target. Source-import loading calls only the
// for byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww // directory arm; the file arm belongs exclusively to locatemodule.
// form was dropped in task #22 — directory-as-module enumeration
// replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no
// `foo/foo.ha` fallback; a module IS the directory).
fn locatein(dir: *u8, dirlen: u64, fn locatein(dir: *u8, dirlen: u64,
pathform: *u8, pflen: u64, isdir: *i32, wantdir: i32) *u8 = { pathform: *u8, pflen: u64, isdir: *i32, wantdir: i32) *u8 = {
let tail: u64 = 1u64; // NUL for the directory form let tail: u64 = 1u64; // NUL for the directory form
@@ -333,44 +330,55 @@ fn locatein(dir: *u8, dirlen: u64,
return nil; return nil;
}; };
// Walk a colon-separated dirlist, return first hit or nil. Sets // Walk every ordered root for <root>/<path>/ only. A sibling or earlier-root
// *isdir on hit. // <path>.ww is neither a match nor a shadow: every source import identifies
// // one canonical directory package.
// #98: "a module IS the directory" — a directory-package on ANY entry fn locateimport(dirs: *u8, name: *u8, namelen: u64) *u8 = {
// wins over a same-named sibling FILE on an EARLIER entry. The driver
// builds the searchpath srcd-first; a co-located `lib/<mod>/<mod>test.ww`
// entry makes srcd = lib/<mod>, so a self-named `import <mod>` would
// else file-hit the sibling lib/<mod>/<mod>.ww and fold it
// inline under the wrong module-reset. Two passes — directories first,
// files only if no directory matches anywhere — let lib/<mod>/ resolve
// as the dir while a genuine leaf package with no directory (e.g.
// lib/encoding/hex imported bare as `hex`, reachable only via its file
// in srcd) still resolves in the file pass. Latent: a dir-package now
// beats an earlier-entry same-named sibling FILE — loud-failing, none
// in the corpus; tracked as #101.
fn locateimport(dirs: *u8, name: *u8, namelen: u64,
isdir: *i32) *u8 = {
let pathform: *u8 = importpathform(name, namelen); let pathform: *u8 = importpathform(name, namelen);
let pflen: u64 = cstrlen(pathform); let pflen: u64 = cstrlen(pathform);
let total: u64 = cstrlen(dirs); let total: u64 = cstrlen(dirs);
let wantdir: i32 = 1i32; let p: u64 = 0u64;
for (wantdir >= 0i32) { for (p < total) {
let p: u64 = 0u64; let q: u64 = p;
for (p < total) { for (q < total) {
let q: u64 = p; if (dirs[q] == ':') { break; };
for (q < total) { q += 1u64;
if (dirs[q] == ':') { break; };
q += 1u64;
};
let seglen: u64 = q - p;
if (seglen > 0u64) {
let hit: *u8 = locatein(dirs + p, seglen,
pathform, pflen, isdir, wantdir);
if (hit != nil) { return hit; };
};
p = q + 1u64;
}; };
wantdir -= 1i32; let seglen: u64 = q - p;
if (seglen > 0u64) {
let isdir: i32 = 0;
let hit: *u8 = locatein(dirs + p, seglen,
pathform, pflen, &isdir, 1i32);
if (hit != nil) { return hit; };
};
p = q + 1u64;
};
return nil;
};
// CLI target compatibility: directory packages win globally, then a bare
// target may resolve to <root>/<path>.ww. Never called for a source import.
fn locatemodule(dirs: *u8, name: *u8, namelen: u64,
isdir: *i32) *u8 = {
let hit: *u8 = locateimport(dirs, name, namelen);
if (hit != nil) { *isdir = 1; return hit; };
let pathform: *u8 = importpathform(name, namelen);
let pflen: u64 = cstrlen(pathform);
let total: u64 = cstrlen(dirs);
let p: u64 = 0u64;
for (p < total) {
let q: u64 = p;
for (q < total) {
if (dirs[q] == ':') { break; };
q += 1u64;
};
let seglen: u64 = q - p;
if (seglen > 0u64) {
hit = locatein(dirs + p, seglen, pathform, pflen,
isdir, 0i32);
if (hit != nil) { return hit; };
};
p = q + 1u64;
}; };
return nil; return nil;
}; };
@@ -1240,79 +1248,68 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
cerr(": error: import path is too long (limit 255 bytes)\n"); cerr(": error: import path is too long (limit 255 bytes)\n");
return -1; return -1;
}; };
let isdir: i32 = 0;
let externalproduction: bool = sepexternalproduction( let externalproduction: bool = sepexternalproduction(
&g.pkg[pi], idp, idn); &g.pkg[pi], idp, idn);
let ipath: *u8 = nil; let ipath: *u8 = nil;
if (externalproduction) { if (externalproduction) {
ipath = g.pkg[pi].entry; ipath = g.pkg[pi].entry;
isdir = 1;
} else { } else {
ipath = locateimport(searchpath, idp, idn, &isdir); ipath = locateimport(searchpath, idp, idn);
}; };
if (ipath != nil) { if (ipath != nil) {
if (isdir != 0) { sepbindadd(bindings, 'D': u8, u.usepath, ipath);
sepbindadd(bindings, 'D': u8, u.usepath, ipath); let self: bool = os.samefile(pathstr(ipath),
let self: bool = os.samefile(pathstr(ipath), pathstr(g.pkg[pi].entry));
pathstr(g.pkg[pi].entry)); if (self && sepexternalname(&g.pkg[pi], idp, idn, true)) {
if (self && sepexternalname(&g.pkg[pi], idp, idn, true)) { externalproduction = true;
externalproduction = true; };
}; if (self && !externalproduction) {
if (self cerrpos(u.file, u.line, u.col);
&& !externalproduction) { cerr(": error: self-import: package '");
cerrpos(u.file, u.line, u.col); if (g.pkg[pi].path[0u64] != 0u8) {
cerr(": error: self-import: package '"); cerr(pathstr(g.pkg[pi].path));
if (g.pkg[pi].path[0u64] != 0u8) { } else { cerr(pathstr(g.pkg[pi].name)); };
cerr(pathstr(g.pkg[pi].path)); cerr("' cannot import itself\n");
} else { cerr(pathstr(g.pkg[pi].name)); }; return -1;
cerr("' cannot import itself\n"); };
return -1; let runtimeproduction: bool = false;
}; if (externalproduction) {
let runtimeproduction: bool = false; let gi: i32 = 0;
if (externalproduction) { for (gi < g.n) {
let gi: i32 = 0; if (g.pkg[gi].testsupport
for (gi < g.n) { && syntax.streq(pathstr(g.pkg[gi].path),
if (g.pkg[gi].testsupport u.usepath)
&& syntax.streq(pathstr(g.pkg[gi].path), && os.samefile(pathstr(g.pkg[gi].entry),
u.usepath) pathstr(ipath))) {
&& os.samefile(pathstr(g.pkg[gi].entry), runtimeproduction = true;
pathstr(ipath))) {
runtimeproduction = true;
};
gi += 1;
}; };
gi += 1;
}; };
let nm: []u8 = alloc([], idn + 1u64)!; };
let k: u64 = 0u64; let nm: []u8 = alloc([], idn + 1u64)!;
for (k < idn) { nm[k] = idp[k]; k += 1u64; }; let k: u64 = 0u64;
nm[idn] = 0u8; for (k < idn) { nm[k] = idp[k]; k += 1u64; };
let di: i32 = -1; nm[idn] = 0u8;
if (externalproduction && !runtimeproduction) { let di: i32 = -1;
let art: *u8 = appendlit(g.pkg[pi].artifact, if (externalproduction && !runtimeproduction) {
"-production"); let art: *u8 = appendlit(g.pkg[pi].artifact,
di = sepfindoraddrole(g, nm.ptr, ipath, 1, "-production");
SEP_ROLE_EXTERNAL_PRODUCTION, art); di = sepfindoraddrole(g, nm.ptr, ipath, 1,
} else { SEP_ROLE_EXTERNAL_PRODUCTION, art);
di = sepfindoradd(g, nm.ptr, ipath, 1);
};
if (di < 0) { return -1; };
let seen: bool = false;
let m: i32 = 0;
for (m < g.pkg[pi].ndeps) {
if (g.pkg[pi].deps[m] == di) { seen = true; };
m += 1;
};
if (!seen) {
if (g.pkg[pi].ndeps >= SEP_MAXPKG) { return -1; };
g.pkg[pi].deps[g.pkg[pi].ndeps] = di;
g.pkg[pi].ndeps += 1;
};
} else { } else {
sepbindadd(bindings, 'F': u8, u.usepath, ipath); di = sepfindoradd(g, nm.ptr, ipath, 1);
if (sepscanfile(g, pi, ipath, searchpath, fv, };
bindings, 0) < 0) { if (di < 0) { return -1; };
return -1; let seen: bool = false;
}; let m: i32 = 0;
for (m < g.pkg[pi].ndeps) {
if (g.pkg[pi].deps[m] == di) { seen = true; };
m += 1;
};
if (!seen) {
if (g.pkg[pi].ndeps >= SEP_MAXPKG) { return -1; };
g.pkg[pi].deps[g.pkg[pi].ndeps] = di;
g.pkg[pi].ndeps += 1;
}; };
} else { } else {
let lstart: u64 = 0u64; let lstart: u64 = 0u64;
@@ -1324,11 +1321,13 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
let leafp: *u8 = idp + lstart; let leafp: *u8 = idp + lstart;
let leafn: u64 = idn - lstart; let leafn: u64 = idn - lstart;
let inlinepackage: bool = false; let inlinepackage: bool = false;
let pm: *syntax.node = imports.body; if (g.pkg[pi].isdir == 0) {
for (pm != nil) { let pm: *syntax.node = imports.body;
if (bytecmp(pm.nmod.ptr, pm.nmod.len: u64, for (pm != nil) {
leafp, leafn) == 0) { inlinepackage = true; }; if (bytecmp(pm.nmod.ptr, pm.nmod.len: u64,
pm = pm.next; leafp, leafn) == 0) { inlinepackage = true; };
pm = pm.next;
};
}; };
if (!inlinepackage) { if (!inlinepackage) {
cerrpos(u.file, u.line, u.col); cerrpos(u.file, u.line, u.col);
@@ -1547,10 +1546,8 @@ fn sepvalidatemoduleclosure(g: *sepgraph, order: []i32, n: i32,
return 0; return 0;
}; };
// Emit one of pi's own source files into the sep-unit under the // Imported source never enters this body: its direct export data was
// //ww:module-reset primary boundary (so -c emits its decls, imported // prepended above, while this package owns exactly its sorted source set.
// ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead);
// FILE imports fold in (intra-package split).
fn sepwriteall(fd: i32, buf: *u8, n: u64) bool = { fn sepwriteall(fd: i32, buf: *u8, n: u64) bool = {
match (os.writeall(fd, buf, n)) { match (os.writeall(fd, buf, n)) {
case let wrote: i64 => return wrote == n: i64; case let wrote: i64 => return wrote == n: i64;
@@ -1558,14 +1555,7 @@ fn sepwriteall(fd: i32, buf: *u8, n: u64) bool = {
}; };
}; };
fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8, fn sepemitbody(fd: i32, path: *u8, modpath: *u8) i32 = {
modpath: *u8, pkg: *seppkg) i32 = {
let pview: str;
pview.ptr = path;
pview.len = cstrlen(path): i32;
let pdup: str = strings.dup(pview);
if (visitseen(visit, pdup)) { return 0; };
visitadd(visit, pdup);
let bufp: *u8; let bufp: *u8;
let blen: u64; let blen: u64;
bufp, blen = slurp(path); bufp, blen = slurp(path);
@@ -1573,64 +1563,6 @@ fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8,
cerr("ww: cannot read source\n"); cerr("ww: cannot read source\n");
return -1; return -1;
}; };
let l: syntax.lex;
syntax.lexinit(&l, pdup, bufp, blen);
let ps: syntax.parser;
syntax.parserinit(&ps, &l);
let imports: *syntax.node = syntax.parseimports(&ps);
if (l.errs > 0 || ps.errs > 0) { return -1; };
let nuse: i32 = 0;
let u: *syntax.node = imports.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE) { nuse += 1; };
u = u.next;
};
let uses: []*syntax.node = [];
if (nuse > 0) {
let allocated: []*syntax.node = alloc([], nuse: u64)!;
uses = allocated;
uses.len = nuse;
};
let ui: i32 = 0;
u = imports.list;
for (u != nil) {
if (u.kind == syntax.nkind.N_USE) { uses[ui] = u; ui += 1; };
u = u.next;
};
let si: i32 = 1;
for (si < nuse) {
let sj: i32 = si;
for (sj > 0) {
if (strings.compare(uses[sj - 1].usepath,
uses[sj].usepath) <= 0) { sj = 0; }
else {
let t: *syntax.node = uses[sj];
uses[sj] = uses[sj - 1];
uses[sj - 1] = t;
sj -= 1;
};
};
si += 1;
};
ui = 0;
for (ui < nuse) {
u = uses[ui];
let idp: *u8 = u.usepath.ptr;
let idn: u64 = u.usepath.len: u64;
if (sepexternalproduction(pkg, idp, idn)) {
ui += 1;
continue;
};
let isdir: i32 = 0;
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
if (ipath != nil) {
if (isdir == 0) {
if (sepemitbody(fd, ipath, visit, searchpath,
modpath, pkg) < 0) { return -1; };
};
};
ui += 1;
};
// #57: tag the primary body by its full dotted import path so the // #57: tag the primary body by its full dotted import path so the
// definer mangles == the importer reference; a root build (path "") // definer mangles == the importer reference; a root build (path "")
// stays a bare reset (keeps bare main). // stays a bare reset (keeps bare main).
@@ -1665,7 +1597,6 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8,
unitf: *u8) i32 = { unitf: *u8) i32 = {
if (g.pkg[pi].emitcontext < 0 if (g.pkg[pi].emitcontext < 0
|| g.pkg[pi].emitcontext >= g.ncontext) { return -1; }; || g.pkg[pi].emitcontext >= g.ncontext) { return -1; };
let searchpath: *u8 = g.context[g.pkg[pi].emitcontext].searchpath;
let u: i32 = os.open(pathstr(unitf), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644 let u: i32 = os.open(pathstr(unitf), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
if (u < 0) { if (u < 0) {
cerr("ww: cannot open unit\n"); cerr("ww: cannot open unit\n");
@@ -1696,21 +1627,15 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8,
}; };
k += 1; k += 1;
}; };
let bv: expctx;
bv.out = u;
bv.dirs = searchpath;
bv.visit = nil;
let bodyrc: i32 = 0; let bodyrc: i32 = 0;
if (g.pkg[pi].isdir != 0) { if (g.pkg[pi].isdir != 0) {
let i: i32 = 0; let i: i32 = 0;
for (i < g.pkg[pi].nsources && bodyrc == 0) { for (i < g.pkg[pi].nsources && bodyrc == 0) {
bodyrc = sepemitbody(u, g.pkg[pi].sources[i], &bv, searchpath, bodyrc = sepemitbody(u, g.pkg[pi].sources[i], g.pkg[pi].path);
g.pkg[pi].path, &g.pkg[pi]);
i += 1; i += 1;
}; };
} else { } else {
bodyrc = sepemitbody(u, g.pkg[pi].entry, &bv, searchpath, bodyrc = sepemitbody(u, g.pkg[pi].entry, g.pkg[pi].path);
g.pkg[pi].path, &g.pkg[pi]);
}; };
if (os.close(u) != 0) { if (os.close(u) != 0) {
cerr("ww: cannot close package unit\n"); cerr("ww: cannot close package unit\n");
@@ -2226,9 +2151,9 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
// when user source occupies that identity, the reserved graph alias keeps // when user source occupies that identity, the reserved graph alias keeps
// it distinct. The linker receives the same support archive closure. // it distinct. The linker receives the same support archive closure.
if (istest != 0) { if (istest != 0) {
let td: i32 = 0; let td: i32 = 1;
let tp: *u8 = locateimport(toolsrcdir, "test".ptr, let tp: *u8 = locateimport(toolsrcdir, "test".ptr,
"test".len: u64, &td); "test".len: u64);
if (tp != nil) { if (tp != nil) {
g.supportcontext = sepcontextfor(g, toolsrcdir, nil, g.supportcontext = sepcontextfor(g, toolsrcdir, nil,
toolsrcdir); toolsrcdir);
@@ -2250,11 +2175,10 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
}; };
producti = 0; producti = 0;
for (producti < nproducts && !collision) { for (producti < nproducts && !collision) {
let ud: i32 = 0;
let up: *u8 = locateimport( let up: *u8 = locateimport(
g.context[products[producti].context].searchpath, g.context[products[producti].context].searchpath,
"test".ptr, "test".ptr,
"test".len: u64, &ud); "test".len: u64);
if (up != nil && !os.samefile(pathstr(tp), pathstr(up))) { if (up != nil && !os.samefile(pathstr(tp), pathstr(up))) {
collision = true; collision = true;
}; };
@@ -2927,7 +2851,7 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
}; };
let search: *u8 = buildsearchpath(selfdir, incs); let search: *u8 = buildsearchpath(selfdir, incs);
return locateimport(search, name, nlen, isdir); return locatemodule(search, name, nlen, isdir);
}; };
fn writeusage(fd: i32) void = { fn writeusage(fd: i32) void = {