wcc+w6c_ww: loud-gate try-propagation over multi-success unions (F8/F9 interim)
? and ! assume ONE success member end-to-end: the checker collapses the result to the first non-error variant (check.c tagged_success_type / check.ww exprtype) and cgen emits a single tag compare, so any other success member is silently mistaken for an error — ? propagates it to the caller (p11h: []capture read back as nomem, exit 21), ! aborts on it. Until the honest subset-union result typing lands (task #14, harec check.c:2759-2835), both stages loud-reject |success| > 1 at the checker choke-points (one per stage), identical diagnostic, both ops per rob's one-class ruling (#133 precedent). (T|err1|err2) — one success, many errors — stays legal (925 canary + new accept rows). F9 rides along (task #12): wwstage scruttype only resolves IDENT/DOT, so the direct forms f()? is T / match(f()?) / f()! is T slipped its lenient-miss contract and were silently ACCEPTED where cstage rejects (cs!=ww, gate-blind). checkisas/checkmatchexhaust now resolve the try-result via exprtype, keyed on the RESOLVED success type — a named tagged success ((ab|nomem)? is i32) keeps being accepted, matching cstage's verdict empirically. test/wcc/806: 11 rows x dual driver + byte-id accepts (26 fixtures); reject rows pin exact per-stage diagnostic text; p11h + q_card2_unw graduated to rejects; call-arg-position reject + void-success accept pin position-independence and the dominant lib/ (void|err)? shape. Tasks #5 + #12; #14 lifts both gates together.
This commit is contained in:
@@ -10642,6 +10642,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
// is examined before the arm bodies install new bindings.
|
||||
if (k == nkind.N_MATCH) { checkmatchexhaust(c, n); };
|
||||
if (k == nkind.N_TRYPROP) { checktryprop(c, n); };
|
||||
if (k == nkind.N_TRYUNW) { checktryprop(c, n); };
|
||||
if (k == nkind.N_TYPETEST) { checkisas(c, n); };
|
||||
if (k == nkind.N_TYPEASSERT) { checkisas(c, n); };
|
||||
if (k == nkind.N_LET) { checkletassign(c, n); };
|
||||
@@ -14029,9 +14030,30 @@ fn checkmatchexhaust(c: *checker, n: *node) void = {
|
||||
if (n == nil) { return; };
|
||||
if (n.lhs == nil) { return; };
|
||||
let st: *node = scruttype(c, n.lhs);
|
||||
// F9 (task #12): direct `match (expr?)` / `match (expr!)` — cstage
|
||||
// types the try-result as the success variant and rejects when it
|
||||
// is not itself a tagged union (check.c "match on non-tagged-
|
||||
// union"); scruttype's IDENT/DOT-only resolution let the form slip
|
||||
// through silently (cs≠ww). The reject below is gated on the
|
||||
// try-form so the lenient-miss contract for other unresolvable
|
||||
// scrutinees is untouched; a tagged success keeps flowing into the
|
||||
// normal exhaustiveness walk, matching cstage's accept.
|
||||
let istry: bool = false;
|
||||
if (st == nil) {
|
||||
if (n.lhs.kind == nkind.N_TRYPROP || n.lhs.kind == nkind.N_TRYUNW) {
|
||||
istry = true;
|
||||
st = exprtype(c, n.lhs, nil);
|
||||
};
|
||||
};
|
||||
let u: *node = resolvealias(c, unwrapbang(st));
|
||||
if (u == nil) { return; };
|
||||
if (u.kind != nkind.N_TTAGGED) { return; };
|
||||
if (u.kind != nkind.N_TTAGGED) {
|
||||
if (istry) {
|
||||
cerr("match on non-tagged-union try-result\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
// #13: the union's defining module, so a bare body variant can be
|
||||
// matched against a module-qualified cross-module case pattern (and
|
||||
// a foreign-qualifier pattern correctly rejected). See
|
||||
@@ -14610,6 +14632,19 @@ fn checkisas(c: *checker, n: *node) void = {
|
||||
if (n == nil) { return; };
|
||||
// e is in n.lhs (value), T is in n.rhs (type expr).
|
||||
let st: *node = scruttype(c, n.lhs);
|
||||
// F9 (task #12): direct `expr? is T` / `expr! is T` — cstage cexpr
|
||||
// types the ?/! result as the success variant and the non-tagged
|
||||
// gate below then rejects (check.c "is on non-tagged-union").
|
||||
// scruttype only resolves IDENT/DOT, so the direct try-form slipped
|
||||
// through the lenient-miss contract and wwstage silently ACCEPTED
|
||||
// (cs≠ww). Resolve the try-result here; a tagged success (named
|
||||
// union variant) flows on into the variant checks, matching
|
||||
// cstage's accept.
|
||||
if (st == nil && n.lhs != nil) {
|
||||
if (n.lhs.kind == nkind.N_TRYPROP || n.lhs.kind == nkind.N_TRYUNW) {
|
||||
st = exprtype(c, n.lhs, nil);
|
||||
};
|
||||
};
|
||||
let u: *node = resolvealias(c, unwrapbang(st));
|
||||
if (u == nil) { return; };
|
||||
// #52: enum ↔ int reinterpret (`enum as intT` / `intT as enum`).
|
||||
@@ -14666,8 +14701,10 @@ fn checkisas(c: *checker, n: *node) void = {
|
||||
//
|
||||
// For `expr?`, the operand's error subset must be a subset of the
|
||||
// enclosing fn's return-type variants. Mirrors C check.c. Operand
|
||||
// is nkind.N_TRYPROP; its lhs is the value-bearing expr; we look at the
|
||||
// expr's *declared* type for nkind.N_IDENT/nkind.N_CALL cases.
|
||||
// is nkind.N_TRYPROP or nkind.N_TRYUNW (the F8 cardinality gate covers
|
||||
// both; the subset walk is ?-only); its lhs is the value-bearing expr;
|
||||
// we look at the expr's *declared* type for nkind.N_IDENT/nkind.N_CALL
|
||||
// cases.
|
||||
|
||||
fn exprtypeoftry(c: *checker, e: *node) *node = {
|
||||
if (e == nil) { return nil; };
|
||||
@@ -14702,13 +14739,30 @@ fn checktryprop(c: *checker, n: *node) void = {
|
||||
let u: *node = resolvealias(c, unwrapbang(t));
|
||||
if (u == nil) { return; };
|
||||
if (u.kind != nkind.N_TTAGGED) { return; };
|
||||
// Does the operand have any error variants?
|
||||
// F8 interim gate (task #5): try-propagation assumes ONE success
|
||||
// member end-to-end — exprtype collapses to the first non-error
|
||||
// variant and cgen emits a single tag compare, so any OTHER
|
||||
// success member is silently mistaken for an error (? propagates
|
||||
// it; ! aborts on it). One class, both ops (#133 precedent).
|
||||
// Until the honest subset-union result typing lands (task #14,
|
||||
// harec check.c:2759-2835), reject loud. Mirrors cstage check.c
|
||||
// N_TRYPROP/N_TRYUNW.
|
||||
let haserr: bool = false;
|
||||
let nsucc: int = 0;
|
||||
let v: *node = u.list;
|
||||
for (v != nil) {
|
||||
if (iserrvariant(c, u, v)) { haserr = true; };
|
||||
if (iserrvariant(c, u, v)) { haserr = true; } else { nsucc += 1; };
|
||||
v = v.next;
|
||||
};
|
||||
if (nsucc > 1) {
|
||||
if (n.kind == nkind.N_TRYPROP) { cerr("?"); } else { cerr("!"); };
|
||||
cerr(": multi-success union unwired (task #14): bind and match instead\n");
|
||||
c.errs += 1;
|
||||
return;
|
||||
};
|
||||
// `!` has no propagation, so no error-subset check (mirrors
|
||||
// cstage's N_TRYPROP-only guard on the subset walk).
|
||||
if (n.kind != nkind.N_TRYPROP) { return; };
|
||||
if (!haserr) { return; };
|
||||
// Enclosing fn must return a tagged union with each operand
|
||||
// error variant present.
|
||||
|
||||
@@ -315,6 +315,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
// is examined before the arm bodies install new bindings.
|
||||
if (k == nkind.N_MATCH) { checkmatchexhaust(c, n); };
|
||||
if (k == nkind.N_TRYPROP) { checktryprop(c, n); };
|
||||
if (k == nkind.N_TRYUNW) { checktryprop(c, n); };
|
||||
if (k == nkind.N_TYPETEST) { checkisas(c, n); };
|
||||
if (k == nkind.N_TYPEASSERT) { checkisas(c, n); };
|
||||
if (k == nkind.N_LET) { checkletassign(c, n); };
|
||||
@@ -3702,9 +3703,30 @@ fn checkmatchexhaust(c: *checker, n: *node) void = {
|
||||
if (n == nil) { return; };
|
||||
if (n.lhs == nil) { return; };
|
||||
let st: *node = scruttype(c, n.lhs);
|
||||
// F9 (task #12): direct `match (expr?)` / `match (expr!)` — cstage
|
||||
// types the try-result as the success variant and rejects when it
|
||||
// is not itself a tagged union (check.c "match on non-tagged-
|
||||
// union"); scruttype's IDENT/DOT-only resolution let the form slip
|
||||
// through silently (cs≠ww). The reject below is gated on the
|
||||
// try-form so the lenient-miss contract for other unresolvable
|
||||
// scrutinees is untouched; a tagged success keeps flowing into the
|
||||
// normal exhaustiveness walk, matching cstage's accept.
|
||||
let istry: bool = false;
|
||||
if (st == nil) {
|
||||
if (n.lhs.kind == nkind.N_TRYPROP || n.lhs.kind == nkind.N_TRYUNW) {
|
||||
istry = true;
|
||||
st = exprtype(c, n.lhs, nil);
|
||||
};
|
||||
};
|
||||
let u: *node = resolvealias(c, unwrapbang(st));
|
||||
if (u == nil) { return; };
|
||||
if (u.kind != nkind.N_TTAGGED) { return; };
|
||||
if (u.kind != nkind.N_TTAGGED) {
|
||||
if (istry) {
|
||||
cerr("match on non-tagged-union try-result\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
// #13: the union's defining module, so a bare body variant can be
|
||||
// matched against a module-qualified cross-module case pattern (and
|
||||
// a foreign-qualifier pattern correctly rejected). See
|
||||
@@ -4283,6 +4305,19 @@ fn checkisas(c: *checker, n: *node) void = {
|
||||
if (n == nil) { return; };
|
||||
// e is in n.lhs (value), T is in n.rhs (type expr).
|
||||
let st: *node = scruttype(c, n.lhs);
|
||||
// F9 (task #12): direct `expr? is T` / `expr! is T` — cstage cexpr
|
||||
// types the ?/! result as the success variant and the non-tagged
|
||||
// gate below then rejects (check.c "is on non-tagged-union").
|
||||
// scruttype only resolves IDENT/DOT, so the direct try-form slipped
|
||||
// through the lenient-miss contract and wwstage silently ACCEPTED
|
||||
// (cs≠ww). Resolve the try-result here; a tagged success (named
|
||||
// union variant) flows on into the variant checks, matching
|
||||
// cstage's accept.
|
||||
if (st == nil && n.lhs != nil) {
|
||||
if (n.lhs.kind == nkind.N_TRYPROP || n.lhs.kind == nkind.N_TRYUNW) {
|
||||
st = exprtype(c, n.lhs, nil);
|
||||
};
|
||||
};
|
||||
let u: *node = resolvealias(c, unwrapbang(st));
|
||||
if (u == nil) { return; };
|
||||
// #52: enum ↔ int reinterpret (`enum as intT` / `intT as enum`).
|
||||
@@ -4339,8 +4374,10 @@ fn checkisas(c: *checker, n: *node) void = {
|
||||
//
|
||||
// For `expr?`, the operand's error subset must be a subset of the
|
||||
// enclosing fn's return-type variants. Mirrors C check.c. Operand
|
||||
// is nkind.N_TRYPROP; its lhs is the value-bearing expr; we look at the
|
||||
// expr's *declared* type for nkind.N_IDENT/nkind.N_CALL cases.
|
||||
// is nkind.N_TRYPROP or nkind.N_TRYUNW (the F8 cardinality gate covers
|
||||
// both; the subset walk is ?-only); its lhs is the value-bearing expr;
|
||||
// we look at the expr's *declared* type for nkind.N_IDENT/nkind.N_CALL
|
||||
// cases.
|
||||
|
||||
fn exprtypeoftry(c: *checker, e: *node) *node = {
|
||||
if (e == nil) { return nil; };
|
||||
@@ -4375,13 +4412,30 @@ fn checktryprop(c: *checker, n: *node) void = {
|
||||
let u: *node = resolvealias(c, unwrapbang(t));
|
||||
if (u == nil) { return; };
|
||||
if (u.kind != nkind.N_TTAGGED) { return; };
|
||||
// Does the operand have any error variants?
|
||||
// F8 interim gate (task #5): try-propagation assumes ONE success
|
||||
// member end-to-end — exprtype collapses to the first non-error
|
||||
// variant and cgen emits a single tag compare, so any OTHER
|
||||
// success member is silently mistaken for an error (? propagates
|
||||
// it; ! aborts on it). One class, both ops (#133 precedent).
|
||||
// Until the honest subset-union result typing lands (task #14,
|
||||
// harec check.c:2759-2835), reject loud. Mirrors cstage check.c
|
||||
// N_TRYPROP/N_TRYUNW.
|
||||
let haserr: bool = false;
|
||||
let nsucc: int = 0;
|
||||
let v: *node = u.list;
|
||||
for (v != nil) {
|
||||
if (iserrvariant(c, u, v)) { haserr = true; };
|
||||
if (iserrvariant(c, u, v)) { haserr = true; } else { nsucc += 1; };
|
||||
v = v.next;
|
||||
};
|
||||
if (nsucc > 1) {
|
||||
if (n.kind == nkind.N_TRYPROP) { cerr("?"); } else { cerr("!"); };
|
||||
cerr(": multi-success union unwired (task #14): bind and match instead\n");
|
||||
c.errs += 1;
|
||||
return;
|
||||
};
|
||||
// `!` has no propagation, so no error-subset check (mirrors
|
||||
// cstage's N_TRYPROP-only guard on the subset walk).
|
||||
if (n.kind != nkind.N_TRYPROP) { return; };
|
||||
if (!haserr) { return; };
|
||||
// Enclosing fn must return a tagged union with each operand
|
||||
// error variant present.
|
||||
|
||||
@@ -10642,6 +10642,7 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
// is examined before the arm bodies install new bindings.
|
||||
if (k == nkind.N_MATCH) { checkmatchexhaust(c, n); };
|
||||
if (k == nkind.N_TRYPROP) { checktryprop(c, n); };
|
||||
if (k == nkind.N_TRYUNW) { checktryprop(c, n); };
|
||||
if (k == nkind.N_TYPETEST) { checkisas(c, n); };
|
||||
if (k == nkind.N_TYPEASSERT) { checkisas(c, n); };
|
||||
if (k == nkind.N_LET) { checkletassign(c, n); };
|
||||
@@ -14029,9 +14030,30 @@ fn checkmatchexhaust(c: *checker, n: *node) void = {
|
||||
if (n == nil) { return; };
|
||||
if (n.lhs == nil) { return; };
|
||||
let st: *node = scruttype(c, n.lhs);
|
||||
// F9 (task #12): direct `match (expr?)` / `match (expr!)` — cstage
|
||||
// types the try-result as the success variant and rejects when it
|
||||
// is not itself a tagged union (check.c "match on non-tagged-
|
||||
// union"); scruttype's IDENT/DOT-only resolution let the form slip
|
||||
// through silently (cs≠ww). The reject below is gated on the
|
||||
// try-form so the lenient-miss contract for other unresolvable
|
||||
// scrutinees is untouched; a tagged success keeps flowing into the
|
||||
// normal exhaustiveness walk, matching cstage's accept.
|
||||
let istry: bool = false;
|
||||
if (st == nil) {
|
||||
if (n.lhs.kind == nkind.N_TRYPROP || n.lhs.kind == nkind.N_TRYUNW) {
|
||||
istry = true;
|
||||
st = exprtype(c, n.lhs, nil);
|
||||
};
|
||||
};
|
||||
let u: *node = resolvealias(c, unwrapbang(st));
|
||||
if (u == nil) { return; };
|
||||
if (u.kind != nkind.N_TTAGGED) { return; };
|
||||
if (u.kind != nkind.N_TTAGGED) {
|
||||
if (istry) {
|
||||
cerr("match on non-tagged-union try-result\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
// #13: the union's defining module, so a bare body variant can be
|
||||
// matched against a module-qualified cross-module case pattern (and
|
||||
// a foreign-qualifier pattern correctly rejected). See
|
||||
@@ -14610,6 +14632,19 @@ fn checkisas(c: *checker, n: *node) void = {
|
||||
if (n == nil) { return; };
|
||||
// e is in n.lhs (value), T is in n.rhs (type expr).
|
||||
let st: *node = scruttype(c, n.lhs);
|
||||
// F9 (task #12): direct `expr? is T` / `expr! is T` — cstage cexpr
|
||||
// types the ?/! result as the success variant and the non-tagged
|
||||
// gate below then rejects (check.c "is on non-tagged-union").
|
||||
// scruttype only resolves IDENT/DOT, so the direct try-form slipped
|
||||
// through the lenient-miss contract and wwstage silently ACCEPTED
|
||||
// (cs≠ww). Resolve the try-result here; a tagged success (named
|
||||
// union variant) flows on into the variant checks, matching
|
||||
// cstage's accept.
|
||||
if (st == nil && n.lhs != nil) {
|
||||
if (n.lhs.kind == nkind.N_TRYPROP || n.lhs.kind == nkind.N_TRYUNW) {
|
||||
st = exprtype(c, n.lhs, nil);
|
||||
};
|
||||
};
|
||||
let u: *node = resolvealias(c, unwrapbang(st));
|
||||
if (u == nil) { return; };
|
||||
// #52: enum ↔ int reinterpret (`enum as intT` / `intT as enum`).
|
||||
@@ -14666,8 +14701,10 @@ fn checkisas(c: *checker, n: *node) void = {
|
||||
//
|
||||
// For `expr?`, the operand's error subset must be a subset of the
|
||||
// enclosing fn's return-type variants. Mirrors C check.c. Operand
|
||||
// is nkind.N_TRYPROP; its lhs is the value-bearing expr; we look at the
|
||||
// expr's *declared* type for nkind.N_IDENT/nkind.N_CALL cases.
|
||||
// is nkind.N_TRYPROP or nkind.N_TRYUNW (the F8 cardinality gate covers
|
||||
// both; the subset walk is ?-only); its lhs is the value-bearing expr;
|
||||
// we look at the expr's *declared* type for nkind.N_IDENT/nkind.N_CALL
|
||||
// cases.
|
||||
|
||||
fn exprtypeoftry(c: *checker, e: *node) *node = {
|
||||
if (e == nil) { return nil; };
|
||||
@@ -14702,13 +14739,30 @@ fn checktryprop(c: *checker, n: *node) void = {
|
||||
let u: *node = resolvealias(c, unwrapbang(t));
|
||||
if (u == nil) { return; };
|
||||
if (u.kind != nkind.N_TTAGGED) { return; };
|
||||
// Does the operand have any error variants?
|
||||
// F8 interim gate (task #5): try-propagation assumes ONE success
|
||||
// member end-to-end — exprtype collapses to the first non-error
|
||||
// variant and cgen emits a single tag compare, so any OTHER
|
||||
// success member is silently mistaken for an error (? propagates
|
||||
// it; ! aborts on it). One class, both ops (#133 precedent).
|
||||
// Until the honest subset-union result typing lands (task #14,
|
||||
// harec check.c:2759-2835), reject loud. Mirrors cstage check.c
|
||||
// N_TRYPROP/N_TRYUNW.
|
||||
let haserr: bool = false;
|
||||
let nsucc: int = 0;
|
||||
let v: *node = u.list;
|
||||
for (v != nil) {
|
||||
if (iserrvariant(c, u, v)) { haserr = true; };
|
||||
if (iserrvariant(c, u, v)) { haserr = true; } else { nsucc += 1; };
|
||||
v = v.next;
|
||||
};
|
||||
if (nsucc > 1) {
|
||||
if (n.kind == nkind.N_TRYPROP) { cerr("?"); } else { cerr("!"); };
|
||||
cerr(": multi-success union unwired (task #14): bind and match instead\n");
|
||||
c.errs += 1;
|
||||
return;
|
||||
};
|
||||
// `!` has no propagation, so no error-subset check (mirrors
|
||||
// cstage's N_TRYPROP-only guard on the subset walk).
|
||||
if (n.kind != nkind.N_TRYPROP) { return; };
|
||||
if (!haserr) { return; };
|
||||
// Enclosing fn must return a tagged union with each operand
|
||||
// error variant present.
|
||||
|
||||
Reference in New Issue
Block a user