selfhost/cmd/wcc: stamp e.type_ for N_MATCH + matchyieldtype port (A.6.2.0g)
Port cstage's match_yield_type walker (cmd/wcc/check.c:110-135) as `matchyieldtype` (Plan-9-cased) — recursive arm-body walker that finds the first reachable yield's operand type, descends N_BLOCK / N_IF / N_FOR / N_FORRANGE, and stops at nested N_MATCH (each match opens its own yield scope). Sole structural divergence from cstage: where cstage reads `body->lhs->type`, wwstage calls `exprtype(c, body.lhs, nil)` — wwstage's AST is untyped at parse time and the type lives in the tinfocache; exprtype is the canonical reader (tinfocache-idempotent per L467). Port cstage's N_MATCH match-as-expression stamp (check.c:1316- 1330) into a new exprtype arm. Walks the first non-nil arm yield via matchyieldtype, defaults to void if no arm yields. Closes the consumer half of the match-as-expression contract that A.6.2.0f opened on the producer side (N_YIELD). One documented divergence from cstage (lenient, intentional): the arm-yield-unification check (cstage L1322-1327) is skipped. That's a checker-correctness concern; this arm only stamps. Cstage's `match_yield_type` IS a helper there too — porting it is structural fidelity per rule 10, not a new helper invention under rule 9. β scope per Drew (2026-05-21): α (stamp void unconditionally) would bury a latent miscompile that A.6.2.1 assertion can't catch (it sees nil, not wrong). Hare's `match_expr` AST has no type field (ref/hare/hare/ast/expr.ha:341-348); harec/cstage unify arms at check time — same architecture wwstage mirrors. A.6.2 step 7 of 8 (γ order). The assertion closer (#15) is next, which lands the invariant on a green tree. `make test-unit` green; full `make test` batched per option B.
This commit is contained in:
@@ -7785,6 +7785,40 @@ fn astsize(c: *checker, t: *node) i64 = {
|
||||
return 0i64;
|
||||
};
|
||||
|
||||
// matchyieldtype — port of cstage cmd/wcc/check.c:110-135. Walks a
|
||||
// match arm body for the first `yield expr;` and returns its operand
|
||||
// type. Returns nil if no yield is reachable from `body`. Doesn't
|
||||
// descend into a nested N_MATCH — each match opens its own yield
|
||||
// scope. exprtype is idempotent on already-stamped nodes (tinfocache
|
||||
// path at L467) so re-entering it on the yield operand here is safe.
|
||||
fn matchyieldtype(c: *checker, body: *node) *node = {
|
||||
if (body == nil) { return nil; };
|
||||
let k: nkind = body.kind;
|
||||
if (k == nkind.N_YIELD) {
|
||||
if (body.lhs == nil) { return nil; };
|
||||
return exprtype(c, body.lhs, nil);
|
||||
};
|
||||
if (k == nkind.N_MATCH) { return nil; };
|
||||
if (k == nkind.N_BLOCK) {
|
||||
let s: *node = body.list;
|
||||
for (s != nil) {
|
||||
let t: *node = matchyieldtype(c, s);
|
||||
if (t != nil) { return t; };
|
||||
s = s.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
if (k == nkind.N_IF) {
|
||||
let t: *node = matchyieldtype(c, body.body);
|
||||
if (t != nil) { return t; };
|
||||
return matchyieldtype(c, body.els);
|
||||
};
|
||||
if (k == nkind.N_FOR || k == nkind.N_FORRANGE) {
|
||||
return matchyieldtype(c, body.body);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// astoffset — byte offset of `dot.str` inside the struct type of
|
||||
// `dot.lhs`. Mirrors cstage cmd/wcc/check.c:932-961: peel one N_TPTR
|
||||
// (for `p.field` where p is *Struct), require N_TSTRUCT, walk fields
|
||||
@@ -8897,6 +8931,27 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (t != nil) { e.type_ = tinfofornode(c, t): *void; };
|
||||
return t;
|
||||
};
|
||||
if (k == nkind.N_MATCH) {
|
||||
// A.6.2.0g — port of cstage cmd/wcc/check.c:1316-1330 match-as-
|
||||
// expression stamp. The match's type is the first arm's yield
|
||||
// operand type; void if no arm yields. Wwstage skips cstage's
|
||||
// arm-yield-unification check (L1322-1327) — that's a checker
|
||||
// concern, this arm only stamps. Closes the consumer half of
|
||||
// the match-as-expression contract that A.6.2.0f opened on the
|
||||
// producer side (N_YIELD).
|
||||
let yt: *node = nil;
|
||||
let cs: *node = e.list;
|
||||
for (cs != nil) {
|
||||
let t: *node = matchyieldtype(c, cs.body);
|
||||
if (t != nil) { yt = t; break; };
|
||||
cs = cs.next;
|
||||
};
|
||||
if (yt == nil) {
|
||||
yt = mktname(c, "void");
|
||||
};
|
||||
e.type_ = tinfofornode(c, yt): *void;
|
||||
return yt;
|
||||
};
|
||||
if (k == nkind.N_YIELD) {
|
||||
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
||||
// stamp N_YIELD (statement-shaped). Wwstage's A.6.2 invariant
|
||||
|
||||
@@ -821,6 +821,40 @@ fn astsize(c: *checker, t: *node) i64 = {
|
||||
return 0i64;
|
||||
};
|
||||
|
||||
// matchyieldtype — port of cstage cmd/wcc/check.c:110-135. Walks a
|
||||
// match arm body for the first `yield expr;` and returns its operand
|
||||
// type. Returns nil if no yield is reachable from `body`. Doesn't
|
||||
// descend into a nested N_MATCH — each match opens its own yield
|
||||
// scope. exprtype is idempotent on already-stamped nodes (tinfocache
|
||||
// path at L467) so re-entering it on the yield operand here is safe.
|
||||
fn matchyieldtype(c: *checker, body: *node) *node = {
|
||||
if (body == nil) { return nil; };
|
||||
let k: nkind = body.kind;
|
||||
if (k == nkind.N_YIELD) {
|
||||
if (body.lhs == nil) { return nil; };
|
||||
return exprtype(c, body.lhs, nil);
|
||||
};
|
||||
if (k == nkind.N_MATCH) { return nil; };
|
||||
if (k == nkind.N_BLOCK) {
|
||||
let s: *node = body.list;
|
||||
for (s != nil) {
|
||||
let t: *node = matchyieldtype(c, s);
|
||||
if (t != nil) { return t; };
|
||||
s = s.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
if (k == nkind.N_IF) {
|
||||
let t: *node = matchyieldtype(c, body.body);
|
||||
if (t != nil) { return t; };
|
||||
return matchyieldtype(c, body.els);
|
||||
};
|
||||
if (k == nkind.N_FOR || k == nkind.N_FORRANGE) {
|
||||
return matchyieldtype(c, body.body);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// astoffset — byte offset of `dot.str` inside the struct type of
|
||||
// `dot.lhs`. Mirrors cstage cmd/wcc/check.c:932-961: peel one N_TPTR
|
||||
// (for `p.field` where p is *Struct), require N_TSTRUCT, walk fields
|
||||
@@ -1933,6 +1967,27 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (t != nil) { e.type_ = tinfofornode(c, t): *void; };
|
||||
return t;
|
||||
};
|
||||
if (k == nkind.N_MATCH) {
|
||||
// A.6.2.0g — port of cstage cmd/wcc/check.c:1316-1330 match-as-
|
||||
// expression stamp. The match's type is the first arm's yield
|
||||
// operand type; void if no arm yields. Wwstage skips cstage's
|
||||
// arm-yield-unification check (L1322-1327) — that's a checker
|
||||
// concern, this arm only stamps. Closes the consumer half of
|
||||
// the match-as-expression contract that A.6.2.0f opened on the
|
||||
// producer side (N_YIELD).
|
||||
let yt: *node = nil;
|
||||
let cs: *node = e.list;
|
||||
for (cs != nil) {
|
||||
let t: *node = matchyieldtype(c, cs.body);
|
||||
if (t != nil) { yt = t; break; };
|
||||
cs = cs.next;
|
||||
};
|
||||
if (yt == nil) {
|
||||
yt = mktname(c, "void");
|
||||
};
|
||||
e.type_ = tinfofornode(c, yt): *void;
|
||||
return yt;
|
||||
};
|
||||
if (k == nkind.N_YIELD) {
|
||||
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
||||
// stamp N_YIELD (statement-shaped). Wwstage's A.6.2 invariant
|
||||
|
||||
@@ -7785,6 +7785,40 @@ fn astsize(c: *checker, t: *node) i64 = {
|
||||
return 0i64;
|
||||
};
|
||||
|
||||
// matchyieldtype — port of cstage cmd/wcc/check.c:110-135. Walks a
|
||||
// match arm body for the first `yield expr;` and returns its operand
|
||||
// type. Returns nil if no yield is reachable from `body`. Doesn't
|
||||
// descend into a nested N_MATCH — each match opens its own yield
|
||||
// scope. exprtype is idempotent on already-stamped nodes (tinfocache
|
||||
// path at L467) so re-entering it on the yield operand here is safe.
|
||||
fn matchyieldtype(c: *checker, body: *node) *node = {
|
||||
if (body == nil) { return nil; };
|
||||
let k: nkind = body.kind;
|
||||
if (k == nkind.N_YIELD) {
|
||||
if (body.lhs == nil) { return nil; };
|
||||
return exprtype(c, body.lhs, nil);
|
||||
};
|
||||
if (k == nkind.N_MATCH) { return nil; };
|
||||
if (k == nkind.N_BLOCK) {
|
||||
let s: *node = body.list;
|
||||
for (s != nil) {
|
||||
let t: *node = matchyieldtype(c, s);
|
||||
if (t != nil) { return t; };
|
||||
s = s.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
if (k == nkind.N_IF) {
|
||||
let t: *node = matchyieldtype(c, body.body);
|
||||
if (t != nil) { return t; };
|
||||
return matchyieldtype(c, body.els);
|
||||
};
|
||||
if (k == nkind.N_FOR || k == nkind.N_FORRANGE) {
|
||||
return matchyieldtype(c, body.body);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// astoffset — byte offset of `dot.str` inside the struct type of
|
||||
// `dot.lhs`. Mirrors cstage cmd/wcc/check.c:932-961: peel one N_TPTR
|
||||
// (for `p.field` where p is *Struct), require N_TSTRUCT, walk fields
|
||||
@@ -8897,6 +8931,27 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (t != nil) { e.type_ = tinfofornode(c, t): *void; };
|
||||
return t;
|
||||
};
|
||||
if (k == nkind.N_MATCH) {
|
||||
// A.6.2.0g — port of cstage cmd/wcc/check.c:1316-1330 match-as-
|
||||
// expression stamp. The match's type is the first arm's yield
|
||||
// operand type; void if no arm yields. Wwstage skips cstage's
|
||||
// arm-yield-unification check (L1322-1327) — that's a checker
|
||||
// concern, this arm only stamps. Closes the consumer half of
|
||||
// the match-as-expression contract that A.6.2.0f opened on the
|
||||
// producer side (N_YIELD).
|
||||
let yt: *node = nil;
|
||||
let cs: *node = e.list;
|
||||
for (cs != nil) {
|
||||
let t: *node = matchyieldtype(c, cs.body);
|
||||
if (t != nil) { yt = t; break; };
|
||||
cs = cs.next;
|
||||
};
|
||||
if (yt == nil) {
|
||||
yt = mktname(c, "void");
|
||||
};
|
||||
e.type_ = tinfofornode(c, yt): *void;
|
||||
return yt;
|
||||
};
|
||||
if (k == nkind.N_YIELD) {
|
||||
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
||||
// stamp N_YIELD (statement-shaped). Wwstage's A.6.2 invariant
|
||||
|
||||
Reference in New Issue
Block a user