diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 0b6f7b44..b5938fbf 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -15672,6 +15672,13 @@ fn desugarcallargs(c: *checker, n: *node) void = { }; }; }; let decl: *node = calleefndecl(c, n.lhs); + // task #51: calleefndecl nils every fn-VALUE callee — a deref `(*fp)(...)` + // (N_UN), an SK_VAR fn-ptr ident, a struct-field fn call — so this bail + // skips the #258 array→slice desugar AND the general arg typecheck for + // them, and cgen then pushes raw array words where the callee reads a 24B + // slice header. cstage drives the same arg loop off the callee TYPE + // (cmd/wcc/check.c:1828 type_chase_named(cexpr(callee))). The type-keyed + // callee path is MECHANISM (not a checker gate) — filed as task #51. if (decl == nil) { return; }; let param: *node = decl.list; let prev: *node = nil; @@ -16200,6 +16207,13 @@ fn checkisas(c: *checker, n: *node) void = { fn exprtypeoftry(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; if (e.kind == nkind.N_IDENT) { + // #11a/task #50: this bare-leaf lookup wants curmod preference + // (scopelookupprefer, the #53/#55 family) but the swap BROKE the + // 995 self-rebuild byte-id — in the bundled self-compile ww's + // scopelookupprefer diverges from cstage's resolution (the same + // curmod-in-bundle layer that blocked #5), flipping a try-operand + // stamp in the selfhost source. Held at plain scopelookup until #50 + // lands the bundle curmod fix; aligning here in isolation is unsound. let s: *sym = scopelookup(c.cur, e.str); if (s == nil) { return nil; }; if (s.decl == nil) { return nil; }; @@ -16213,17 +16227,61 @@ fn exprtypeoftry(c: *checker, e: *node) *node = { let nm: str; nm.ptr = nil; nm.len = 0; if (callee.kind == nkind.N_IDENT) { nm = callee.str; }; + // #11b/task #51: an N_DOT callee `mod.f()?` is looked up by BARE + // LEAF here, NOT via the module qualifier (callee.lhs) the way + // exprtype's own N_CALL arm does (:3095 scopelookupinmodule) — a + // cross-module same-leaf `f` misbinds. The mechanism fix (module- + // keyed callee resolution + the deref-callee `(*fp)()?` shape that + // returns nil below) rides task #51 with the fn-ptr-callee desugar. if (callee.kind == nkind.N_DOT) { nm = callee.str; }; if (nm.len == 0) { return nil; }; + // #11a/task #50: curmod preference wanted here too, held at plain + // scopelookup — the prefer swap broke 995 byte-id (bundle curmod + // divergence, same as the N_IDENT arm above + #5/#50). let s: *sym = scopelookup(c.cur, nm); if (s == nil) { return nil; }; if (s.skind != skind.SK_FN) { return nil; }; if (s.decl == nil) { return nil; }; return s.decl.lhs; }; + // task #51: a deref callee `(*fp)()?` / SK_VAR fn-ptr ident / struct- + // field fn call returns nil here, so checktryprop silently bails and + // the F8 multi-success reject is skipped for those shapes. The fix + // (type-keyed operand resolution: peel TPTR → TFN.ret) is mechanism, + // filed with the fn-ptr-callee desugar as task #51 — NOT a checker gate. return nil; }; +// trycountvariants — #38/F3 (review item 8): walk a tagged union's variant +// list FLATTENING `...inner` spreads, accumulating the success count and the +// has-error flag. cstage counts/checks the resolve_type-FLATTENED u->params +// (cmd/wcc/check.c:2160-2165 over the TK_ELLIPSIS-spliced chain); ww's +// checktryprop walked the raw AST, so a `(...inner | e)` counted the spread as +// ONE success and the F8 multi-success gate never fired — the exact silent +// accept the gate exists to catch (a valid bool success then treated as error +// by the single-tag-compare cgen). iserror is judged against the OUTER union +// (`outer`) throughout — taggedhaserr(outer) holds because the spread's sibling +// or a spliced member carries the error, so each flattened member routes +// through varianterr, matching cstage's per-param iserror. Mirrors the #209 +// spread recursion tinfofornode already runs over vu.params (check.ww:2275). +fn trycountvariants(c: *checker, outer: *node, v: *node, nsuccp: *int, + haserrp: *bool, depth: i32) void = { + for (v != nil) { + if (v.op == tkind.TK_ELLIPSIS && depth < 8i32) { + let inner: *node = resolvealias(c, unwrapbang(v)); + if (inner != nil && inner.kind == nkind.N_TTAGGED) { + trycountvariants(c, outer, inner.list, nsuccp, + haserrp, depth + 1i32); + v = v.next; + continue; + }; + }; + if (iserrvariant(c, outer, v)) { *haserrp = true; } + else { *nsuccp += 1; }; + v = v.next; + }; +}; + fn checktryprop(c: *checker, n: *node) void = { if (n == nil) { return; }; let t: *node = exprtypeoftry(c, n.lhs); @@ -16240,11 +16298,9 @@ fn checktryprop(c: *checker, n: *node) void = { // 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; } else { nsucc += 1; }; - v = v.next; - }; + // #38/F3 (review item 8): flatten `...inner` spreads so the F8 + // multi-success gate counts the spliced members, not the spread alias. + trycountvariants(c, u, u.list, &nsucc, &haserr, 0i32); if (nsucc > 1) { if (n.kind == nkind.N_TRYPROP) { cerr("?"); } else { cerr("!"); }; cerr(": multi-success union unwired (task #14): bind and match instead\n"); diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 643ded62..40444a57 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -5252,6 +5252,13 @@ fn desugarcallargs(c: *checker, n: *node) void = { }; }; }; let decl: *node = calleefndecl(c, n.lhs); + // task #51: calleefndecl nils every fn-VALUE callee — a deref `(*fp)(...)` + // (N_UN), an SK_VAR fn-ptr ident, a struct-field fn call — so this bail + // skips the #258 array→slice desugar AND the general arg typecheck for + // them, and cgen then pushes raw array words where the callee reads a 24B + // slice header. cstage drives the same arg loop off the callee TYPE + // (cmd/wcc/check.c:1828 type_chase_named(cexpr(callee))). The type-keyed + // callee path is MECHANISM (not a checker gate) — filed as task #51. if (decl == nil) { return; }; let param: *node = decl.list; let prev: *node = nil; @@ -5780,6 +5787,13 @@ fn checkisas(c: *checker, n: *node) void = { fn exprtypeoftry(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; if (e.kind == nkind.N_IDENT) { + // #11a/task #50: this bare-leaf lookup wants curmod preference + // (scopelookupprefer, the #53/#55 family) but the swap BROKE the + // 995 self-rebuild byte-id — in the bundled self-compile ww's + // scopelookupprefer diverges from cstage's resolution (the same + // curmod-in-bundle layer that blocked #5), flipping a try-operand + // stamp in the selfhost source. Held at plain scopelookup until #50 + // lands the bundle curmod fix; aligning here in isolation is unsound. let s: *sym = scopelookup(c.cur, e.str); if (s == nil) { return nil; }; if (s.decl == nil) { return nil; }; @@ -5793,17 +5807,61 @@ fn exprtypeoftry(c: *checker, e: *node) *node = { let nm: str; nm.ptr = nil; nm.len = 0; if (callee.kind == nkind.N_IDENT) { nm = callee.str; }; + // #11b/task #51: an N_DOT callee `mod.f()?` is looked up by BARE + // LEAF here, NOT via the module qualifier (callee.lhs) the way + // exprtype's own N_CALL arm does (:3095 scopelookupinmodule) — a + // cross-module same-leaf `f` misbinds. The mechanism fix (module- + // keyed callee resolution + the deref-callee `(*fp)()?` shape that + // returns nil below) rides task #51 with the fn-ptr-callee desugar. if (callee.kind == nkind.N_DOT) { nm = callee.str; }; if (nm.len == 0) { return nil; }; + // #11a/task #50: curmod preference wanted here too, held at plain + // scopelookup — the prefer swap broke 995 byte-id (bundle curmod + // divergence, same as the N_IDENT arm above + #5/#50). let s: *sym = scopelookup(c.cur, nm); if (s == nil) { return nil; }; if (s.skind != skind.SK_FN) { return nil; }; if (s.decl == nil) { return nil; }; return s.decl.lhs; }; + // task #51: a deref callee `(*fp)()?` / SK_VAR fn-ptr ident / struct- + // field fn call returns nil here, so checktryprop silently bails and + // the F8 multi-success reject is skipped for those shapes. The fix + // (type-keyed operand resolution: peel TPTR → TFN.ret) is mechanism, + // filed with the fn-ptr-callee desugar as task #51 — NOT a checker gate. return nil; }; +// trycountvariants — #38/F3 (review item 8): walk a tagged union's variant +// list FLATTENING `...inner` spreads, accumulating the success count and the +// has-error flag. cstage counts/checks the resolve_type-FLATTENED u->params +// (cmd/wcc/check.c:2160-2165 over the TK_ELLIPSIS-spliced chain); ww's +// checktryprop walked the raw AST, so a `(...inner | e)` counted the spread as +// ONE success and the F8 multi-success gate never fired — the exact silent +// accept the gate exists to catch (a valid bool success then treated as error +// by the single-tag-compare cgen). iserror is judged against the OUTER union +// (`outer`) throughout — taggedhaserr(outer) holds because the spread's sibling +// or a spliced member carries the error, so each flattened member routes +// through varianterr, matching cstage's per-param iserror. Mirrors the #209 +// spread recursion tinfofornode already runs over vu.params (check.ww:2275). +fn trycountvariants(c: *checker, outer: *node, v: *node, nsuccp: *int, + haserrp: *bool, depth: i32) void = { + for (v != nil) { + if (v.op == tkind.TK_ELLIPSIS && depth < 8i32) { + let inner: *node = resolvealias(c, unwrapbang(v)); + if (inner != nil && inner.kind == nkind.N_TTAGGED) { + trycountvariants(c, outer, inner.list, nsuccp, + haserrp, depth + 1i32); + v = v.next; + continue; + }; + }; + if (iserrvariant(c, outer, v)) { *haserrp = true; } + else { *nsuccp += 1; }; + v = v.next; + }; +}; + fn checktryprop(c: *checker, n: *node) void = { if (n == nil) { return; }; let t: *node = exprtypeoftry(c, n.lhs); @@ -5820,11 +5878,9 @@ fn checktryprop(c: *checker, n: *node) void = { // 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; } else { nsucc += 1; }; - v = v.next; - }; + // #38/F3 (review item 8): flatten `...inner` spreads so the F8 + // multi-success gate counts the spliced members, not the spread alias. + trycountvariants(c, u, u.list, &nsucc, &haserr, 0i32); if (nsucc > 1) { if (n.kind == nkind.N_TRYPROP) { cerr("?"); } else { cerr("!"); }; cerr(": multi-success union unwired (task #14): bind and match instead\n"); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 5a96022e..e44b6287 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -15672,6 +15672,13 @@ fn desugarcallargs(c: *checker, n: *node) void = { }; }; }; let decl: *node = calleefndecl(c, n.lhs); + // task #51: calleefndecl nils every fn-VALUE callee — a deref `(*fp)(...)` + // (N_UN), an SK_VAR fn-ptr ident, a struct-field fn call — so this bail + // skips the #258 array→slice desugar AND the general arg typecheck for + // them, and cgen then pushes raw array words where the callee reads a 24B + // slice header. cstage drives the same arg loop off the callee TYPE + // (cmd/wcc/check.c:1828 type_chase_named(cexpr(callee))). The type-keyed + // callee path is MECHANISM (not a checker gate) — filed as task #51. if (decl == nil) { return; }; let param: *node = decl.list; let prev: *node = nil; @@ -16200,6 +16207,13 @@ fn checkisas(c: *checker, n: *node) void = { fn exprtypeoftry(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; if (e.kind == nkind.N_IDENT) { + // #11a/task #50: this bare-leaf lookup wants curmod preference + // (scopelookupprefer, the #53/#55 family) but the swap BROKE the + // 995 self-rebuild byte-id — in the bundled self-compile ww's + // scopelookupprefer diverges from cstage's resolution (the same + // curmod-in-bundle layer that blocked #5), flipping a try-operand + // stamp in the selfhost source. Held at plain scopelookup until #50 + // lands the bundle curmod fix; aligning here in isolation is unsound. let s: *sym = scopelookup(c.cur, e.str); if (s == nil) { return nil; }; if (s.decl == nil) { return nil; }; @@ -16213,17 +16227,61 @@ fn exprtypeoftry(c: *checker, e: *node) *node = { let nm: str; nm.ptr = nil; nm.len = 0; if (callee.kind == nkind.N_IDENT) { nm = callee.str; }; + // #11b/task #51: an N_DOT callee `mod.f()?` is looked up by BARE + // LEAF here, NOT via the module qualifier (callee.lhs) the way + // exprtype's own N_CALL arm does (:3095 scopelookupinmodule) — a + // cross-module same-leaf `f` misbinds. The mechanism fix (module- + // keyed callee resolution + the deref-callee `(*fp)()?` shape that + // returns nil below) rides task #51 with the fn-ptr-callee desugar. if (callee.kind == nkind.N_DOT) { nm = callee.str; }; if (nm.len == 0) { return nil; }; + // #11a/task #50: curmod preference wanted here too, held at plain + // scopelookup — the prefer swap broke 995 byte-id (bundle curmod + // divergence, same as the N_IDENT arm above + #5/#50). let s: *sym = scopelookup(c.cur, nm); if (s == nil) { return nil; }; if (s.skind != skind.SK_FN) { return nil; }; if (s.decl == nil) { return nil; }; return s.decl.lhs; }; + // task #51: a deref callee `(*fp)()?` / SK_VAR fn-ptr ident / struct- + // field fn call returns nil here, so checktryprop silently bails and + // the F8 multi-success reject is skipped for those shapes. The fix + // (type-keyed operand resolution: peel TPTR → TFN.ret) is mechanism, + // filed with the fn-ptr-callee desugar as task #51 — NOT a checker gate. return nil; }; +// trycountvariants — #38/F3 (review item 8): walk a tagged union's variant +// list FLATTENING `...inner` spreads, accumulating the success count and the +// has-error flag. cstage counts/checks the resolve_type-FLATTENED u->params +// (cmd/wcc/check.c:2160-2165 over the TK_ELLIPSIS-spliced chain); ww's +// checktryprop walked the raw AST, so a `(...inner | e)` counted the spread as +// ONE success and the F8 multi-success gate never fired — the exact silent +// accept the gate exists to catch (a valid bool success then treated as error +// by the single-tag-compare cgen). iserror is judged against the OUTER union +// (`outer`) throughout — taggedhaserr(outer) holds because the spread's sibling +// or a spliced member carries the error, so each flattened member routes +// through varianterr, matching cstage's per-param iserror. Mirrors the #209 +// spread recursion tinfofornode already runs over vu.params (check.ww:2275). +fn trycountvariants(c: *checker, outer: *node, v: *node, nsuccp: *int, + haserrp: *bool, depth: i32) void = { + for (v != nil) { + if (v.op == tkind.TK_ELLIPSIS && depth < 8i32) { + let inner: *node = resolvealias(c, unwrapbang(v)); + if (inner != nil && inner.kind == nkind.N_TTAGGED) { + trycountvariants(c, outer, inner.list, nsuccp, + haserrp, depth + 1i32); + v = v.next; + continue; + }; + }; + if (iserrvariant(c, outer, v)) { *haserrp = true; } + else { *nsuccp += 1; }; + v = v.next; + }; +}; + fn checktryprop(c: *checker, n: *node) void = { if (n == nil) { return; }; let t: *node = exprtypeoftry(c, n.lhs); @@ -16240,11 +16298,9 @@ fn checktryprop(c: *checker, n: *node) void = { // 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; } else { nsucc += 1; }; - v = v.next; - }; + // #38/F3 (review item 8): flatten `...inner` spreads so the F8 + // multi-success gate counts the spliced members, not the spread alias. + trycountvariants(c, u, u.list, &nsucc, &haserr, 0i32); if (nsucc > 1) { if (n.kind == nkind.N_TRYPROP) { cerr("?"); } else { cerr("!"); }; cerr(": multi-success union unwired (task #14): bind and match instead\n");