ww: loud fatal on unresolvable import, inline-package aware (#16 ENFORCE-driver)

Both driver twins: an import that neither locates as a file nor is
satisfied by an inline 'package <name>' declaration in the unit is now
fatal "ww: cannot find package <name>" (was a silent skip that masked
dead imports and typos). The inline scan is a new every-line helper on
the uncapped comment-skip core - peekpackage stops at the first decl,
and single-file multi-package fixtures declare several. 949 pins both
branches (miss->fatal, inline->build+run); 993 adds ww_ww parity.
This commit is contained in:
2026-06-10 15:19:10 +09:00
parent 6f781f3d71
commit 4ff79bff0f
6 changed files with 399 additions and 3 deletions

View File

@@ -230,6 +230,40 @@ peek_package(const char *path, char *out, size_t outsz)
return found;
}
/* unit_has_package — does `path` declare `package <leaf>;` ANYWHERE?
* #16 ENFORCE-driver (rob A): distinguishes a genuinely-missing import
* from one satisfied by an INLINE package in the same unit. Unlike
* peek_package (stops at the FIRST package decl), this scans every line
* — single-file multi-package fixtures carry several `package` decls. The
* comment-skip line scan + name match mirror peek_package, uncapped. The
* wwstage twin unithaspackage must stay byte-identical (rule 10). */
static int
unit_has_package(const char *path, const char *leaf)
{
FILE *in = fopen(path, "rb");
if (in == NULL) return 0;
char line[2048];
int found = 0;
while (fgets(line, sizeof line, in)) {
const char *p = line;
while (*p == ' ' || *p == '\t') p++;
if (p[0] == '/' && p[1] == '/') continue;
if (strncmp(p, "package ", 8) != 0
&& strncmp(p, "package\t", 8) != 0) continue;
p += 8;
while (*p == ' ' || *p == '\t') p++;
size_t i = 0;
while (leaf[i] != '\0' && leaf[i] == p[i]) i++;
if (leaf[i] == '\0') {
char c = p[i];
if (c == ';' || c == ' ' || c == '\t'
|| c == '\n' || c == '\0') { found = 1; break; }
}
}
fclose(in);
return found;
}
/* expand_dir — enumerate <dirpath>/*.ww (skip *test.ww), byte-sort,
* recurse into each. Mirrors ref/hare/hare/module/srcs.ha:183
* `_findsrcs` minus tag handling. The visited set still keys on
@@ -299,9 +333,24 @@ expand(FILE *out, const char *path, struct ImportSet *visited,
import_path_form(name, path_form, sizeof path_form);
char ipath[1024];
int is_dir = 0;
/* #16 ENFORCE-driver (rob A): an unresolvable import is a hard
* error, not a silent skip — the old `continue` let a typo'd/
* missing package drop its symbols and surface later as a
* confusing downstream failure. BUT a locate-miss is legal when
* the package is defined INLINE in the same unit (single-file
* multi-package: `package aa; ... package main; import aa;`) —
* the bundler can't pull it as a file but the checker binds it.
* So: miss + inline `package <leaf>` present -> silent skip;
* miss + not inline -> fatal. */
if (!locate_import(libdir, path_form, ipath, sizeof ipath,
&is_dir))
continue; /* silently skip if not found */
&is_dir)) {
const char *dot = strrchr(name, '.');
const char *leaf = dot ? dot + 1 : name;
if (unit_has_package(path, leaf))
continue; /* inline-satisfied */
fprintf(stderr, "ww: cannot find package %s\n", name);
exit(1);
}
if (is_dir) expand_dir(out, ipath, visited, libdir);
else expand(out, ipath, visited, libdir);
}