wcc: checker-seam drain — spreads, tuple aliases, yield scoping
Four seams from one review cluster, all landing on the same checker files; gates ran on the union (rule 11 body). ?-subset: the wwstage walk compared `...spread` ALIAS nodes on both sides of the error-subset check, falsely rejecting a spread-carried error return (`(i64 | ...errs)`, cs-accept/ww-reject) — both sides now flatten like trycountvariants (cstage Tparams are pre-flattened at type level). Multi-let/multi-assign: the tuple gates keyed on the RAW rhs kind, rejecting a NAMED tuple alias (`type pair = (i64,i64)`; cs clean- reject, ww asserttyped stop) — both stages chase per #99 alias transparency, wwstage rettupleof peels the alias so cgmassign keeps the str element's len/cap stores, and the catA massign-alias error pin re-rules to a run fixture. The N_MLET diagnostic also printed its operands swapped (elem/declared reversed vs its wording). Yield: match_yield_type didn't descend into N_SWITCH, so a yield inside a switch arm typed the match void and dropped the value (both stages); and a stray yield outside any match arm reached cgen unchecked — both stages now gate on a match-arm counter (the c->loops discipline).
This commit is contained in:
@@ -15,6 +15,8 @@ type checker = struct {
|
||||
errs: i32,
|
||||
loops: i32, // break/continue loop-nesting guard; cstage
|
||||
// twin cmd/wcc/check.c:598 (c->loops)
|
||||
matcharms: i32, // yield match-arm-nesting guard; cstage
|
||||
// twin (c->matcharms)
|
||||
istest: i32, // #15: `w6c_ww -T` — collect @test fns +
|
||||
// synth the entry; loud-reject a user main.
|
||||
verbose: i32, // when non-zero, log each unresolved name
|
||||
@@ -692,7 +694,11 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
checkmoduleshadow(c, nm, "binding");
|
||||
syntax.scopedefine(c.cur, nm, syntax.skind.SK_VAR, nil, n);
|
||||
};
|
||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||
if (n.body != nil) {
|
||||
c.matcharms += 1;
|
||||
resolvewalk(c, n.body);
|
||||
c.matcharms -= 1;
|
||||
};
|
||||
c.cur = outer;
|
||||
return;
|
||||
};
|
||||
@@ -754,7 +760,12 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
if (n.rhs != nil) {
|
||||
// `rt` would shadow the imported lib/rt module
|
||||
// (checkmoduleshadow errors); `rty` avoids it.
|
||||
// #99 alias transparency: a NAMED tuple alias rhs
|
||||
// (`type pair = (i64,i64)`) destructures like its
|
||||
// base — resolvealias mirrors cstage's
|
||||
// type_chase_named at the N_MLET gate.
|
||||
let rty: *syntax.node = exprtype(c, n.rhs, nil);
|
||||
if (rty != nil) { rty = resolvealias(c, unwrapbang(rty)); };
|
||||
if (rty != nil) { if (rty.kind == syntax.nkind.N_TTUPLE) {
|
||||
pt = rty.list;
|
||||
}; };
|
||||
@@ -775,20 +786,19 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
let pt: *syntax.node = nil;
|
||||
// #242: consume the rhs tuple type for ANY rhs (see N_MLET).
|
||||
if (n.rhs != nil) {
|
||||
// #99 alias transparency — the N_MLET chase's twin
|
||||
// (both stages now chase the NAMED wrapper here).
|
||||
let rty: *syntax.node = exprtype(c, n.rhs, nil);
|
||||
if (rty != nil) { rty = resolvealias(c, unwrapbang(rty)); };
|
||||
if (rty != nil && rty.kind == syntax.nkind.N_TTUPLE) {
|
||||
pt = rty.list;
|
||||
} else {
|
||||
// #38/F2 (review item 39): the multi-assign rhs must be a
|
||||
// tuple. cstage check.c:2562-2568 errors "multi-assign rhs
|
||||
// is not a tuple (got %s)" when cexpr(rhs)->kind != TY_TUPLE
|
||||
// — and, like ww's exprtype (N_CALL returns the decl's bare
|
||||
// return tnode, :3319), it does NOT chase the NAMED wrapper,
|
||||
// so a tuple-ALIAS return (`fn f() pair`) is rejected too.
|
||||
// Without this ww distributed nil element widths and
|
||||
// cgmassign silently dropped the str len/cap stores. ww's
|
||||
// piecewise cerr can't splice the type spelling, so the
|
||||
// "(got %s)" tail is omitted.
|
||||
// tuple. cstage check.c errors "multi-assign rhs is not a
|
||||
// tuple (got %s)". Without this ww distributed nil element
|
||||
// widths and cgmassign silently dropped the str len/cap
|
||||
// stores. ww's piecewise cerr can't splice the type
|
||||
// spelling, so the "(got %s)" tail is omitted.
|
||||
deffolderr(c, n, "multi-assign rhs is not a tuple");
|
||||
};
|
||||
};
|
||||
@@ -919,6 +929,15 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
if (k == syntax.nkind.N_CALL) { desugarcallargs(c, n); };
|
||||
if (k == syntax.nkind.N_ASSIGN) { checkassign(c, n); };
|
||||
|
||||
// break/continue get the loops gate; a stray yield outside any
|
||||
// match arm reached cgen unchecked and its value silently
|
||||
// vanished. Mirror cstage cstmt N_YIELD.
|
||||
if (k == syntax.nkind.N_YIELD) {
|
||||
if (c.matcharms == 0) {
|
||||
cerr("error: yield outside match\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
if (k == syntax.nkind.N_INTLIT || k == syntax.nkind.N_FLOATLIT ||
|
||||
k == syntax.nkind.N_STRLIT || k == syntax.nkind.N_RUNELIT ||
|
||||
k == syntax.nkind.N_TRUE || k == syntax.nkind.N_FALSE ||
|
||||
@@ -1555,6 +1574,18 @@ fn matchyieldtype(c: *checker, body: *syntax.node, bname: str, btype: *syntax.no
|
||||
if (k == syntax.nkind.N_FOR || k == syntax.nkind.N_FORRANGE) {
|
||||
return matchyieldtype(c, body.body, bname, btype, nodeout);
|
||||
};
|
||||
// a yield inside a switch arm yields from the enclosing MATCH
|
||||
// (switch is a statement, not a yield scope) — invisible here,
|
||||
// the match typed void and the yielded value was dropped.
|
||||
if (k == syntax.nkind.N_SWITCH) {
|
||||
let cs: *syntax.node = body.list;
|
||||
for (cs != nil) {
|
||||
let t: *syntax.tinfo = matchyieldtype(c, cs.body, bname, btype, nodeout);
|
||||
if (t != nil) { return t; };
|
||||
cs = cs.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
@@ -6791,6 +6822,54 @@ fn trycountvariants(c: *checker, outer: *syntax.node, v: *syntax.node, nsuccp: *
|
||||
};
|
||||
};
|
||||
|
||||
// tryerrinreturn — is error variant `ev` present in the return union's
|
||||
// variant list, flattening `...inner` spreads? cstage compares over
|
||||
// PRE-FLATTENED Tparams (resolve_type inlines spreads, #61a), so its
|
||||
// per-param walk sees spliced members natively; the wwstage AST walk
|
||||
// compared the spread ALIAS node and falsely rejected a spread-carried
|
||||
// error (`(i64 | ...errs)` return, cs-accept/ww-reject).
|
||||
fn tryerrinreturn(c: *checker, rvlist: *syntax.node, ev: *syntax.node, depth: i32) bool = {
|
||||
let rv: *syntax.node = rvlist;
|
||||
for (rv != nil) {
|
||||
if (rv.op == syntax.tkind.TK_ELLIPSIS && depth < 8i32) {
|
||||
let inner: *syntax.node = resolvealias(c, unwrapbang(rv));
|
||||
if (inner != nil && inner.kind == syntax.nkind.N_TTAGGED) {
|
||||
if (tryerrinreturn(c, inner.list, ev, depth + 1i32)) { return true; };
|
||||
rv = rv.next;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
if (typeeqast(c, rv, ev)) { return true; };
|
||||
rv = rv.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// trysubseterrs — the ?-subset walk over the OPERAND union's error
|
||||
// variants, flattening its `...inner` spreads the same way (the
|
||||
// trycountvariants pattern; outer `u` rides through for iserrvariant
|
||||
// exactly as there).
|
||||
fn trysubseterrs(c: *checker, u: *syntax.node, evlist: *syntax.node, r: *syntax.node, depth: i32) void = {
|
||||
let ev: *syntax.node = evlist;
|
||||
for (ev != nil) {
|
||||
if (ev.op == syntax.tkind.TK_ELLIPSIS && depth < 8i32) {
|
||||
let inner: *syntax.node = resolvealias(c, unwrapbang(ev));
|
||||
if (inner != nil && inner.kind == syntax.nkind.N_TTAGGED) {
|
||||
trysubseterrs(c, u, inner.list, r, depth + 1i32);
|
||||
ev = ev.next;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
if (iserrvariant(c, u, ev)) {
|
||||
if (!tryerrinreturn(c, r.list, ev, 0i32)) {
|
||||
cerr("?: error variant not in enclosing return\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
ev = ev.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn checktryprop(c: *checker, n: *syntax.node) void = {
|
||||
if (n == nil) { return; };
|
||||
let t: *syntax.node = exprtypeoftry(c, n.lhs);
|
||||
@@ -6833,24 +6912,7 @@ fn checktryprop(c: *checker, n: *syntax.node) void = {
|
||||
c.errs += 1;
|
||||
return;
|
||||
};
|
||||
let ev: *syntax.node = u.list;
|
||||
for (ev != nil) {
|
||||
if (iserrvariant(c, u, ev)) {
|
||||
let found: bool = false;
|
||||
let rv: *syntax.node = r.list;
|
||||
for (rv != nil) {
|
||||
if (typeeqast(c, rv, ev)) {
|
||||
found = true;
|
||||
rv = nil;
|
||||
} else { rv = rv.next; };
|
||||
};
|
||||
if (!found) {
|
||||
cerr("?: error variant not in enclosing return\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
ev = ev.next;
|
||||
};
|
||||
trysubseterrs(c, u, u.list, r, 0i32);
|
||||
};
|
||||
|
||||
// hascvariadic — true iff the param list ends in a bare C-style `...`
|
||||
|
||||
Reference in New Issue
Block a user