w6c+wwstage: #264 read cached operand tinfo for match-expr yield type
The wwstage checker rejected a match-bound binder used in a `yield` arm
of a match-AS-EXPRESSION (`let v = match (x) { case let p: *T => yield
*p; ... }`) with asserttyped:un/bin/index; cstage compiled it.
resolvewalk stamps the yield operand's type_ during the in-scope N_MCASE
arm walk. exprtype's N_MATCH arm then derived the match's type by
re-running exprtype on the same operand to recover a type NODE — but the
arm binder's scope is already popped, so the re-derive returned nil and
the N_UN/N_BIN/N_INDEX restamp arms overwrote the good in-scope stamp
with nil. cstage never re-runs: match_yield_type reads the operand's
cached ->type (cmd/wcc/check.c:121).
Root fix (align wwstage UP): matchyieldtype now returns a *tinfo and, at
the post-walk call, READS the operand's cached node.type_ instead of
re-running exprtype — so no operand shape can be clobbered by
construction (deref/bin/index all vanish, no per-arm guards). The
exprtype N_MATCH consumer stamps e.type_ from that tinfo directly (no
tinfofornode round-trip). The pre-walk call (checkletassign L302 /
checkretassign L303 run before the in-scope arm walk, so the operand is
nil there) keeps the nil-safe re-derive — benign and load-bearing: it
types the void-arm literal so let/return-assign has a usable node. The
re-derived node (or btype for the bare-binder idiom) is carried back via
an out-param for the assignability check and for the N_MLET/N_MASSIGN
tuple-destructure consumers (`let (a,b) = match { case let t => yield t
}`, test 945). cstage is single-pass so its else is dead; eliminating
the pre-walk call is #279.
Supersedes the narrow N_UN non-clobber guard (removed — its match
consumer is gone). @test check_match_ptr_deref extended to pin the whole
operand class (deref / bin / slice-index / deref-then-field), dual-stage
(910 + 997) with correct runtime + cs==ww byte-id. The *[N]T ptr-to-
array index variant is blocked separately by #278. Both compiler-
imported combined.ww regenerated. smoke + test-unit (242) + 994 w6c_ww
byte-id (18 corpus incl. selfhost combined.ww) green.
This commit is contained in:
@@ -11501,30 +11501,68 @@ fn astunsized(c: *checker, t: *node) bool = {
|
||||
|
||||
// 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.
|
||||
// bname/btype carry the enclosing arm's case-binding name + declared type
|
||||
// node. #241: when exprtype can't re-derive a `yield <binder>` operand —
|
||||
// the arm binder's scope is already popped by the time exprtype(N_MATCH)
|
||||
// runs post-order (resolvewalk's N_MCASE restores c.cur before this), so
|
||||
// scopelookup of the bare binder ident returns nil — the yielded type IS
|
||||
// the binding type (btype). cstage avoids this by reading the operand's
|
||||
// already-stamped ->type (check.c:122) rather than re-running cexpr; wwstage
|
||||
// caches only node.type_ (a tinfo, not a type NODE), so this binder-typed
|
||||
// fallback is the node-form recovery for the dominant match-bind-then-yield
|
||||
// idiom (Hare's parseint `case let t => yield t`).
|
||||
fn matchyieldtype(c: *checker, body: *node, bname: str, btype: *node) *node = {
|
||||
// type as a resolved *tinfo (the match-as-expression's 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. bname/btype
|
||||
// carry the enclosing arm's case-binding name + declared type node.
|
||||
//
|
||||
// #264: returns a *tinfo (not a type *node) because the post-walk call
|
||||
// READS the operand's cached node.type_ (a tinfo), mirroring cstage's
|
||||
// match_yield_type which reads body->lhs->type (cmd/wcc/check.c:121-122).
|
||||
// The two callsites differ on whether the operand is stamped yet:
|
||||
// - POST-walk (exprtype N_MATCH post-order, resolvewalk L631): the
|
||||
// in-scope N_MCASE arm walk (L411) has already stamped the operand,
|
||||
// so we read body.lhs.type_ directly — NO exprtype re-run. Re-running
|
||||
// exprtype out of the (popped) arm scope returned nil and the
|
||||
// N_UN/N_BIN/N_INDEX restamp arms overwrote the good deref/element
|
||||
// stamp with nil -> asserttyped:un/bin/index. Reading cached avoids
|
||||
// the re-derive entirely, so the clobber cannot occur by construction.
|
||||
// - PRE-walk (checkletassign L302 / checkretassign L303 run exprtype
|
||||
// on the match rhs BEFORE the L533 in-scope descent): the operand is
|
||||
// nil here, so we re-derive via exprtype — benign (nil->nil no-op on
|
||||
// the unstamped operand) AND load-bearing: it types the void-arm
|
||||
// literal (`yield -1`) so the let/return-assign has a usable type
|
||||
// node for isassignable. cstage is single-pass (no pre-walk call), so
|
||||
// its match_yield_type has no such branch; eliminating this pre-walk
|
||||
// call is #279. `nodeout` carries that re-derived type NODE back to
|
||||
// the consumer for the pre-walk assignability check (isassignable is
|
||||
// node-based); it stays nil on the post-walk cached read, where the
|
||||
// consumer's *node return is discarded (no nested match-as-subexpr
|
||||
// consumes it — only checkletassign/checkretassign at the pre-walk
|
||||
// call use it). The #241 `yield <binder>` fallback (the dominant
|
||||
// match-bind-then-yield idiom, Hare's parseint `case let t => yield t`)
|
||||
// stays, now resolving btype to a tinfo.
|
||||
fn matchyieldtype(c: *checker, body: *node, bname: str, btype: *node,
|
||||
nodeout: **node) *tinfo = {
|
||||
if (body == nil) { return nil; };
|
||||
let k: nkind = body.kind;
|
||||
if (k == nkind.N_YIELD) {
|
||||
if (body.lhs == nil) { return nil; };
|
||||
if (body.lhs.type_ != nil) {
|
||||
// For the bare-binder idiom `yield <binder>`, ALSO surface
|
||||
// btype as the *node: the tuple-destructure consumers
|
||||
// (N_MLET/N_MASSIGN at resolvewalk L482/L503) read
|
||||
// exprtype(N_MATCH)'s *node return as an N_TTUPLE to
|
||||
// distribute onto `let (a,b) = match(x){ case let t => yield
|
||||
// t }` (test 945 match_yield — the only tuple-destructure-of-
|
||||
// match idiom in tree, grep-confirmed). btype is the binder's
|
||||
// declared type node, which IS the match's type here; the
|
||||
// cached tinfo returned below equals tinfofornode(btype).
|
||||
if (body.lhs.kind == nkind.N_IDENT && bname.len > 0
|
||||
&& streq(body.lhs.str, bname)) {
|
||||
*nodeout = btype;
|
||||
};
|
||||
return body.lhs.type_: *tinfo;
|
||||
};
|
||||
let t: *node = exprtype(c, body.lhs, nil);
|
||||
if (t != nil) { return t; };
|
||||
if (t != nil) {
|
||||
*nodeout = t;
|
||||
return tinfofornode(c, t);
|
||||
};
|
||||
if (body.lhs.kind == nkind.N_IDENT && bname.len > 0
|
||||
&& streq(body.lhs.str, bname)) {
|
||||
return btype;
|
||||
*nodeout = btype;
|
||||
return tinfofornode(c, btype);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
@@ -11532,19 +11570,19 @@ fn matchyieldtype(c: *checker, body: *node, bname: str, btype: *node) *node = {
|
||||
if (k == nkind.N_BLOCK) {
|
||||
let s: *node = body.list;
|
||||
for (s != nil) {
|
||||
let t: *node = matchyieldtype(c, s, bname, btype);
|
||||
let t: *tinfo = matchyieldtype(c, s, bname, btype, nodeout);
|
||||
if (t != nil) { return t; };
|
||||
s = s.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
if (k == nkind.N_IF) {
|
||||
let t: *node = matchyieldtype(c, body.body, bname, btype);
|
||||
let t: *tinfo = matchyieldtype(c, body.body, bname, btype, nodeout);
|
||||
if (t != nil) { return t; };
|
||||
return matchyieldtype(c, body.els, bname, btype);
|
||||
return matchyieldtype(c, body.els, bname, btype, nodeout);
|
||||
};
|
||||
if (k == nkind.N_FOR || k == nkind.N_FORRANGE) {
|
||||
return matchyieldtype(c, body.body, bname, btype);
|
||||
return matchyieldtype(c, body.body, bname, btype, nodeout);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
@@ -13379,21 +13417,30 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// 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;
|
||||
// #264: consume matchyieldtype's *tinfo DIRECTLY (no tinfofornode
|
||||
// round-trip) — the post-walk call reads the operand's cached
|
||||
// stamp, so the out-of-scope re-derive that clobbered *p/p[i]/*p+1
|
||||
// is never reached. `retn` captures the pre-walk re-derived type
|
||||
// node (nil on the post-walk cached read) for the assignability
|
||||
// node the consumers (checkletassign/checkretassign) read off this
|
||||
// arm's *node return; see matchyieldtype's docstring + #279.
|
||||
let yt: *tinfo = nil;
|
||||
let retn: *node = nil;
|
||||
let cs: *node = e.list;
|
||||
for (cs != nil) {
|
||||
// cs.str/cs.lhs = the arm's case-binding name + declared
|
||||
// type (the N_MCASE binder); fed to matchyieldtype's #241
|
||||
// scope-popped `yield <binder>` fallback.
|
||||
let t: *node = matchyieldtype(c, cs.body, cs.str, cs.lhs);
|
||||
if (t != nil) { yt = t; break; };
|
||||
let armn: *node = nil;
|
||||
let t: *tinfo = matchyieldtype(c, cs.body, cs.str, cs.lhs, &armn);
|
||||
if (t != nil) { yt = t; retn = armn; break; };
|
||||
cs = cs.next;
|
||||
};
|
||||
if (yt == nil) {
|
||||
yt = mktname(c, "void");
|
||||
yt = tinfofornode(c, mktname(c, "void"));
|
||||
};
|
||||
e.type_ = tinfofornode(c, yt): *void;
|
||||
return yt;
|
||||
e.type_ = yt: *void;
|
||||
return retn;
|
||||
};
|
||||
if (k == nkind.N_YIELD) {
|
||||
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
||||
|
||||
@@ -1096,30 +1096,68 @@ fn astunsized(c: *checker, t: *node) bool = {
|
||||
|
||||
// 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.
|
||||
// bname/btype carry the enclosing arm's case-binding name + declared type
|
||||
// node. #241: when exprtype can't re-derive a `yield <binder>` operand —
|
||||
// the arm binder's scope is already popped by the time exprtype(N_MATCH)
|
||||
// runs post-order (resolvewalk's N_MCASE restores c.cur before this), so
|
||||
// scopelookup of the bare binder ident returns nil — the yielded type IS
|
||||
// the binding type (btype). cstage avoids this by reading the operand's
|
||||
// already-stamped ->type (check.c:122) rather than re-running cexpr; wwstage
|
||||
// caches only node.type_ (a tinfo, not a type NODE), so this binder-typed
|
||||
// fallback is the node-form recovery for the dominant match-bind-then-yield
|
||||
// idiom (Hare's parseint `case let t => yield t`).
|
||||
fn matchyieldtype(c: *checker, body: *node, bname: str, btype: *node) *node = {
|
||||
// type as a resolved *tinfo (the match-as-expression's 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. bname/btype
|
||||
// carry the enclosing arm's case-binding name + declared type node.
|
||||
//
|
||||
// #264: returns a *tinfo (not a type *node) because the post-walk call
|
||||
// READS the operand's cached node.type_ (a tinfo), mirroring cstage's
|
||||
// match_yield_type which reads body->lhs->type (cmd/wcc/check.c:121-122).
|
||||
// The two callsites differ on whether the operand is stamped yet:
|
||||
// - POST-walk (exprtype N_MATCH post-order, resolvewalk L631): the
|
||||
// in-scope N_MCASE arm walk (L411) has already stamped the operand,
|
||||
// so we read body.lhs.type_ directly — NO exprtype re-run. Re-running
|
||||
// exprtype out of the (popped) arm scope returned nil and the
|
||||
// N_UN/N_BIN/N_INDEX restamp arms overwrote the good deref/element
|
||||
// stamp with nil -> asserttyped:un/bin/index. Reading cached avoids
|
||||
// the re-derive entirely, so the clobber cannot occur by construction.
|
||||
// - PRE-walk (checkletassign L302 / checkretassign L303 run exprtype
|
||||
// on the match rhs BEFORE the L533 in-scope descent): the operand is
|
||||
// nil here, so we re-derive via exprtype — benign (nil->nil no-op on
|
||||
// the unstamped operand) AND load-bearing: it types the void-arm
|
||||
// literal (`yield -1`) so the let/return-assign has a usable type
|
||||
// node for isassignable. cstage is single-pass (no pre-walk call), so
|
||||
// its match_yield_type has no such branch; eliminating this pre-walk
|
||||
// call is #279. `nodeout` carries that re-derived type NODE back to
|
||||
// the consumer for the pre-walk assignability check (isassignable is
|
||||
// node-based); it stays nil on the post-walk cached read, where the
|
||||
// consumer's *node return is discarded (no nested match-as-subexpr
|
||||
// consumes it — only checkletassign/checkretassign at the pre-walk
|
||||
// call use it). The #241 `yield <binder>` fallback (the dominant
|
||||
// match-bind-then-yield idiom, Hare's parseint `case let t => yield t`)
|
||||
// stays, now resolving btype to a tinfo.
|
||||
fn matchyieldtype(c: *checker, body: *node, bname: str, btype: *node,
|
||||
nodeout: **node) *tinfo = {
|
||||
if (body == nil) { return nil; };
|
||||
let k: nkind = body.kind;
|
||||
if (k == nkind.N_YIELD) {
|
||||
if (body.lhs == nil) { return nil; };
|
||||
if (body.lhs.type_ != nil) {
|
||||
// For the bare-binder idiom `yield <binder>`, ALSO surface
|
||||
// btype as the *node: the tuple-destructure consumers
|
||||
// (N_MLET/N_MASSIGN at resolvewalk L482/L503) read
|
||||
// exprtype(N_MATCH)'s *node return as an N_TTUPLE to
|
||||
// distribute onto `let (a,b) = match(x){ case let t => yield
|
||||
// t }` (test 945 match_yield — the only tuple-destructure-of-
|
||||
// match idiom in tree, grep-confirmed). btype is the binder's
|
||||
// declared type node, which IS the match's type here; the
|
||||
// cached tinfo returned below equals tinfofornode(btype).
|
||||
if (body.lhs.kind == nkind.N_IDENT && bname.len > 0
|
||||
&& streq(body.lhs.str, bname)) {
|
||||
*nodeout = btype;
|
||||
};
|
||||
return body.lhs.type_: *tinfo;
|
||||
};
|
||||
let t: *node = exprtype(c, body.lhs, nil);
|
||||
if (t != nil) { return t; };
|
||||
if (t != nil) {
|
||||
*nodeout = t;
|
||||
return tinfofornode(c, t);
|
||||
};
|
||||
if (body.lhs.kind == nkind.N_IDENT && bname.len > 0
|
||||
&& streq(body.lhs.str, bname)) {
|
||||
return btype;
|
||||
*nodeout = btype;
|
||||
return tinfofornode(c, btype);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
@@ -1127,19 +1165,19 @@ fn matchyieldtype(c: *checker, body: *node, bname: str, btype: *node) *node = {
|
||||
if (k == nkind.N_BLOCK) {
|
||||
let s: *node = body.list;
|
||||
for (s != nil) {
|
||||
let t: *node = matchyieldtype(c, s, bname, btype);
|
||||
let t: *tinfo = matchyieldtype(c, s, bname, btype, nodeout);
|
||||
if (t != nil) { return t; };
|
||||
s = s.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
if (k == nkind.N_IF) {
|
||||
let t: *node = matchyieldtype(c, body.body, bname, btype);
|
||||
let t: *tinfo = matchyieldtype(c, body.body, bname, btype, nodeout);
|
||||
if (t != nil) { return t; };
|
||||
return matchyieldtype(c, body.els, bname, btype);
|
||||
return matchyieldtype(c, body.els, bname, btype, nodeout);
|
||||
};
|
||||
if (k == nkind.N_FOR || k == nkind.N_FORRANGE) {
|
||||
return matchyieldtype(c, body.body, bname, btype);
|
||||
return matchyieldtype(c, body.body, bname, btype, nodeout);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
@@ -2974,21 +3012,30 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// 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;
|
||||
// #264: consume matchyieldtype's *tinfo DIRECTLY (no tinfofornode
|
||||
// round-trip) — the post-walk call reads the operand's cached
|
||||
// stamp, so the out-of-scope re-derive that clobbered *p/p[i]/*p+1
|
||||
// is never reached. `retn` captures the pre-walk re-derived type
|
||||
// node (nil on the post-walk cached read) for the assignability
|
||||
// node the consumers (checkletassign/checkretassign) read off this
|
||||
// arm's *node return; see matchyieldtype's docstring + #279.
|
||||
let yt: *tinfo = nil;
|
||||
let retn: *node = nil;
|
||||
let cs: *node = e.list;
|
||||
for (cs != nil) {
|
||||
// cs.str/cs.lhs = the arm's case-binding name + declared
|
||||
// type (the N_MCASE binder); fed to matchyieldtype's #241
|
||||
// scope-popped `yield <binder>` fallback.
|
||||
let t: *node = matchyieldtype(c, cs.body, cs.str, cs.lhs);
|
||||
if (t != nil) { yt = t; break; };
|
||||
let armn: *node = nil;
|
||||
let t: *tinfo = matchyieldtype(c, cs.body, cs.str, cs.lhs, &armn);
|
||||
if (t != nil) { yt = t; retn = armn; break; };
|
||||
cs = cs.next;
|
||||
};
|
||||
if (yt == nil) {
|
||||
yt = mktname(c, "void");
|
||||
yt = tinfofornode(c, mktname(c, "void"));
|
||||
};
|
||||
e.type_ = tinfofornode(c, yt): *void;
|
||||
return yt;
|
||||
e.type_ = yt: *void;
|
||||
return retn;
|
||||
};
|
||||
if (k == nkind.N_YIELD) {
|
||||
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
||||
|
||||
@@ -11501,30 +11501,68 @@ fn astunsized(c: *checker, t: *node) bool = {
|
||||
|
||||
// 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.
|
||||
// bname/btype carry the enclosing arm's case-binding name + declared type
|
||||
// node. #241: when exprtype can't re-derive a `yield <binder>` operand —
|
||||
// the arm binder's scope is already popped by the time exprtype(N_MATCH)
|
||||
// runs post-order (resolvewalk's N_MCASE restores c.cur before this), so
|
||||
// scopelookup of the bare binder ident returns nil — the yielded type IS
|
||||
// the binding type (btype). cstage avoids this by reading the operand's
|
||||
// already-stamped ->type (check.c:122) rather than re-running cexpr; wwstage
|
||||
// caches only node.type_ (a tinfo, not a type NODE), so this binder-typed
|
||||
// fallback is the node-form recovery for the dominant match-bind-then-yield
|
||||
// idiom (Hare's parseint `case let t => yield t`).
|
||||
fn matchyieldtype(c: *checker, body: *node, bname: str, btype: *node) *node = {
|
||||
// type as a resolved *tinfo (the match-as-expression's 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. bname/btype
|
||||
// carry the enclosing arm's case-binding name + declared type node.
|
||||
//
|
||||
// #264: returns a *tinfo (not a type *node) because the post-walk call
|
||||
// READS the operand's cached node.type_ (a tinfo), mirroring cstage's
|
||||
// match_yield_type which reads body->lhs->type (cmd/wcc/check.c:121-122).
|
||||
// The two callsites differ on whether the operand is stamped yet:
|
||||
// - POST-walk (exprtype N_MATCH post-order, resolvewalk L631): the
|
||||
// in-scope N_MCASE arm walk (L411) has already stamped the operand,
|
||||
// so we read body.lhs.type_ directly — NO exprtype re-run. Re-running
|
||||
// exprtype out of the (popped) arm scope returned nil and the
|
||||
// N_UN/N_BIN/N_INDEX restamp arms overwrote the good deref/element
|
||||
// stamp with nil -> asserttyped:un/bin/index. Reading cached avoids
|
||||
// the re-derive entirely, so the clobber cannot occur by construction.
|
||||
// - PRE-walk (checkletassign L302 / checkretassign L303 run exprtype
|
||||
// on the match rhs BEFORE the L533 in-scope descent): the operand is
|
||||
// nil here, so we re-derive via exprtype — benign (nil->nil no-op on
|
||||
// the unstamped operand) AND load-bearing: it types the void-arm
|
||||
// literal (`yield -1`) so the let/return-assign has a usable type
|
||||
// node for isassignable. cstage is single-pass (no pre-walk call), so
|
||||
// its match_yield_type has no such branch; eliminating this pre-walk
|
||||
// call is #279. `nodeout` carries that re-derived type NODE back to
|
||||
// the consumer for the pre-walk assignability check (isassignable is
|
||||
// node-based); it stays nil on the post-walk cached read, where the
|
||||
// consumer's *node return is discarded (no nested match-as-subexpr
|
||||
// consumes it — only checkletassign/checkretassign at the pre-walk
|
||||
// call use it). The #241 `yield <binder>` fallback (the dominant
|
||||
// match-bind-then-yield idiom, Hare's parseint `case let t => yield t`)
|
||||
// stays, now resolving btype to a tinfo.
|
||||
fn matchyieldtype(c: *checker, body: *node, bname: str, btype: *node,
|
||||
nodeout: **node) *tinfo = {
|
||||
if (body == nil) { return nil; };
|
||||
let k: nkind = body.kind;
|
||||
if (k == nkind.N_YIELD) {
|
||||
if (body.lhs == nil) { return nil; };
|
||||
if (body.lhs.type_ != nil) {
|
||||
// For the bare-binder idiom `yield <binder>`, ALSO surface
|
||||
// btype as the *node: the tuple-destructure consumers
|
||||
// (N_MLET/N_MASSIGN at resolvewalk L482/L503) read
|
||||
// exprtype(N_MATCH)'s *node return as an N_TTUPLE to
|
||||
// distribute onto `let (a,b) = match(x){ case let t => yield
|
||||
// t }` (test 945 match_yield — the only tuple-destructure-of-
|
||||
// match idiom in tree, grep-confirmed). btype is the binder's
|
||||
// declared type node, which IS the match's type here; the
|
||||
// cached tinfo returned below equals tinfofornode(btype).
|
||||
if (body.lhs.kind == nkind.N_IDENT && bname.len > 0
|
||||
&& streq(body.lhs.str, bname)) {
|
||||
*nodeout = btype;
|
||||
};
|
||||
return body.lhs.type_: *tinfo;
|
||||
};
|
||||
let t: *node = exprtype(c, body.lhs, nil);
|
||||
if (t != nil) { return t; };
|
||||
if (t != nil) {
|
||||
*nodeout = t;
|
||||
return tinfofornode(c, t);
|
||||
};
|
||||
if (body.lhs.kind == nkind.N_IDENT && bname.len > 0
|
||||
&& streq(body.lhs.str, bname)) {
|
||||
return btype;
|
||||
*nodeout = btype;
|
||||
return tinfofornode(c, btype);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
@@ -11532,19 +11570,19 @@ fn matchyieldtype(c: *checker, body: *node, bname: str, btype: *node) *node = {
|
||||
if (k == nkind.N_BLOCK) {
|
||||
let s: *node = body.list;
|
||||
for (s != nil) {
|
||||
let t: *node = matchyieldtype(c, s, bname, btype);
|
||||
let t: *tinfo = matchyieldtype(c, s, bname, btype, nodeout);
|
||||
if (t != nil) { return t; };
|
||||
s = s.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
if (k == nkind.N_IF) {
|
||||
let t: *node = matchyieldtype(c, body.body, bname, btype);
|
||||
let t: *tinfo = matchyieldtype(c, body.body, bname, btype, nodeout);
|
||||
if (t != nil) { return t; };
|
||||
return matchyieldtype(c, body.els, bname, btype);
|
||||
return matchyieldtype(c, body.els, bname, btype, nodeout);
|
||||
};
|
||||
if (k == nkind.N_FOR || k == nkind.N_FORRANGE) {
|
||||
return matchyieldtype(c, body.body, bname, btype);
|
||||
return matchyieldtype(c, body.body, bname, btype, nodeout);
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
@@ -13379,21 +13417,30 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// 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;
|
||||
// #264: consume matchyieldtype's *tinfo DIRECTLY (no tinfofornode
|
||||
// round-trip) — the post-walk call reads the operand's cached
|
||||
// stamp, so the out-of-scope re-derive that clobbered *p/p[i]/*p+1
|
||||
// is never reached. `retn` captures the pre-walk re-derived type
|
||||
// node (nil on the post-walk cached read) for the assignability
|
||||
// node the consumers (checkletassign/checkretassign) read off this
|
||||
// arm's *node return; see matchyieldtype's docstring + #279.
|
||||
let yt: *tinfo = nil;
|
||||
let retn: *node = nil;
|
||||
let cs: *node = e.list;
|
||||
for (cs != nil) {
|
||||
// cs.str/cs.lhs = the arm's case-binding name + declared
|
||||
// type (the N_MCASE binder); fed to matchyieldtype's #241
|
||||
// scope-popped `yield <binder>` fallback.
|
||||
let t: *node = matchyieldtype(c, cs.body, cs.str, cs.lhs);
|
||||
if (t != nil) { yt = t; break; };
|
||||
let armn: *node = nil;
|
||||
let t: *tinfo = matchyieldtype(c, cs.body, cs.str, cs.lhs, &armn);
|
||||
if (t != nil) { yt = t; retn = armn; break; };
|
||||
cs = cs.next;
|
||||
};
|
||||
if (yt == nil) {
|
||||
yt = mktname(c, "void");
|
||||
yt = tinfofornode(c, mktname(c, "void"));
|
||||
};
|
||||
e.type_ = tinfofornode(c, yt): *void;
|
||||
return yt;
|
||||
e.type_ = yt: *void;
|
||||
return retn;
|
||||
};
|
||||
if (k == nkind.N_YIELD) {
|
||||
// A.6.2.0f — pass-through stamp; cstage check.c:1708 does NOT
|
||||
|
||||
@@ -44,3 +44,63 @@ type point = struct { x: i32, y: i32 };
|
||||
let _: i32 = 1 / 0;
|
||||
};
|
||||
};
|
||||
|
||||
// #264: a match-bound binder used inside a `yield` arm of a match-AS-
|
||||
// EXPRESSION. Pre-fix the wwstage checker stamped the operand in-scope
|
||||
// during the N_MCASE arm walk, then RE-TYPED it out of the (popped) arm
|
||||
// scope while deriving the match's yield type (exprtype N_MATCH →
|
||||
// matchyieldtype). Out of scope the operand couldn't re-resolve, the
|
||||
// re-derive returned nil, and the N_UN/N_BIN/N_INDEX restamp arms
|
||||
// overwrote the good in-scope stamp with nil → asserttyped:un/bin/index
|
||||
// aborted (cstage accepted it). Root fix: matchyieldtype now READS the
|
||||
// cached operand tinfo at the post-walk call instead of re-deriving
|
||||
// (mirrors cstage match_yield_type reading body->lhs->type, check.c:121),
|
||||
// so no operand shape can be clobbered. These rows pin the whole operand
|
||||
// class by construction: deref (*p), bin (*p+1), slice-index (p[i]), and
|
||||
// deref-then-field ((*p).x). (The *[N]T ptr-to-array index variant is
|
||||
// blocked separately by #278's exhaustiveness false-reject, so it uses a
|
||||
// []i32 slice binder here.)
|
||||
@test fn check_match_ptr_deref() void = {
|
||||
let n: i32 = 42i32;
|
||||
let xi: (*i32 | void) = &n;
|
||||
let v: i32 = match (xi) {
|
||||
case let p: *i32 => yield *p;
|
||||
case void => yield -1i32;
|
||||
};
|
||||
if (v != 42) {
|
||||
let _: i32 = 1 / 0;
|
||||
};
|
||||
|
||||
// N_BIN operand: `yield *p + 1`.
|
||||
let m: i32 = 9i32;
|
||||
let xb: (*i32 | void) = &m;
|
||||
let vb: i32 = match (xb) {
|
||||
case let p: *i32 => yield *p + 1i32;
|
||||
case void => yield -1i32;
|
||||
};
|
||||
if (vb != 10) {
|
||||
let _: i32 = 1 / 0;
|
||||
};
|
||||
|
||||
// N_INDEX operand: `yield p[1]` over a []i32 binder.
|
||||
let arr: [3]i32 = [4i32, 5i32, 6i32];
|
||||
let sl: []i32 = arr;
|
||||
let xs: ([]i32 | void) = sl;
|
||||
let vs: i32 = match (xs) {
|
||||
case let p: []i32 => yield p[1];
|
||||
case void => yield -1i32;
|
||||
};
|
||||
if (vs != 5) {
|
||||
let _: i32 = 1 / 0;
|
||||
};
|
||||
|
||||
let pt: point = point { x = 7i32, y = 9i32 };
|
||||
let xp: (*point | void) = &pt;
|
||||
let w: i32 = match (xp) {
|
||||
case let p: *point => yield (*p).x;
|
||||
case void => yield -1i32;
|
||||
};
|
||||
if (w != 7) {
|
||||
let _: i32 = 1 / 0;
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user