ww: resolve self-named import to the dir-package, not a sibling file (M4 E3, #98)

The driver searchpath is srcd-first (srcd = the entry file's directory).
A co-located black-box test lib/<mod>/<mod>test.ww makes srcd=lib/<mod>,
so resolving `import <mod>` hit the sibling-FILE branch lib/<mod>/<mod>.ww
and folded it inline into the consumer unit under the wrong module tag
("package <mod> does not match import path <importer>") — 7 lib-run tests
fail under separate compilation. The combined amalgamator tolerated the
co-location; only sep surfaced it.

Resolve a package directory-first: walk ALL searchpath entries for a
directory match, and only fall back to a file match if no directory
exists anywhere. A dir-package now beats a same-named sibling file (fixes
the self-named shadow), while a leaf package with no directory (e.g.
lib/encoding/hex) still resolves via its file. This realizes the driver's
"a module is the directory" intent; the originally-specced per-directory
suppression was rejected because it broke leaf packages (rob-pike). Both
stages (cmd/ww/main.c + selfhost twin). The dir-beats-earlier-file
precedence change is latent and loud-failing (#101).

Move-set: ww + ww_ww (driver) only; w6c_ww/wwdump_ww/w6a_ww/w6l_ww HOLD.
Gate: test/wcc/989_coloimport_sep.c (table-driven, both stages).
This commit is contained in:
2026-06-18 12:09:25 +09:00
parent eb6083e54a
commit 200f51ca94
8 changed files with 421 additions and 119 deletions

View File

@@ -119,21 +119,24 @@ import_path_form(const char *name, char *out, size_t outsz)
out[i] = '\0';
}
/* try <dir>/<path>/ as a directory, then <dir>/<path>.ww as a file.
* Sets *is_dir on hit. Symmetric with wwstage locatein for byte-id
* driver output (rule 10). The legacy <dir>/<name>/<name>.ww form
* was dropped in task #22 — directory-as-module enumeration replaces
* it, mirroring ref/hare/hare/module/srcs.ha (Hare has no fallback
* matching `foo/foo.ha`; a module IS the directory). */
/* try <dir>/<path>/ as a directory (want_dir), else <dir>/<path>.ww as
* a file. Sets *is_dir on hit. Symmetric with wwstage locatein for
* byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww
* form was dropped in task #22 — directory-as-module enumeration
* replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no
* fallback matching `foo/foo.ha`; a module IS the directory). */
static int
locate_import_in(const char *dir, const char *path_form, char *out,
size_t outsz, int *is_dir)
size_t outsz, int *is_dir, int want_dir)
{
struct stat st;
snprintf(out, outsz, "%s/%s", dir, path_form);
if (stat(out, &st) == 0 && S_ISDIR(st.st_mode)) {
*is_dir = 1;
return 1;
if (want_dir) {
snprintf(out, outsz, "%s/%s", dir, path_form);
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) {
@@ -144,25 +147,41 @@ locate_import_in(const char *dir, const char *path_form, char *out,
}
/* Walk a colon-separated dirlist trying to resolve `path_form`. Returns
* 1 on the first hit and writes the concrete path + dir/file marker. */
* 1 on the first hit and writes the concrete path + dir/file marker.
*
* #98: "a module IS the directory" — a directory-package on ANY entry
* 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 (under --sep) fold
* 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
locate_import(const char *dirs, const char *path_form, char *out,
size_t outsz, int *is_dir)
{
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,
is_dir)) return 1;
for (int want_dir = 1; want_dir >= 0; want_dir--) {
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, is_dir, want_dir)) return 1;
}
if (!e) break;
p = e + 1;
}
if (!e) break;
p = e + 1;
}
return 0;
}