wcc/ww: loud dep_cycle reject + #31 dup-symbol gate (M3-tail c4, #46)

Promote commit-3's tri-color topo bail into a loud dep_cycle error that names the full import cycle, byte-identical both stages (cmd/ww/main.c + selfhost/cmd/ww/main.ww, deps.ha:243 parity).

Add a non-vacuous negative gate (989_sepcycle_dup) proving the pre-existing w6l duplicate-symbol reject fires loud + non-zero on a cross-package collision; no new linker code.

#58(b)(c) link-arg parity deferred (system()-string vs procrun()-argv is structurally un-unifiable in this scope); #61 filed for the byte-id-blind w6l_ww dup-message divergence.
This commit is contained in:
2026-06-16 13:30:45 +09:00
parent e37886b27d
commit 9cceb4ca8d
5 changed files with 400 additions and 21 deletions

View File

@@ -740,21 +740,31 @@ sep_scan_pkg(struct sepgraph *g, int pi, const char *searchpath)
}
/* DFS post-order over the dep DAG → reverse-topo (deps before importer),
* cite Hare gather (deps.ha:123). Tri-color: a back-edge BAILS rather
* than spinning. Commit 3 assumes acyclic; the LOUD cycle reject is
* commit 4 (this just must not infinite-loop). */
* cite Hare gather (deps.ha:123). Tri-color: a gray back-edge is a loud
* dep-cycle reject naming the chain (Hare deps.ha:243); `stack[0..depth)`
* is the live DFS path, so the cycle runs from pi's first occurrence on
* it to the top, closing back on pi. */
static int
sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no)
sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no,
int *stack, int depth)
{
if (g->pkg[pi].color == 2) return 0;
if (g->pkg[pi].color == 1) {
fprintf(stderr,
"ww --sep: import cycle (loud reject lands commit 4)\n");
int j = 0;
while (j < depth && stack[j] != pi) j++;
fprintf(stderr, "ww --sep: dependency cycle: ");
for (int s = j; s < depth; s++)
fprintf(stderr, "%s -> ", g->pkg[stack[s]].path[0]
? g->pkg[stack[s]].path : "(root)");
fprintf(stderr, "%s\n",
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
return -1;
}
g->pkg[pi].color = 1;
stack[depth] = pi;
for (int k = 0; k < g->pkg[pi].ndeps; k++)
if (sep_topo_visit(g, g->pkg[pi].deps[k], order, no) < 0)
if (sep_topo_visit(g, g->pkg[pi].deps[k], order, no,
stack, depth + 1) < 0)
return -1;
g->pkg[pi].color = 2;
order[(*no)++] = pi;
@@ -951,10 +961,13 @@ build_one_sep(const char *src, int entry_is_dir, const char *out,
if (root < 0 || sep_scan_pkg(g, root, srcdir) < 0) { free(g); return 1; }
for (int i = 0; i < g->n; i++) g->pkg[i].color = 0;
int *order = calloc((size_t)g->n, sizeof *order);
int *stack = calloc((size_t)g->n, sizeof *stack);
int norder = 0;
if (order == NULL || sep_topo_visit(g, root, order, &norder) < 0) {
free(order); free(g); return 1;
if (order == NULL || stack == NULL ||
sep_topo_visit(g, root, order, &norder, stack, 0) < 0) {
free(stack); free(order); free(g); return 1;
}
free(stack);
/* producer loop — dep-first, one `w6c -c -I` pass per package. */
for (int oi = 0; oi < norder; oi++) {