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

@@ -3905,19 +3905,41 @@ fn sepscanpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
return 0;
};
// Print one cycle-chain node: a package path, or "(root)" for the
// empty root path.
fn sepcyclenode(p: *u8) void = {
if (p[0] == 0u8) { cerr("(root)"); } else { cerr(pathstr(p)); };
};
// DFS post-order over the dep DAG → reverse-topo (deps before importer).
// Tri-color: a back-edge BAILS rather than spinning (loud cycle reject is
// commit 4). Cite Hare gather (deps.ha:123).
fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32) i32 = {
// 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 on pi.
// Cite Hare gather (deps.ha:123).
fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32,
stack: []i32, depth: i32) i32 = {
if (g.pkg[pi].color == 2) { return 0; };
if (g.pkg[pi].color == 1) {
cerr("ww --sep: import cycle (loud reject lands commit 4)\n");
let j: i32 = 0;
for (j < depth && stack[j] != pi) { j += 1; };
cerr("ww --sep: dependency cycle: ");
let s: i32 = j;
for (s < depth) {
sepcyclenode(g.pkg[stack[s]].path);
cerr(" -> ");
s += 1;
};
sepcyclenode(g.pkg[pi].path);
cerr("\n");
return -1;
};
g.pkg[pi].color = 1;
stack[depth] = pi;
let k: i32 = 0;
for (k < g.pkg[pi].ndeps) {
if (septopovisit(g, g.pkg[pi].deps[k], order, no) < 0) { return -1; };
if (septopovisit(g, g.pkg[pi].deps[k], order, no, stack, depth + 1) < 0) {
return -1;
};
k += 1;
};
g.pkg[pi].color = 2;
@@ -4164,8 +4186,10 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
let order: []i32 = alloc([], g.n: u64)!;
order.len = g.n;
let stack: []i32 = alloc([], g.n: u64)!;
stack.len = g.n;
let norder: i32 = 0;
if (septopovisit(g, root, order, &norder) < 0) { return 1; };
if (septopovisit(g, root, order, &norder, stack, 0) < 0) { return 1; };
// Producer loop — dep-first, one `w6c -c -I` per package.
let oi: i32 = 0;

View File

@@ -1031,19 +1031,41 @@ fn sepscanpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
return 0;
};
// Print one cycle-chain node: a package path, or "(root)" for the
// empty root path.
fn sepcyclenode(p: *u8) void = {
if (p[0] == 0u8) { cerr("(root)"); } else { cerr(pathstr(p)); };
};
// DFS post-order over the dep DAG → reverse-topo (deps before importer).
// Tri-color: a back-edge BAILS rather than spinning (loud cycle reject is
// commit 4). Cite Hare gather (deps.ha:123).
fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32) i32 = {
// 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 on pi.
// Cite Hare gather (deps.ha:123).
fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32,
stack: []i32, depth: i32) i32 = {
if (g.pkg[pi].color == 2) { return 0; };
if (g.pkg[pi].color == 1) {
cerr("ww --sep: import cycle (loud reject lands commit 4)\n");
let j: i32 = 0;
for (j < depth && stack[j] != pi) { j += 1; };
cerr("ww --sep: dependency cycle: ");
let s: i32 = j;
for (s < depth) {
sepcyclenode(g.pkg[stack[s]].path);
cerr(" -> ");
s += 1;
};
sepcyclenode(g.pkg[pi].path);
cerr("\n");
return -1;
};
g.pkg[pi].color = 1;
stack[depth] = pi;
let k: i32 = 0;
for (k < g.pkg[pi].ndeps) {
if (septopovisit(g, g.pkg[pi].deps[k], order, no) < 0) { return -1; };
if (septopovisit(g, g.pkg[pi].deps[k], order, no, stack, depth + 1) < 0) {
return -1;
};
k += 1;
};
g.pkg[pi].color = 2;
@@ -1290,8 +1312,10 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
let order: []i32 = alloc([], g.n: u64)!;
order.len = g.n;
let stack: []i32 = alloc([], g.n: u64)!;
stack.len = g.n;
let norder: i32 = 0;
if (septopovisit(g, root, order, &norder) < 0) { return 1; };
if (septopovisit(g, root, order, &norder, stack, 0) < 0) { return 1; };
// Producer loop — dep-first, one `w6c -c -I` per package.
let oi: i32 = 0;