selfhost+lib/ww: N_TPARAM wrapper for tuple chains (A.6.2.0b-pre)

A.6.2.0b worker hit a real shared-`.next`-aliasing bug and stopped
per rule 7. Wwstage's N_TTUPLE chained element type ASTs via the
nodes' own `.next` field. `exprtype` routinely returns shared
nodes (sym.decl.lhs, struct field's `.lhs`, another N_TTUPLE's
`.list` element). Naive chain construction in the checker
corrupts source ASTs.

Introduce N_TPARAM = 67 as a chain wrapper for N_TTUPLE.list:

  - `.lhs` holds the (possibly-shared) element type AST.
  - `.next` chains within the parent N_TTUPLE.
  - Other fields unused; never appears outside N_TTUPLE.list.

Mirrors cstage's Tparam at cmd/wcc/check.c:1437-1451. Cstage
keeps it at the Type layer; wwstage has no separate type layer
for tuple chains so the wrapper sits at the AST. Hare's design
intent at ref/hare/hare/ast/type.ha:117 uses `[]*_type` slice-of-
pointer — same principle, slice-flavored.

Migrations:
  - lib/ww/ast.ww: kind + nkname + pr() unwrap (transparent for
    the 990 -a astprint byte-diff).
  - lib/ww/parse/parse.ww: parsetype N_TTUPLE construction wraps
    each element in N_TPARAM (sole construction site).
  - selfhost/cmd/wcc/check.ww: 4 readers (astalign, astsize,
    tinfofornode TY_TUPLE, exprtype N_DOT-tuple-positional). The
    last change retires the latent A.6.1.5b shared-`p` return.
  - selfhost/cmd/wcc/cgenutil.ww: slotsize TY_TUPLE arm.
  - selfhost/cmd/wcc/cgenexpr.ww: cgdot tuple-positional
    (size/load op + str-check).
  - selfhost/cmd/wcc/cgenstmt.ww: cglet TTUPLE init, cgmlet
    call-return walk, cgforrange elem-size + bind-walk.

Out of scope: N_TFN params, N_TTAGGED variants, N_TSTRUCT fields.
N_TFIELD already wraps struct fields; N_TFN/N_TTAGGED aren't
currently chain-mutated by checker synthesis. If they ever are,
the same pattern applies.

Unblocks A.6.2.0b stamp on a clean foundation. Retires task #16.

Verified 132/132 incl. 990 AST byte-diff (astprint unwrap) + 995
self-rebuild byte-identity.
This commit is contained in:
2026-05-21 20:55:40 +09:00
parent c564d7d0fd
commit 805c841f34
8 changed files with 216 additions and 75 deletions

View File

@@ -98,7 +98,20 @@ type nkind = enum i32 {
N_TENUM = 65,
N_TENUMMEMBER = 66,
N_LAST = 67,
// N_TPARAM — chain wrapper for N_TTUPLE.list elements. Mirror of
// cstage's Tparam (cmd/wcc/check.c:1437-1451) lifted to the AST so
// `exprtype` can return shared element-type nodes (sym.decl.lhs,
// struct field's .lhs, another N_TTUPLE's .list element) without
// corrupting source ASTs by reusing their .next. .lhs holds the
// element type AST (possibly shared); .next chains within the
// parent N_TTUPLE.list. Cstage keeps Tparam at the Type-layer; ww
// has no separate type layer for tuple chains, so the wrapper sits
// at the AST layer. Other node fields are unused. Never appears
// outside an N_TTUPLE.list; astprint unwraps transparently to keep
// the 990 -a byte-diff against cstage.
N_TPARAM = 67,
N_LAST = 68,
};
// ---- Node -------------------------------------------------------------
@@ -204,6 +217,7 @@ fn nkname(k: nkind) str = {
if (k == nkind.N_YIELD) { return "yield"; };
if (k == nkind.N_TENUM) { return "tenum"; };
if (k == nkind.N_TENUMMEMBER) { return "tenummember"; };
if (k == nkind.N_TPARAM) { return "tparam"; };
if (k == nkind.N_LAST) { return "last"; };
return "?";
};
@@ -262,6 +276,14 @@ fn pr(fd: i32, n: *node, d: i32) void = {
os.write(fd, "()\n".ptr, 3u64);
return;
};
// N_TPARAM wraps an N_TTUPLE.list element so exprtype can return
// shared element-type nodes without corrupting their .next chain.
// Cstage has no AST-level wrapper, so unwrap here to keep the 990
// -a byte-diff with cstage's astprint.
if (n.kind == nkind.N_TPARAM) {
pr(fd, n.lhs, d);
return;
};
ind(fd, d);
putc1(fd, 40u8); // '('
let nm: str = nkname(n.kind);

View File

@@ -299,13 +299,23 @@ fn parsetype(p: *parser) *node = {
expecttok(p, tkind.TK_RPAREN, "expected ')' after parenthesised type");
return first;
};
// Wrap each element in N_TPARAM so the chain owns its .next.
// Mirrors cstage's Tparam (cmd/wcc/check.c:1437-1451); lifted
// to the AST layer here because wwstage has no separate type
// layer, and exprtype must return shared element-type nodes
// (sym.decl.lhs, struct field's .lhs, another N_TTUPLE element)
// without corrupting source ASTs.
let n: *node = newnode(nkind.N_TTUPLE, pf, pl, pc);
let head: *node = first;
let tail: *node = first;
let firstwrap: *node = newnode(nkind.N_TPARAM, pf, pl, pc);
firstwrap.lhs = first;
let head: *node = firstwrap;
let tail: *node = firstwrap;
for (true) {
let e: *node = parsetype(p);
tail.next = e;
tail = e;
let w: *node = newnode(nkind.N_TPARAM, e.file, e.line, e.col);
w.lhs = e;
tail.next = w;
tail = w;
if (!accepttok(p, tkind.TK_COMMA)) { break; };
if (p.curkind == tkind.TK_RPAREN) { break; };
};