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;
|
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
|
// 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
|
// `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
|
// (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; };
|
if (t != nil) { e.type_ = tinfofornode(c, t): *void; };
|
||||||
return t;
|
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) {
|
if (k == nkind.N_YIELD) {
|
||||||
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
||||||
// stamp N_YIELD (statement-shaped). Wwstage's A.6.2 invariant
|
// stamp N_YIELD (statement-shaped). Wwstage's A.6.2 invariant
|
||||||
|
|||||||
@@ -821,6 +821,40 @@ fn astsize(c: *checker, t: *node) i64 = {
|
|||||||
return 0i64;
|
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
|
// 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
|
// `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
|
// (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; };
|
if (t != nil) { e.type_ = tinfofornode(c, t): *void; };
|
||||||
return t;
|
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) {
|
if (k == nkind.N_YIELD) {
|
||||||
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
||||||
// stamp N_YIELD (statement-shaped). Wwstage's A.6.2 invariant
|
// stamp N_YIELD (statement-shaped). Wwstage's A.6.2 invariant
|
||||||
|
|||||||
@@ -7785,6 +7785,40 @@ fn astsize(c: *checker, t: *node) i64 = {
|
|||||||
return 0i64;
|
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
|
// 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
|
// `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
|
// (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; };
|
if (t != nil) { e.type_ = tinfofornode(c, t): *void; };
|
||||||
return t;
|
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) {
|
if (k == nkind.N_YIELD) {
|
||||||
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
||||||
// stamp N_YIELD (statement-shaped). Wwstage's A.6.2 invariant
|
// stamp N_YIELD (statement-shaped). Wwstage's A.6.2 invariant
|
||||||
|
|||||||
Reference in New Issue
Block a user