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.
107 lines
2.9 KiB
Plaintext
107 lines
2.9 KiB
Plaintext
// @test fixture: every test passes (exits without aborting).
|
|
|
|
package data;
|
|
|
|
type point = struct { x: i32, y: i32 };
|
|
|
|
@test fn check_add() void = {
|
|
let a: i32 = 2;
|
|
let b: i32 = 3;
|
|
let c: i32 = a + b;
|
|
if (c != 5) {
|
|
let _: i32 = 1 / 0; // abort via div-by-zero would also work
|
|
};
|
|
};
|
|
|
|
@test fn check_match() void = {
|
|
let r: (i32 | str) = 7;
|
|
let v: i32 = match (r) {
|
|
case let n: i32 => yield n;
|
|
case let s: str => yield 0;
|
|
};
|
|
if (v != 7) {
|
|
let _: i32 = 1 / 0;
|
|
};
|
|
};
|
|
|
|
// #227: compound assign (`-=`/`+=`) on a local field must
|
|
// load-combine-store, not drop the op. Pre-fix wwstage stored the bare
|
|
// rhs (p.x->4, p.y->5, view.len->1), so each mismatch aborts via 1/0.
|
|
@test fn check_local_field_compound() void = {
|
|
let p: point = point { x = 10i32, y = 3i32 };
|
|
p.x -= 4i32;
|
|
p.y += 5i32;
|
|
if (p.x != 6) {
|
|
let _: i32 = 1 / 0;
|
|
};
|
|
if (p.y != 8) {
|
|
let _: i32 = 1 / 0;
|
|
};
|
|
let view: str = "hello";
|
|
view.len -= 1;
|
|
let n: i32 = view.len: i32;
|
|
if (n != 4) {
|
|
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;
|
|
};
|
|
};
|