selfhost/cmd/wcc: stamp e.type_ for N_TUPLE (A.6.2.0b)

Port head-only of cstage cmd/wcc/check.c:1437-1451 to exprtype's
new N_TUPLE arm. The arm walks e.list, types each element via
recursive exprtype, and assembles an N_TTUPLE whose .list chains
N_TPARAM wrappers (one per element) so shared element-type ASTs
(sym.decl.lhs, struct field's .lhs, another tuple's element) keep
their own .next untouched.

Foundation: A.6.2.0b-pre (805c841) introduced N_TPARAM as the
chain wrapper for N_TTUPLE.list, mirroring cstage's Tparam at the
AST layer (cstage keeps it at the Type layer; wwstage has no
separate type layer). This commit is the first checker-side
synthesizer to use it.

One documented divergence: empty list returns nil under the
lenient policy (cstage would synthesize empty TY_TUPLE; ww grammar
requires >= 2 elements per parse/expr.ww:117, so empty is
unreachable either way). Same shape as scruttype L656 / A.6.1.5b
N_DOT lenient-on-miss precedent.

α scope per Drew (no hint plumbing, no field-level walks, no
unify check). Hint param stays unused.

A.6.2 step 2 of 8 (γ order). N_ALLOC next (most likely a
parser-vestigial kind — A.6.2.0c worker audits first).

`make test-unit` green; full `make test` deferred to the batched
gate after the A.6.2.1 assertion commit (option B per user).
This commit is contained in:
2026-05-21 21:01:57 +09:00
parent 805c841f34
commit 1131e7b68d
3 changed files with 99 additions and 0 deletions

View File

@@ -8841,6 +8841,39 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
};
return nil;
};
if (k == nkind.N_TUPLE) {
// A.6.2.0b — head-only stamp of the tuple expression's
// overall type. Mirrors cstage cmd/wcc/check.c:1437-1451
// N_TUPLE: walk e.list, type each element via exprtype, and
// assemble an N_TTUPLE whose .list chains N_TPARAM wrappers
// (one per element) so shared element-type ASTs (sym.decl.lhs,
// another tuple's element, struct field's .lhs) keep their
// own .next untouched — see lib/ww/ast.ww:101 and the
// A.6.2.0b-pre parser precedent at lib/ww/parse/parse.ww:302.
// Per-element exprtype recursion is tinfocache-idempotent
// (resolvewalk L460-489 already dispatches into N_TUPLE
// children). Lenient on empty list: grammar requires >= 2
// elements (lib/ww/parse/expr.ww:117 single-elem returns the
// expression), so empty is unreachable and yields nil here
// (matches scruttype L656 / A.6.1.5b N_DOT lenient-on-miss).
if (e.list == nil) { return nil; };
let head: *node = nil;
let tail: *node = nil;
let it: *node = e.list;
for (it != nil) {
let elemt: *node = exprtype(c, it, nil);
let w: *node = newnode(nkind.N_TPARAM, "", 0, 0);
w.lhs = elemt;
if (head == nil) { head = w; }
else { tail.next = w; };
tail = w;
it = it.next;
};
let tt: *node = newnode(nkind.N_TTUPLE, "", 0, 0);
tt.list = head;
e.type_ = tinfofornode(c, tt): *void;
return tt;
};
if (k == nkind.N_TRYPROP) {
// success unwrap: the success-variant type of operand's
// tagged union.

View File

@@ -1877,6 +1877,39 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
};
return nil;
};
if (k == nkind.N_TUPLE) {
// A.6.2.0b — head-only stamp of the tuple expression's
// overall type. Mirrors cstage cmd/wcc/check.c:1437-1451
// N_TUPLE: walk e.list, type each element via exprtype, and
// assemble an N_TTUPLE whose .list chains N_TPARAM wrappers
// (one per element) so shared element-type ASTs (sym.decl.lhs,
// another tuple's element, struct field's .lhs) keep their
// own .next untouched — see lib/ww/ast.ww:101 and the
// A.6.2.0b-pre parser precedent at lib/ww/parse/parse.ww:302.
// Per-element exprtype recursion is tinfocache-idempotent
// (resolvewalk L460-489 already dispatches into N_TUPLE
// children). Lenient on empty list: grammar requires >= 2
// elements (lib/ww/parse/expr.ww:117 single-elem returns the
// expression), so empty is unreachable and yields nil here
// (matches scruttype L656 / A.6.1.5b N_DOT lenient-on-miss).
if (e.list == nil) { return nil; };
let head: *node = nil;
let tail: *node = nil;
let it: *node = e.list;
for (it != nil) {
let elemt: *node = exprtype(c, it, nil);
let w: *node = newnode(nkind.N_TPARAM, "", 0, 0);
w.lhs = elemt;
if (head == nil) { head = w; }
else { tail.next = w; };
tail = w;
it = it.next;
};
let tt: *node = newnode(nkind.N_TTUPLE, "", 0, 0);
tt.list = head;
e.type_ = tinfofornode(c, tt): *void;
return tt;
};
if (k == nkind.N_TRYPROP) {
// success unwrap: the success-variant type of operand's
// tagged union.

View File

@@ -8841,6 +8841,39 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
};
return nil;
};
if (k == nkind.N_TUPLE) {
// A.6.2.0b — head-only stamp of the tuple expression's
// overall type. Mirrors cstage cmd/wcc/check.c:1437-1451
// N_TUPLE: walk e.list, type each element via exprtype, and
// assemble an N_TTUPLE whose .list chains N_TPARAM wrappers
// (one per element) so shared element-type ASTs (sym.decl.lhs,
// another tuple's element, struct field's .lhs) keep their
// own .next untouched — see lib/ww/ast.ww:101 and the
// A.6.2.0b-pre parser precedent at lib/ww/parse/parse.ww:302.
// Per-element exprtype recursion is tinfocache-idempotent
// (resolvewalk L460-489 already dispatches into N_TUPLE
// children). Lenient on empty list: grammar requires >= 2
// elements (lib/ww/parse/expr.ww:117 single-elem returns the
// expression), so empty is unreachable and yields nil here
// (matches scruttype L656 / A.6.1.5b N_DOT lenient-on-miss).
if (e.list == nil) { return nil; };
let head: *node = nil;
let tail: *node = nil;
let it: *node = e.list;
for (it != nil) {
let elemt: *node = exprtype(c, it, nil);
let w: *node = newnode(nkind.N_TPARAM, "", 0, 0);
w.lhs = elemt;
if (head == nil) { head = w; }
else { tail.next = w; };
tail = w;
it = it.next;
};
let tt: *node = newnode(nkind.N_TTUPLE, "", 0, 0);
tt.list = head;
e.type_ = tinfofornode(c, tt): *void;
return tt;
};
if (k == nkind.N_TRYPROP) {
// success unwrap: the success-variant type of operand's
// tagged union.