From fa17459558389e3d44d88e2d6fb11511cc25c71e Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 11 Jun 2026 20:37:47 +0900 Subject: [PATCH] wcc/ww: exprtype stamps bare fn rvalue with its fn type, not return type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For an SK_FN sym, exprtype's N_IDENT and N_DOT arms returned decl.lhs (the return type), so a bare fn rvalue compared as its return type — latent silent mis-accept of mismatched-signature fn assignment (the 706/768 over-reject under any general check was the visible face). Synthesize the N_TFN instead (mirror of assignableaddrfn's synth), and add the N_TFN-vs-N_TFN confident-mismatch reject in isassignable. harec: resolve_function check.c:4279-4288 (obj->type=fntype), EXP_ACCESS check.c:341-343 (bare ident yields obj->type, no decay), call check.c:1573, assignability types.c:1001; cstage twin check.c:1313 + build_fn_type check.c:2611. cgen is name-keyed on fn rvalues (never reads the stamp): zero asm delta, byte-id held. --- selfhost/cmd/w6c/main.combined.ww | 45 ++++++++++++++++++++++++++++ selfhost/cmd/wcc/check.ww | 45 ++++++++++++++++++++++++++++ selfhost/cmd/wwdump/main.combined.ww | 45 ++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 3c5e41fa..06856511 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -13105,6 +13105,25 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { let s: *sym = scopelookupprefer(c.cur, c.curmod, e.str); if (s == nil) { return nil; }; if (s.decl == nil) { return nil; }; + // #34: a bare fn-name rvalue types as its FN TYPE, not its return + // type. decl.lhs is the RETURN type for an N_FNDECL, so synthesize + // the N_TFN over (ret=decl.lhs, params=decl.list) — the shape + // assignableaddrfn builds (:3841). Mirrors cstage: build_fn_type at + // fn-decl install (cmd/wcc/check.c:2915/2931) stored on the sym and + // returned verbatim by cexpr N_IDENT (check.c:1313); harec EXP_ACCESS + // yields the fn object's type with NO decay (ref/harec/src/check.c + // :341-343; the fn obj is built .storage=STORAGE_FUNCTION at :4279- + // 4288, assignable iff dealias-equal fn types, types.c:1001). Was the + // root of the #24 fn-family over-rejects (`let p: fn()i32 = g` compared + // i32 vs the fn type). cgen lowers a fn rvalue name-keyed via + // fnretlookup (cgenexpr.ww:1100), never off this stamp → byte-id-neutral. + if (s.skind == skind.SK_FN) { + let ft: *node = newnode(nkind.N_TFN, "", 0, 0); + ft.lhs = s.decl.lhs; + ft.list = s.decl.list; + e.type_ = tinfofornode(c, ft): *void; + return ft; + }; let t: *node = s.decl.lhs; // Propagate the declared type's tinfo onto the use site so // downstream cgen walkers can read n.type_ off an ident. @@ -13596,6 +13615,17 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) { let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str); if (fs != nil) { if (fs.decl != nil) { + // #34: a module-qualified bare fn rvalue `mod.fn` types as + // its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is + // the RETURN type for an N_FNDECL. Pins 706 (`let p1: fn()i32 + // = mod1.ping`). + if (fs.skind == skind.SK_FN) { + let ft: *node = newnode(nkind.N_TFN, "", 0, 0); + ft.lhs = fs.decl.lhs; + ft.list = fs.decl.list; + e.type_ = tinfofornode(c, ft): *void; + return ft; + }; let tn: *node = fs.decl.lhs; if (tn != nil) { e.type_ = tinfofornode(c, tn): *void; @@ -14565,6 +14595,21 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = { }; }; }; + // #34: two bare fn types reaching here are NOT structurally equal + // (typeeqast returned true at the top otherwise) — the fn signatures + // differ, a confident reject mirroring cstage's structural fn + // type_assignable (`init fn() str not assignable to declared fn() i32`; + // harec types.c:1001 dealias-equal fn types). Manifests only now that + // S1/S2 stamp a bare fn rvalue with its fn type: `let p: fn()i32 = h` + // (h: fn()str) was a silent mis-accept via the lenient catch-all below. + // The matched-sig case already returned true via typeeqast at the top. + // The `&fn` (*fn) vs bare-fn KIND mismatch (du N_TFN, su N_TPTR) is a + // different shape, closed by c3's aggregate-kind reject, not here. + if (du.kind == nkind.N_TFN) { + if (su.kind == nkind.N_TFN) { + return false; + }; + }; // Anything else: don't claim confidence. *confident = false; return true; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 5576fe31..7a238248 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -2685,6 +2685,25 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { let s: *sym = scopelookupprefer(c.cur, c.curmod, e.str); if (s == nil) { return nil; }; if (s.decl == nil) { return nil; }; + // #34: a bare fn-name rvalue types as its FN TYPE, not its return + // type. decl.lhs is the RETURN type for an N_FNDECL, so synthesize + // the N_TFN over (ret=decl.lhs, params=decl.list) — the shape + // assignableaddrfn builds (:3841). Mirrors cstage: build_fn_type at + // fn-decl install (cmd/wcc/check.c:2915/2931) stored on the sym and + // returned verbatim by cexpr N_IDENT (check.c:1313); harec EXP_ACCESS + // yields the fn object's type with NO decay (ref/harec/src/check.c + // :341-343; the fn obj is built .storage=STORAGE_FUNCTION at :4279- + // 4288, assignable iff dealias-equal fn types, types.c:1001). Was the + // root of the #24 fn-family over-rejects (`let p: fn()i32 = g` compared + // i32 vs the fn type). cgen lowers a fn rvalue name-keyed via + // fnretlookup (cgenexpr.ww:1100), never off this stamp → byte-id-neutral. + if (s.skind == skind.SK_FN) { + let ft: *node = newnode(nkind.N_TFN, "", 0, 0); + ft.lhs = s.decl.lhs; + ft.list = s.decl.list; + e.type_ = tinfofornode(c, ft): *void; + return ft; + }; let t: *node = s.decl.lhs; // Propagate the declared type's tinfo onto the use site so // downstream cgen walkers can read n.type_ off an ident. @@ -3176,6 +3195,17 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) { let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str); if (fs != nil) { if (fs.decl != nil) { + // #34: a module-qualified bare fn rvalue `mod.fn` types as + // its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is + // the RETURN type for an N_FNDECL. Pins 706 (`let p1: fn()i32 + // = mod1.ping`). + if (fs.skind == skind.SK_FN) { + let ft: *node = newnode(nkind.N_TFN, "", 0, 0); + ft.lhs = fs.decl.lhs; + ft.list = fs.decl.list; + e.type_ = tinfofornode(c, ft): *void; + return ft; + }; let tn: *node = fs.decl.lhs; if (tn != nil) { e.type_ = tinfofornode(c, tn): *void; @@ -4145,6 +4175,21 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = { }; }; }; + // #34: two bare fn types reaching here are NOT structurally equal + // (typeeqast returned true at the top otherwise) — the fn signatures + // differ, a confident reject mirroring cstage's structural fn + // type_assignable (`init fn() str not assignable to declared fn() i32`; + // harec types.c:1001 dealias-equal fn types). Manifests only now that + // S1/S2 stamp a bare fn rvalue with its fn type: `let p: fn()i32 = h` + // (h: fn()str) was a silent mis-accept via the lenient catch-all below. + // The matched-sig case already returned true via typeeqast at the top. + // The `&fn` (*fn) vs bare-fn KIND mismatch (du N_TFN, su N_TPTR) is a + // different shape, closed by c3's aggregate-kind reject, not here. + if (du.kind == nkind.N_TFN) { + if (su.kind == nkind.N_TFN) { + return false; + }; + }; // Anything else: don't claim confidence. *confident = false; return true; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 06509582..d92ddfa9 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -13105,6 +13105,25 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { let s: *sym = scopelookupprefer(c.cur, c.curmod, e.str); if (s == nil) { return nil; }; if (s.decl == nil) { return nil; }; + // #34: a bare fn-name rvalue types as its FN TYPE, not its return + // type. decl.lhs is the RETURN type for an N_FNDECL, so synthesize + // the N_TFN over (ret=decl.lhs, params=decl.list) — the shape + // assignableaddrfn builds (:3841). Mirrors cstage: build_fn_type at + // fn-decl install (cmd/wcc/check.c:2915/2931) stored on the sym and + // returned verbatim by cexpr N_IDENT (check.c:1313); harec EXP_ACCESS + // yields the fn object's type with NO decay (ref/harec/src/check.c + // :341-343; the fn obj is built .storage=STORAGE_FUNCTION at :4279- + // 4288, assignable iff dealias-equal fn types, types.c:1001). Was the + // root of the #24 fn-family over-rejects (`let p: fn()i32 = g` compared + // i32 vs the fn type). cgen lowers a fn rvalue name-keyed via + // fnretlookup (cgenexpr.ww:1100), never off this stamp → byte-id-neutral. + if (s.skind == skind.SK_FN) { + let ft: *node = newnode(nkind.N_TFN, "", 0, 0); + ft.lhs = s.decl.lhs; + ft.list = s.decl.list; + e.type_ = tinfofornode(c, ft): *void; + return ft; + }; let t: *node = s.decl.lhs; // Propagate the declared type's tinfo onto the use site so // downstream cgen walkers can read n.type_ off an ident. @@ -13596,6 +13615,17 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (ms.skind == skind.SK_USE || ms.use_alias != 0i32) { let fs: *sym = scopelookupinmodule(c.cur, lhsn.str, e.str); if (fs != nil) { if (fs.decl != nil) { + // #34: a module-qualified bare fn rvalue `mod.fn` types as + // its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is + // the RETURN type for an N_FNDECL. Pins 706 (`let p1: fn()i32 + // = mod1.ping`). + if (fs.skind == skind.SK_FN) { + let ft: *node = newnode(nkind.N_TFN, "", 0, 0); + ft.lhs = fs.decl.lhs; + ft.list = fs.decl.list; + e.type_ = tinfofornode(c, ft): *void; + return ft; + }; let tn: *node = fs.decl.lhs; if (tn != nil) { e.type_ = tinfofornode(c, tn): *void; @@ -14565,6 +14595,21 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = { }; }; }; + // #34: two bare fn types reaching here are NOT structurally equal + // (typeeqast returned true at the top otherwise) — the fn signatures + // differ, a confident reject mirroring cstage's structural fn + // type_assignable (`init fn() str not assignable to declared fn() i32`; + // harec types.c:1001 dealias-equal fn types). Manifests only now that + // S1/S2 stamp a bare fn rvalue with its fn type: `let p: fn()i32 = h` + // (h: fn()str) was a silent mis-accept via the lenient catch-all below. + // The matched-sig case already returned true via typeeqast at the top. + // The `&fn` (*fn) vs bare-fn KIND mismatch (du N_TFN, su N_TPTR) is a + // different shape, closed by c3's aggregate-kind reject, not here. + if (du.kind == nkind.N_TFN) { + if (su.kind == nkind.N_TFN) { + return false; + }; + }; // Anything else: don't claim confidence. *confident = false; return true;