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:
12
Makefile
12
Makefile
@@ -572,6 +572,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_pkgcache_run \
|
$(BIN)/test_pkgcache_run \
|
||||||
$(BIN)/test_c6soak_run \
|
$(BIN)/test_c6soak_run \
|
||||||
$(BIN)/test_septest_run \
|
$(BIN)/test_septest_run \
|
||||||
|
$(BIN)/test_coloimport_sep \
|
||||||
$(BIN)/test_floatlit_run \
|
$(BIN)/test_floatlit_run \
|
||||||
$(BIN)/test_checked_run \
|
$(BIN)/test_checked_run \
|
||||||
$(BIN)/test_floatarr_run \
|
$(BIN)/test_floatarr_run \
|
||||||
@@ -3258,6 +3259,17 @@ $(BIN)/test_septest_run: test/wcc/989_septest_run.c $(BIN)/ww $(BIN)/ww_ww \
|
|||||||
$(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN)
|
$(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
# 989_coloimport_sep — #98 E3 flip-blocker: a co-located `_test` entry must
|
||||||
|
# not let its srcd shadow a self-named transitive `import <mod>` into the
|
||||||
|
# sibling file. Drives BOTH driver stages on a co-located fixture, asserting
|
||||||
|
# run-exit 0 (pre-fix the importer's fold makes w6c reject = the non-vacuity
|
||||||
|
# teeth), the dir-package registers its own .unit.ww/.wwi, and cs==ww per-pkg
|
||||||
|
# .s/.wwi (rule 10). Needs both driver + compiler + asm + linker stages.
|
||||||
|
$(BIN)/test_coloimport_sep: test/wcc/989_coloimport_sep.c $(BIN)/ww $(BIN)/ww_ww \
|
||||||
|
$(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \
|
||||||
|
$(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
$(BIN)/test_floatlit_run: test/wcc/989_floatlit_run.c $(BIN)/ww $(BIN)/w6c \
|
$(BIN)/test_floatlit_run: test/wcc/989_floatlit_run.c $(BIN)/ww $(BIN)/w6c \
|
||||||
$(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
$(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|||||||
@@ -119,21 +119,24 @@ import_path_form(const char *name, char *out, size_t outsz)
|
|||||||
out[i] = '\0';
|
out[i] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* try <dir>/<path>/ as a directory, then <dir>/<path>.ww as a file.
|
/* try <dir>/<path>/ as a directory (want_dir), else <dir>/<path>.ww as
|
||||||
* Sets *is_dir on hit. Symmetric with wwstage locatein for byte-id
|
* a file. Sets *is_dir on hit. Symmetric with wwstage locatein for
|
||||||
* driver output (rule 10). The legacy <dir>/<name>/<name>.ww form
|
* byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww
|
||||||
* was dropped in task #22 — directory-as-module enumeration replaces
|
* form was dropped in task #22 — directory-as-module enumeration
|
||||||
* it, mirroring ref/hare/hare/module/srcs.ha (Hare has no fallback
|
* replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no
|
||||||
* matching `foo/foo.ha`; a module IS the directory). */
|
* 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)
|
size_t outsz, int *is_dir, int want_dir)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
snprintf(out, outsz, "%s/%s", dir, path_form);
|
if (want_dir) {
|
||||||
if (stat(out, &st) == 0 && S_ISDIR(st.st_mode)) {
|
snprintf(out, outsz, "%s/%s", dir, path_form);
|
||||||
*is_dir = 1;
|
if (stat(out, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
return 1;
|
*is_dir = 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
snprintf(out, outsz, "%s/%s.ww", dir, path_form);
|
snprintf(out, outsz, "%s/%s.ww", dir, path_form);
|
||||||
if (access(out, 0) == 0) {
|
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
|
/* 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
|
static int
|
||||||
locate_import(const char *dirs, const char *path_form, char *out,
|
locate_import(const char *dirs, const char *path_form, char *out,
|
||||||
size_t outsz, int *is_dir)
|
size_t outsz, int *is_dir)
|
||||||
{
|
{
|
||||||
const char *p = dirs;
|
for (int want_dir = 1; want_dir >= 0; want_dir--) {
|
||||||
while (*p) {
|
const char *p = dirs;
|
||||||
const char *e = strchr(p, ':');
|
while (*p) {
|
||||||
size_t n = e ? (size_t)(e - p) : strlen(p);
|
const char *e = strchr(p, ':');
|
||||||
if (n > 0 && n < outsz) {
|
size_t n = e ? (size_t)(e - p) : strlen(p);
|
||||||
char dir[1024];
|
if (n > 0 && n < outsz) {
|
||||||
if (n >= sizeof dir) n = sizeof dir - 1;
|
char dir[1024];
|
||||||
memcpy(dir, p, n);
|
if (n >= sizeof dir) n = sizeof dir - 1;
|
||||||
dir[n] = '\0';
|
memcpy(dir, p, n);
|
||||||
if (locate_import_in(dir, path_form, out, outsz,
|
dir[n] = '\0';
|
||||||
is_dir)) return 1;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3112,14 +3112,14 @@ fn importpathform(name: *u8, namelen: u64) *u8 = {
|
|||||||
return buf.ptr;
|
return buf.ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try <dir>/<path>/ as a directory, then <dir>/<path>.ww as a file.
|
// Try <dir>/<path>/ as a directory (wantdir != 0), else <dir>/<path>.ww
|
||||||
// Sets *isdir on hit. Symmetric with cstage locate_import_in for
|
// as a file. Sets *isdir on hit. Symmetric with cstage locate_import_in
|
||||||
// byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww
|
// for byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww
|
||||||
// form was dropped in task #22 — directory-as-module enumeration
|
// form was dropped in task #22 — directory-as-module enumeration
|
||||||
// replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no
|
// replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no
|
||||||
// `foo/foo.ha` fallback; a module IS the directory).
|
// `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) *u8 = {
|
pathform: *u8, pflen: u64, isdir: *i32, wantdir: i32) *u8 = {
|
||||||
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
let off: u64 = 0u64;
|
let off: u64 = 0u64;
|
||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
@@ -3129,63 +3129,71 @@ fn locatein(dir: *u8, dirlen: u64,
|
|||||||
i = 0u64;
|
i = 0u64;
|
||||||
for (i < pflen) { buf[off + i] = pathform[i]; i += 1u64; };
|
for (i < pflen) { buf[off + i] = pathform[i]; i += 1u64; };
|
||||||
off += pflen;
|
off += pflen;
|
||||||
|
if (wantdir != 0i32) {
|
||||||
|
buf[off] = 0u8;
|
||||||
|
let fi: os.filestat;
|
||||||
|
let r: (void | os.oserror) = os.stat(&fi, pathstr(buf.ptr));
|
||||||
|
match (r) {
|
||||||
|
case void => {
|
||||||
|
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
|
||||||
|
if (t == os.mode.DIR: u32) {
|
||||||
|
*isdir = 1;
|
||||||
|
return buf.ptr;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case let e: os.oserror => void;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
buf[off] = 46u8; off += 1u64; // '.'
|
||||||
|
buf[off] = 119u8; off += 1u64; // 'w'
|
||||||
|
buf[off] = 119u8; off += 1u64; // 'w'
|
||||||
buf[off] = 0u8;
|
buf[off] = 0u8;
|
||||||
let fi: os.filestat;
|
if (os.access(pathstr(buf.ptr), 0i32) == 0) {
|
||||||
let r: (void | os.oserror) = os.stat(&fi, pathstr(buf.ptr));
|
|
||||||
let isdirhit: bool = false;
|
|
||||||
match (r) {
|
|
||||||
case void => {
|
|
||||||
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
|
|
||||||
if (t == os.mode.DIR: u32) { isdirhit = true; };
|
|
||||||
};
|
|
||||||
case let e: os.oserror => void;
|
|
||||||
};
|
|
||||||
if (isdirhit) {
|
|
||||||
*isdir = 1;
|
|
||||||
return buf.ptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
let buf2: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
|
||||||
off = 0u64;
|
|
||||||
i = 0u64;
|
|
||||||
for (i < dirlen) { buf2[off + i] = dir[i]; i += 1u64; };
|
|
||||||
off += dirlen;
|
|
||||||
buf2[off] = 47u8; off += 1u64;
|
|
||||||
i = 0u64;
|
|
||||||
for (i < pflen) { buf2[off + i] = pathform[i]; i += 1u64; };
|
|
||||||
off += pflen;
|
|
||||||
buf2[off] = 46u8; off += 1u64; // '.'
|
|
||||||
buf2[off] = 119u8; off += 1u64; // 'w'
|
|
||||||
buf2[off] = 119u8; off += 1u64; // 'w'
|
|
||||||
buf2[off] = 0u8;
|
|
||||||
if (os.access(pathstr(buf2.ptr), 0i32) == 0) {
|
|
||||||
*isdir = 0;
|
*isdir = 0;
|
||||||
return buf2.ptr;
|
return buf.ptr;
|
||||||
};
|
};
|
||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Walk a colon-separated dirlist, return first hit or nil. Sets
|
// Walk a colon-separated dirlist, return first hit or nil. Sets
|
||||||
// *isdir on hit.
|
// *isdir on hit.
|
||||||
|
//
|
||||||
|
// #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. 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,
|
fn locateimport(dirs: *u8, name: *u8, namelen: u64,
|
||||||
isdir: *i32) *u8 = {
|
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 p: u64 = 0u64;
|
let wantdir: i32 = 1i32;
|
||||||
for (p < total) {
|
for (wantdir >= 0i32) {
|
||||||
let q: u64 = p;
|
let p: u64 = 0u64;
|
||||||
for (q < total) {
|
for (p < total) {
|
||||||
if (dirs[q] == ':') { break; };
|
let q: u64 = p;
|
||||||
q += 1u64;
|
for (q < total) {
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
let seglen: u64 = q - p;
|
wantdir -= 1i32;
|
||||||
if (seglen > 0u64) {
|
|
||||||
let hit: *u8 = locatein(dirs + p, seglen,
|
|
||||||
pathform, pflen, isdir);
|
|
||||||
if (hit != nil) { return hit; };
|
|
||||||
};
|
|
||||||
p = q + 1u64;
|
|
||||||
};
|
};
|
||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -238,14 +238,14 @@ fn importpathform(name: *u8, namelen: u64) *u8 = {
|
|||||||
return buf.ptr;
|
return buf.ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try <dir>/<path>/ as a directory, then <dir>/<path>.ww as a file.
|
// Try <dir>/<path>/ as a directory (wantdir != 0), else <dir>/<path>.ww
|
||||||
// Sets *isdir on hit. Symmetric with cstage locate_import_in for
|
// as a file. Sets *isdir on hit. Symmetric with cstage locate_import_in
|
||||||
// byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww
|
// for byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww
|
||||||
// form was dropped in task #22 — directory-as-module enumeration
|
// form was dropped in task #22 — directory-as-module enumeration
|
||||||
// replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no
|
// replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no
|
||||||
// `foo/foo.ha` fallback; a module IS the directory).
|
// `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) *u8 = {
|
pathform: *u8, pflen: u64, isdir: *i32, wantdir: i32) *u8 = {
|
||||||
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||||
let off: u64 = 0u64;
|
let off: u64 = 0u64;
|
||||||
let i: u64 = 0u64;
|
let i: u64 = 0u64;
|
||||||
@@ -255,63 +255,71 @@ fn locatein(dir: *u8, dirlen: u64,
|
|||||||
i = 0u64;
|
i = 0u64;
|
||||||
for (i < pflen) { buf[off + i] = pathform[i]; i += 1u64; };
|
for (i < pflen) { buf[off + i] = pathform[i]; i += 1u64; };
|
||||||
off += pflen;
|
off += pflen;
|
||||||
|
if (wantdir != 0i32) {
|
||||||
|
buf[off] = 0u8;
|
||||||
|
let fi: os.filestat;
|
||||||
|
let r: (void | os.oserror) = os.stat(&fi, pathstr(buf.ptr));
|
||||||
|
match (r) {
|
||||||
|
case void => {
|
||||||
|
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
|
||||||
|
if (t == os.mode.DIR: u32) {
|
||||||
|
*isdir = 1;
|
||||||
|
return buf.ptr;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case let e: os.oserror => void;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
buf[off] = 46u8; off += 1u64; // '.'
|
||||||
|
buf[off] = 119u8; off += 1u64; // 'w'
|
||||||
|
buf[off] = 119u8; off += 1u64; // 'w'
|
||||||
buf[off] = 0u8;
|
buf[off] = 0u8;
|
||||||
let fi: os.filestat;
|
if (os.access(pathstr(buf.ptr), 0i32) == 0) {
|
||||||
let r: (void | os.oserror) = os.stat(&fi, pathstr(buf.ptr));
|
|
||||||
let isdirhit: bool = false;
|
|
||||||
match (r) {
|
|
||||||
case void => {
|
|
||||||
let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT
|
|
||||||
if (t == os.mode.DIR: u32) { isdirhit = true; };
|
|
||||||
};
|
|
||||||
case let e: os.oserror => void;
|
|
||||||
};
|
|
||||||
if (isdirhit) {
|
|
||||||
*isdir = 1;
|
|
||||||
return buf.ptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
let buf2: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
|
||||||
off = 0u64;
|
|
||||||
i = 0u64;
|
|
||||||
for (i < dirlen) { buf2[off + i] = dir[i]; i += 1u64; };
|
|
||||||
off += dirlen;
|
|
||||||
buf2[off] = 47u8; off += 1u64;
|
|
||||||
i = 0u64;
|
|
||||||
for (i < pflen) { buf2[off + i] = pathform[i]; i += 1u64; };
|
|
||||||
off += pflen;
|
|
||||||
buf2[off] = 46u8; off += 1u64; // '.'
|
|
||||||
buf2[off] = 119u8; off += 1u64; // 'w'
|
|
||||||
buf2[off] = 119u8; off += 1u64; // 'w'
|
|
||||||
buf2[off] = 0u8;
|
|
||||||
if (os.access(pathstr(buf2.ptr), 0i32) == 0) {
|
|
||||||
*isdir = 0;
|
*isdir = 0;
|
||||||
return buf2.ptr;
|
return buf.ptr;
|
||||||
};
|
};
|
||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Walk a colon-separated dirlist, return first hit or nil. Sets
|
// Walk a colon-separated dirlist, return first hit or nil. Sets
|
||||||
// *isdir on hit.
|
// *isdir on hit.
|
||||||
|
//
|
||||||
|
// #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. 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,
|
fn locateimport(dirs: *u8, name: *u8, namelen: u64,
|
||||||
isdir: *i32) *u8 = {
|
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 p: u64 = 0u64;
|
let wantdir: i32 = 1i32;
|
||||||
for (p < total) {
|
for (wantdir >= 0i32) {
|
||||||
let q: u64 = p;
|
let p: u64 = 0u64;
|
||||||
for (q < total) {
|
for (p < total) {
|
||||||
if (dirs[q] == ':') { break; };
|
let q: u64 = p;
|
||||||
q += 1u64;
|
for (q < total) {
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
let seglen: u64 = q - p;
|
wantdir -= 1i32;
|
||||||
if (seglen > 0u64) {
|
|
||||||
let hit: *u8 = locatein(dirs + p, seglen,
|
|
||||||
pathform, pflen, isdir);
|
|
||||||
if (hit != nil) { return hit; };
|
|
||||||
};
|
|
||||||
p = q + 1u64;
|
|
||||||
};
|
};
|
||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|||||||
236
test/wcc/989_coloimport_sep.c
Normal file
236
test/wcc/989_coloimport_sep.c
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
/*
|
||||||
|
* 989_coloimport_sep — #98 E3 flip-blocker: a co-located `_test` entry must
|
||||||
|
* not shadow the dir-package it imports under `ww test --sep`.
|
||||||
|
*
|
||||||
|
* The driver builds the sep searchpath srcd-first (srcd = the entry file's
|
||||||
|
* own directory). When the entry is a co-located black-box test
|
||||||
|
* `lib/<mod>/<mod>test.ww`, srcd = `lib/<mod>`, so a transitive `import
|
||||||
|
* <mod>` (reached from another package in the closure) hits the sibling
|
||||||
|
* FILE `lib/<mod>/<mod>.ww` (is_dir=0) FIRST — treated as an intra-package
|
||||||
|
* split, folded INLINE into the importer's unit under that importer's
|
||||||
|
* module-reset → w6c rejects `package <mod> does not match import path
|
||||||
|
* <importer>`. locate_import / locateimport now walk the searchpath in
|
||||||
|
* TWO passes — pass 1 seeks a DIRECTORY on any entry, pass 2 (only if no
|
||||||
|
* directory matches anywhere) seeks a FILE — so the dir-package `<mod>/`
|
||||||
|
* on a later entry beats the same-named sibling FILE on the srcd entry,
|
||||||
|
* while a genuine leaf package with no directory still resolves in the
|
||||||
|
* file pass. This gate stands for the 7 lib-run tests
|
||||||
|
* (904/966/967/972/974/976/977) the bug blocked under sep.
|
||||||
|
*
|
||||||
|
* Fixture (test/wcc/data/colo98/, the exact srcd-shadow shape, minimised):
|
||||||
|
* widget/widget.ww package widget — the sibling primary file
|
||||||
|
* that shadows the dir.
|
||||||
|
* widget/widgettest.ww package widget_test, import gadget — the
|
||||||
|
* co-located test ENTRY;
|
||||||
|
* srcd = .../widget.
|
||||||
|
* gadget/gadget.ww package gadget, import widget — the NON-root
|
||||||
|
* importer compiled with -I,
|
||||||
|
* whose self-named `import
|
||||||
|
* widget` triggers the fold.
|
||||||
|
* `-I test/wcc/data/colo98` puts widget's parent on the fallback path,
|
||||||
|
* mirroring how `lib` holds the real `lib/<mod>/` dirs.
|
||||||
|
*
|
||||||
|
* Asserts (both driver stages, COLD per-stage WW_PKGCACHE):
|
||||||
|
* 1. RUN-EXIT 0 — the @test resolves, builds, links, runs. Pre-fix the
|
||||||
|
* non-root importer's fold makes w6c reject → non-zero; this is the
|
||||||
|
* non-vacuity teeth (verified: revert the two-pass walker → reddens
|
||||||
|
* with `package widget does not match import path gadget`).
|
||||||
|
* 2. REGISTERED — widget.unit.ww + widget.wwi appear in .sepwork: the
|
||||||
|
* import resolved to the DIR-package, not the sibling file. Pre-fix
|
||||||
|
* widget is never registered (folds into the importer), so both are
|
||||||
|
* absent — the "right module tag" signal.
|
||||||
|
* 3. cs==ww (rule 10) — per-package .s/.wwi byte-identical across the
|
||||||
|
* cstage `ww` and wwstage `ww_ww` sep-drivers, proving the `locatein`
|
||||||
|
* port is symmetric with `locate_import_in`.
|
||||||
|
*
|
||||||
|
* Light wwstage-driver test (CLAUDE.md rule 14): every intermediate is
|
||||||
|
* `-o`-redirected to /tmp, so it is phase-1 parallel-safe. Models
|
||||||
|
* 989_septest_run conventions; 989 prefix per the sep-gate precedent.
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
static int
|
||||||
|
runwait(const char *cmd)
|
||||||
|
{
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc == -1) return -1;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
absbin(void)
|
||||||
|
{
|
||||||
|
const char *b = getenv("BIN");
|
||||||
|
if (!b) b = "out/bin";
|
||||||
|
if (b[0] == '/') return b;
|
||||||
|
static char buf[2048];
|
||||||
|
char cwd[1024];
|
||||||
|
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
|
||||||
|
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
slurp(const char *path, char **outbuf, size_t *outlen)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
long n = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
if (n < 0) { fclose(f); return -1; }
|
||||||
|
char *b = malloc((size_t)n + 1);
|
||||||
|
if (!b) { fclose(f); return -1; }
|
||||||
|
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
|
||||||
|
b[n] = '\0';
|
||||||
|
fclose(f);
|
||||||
|
*outbuf = b;
|
||||||
|
*outlen = (size_t)n;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
files_eq(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
char *ba = NULL, *bb = NULL;
|
||||||
|
size_t na = 0, nb = 0;
|
||||||
|
if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) {
|
||||||
|
free(ba); free(bb);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int eq = (na == nb && memcmp(ba, bb, na) == 0);
|
||||||
|
free(ba); free(bb);
|
||||||
|
return eq ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cs==ww over a sep build's per-package output: every .s/.wwi the cstage
|
||||||
|
* driver produced in `csdir` must be byte-identical to the wwstage
|
||||||
|
* driver's same-named file in `wwdir`. Returns the count of mismatches. */
|
||||||
|
static int
|
||||||
|
cmp_sepwork(const char *csdir, const char *wwdir, const char *label)
|
||||||
|
{
|
||||||
|
DIR *d = opendir(csdir);
|
||||||
|
if (!d) {
|
||||||
|
fprintf(stderr, "coloimport FAIL: %s — no cs sepwork %s\n", label, csdir);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int bad = 0, seen = 0;
|
||||||
|
struct dirent *ent;
|
||||||
|
while ((ent = readdir(d)) != NULL) {
|
||||||
|
const char *nm = ent->d_name;
|
||||||
|
size_t nl = strlen(nm);
|
||||||
|
int is_s = (nl > 2 && strcmp(nm + nl - 2, ".s") == 0);
|
||||||
|
int is_wwi = (nl > 4 && strcmp(nm + nl - 4, ".wwi") == 0);
|
||||||
|
if (!is_s && !is_wwi) continue;
|
||||||
|
seen++;
|
||||||
|
char a[2048], b[2048];
|
||||||
|
snprintf(a, sizeof a, "%s/%s", csdir, nm);
|
||||||
|
snprintf(b, sizeof b, "%s/%s", wwdir, nm);
|
||||||
|
if (files_eq(a, b) != 0) {
|
||||||
|
fprintf(stderr, "coloimport FAIL: %s — cs!=ww for %s (rule 10)\n",
|
||||||
|
label, nm);
|
||||||
|
bad++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
if (seen == 0) {
|
||||||
|
fprintf(stderr, "coloimport FAIL: %s — no .s/.wwi in %s\n", label, csdir);
|
||||||
|
bad++;
|
||||||
|
}
|
||||||
|
return bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tcase {
|
||||||
|
const char *label;
|
||||||
|
const char *entry; /* the co-located `_test` entry built under --sep */
|
||||||
|
const char *incdir; /* -I dir (parent that holds the dir-package) */
|
||||||
|
const char *regpkg; /* the dir-package that must register its own unit */
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct tcase cases[] = {
|
||||||
|
/* the transitive self-named-import shadow — the exact lib-run shape. */
|
||||||
|
{ "widget", "test/wcc/data/colo98/widget/widgettest.ww",
|
||||||
|
"test/wcc/data/colo98", "widget" },
|
||||||
|
{ NULL, NULL, NULL, NULL },
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
const char *bin = absbin();
|
||||||
|
if (!bin) return 1;
|
||||||
|
|
||||||
|
char td[64], cmd[8192];
|
||||||
|
int fail = 0;
|
||||||
|
snprintf(td, sizeof td, "/tmp/wwcoloimport_%d", getpid());
|
||||||
|
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||||
|
runwait(cmd);
|
||||||
|
mkdir(td, 0755);
|
||||||
|
|
||||||
|
for (int i = 0; cases[i].label; i++) {
|
||||||
|
struct tcase *t = &cases[i];
|
||||||
|
|
||||||
|
struct { const char *drv, *tag; int rc; }
|
||||||
|
stg[] = { { "ww", "cs", -1 }, { "ww_ww", "ww", -1 } };
|
||||||
|
for (int s = 0; s < 2; s++) {
|
||||||
|
char prog[1024];
|
||||||
|
snprintf(prog, sizeof prog, "%s/%s.%s.bin", td, t->label, stg[s].tag);
|
||||||
|
/* COLD per-(case,stage) cache: every package compiles fresh so
|
||||||
|
* the per-pkg .s/.wwi this gate inspects are produced. */
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"WW_PKGCACHE='%s/cache.%s.%s' %s/%s test --sep -I %s -o %s %s "
|
||||||
|
">/dev/null 2>&1",
|
||||||
|
td, t->label, stg[s].tag, bin, stg[s].drv, t->incdir, prog,
|
||||||
|
t->entry);
|
||||||
|
stg[s].rc = runwait(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. RUN-EXIT 0 on BOTH stages (pre-fix: w6c rejects the importer). */
|
||||||
|
if (stg[0].rc != 0 || stg[1].rc != 0) {
|
||||||
|
fprintf(stderr, "coloimport FAIL: %s exits cs=%d ww=%d (expected 0)\n",
|
||||||
|
t->label, stg[0].rc, stg[1].rc);
|
||||||
|
fail++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char csdir[1024], wwdir[1024];
|
||||||
|
snprintf(csdir, sizeof csdir, "%s/%s.cs.bin.sepwork", td, t->label);
|
||||||
|
snprintf(wwdir, sizeof wwdir, "%s/%s.ww.bin.sepwork", td, t->label);
|
||||||
|
|
||||||
|
/* 2. REGISTERED: the dir-package owns its unit + interface (pre-fix
|
||||||
|
* it folds into the importer and these are absent). */
|
||||||
|
char u[2048], w[2048];
|
||||||
|
snprintf(u, sizeof u, "%s/%s.unit.ww", csdir, t->regpkg);
|
||||||
|
snprintf(w, sizeof w, "%s/%s.wwi", csdir, t->regpkg);
|
||||||
|
if (access(u, 0) != 0) {
|
||||||
|
fprintf(stderr, "coloimport FAIL: %s — %s missing (import folded "
|
||||||
|
"to sibling file, not the dir-package)\n", t->label, u);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
if (access(w, 0) != 0) {
|
||||||
|
fprintf(stderr, "coloimport FAIL: %s — %s missing\n", t->label, w);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. cs==ww (rule 10): per-package .s/.wwi byte-identical. */
|
||||||
|
fail += cmp_sepwork(csdir, wwdir, t->label);
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||||
|
runwait(cmd);
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr, "coloimport: %d check(s) failed\n", fail);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("coloimport: co-located `_test` entry resolves `import <mod>` to the "
|
||||||
|
"dir-package (not the sibling file) under `ww test --sep` on both driver "
|
||||||
|
"stages + cs==ww per-pkg .s/.wwi\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
7
test/wcc/data/colo98/gadget/gadget.ww
Normal file
7
test/wcc/data/colo98/gadget/gadget.ww
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package gadget;
|
||||||
|
|
||||||
|
import widget;
|
||||||
|
|
||||||
|
export fn frob(x: i32) i32 = {
|
||||||
|
return widget.bump(x) + 1;
|
||||||
|
};
|
||||||
5
test/wcc/data/colo98/widget/widget.ww
Normal file
5
test/wcc/data/colo98/widget/widget.ww
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package widget;
|
||||||
|
|
||||||
|
export fn bump(x: i32) i32 = {
|
||||||
|
return x + 1;
|
||||||
|
};
|
||||||
7
test/wcc/data/colo98/widget/widgettest.ww
Normal file
7
test/wcc/data/colo98/widget/widgettest.ww
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package widget_test;
|
||||||
|
|
||||||
|
import gadget;
|
||||||
|
|
||||||
|
@test fn t_frob() void = {
|
||||||
|
if (gadget.frob(40) != 42) { abort(); };
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user