From 360b58b2679b557aa8b8f41c0e7b747f07454f70 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 28 May 2026 06:28:59 +0900 Subject: [PATCH] wcc: stamp fn-ptr-value call results from the callee fn-type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A call whose callee is a fn-VALUE (a fn-pointer struct field like w.emit(...), or a local/param) had no free SK_FN entry, so exprtype's name lookup missed and the N_CALL went nil-stamped (the fn-ptr-field class of the asserttyped gap audit — 3 warns at smoke). When the name lookup misses, fall back to harec's check_expr_call shape: read the result off the checked callee node's own type — autodereference + dealias to the TY_FN, take its result (ref/harec/src/check.c :1566-1581). Name lookup stays primary: a fn-NAME callee node carries its return type, not its fn-type, so an N_TFN check first would mis-yield void for a fn-returning-fn; only genuine fn-value callees reach the fallback. Drives the 901 gap-corpus B count to 0 — with A/D already closed, only the legitimate abort exemption (C) remains before the bail can arm. Byte-id holds (cgen's fn-ptr detection is structural, independent of the stamp; the lib/io return-forwarding site is cs==ww on both bootstrap combined.ww). --- selfhost/cmd/w6c/main.combined.ww | 35 +++++++++++++++++++++------- selfhost/cmd/wcc/check.ww | 35 +++++++++++++++++++++------- selfhost/cmd/wwdump/main.combined.ww | 35 +++++++++++++++++++++------- test/wcc/901_asserttyped_gap.c | 6 ++--- 4 files changed, 84 insertions(+), 27 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 88948782..f2181347 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -12327,14 +12327,33 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { s = scopelookup(c.cur, nm); }; }; - if (s == nil) { return nil; }; - if (s.skind != skind.SK_FN) { return nil; }; - if (s.decl == nil) { return nil; }; - // fn-decl's lhs is the return-type AST node. Mirrors cstage - // cmd/wcc/check.c:984+ regular-CALL `n->type = build_fn_type(c, - // s->decl)->ret` shape. - e.type_ = tinfofornode(c, s.decl.lhs): *void; - return s.decl.lhs; + if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) { + // fn-decl's lhs is the return-type AST node. Mirrors cstage + // cmd/wcc/check.c:984+ regular-CALL `n->type = + // build_fn_type(c, s->decl)->ret` shape. + e.type_ = tinfofornode(c, s.decl.lhs): *void; + return s.decl.lhs; + }; }; }; + // A callee that is a fn-VALUE — a fn-pointer struct field + // (`w.emit(...)`), local, or param — has no free SK_FN entry, so + // the name lookup above misses. Read the result off the checked + // callee node's own type instead: autodereference + dealias to + // the TY_FN, then take its result. Mirrors harec's check_expr_call + // `expr->result = type_dealias(check_autodereference(lvalue-> + // result))->func.result` (ref/harec/src/check.c:1566-1581). The + // name path stays primary because a fn-NAME callee node in wwstage + // already carries its RETURN type (fn-decl.lhs), not its fn-type — + // so a `fn make() fn() void` callee would otherwise mis-yield void. + let ct: *node = resolvealias(c, unwrapbang(exprtype(c, callee, nil))); + for (ct != nil && ct.kind == nkind.N_TPTR) { + ct = resolvealias(c, unwrapbang(ct.lhs)); + }; + if (ct != nil) { if (ct.kind == nkind.N_TFN) { + let res: *node = ct.lhs; + e.type_ = tinfofornode(c, res): *void; + return res; + }; }; + return nil; }; if (k == nkind.N_DOT) { // A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 913ad10e..db5747b7 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -2297,14 +2297,33 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { s = scopelookup(c.cur, nm); }; }; - if (s == nil) { return nil; }; - if (s.skind != skind.SK_FN) { return nil; }; - if (s.decl == nil) { return nil; }; - // fn-decl's lhs is the return-type AST node. Mirrors cstage - // cmd/wcc/check.c:984+ regular-CALL `n->type = build_fn_type(c, - // s->decl)->ret` shape. - e.type_ = tinfofornode(c, s.decl.lhs): *void; - return s.decl.lhs; + if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) { + // fn-decl's lhs is the return-type AST node. Mirrors cstage + // cmd/wcc/check.c:984+ regular-CALL `n->type = + // build_fn_type(c, s->decl)->ret` shape. + e.type_ = tinfofornode(c, s.decl.lhs): *void; + return s.decl.lhs; + }; }; }; + // A callee that is a fn-VALUE — a fn-pointer struct field + // (`w.emit(...)`), local, or param — has no free SK_FN entry, so + // the name lookup above misses. Read the result off the checked + // callee node's own type instead: autodereference + dealias to + // the TY_FN, then take its result. Mirrors harec's check_expr_call + // `expr->result = type_dealias(check_autodereference(lvalue-> + // result))->func.result` (ref/harec/src/check.c:1566-1581). The + // name path stays primary because a fn-NAME callee node in wwstage + // already carries its RETURN type (fn-decl.lhs), not its fn-type — + // so a `fn make() fn() void` callee would otherwise mis-yield void. + let ct: *node = resolvealias(c, unwrapbang(exprtype(c, callee, nil))); + for (ct != nil && ct.kind == nkind.N_TPTR) { + ct = resolvealias(c, unwrapbang(ct.lhs)); + }; + if (ct != nil) { if (ct.kind == nkind.N_TFN) { + let res: *node = ct.lhs; + e.type_ = tinfofornode(c, res): *void; + return res; + }; }; + return nil; }; if (k == nkind.N_DOT) { // A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 96ca22e2..10833299 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -12327,14 +12327,33 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { s = scopelookup(c.cur, nm); }; }; - if (s == nil) { return nil; }; - if (s.skind != skind.SK_FN) { return nil; }; - if (s.decl == nil) { return nil; }; - // fn-decl's lhs is the return-type AST node. Mirrors cstage - // cmd/wcc/check.c:984+ regular-CALL `n->type = build_fn_type(c, - // s->decl)->ret` shape. - e.type_ = tinfofornode(c, s.decl.lhs): *void; - return s.decl.lhs; + if (s != nil) { if (s.skind == skind.SK_FN) { if (s.decl != nil) { + // fn-decl's lhs is the return-type AST node. Mirrors cstage + // cmd/wcc/check.c:984+ regular-CALL `n->type = + // build_fn_type(c, s->decl)->ret` shape. + e.type_ = tinfofornode(c, s.decl.lhs): *void; + return s.decl.lhs; + }; }; }; + // A callee that is a fn-VALUE — a fn-pointer struct field + // (`w.emit(...)`), local, or param — has no free SK_FN entry, so + // the name lookup above misses. Read the result off the checked + // callee node's own type instead: autodereference + dealias to + // the TY_FN, then take its result. Mirrors harec's check_expr_call + // `expr->result = type_dealias(check_autodereference(lvalue-> + // result))->func.result` (ref/harec/src/check.c:1566-1581). The + // name path stays primary because a fn-NAME callee node in wwstage + // already carries its RETURN type (fn-decl.lhs), not its fn-type — + // so a `fn make() fn() void` callee would otherwise mis-yield void. + let ct: *node = resolvealias(c, unwrapbang(exprtype(c, callee, nil))); + for (ct != nil && ct.kind == nkind.N_TPTR) { + ct = resolvealias(c, unwrapbang(ct.lhs)); + }; + if (ct != nil) { if (ct.kind == nkind.N_TFN) { + let res: *node = ct.lhs; + e.type_ = tinfofornode(c, res): *void; + return res; + }; }; + return nil; }; if (k == nkind.N_DOT) { // A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c diff --git a/test/wcc/901_asserttyped_gap.c b/test/wcc/901_asserttyped_gap.c index afd5b77a..0d252a71 100644 --- a/test/wcc/901_asserttyped_gap.c +++ b/test/wcc/901_asserttyped_gap.c @@ -21,7 +21,7 @@ * * Gap classes (counts verified empirically at this revision): * A module-qual N_DOT call result checked_test 0 (closed) - * B fn-ptr struct-field call smoke 3 + * B fn-ptr struct-field call smoke 0 (closed) * C abort intrinsic callee utf8 8 * D module-leaf == type/fn name fnmatch 0 (closed) * D module-leaf == type/fn name random 0 (closed) @@ -109,7 +109,7 @@ main(void) { "lib/math/checked/checked_test.combined.ww", "A module-qual N_DOT call result", 0 }, { "selfhost/test/smoke.combined.ww", - "B fn-ptr struct-field call", 3 }, + "B fn-ptr struct-field call", 0 }, { "lib/encoding/utf8/utf8.combined.ww", "C abort intrinsic callee", 8 }, { "lib/fnmatch/fnmatchtest.combined.ww", @@ -151,6 +151,6 @@ main(void) return 1; } printf("asserttyped_gap: ww-stage checker warn set matches manifest " - "on %d gap-corpus fixtures (B+C pinned, A+D closed)\n", n); + "on %d gap-corpus fixtures (C pinned, A+B+D closed)\n", n); return 0; }