wcc: accept bare &fn into a fn-pointer-alias slot via a caller-site gate (#206)
A bare `&fn_name` was not assignable into a `*reader` / `(*reader | void)` vtable field without an explicit cast: cstage type_eq on TY_NAMED is pointer-identity, so a structural `*fn(...)` referent never matched the named `*reader` variant; wwstage accepted it only via an accidental catch-all leniency. harec accepts bare &fn through hint-directed alias adoption at the address-of site (check.c:3594-3626) while keeping pointer assignability strictly nominal (types.c:1039-1066), so a materialized `*fn` value never launders across alias names. Mirror that decision without threading a type hint through the bottom-up cexpr: keep type_assignable / isassignable fully nominal, and add a caller-site helper (assignable_addrfn) at the assignment boundaries (let-init, struct-literal field-init, assign, return, call-arg, array element) that accepts iff the rhs is a DIRECT &-of-fn-ident and the destination (or exactly one tagged variant) is a pointer-to-fn-alias whose underlying fn signature structurally matches. A materialized `*fn` value, a distinct same-signature alias, and an ambiguous multi-variant target all stay rejected. Both stages share the rule; wwstage's lenient pointer-fn punt becomes a confident reject. ww has no methods, so a `value.leaf` slot is only ever a fn-pointer field and this never over-admits. The tightening surfaced a wwstage typeeqast gap: a TY_FN result that is a tuple (`*fn(...)(i32,i32)`) compared false where cstage type_eq handled it, newly rejecting a legitimate structural assign. Add the N_TTUPLE structural case (rule-10), restoring test 766. cgen-neutral (the cast was a no-op reinterpret); pre/post bootstrap .s zero-delta. Test 783 covers the positive paths (incl. a byte-id-clean three-field-vtable dispatcher) and the negatives. Tagged-slot negatives (ambiguous / tagged-laundering) are rejected on cstage but wwstage's separate `(X|void)` void-variant leniency (#214) still admits them; 783 pins them cstage-only, to graduate when #214 closes (required before wwstage becomes the authoritative selfhost checker). Note: `make clean && make test` is RED at HEAD on 4 alloc fixtures (700/748/758/915) via a pre-existing clean-build defect (#215, malloc vs rt_malloc); identical with or without this change, so bisect-clean for #206.
This commit is contained in:
@@ -10797,9 +10797,27 @@ fn typeeqast(a: *node, b: *node) bool = {
|
||||
};
|
||||
return pb == nil;
|
||||
};
|
||||
// Conservative: anything else (struct/tagged/tuple/array) fails
|
||||
// the cheap check. Selfhost code doesn't currently rely on
|
||||
// equality at these shapes for the targeted checks.
|
||||
// #206: tuple structural equality — mirror of cstage type.c:261-268
|
||||
// (TY_TUPLE). Needed since a tuple-RETURN fn pointer compares its
|
||||
// N_TFN return node (aa.lhs) here; without it `*fn(x)(a,b)` never
|
||||
// proves structurally equal to itself, so the #206 punt-tightening
|
||||
// would confidently reject a bare-&fn into a structural `*fn(...)`
|
||||
// slot (test 766 fn_tuple_return). Elements are N_TPARAM-wrapped
|
||||
// (parse.ww:302-318), so compare each link's .lhs.
|
||||
if (k == nkind.N_TTUPLE) {
|
||||
let pa: *node = aa.list;
|
||||
let pb: *node = bb.list;
|
||||
for (pa != nil) {
|
||||
if (pb == nil) { return false; };
|
||||
if (!typeeqast(pa.lhs, pb.lhs)) { return false; };
|
||||
pa = pa.next;
|
||||
pb = pb.next;
|
||||
};
|
||||
return pb == nil;
|
||||
};
|
||||
// Conservative: anything else (struct/tagged/array) fails the
|
||||
// cheap check. Selfhost code doesn't currently rely on equality
|
||||
// at these shapes for the targeted checks.
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -12069,6 +12087,35 @@ fn unoptype(c: *checker, e: *node) *node = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #206: `&fn` must type as `*fn(...)` — a pointer to the fn's
|
||||
// full signature — not `*<rettype>`. exprtype's N_IDENT arm
|
||||
// returns a fn decl's lhs (the return type), so the generic
|
||||
// `*opt` below would mistype `&myread` as `*i32`; a `*fn`
|
||||
// laundered into a `*alias` slot then slips past the nominal
|
||||
// pointer-fn reject in isassignable. Synthesize the N_TFN from
|
||||
// the fn decl (N_FNDECL and N_TFN share parseparams' param
|
||||
// shape) so the address-of carries the signature. Mirrors
|
||||
// cstage, where a fn ident already types as TY_FN
|
||||
// (cmd/wcc/check.c:668), so `&fn` is `*fn` natively.
|
||||
if (e.lhs != nil) {
|
||||
if (e.lhs.kind == nkind.N_IDENT) {
|
||||
let fs: *sym = scopelookup(c.cur, e.lhs.str);
|
||||
if (fs != nil) {
|
||||
if (fs.skind == skind.SK_FN) {
|
||||
if (fs.decl != nil) {
|
||||
if (fs.decl.kind == nkind.N_FNDECL) {
|
||||
let synth: *node = newnode(nkind.N_TFN, "", 0, 0);
|
||||
synth.lhs = fs.decl.lhs;
|
||||
synth.list = fs.decl.list;
|
||||
let pf: *node = newnode(nkind.N_TPTR, "", 0, 0);
|
||||
pf.lhs = synth;
|
||||
return pf;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// opt nil → propagation from inherent-IDENT bail (5-lite-b
|
||||
// #34). Generic &expr widens to *opt; without opt we can't
|
||||
// synthesize the pointer node.
|
||||
@@ -13020,6 +13067,70 @@ fn isstrtname(t: *node) bool = {
|
||||
return streq(t.str, "str");
|
||||
};
|
||||
|
||||
// addrfnptrmatches — true iff `ptr` (after alias-resolve) is a
|
||||
// pointer whose referent resolves to a fn type structurally equal to
|
||||
// `synth` (a synthetic N_TFN built from a fn decl's ret + params).
|
||||
// Mirror of cstage addrfn_ptr_matches (cmd/wcc/check.c, project #206);
|
||||
// reuses typeeqast — the same structural-fn comparator cstage uses via
|
||||
// type_eq(TY_FN,TY_FN) — for symmetry.
|
||||
fn addrfnptrmatches(c: *checker, ptr: *node, synth: *node) bool = {
|
||||
if (ptr == nil) { return false; };
|
||||
let pu: *node = resolvealias(c, unwrapbang(ptr));
|
||||
if (pu == nil) { return false; };
|
||||
if (pu.kind != nkind.N_TPTR) { return false; };
|
||||
let ref: *node = resolvealias(c, unwrapbang(pu.lhs));
|
||||
if (ref == nil) { return false; };
|
||||
if (ref.kind != nkind.N_TFN) { return false; };
|
||||
return typeeqast(synth, ref);
|
||||
};
|
||||
|
||||
// assignableaddrfn — project #206 Option C gate. Mirror of cstage
|
||||
// assignable_addrfn (cmd/wcc/check.c). A bare `&fn` types structurally
|
||||
// as `*fn(...)`, nominally distinct from a `*alias` fn-pointer slot;
|
||||
// isassignable stays nominal (the pointer-fn arm below confidently
|
||||
// rejects a laundered `*fn` value, like harec types.c:1039-1066). This
|
||||
// admits only the shape harec adopts via its address-of hint (harec
|
||||
// check.c:3594-3626): a DIRECT `&`-of-fn-ident whose signature
|
||||
// structurally matches the destination's pointed-to fn alias, or the
|
||||
// single matching ptr-to-fn variant of a tagged dst (>=2 same-sig
|
||||
// variants → ambiguous, reject). Lives at the assignment-boundary
|
||||
// caller sites — not in exprtype — because the alias identity is
|
||||
// nominal-lossy once typed and the direct-&fn shape survives only on
|
||||
// the rhs node. N_FNDECL and N_TFN share parseparams' param-node shape
|
||||
// (lib/ww/parse/decl.ww + parse.ww), so a synthetic N_TFN over the fn
|
||||
// decl's lhs/list compares correctly under typeeqast.
|
||||
fn assignableaddrfn(c: *checker, dst: *node, rhs: *node) bool = {
|
||||
if (dst == nil) { return false; };
|
||||
if (rhs == nil) { return false; };
|
||||
if (rhs.kind != nkind.N_UN) { return false; };
|
||||
if (rhs.op != tkind.TK_AMP) { return false; };
|
||||
let id: *node = rhs.lhs;
|
||||
if (id == nil) { return false; };
|
||||
if (id.kind != nkind.N_IDENT) { return false; };
|
||||
let s: *sym = scopelookup(c.cur, id.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_FN) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
let d: *node = s.decl;
|
||||
if (d.kind != nkind.N_FNDECL) { return false; };
|
||||
let synth: *node = newnode(nkind.N_TFN, "", 0, 0);
|
||||
synth.lhs = d.lhs;
|
||||
synth.list = d.list;
|
||||
let du: *node = resolvealias(c, unwrapbang(dst));
|
||||
if (du == nil) { return false; };
|
||||
if (du.kind == nkind.N_TPTR) { return addrfnptrmatches(c, du, synth); };
|
||||
if (du.kind == nkind.N_TTAGGED) {
|
||||
let nmatch: i32 = 0;
|
||||
let v: *node = du.list;
|
||||
for (v != nil) {
|
||||
if (addrfnptrmatches(c, v, synth)) { nmatch = nmatch + 1; };
|
||||
v = v.next;
|
||||
};
|
||||
return nmatch == 1;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// isassignable — AST-level approximation of C check.c
|
||||
// type_assignable. Returns true when we know the assignment is
|
||||
// OK, false only when we're confident it isn't, and "skip" (true)
|
||||
@@ -13171,6 +13282,31 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #206: two pointers whose referents both resolve to fn types are
|
||||
// NOMINALLY assignable only when structurally equal — and that case
|
||||
// already returned true via typeeqast at the top. Reaching here
|
||||
// means the fn signatures differ, or a bare structural `*fn` value
|
||||
// is being laundered into a `*alias` slot: confidently NOT
|
||||
// assignable, mirror of cstage's nominal type_assignable (harec
|
||||
// types.c:1039-1066). The direct `&fn` adopt-the-alias case is
|
||||
// handled at the assignment caller sites via assignableaddrfn, NOT
|
||||
// here. Without this the lenient catch-all below silently accepted
|
||||
// the laundering shape.
|
||||
if (du.kind == nkind.N_TPTR) {
|
||||
if (su.kind == nkind.N_TPTR) {
|
||||
let dref: *node = resolvealias(c, unwrapbang(du.lhs));
|
||||
let sref: *node = resolvealias(c, unwrapbang(su.lhs));
|
||||
if (dref != nil) {
|
||||
if (sref != nil) {
|
||||
if (dref.kind == nkind.N_TFN) {
|
||||
if (sref.kind == nkind.N_TFN) {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Anything else: don't claim confidence.
|
||||
*confident = false;
|
||||
return true;
|
||||
@@ -13429,8 +13565,11 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
if (est != nil) {
|
||||
if (!isassignable(c, elemtn, est, &conf2)) {
|
||||
if (conf2) {
|
||||
errnotassign(c, elemtn, est, "let");
|
||||
return;
|
||||
// #206: direct `&fn` array element.
|
||||
if (!assignableaddrfn(c, elemtn, ev)) {
|
||||
errnotassign(c, elemtn, est, "let");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -13443,6 +13582,8 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
};
|
||||
let conf: bool = false;
|
||||
let ok: bool = isassignable(c, n.lhs, src, &conf);
|
||||
// #206: direct `&fn` → `*alias` / `(*alias | void)` slot.
|
||||
if (!ok) { if (assignableaddrfn(c, n.lhs, n.rhs)) { ok = true; }; };
|
||||
if (!conf) { return; };
|
||||
if (!ok) { errnotassign(c, n.lhs, src, "let"); };
|
||||
};
|
||||
@@ -13460,6 +13601,8 @@ fn checkretassign(c: *checker, n: *node) void = {
|
||||
if (src == nil) { return; };
|
||||
let conf: bool = false;
|
||||
let ok: bool = isassignable(c, c.fnret, src, &conf);
|
||||
// #206: direct `&fn` returned into a `*alias` / `(*alias | void)`.
|
||||
if (!ok) { if (assignableaddrfn(c, c.fnret, n.lhs)) { ok = true; }; };
|
||||
if (!conf) { return; };
|
||||
if (!ok) { errnotassign(c, c.fnret, src, "return"); };
|
||||
};
|
||||
|
||||
@@ -767,9 +767,27 @@ fn typeeqast(a: *node, b: *node) bool = {
|
||||
};
|
||||
return pb == nil;
|
||||
};
|
||||
// Conservative: anything else (struct/tagged/tuple/array) fails
|
||||
// the cheap check. Selfhost code doesn't currently rely on
|
||||
// equality at these shapes for the targeted checks.
|
||||
// #206: tuple structural equality — mirror of cstage type.c:261-268
|
||||
// (TY_TUPLE). Needed since a tuple-RETURN fn pointer compares its
|
||||
// N_TFN return node (aa.lhs) here; without it `*fn(x)(a,b)` never
|
||||
// proves structurally equal to itself, so the #206 punt-tightening
|
||||
// would confidently reject a bare-&fn into a structural `*fn(...)`
|
||||
// slot (test 766 fn_tuple_return). Elements are N_TPARAM-wrapped
|
||||
// (parse.ww:302-318), so compare each link's .lhs.
|
||||
if (k == nkind.N_TTUPLE) {
|
||||
let pa: *node = aa.list;
|
||||
let pb: *node = bb.list;
|
||||
for (pa != nil) {
|
||||
if (pb == nil) { return false; };
|
||||
if (!typeeqast(pa.lhs, pb.lhs)) { return false; };
|
||||
pa = pa.next;
|
||||
pb = pb.next;
|
||||
};
|
||||
return pb == nil;
|
||||
};
|
||||
// Conservative: anything else (struct/tagged/array) fails the
|
||||
// cheap check. Selfhost code doesn't currently rely on equality
|
||||
// at these shapes for the targeted checks.
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -2039,6 +2057,35 @@ fn unoptype(c: *checker, e: *node) *node = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #206: `&fn` must type as `*fn(...)` — a pointer to the fn's
|
||||
// full signature — not `*<rettype>`. exprtype's N_IDENT arm
|
||||
// returns a fn decl's lhs (the return type), so the generic
|
||||
// `*opt` below would mistype `&myread` as `*i32`; a `*fn`
|
||||
// laundered into a `*alias` slot then slips past the nominal
|
||||
// pointer-fn reject in isassignable. Synthesize the N_TFN from
|
||||
// the fn decl (N_FNDECL and N_TFN share parseparams' param
|
||||
// shape) so the address-of carries the signature. Mirrors
|
||||
// cstage, where a fn ident already types as TY_FN
|
||||
// (cmd/wcc/check.c:668), so `&fn` is `*fn` natively.
|
||||
if (e.lhs != nil) {
|
||||
if (e.lhs.kind == nkind.N_IDENT) {
|
||||
let fs: *sym = scopelookup(c.cur, e.lhs.str);
|
||||
if (fs != nil) {
|
||||
if (fs.skind == skind.SK_FN) {
|
||||
if (fs.decl != nil) {
|
||||
if (fs.decl.kind == nkind.N_FNDECL) {
|
||||
let synth: *node = newnode(nkind.N_TFN, "", 0, 0);
|
||||
synth.lhs = fs.decl.lhs;
|
||||
synth.list = fs.decl.list;
|
||||
let pf: *node = newnode(nkind.N_TPTR, "", 0, 0);
|
||||
pf.lhs = synth;
|
||||
return pf;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// opt nil → propagation from inherent-IDENT bail (5-lite-b
|
||||
// #34). Generic &expr widens to *opt; without opt we can't
|
||||
// synthesize the pointer node.
|
||||
@@ -2990,6 +3037,70 @@ fn isstrtname(t: *node) bool = {
|
||||
return streq(t.str, "str");
|
||||
};
|
||||
|
||||
// addrfnptrmatches — true iff `ptr` (after alias-resolve) is a
|
||||
// pointer whose referent resolves to a fn type structurally equal to
|
||||
// `synth` (a synthetic N_TFN built from a fn decl's ret + params).
|
||||
// Mirror of cstage addrfn_ptr_matches (cmd/wcc/check.c, project #206);
|
||||
// reuses typeeqast — the same structural-fn comparator cstage uses via
|
||||
// type_eq(TY_FN,TY_FN) — for symmetry.
|
||||
fn addrfnptrmatches(c: *checker, ptr: *node, synth: *node) bool = {
|
||||
if (ptr == nil) { return false; };
|
||||
let pu: *node = resolvealias(c, unwrapbang(ptr));
|
||||
if (pu == nil) { return false; };
|
||||
if (pu.kind != nkind.N_TPTR) { return false; };
|
||||
let ref: *node = resolvealias(c, unwrapbang(pu.lhs));
|
||||
if (ref == nil) { return false; };
|
||||
if (ref.kind != nkind.N_TFN) { return false; };
|
||||
return typeeqast(synth, ref);
|
||||
};
|
||||
|
||||
// assignableaddrfn — project #206 Option C gate. Mirror of cstage
|
||||
// assignable_addrfn (cmd/wcc/check.c). A bare `&fn` types structurally
|
||||
// as `*fn(...)`, nominally distinct from a `*alias` fn-pointer slot;
|
||||
// isassignable stays nominal (the pointer-fn arm below confidently
|
||||
// rejects a laundered `*fn` value, like harec types.c:1039-1066). This
|
||||
// admits only the shape harec adopts via its address-of hint (harec
|
||||
// check.c:3594-3626): a DIRECT `&`-of-fn-ident whose signature
|
||||
// structurally matches the destination's pointed-to fn alias, or the
|
||||
// single matching ptr-to-fn variant of a tagged dst (>=2 same-sig
|
||||
// variants → ambiguous, reject). Lives at the assignment-boundary
|
||||
// caller sites — not in exprtype — because the alias identity is
|
||||
// nominal-lossy once typed and the direct-&fn shape survives only on
|
||||
// the rhs node. N_FNDECL and N_TFN share parseparams' param-node shape
|
||||
// (lib/ww/parse/decl.ww + parse.ww), so a synthetic N_TFN over the fn
|
||||
// decl's lhs/list compares correctly under typeeqast.
|
||||
fn assignableaddrfn(c: *checker, dst: *node, rhs: *node) bool = {
|
||||
if (dst == nil) { return false; };
|
||||
if (rhs == nil) { return false; };
|
||||
if (rhs.kind != nkind.N_UN) { return false; };
|
||||
if (rhs.op != tkind.TK_AMP) { return false; };
|
||||
let id: *node = rhs.lhs;
|
||||
if (id == nil) { return false; };
|
||||
if (id.kind != nkind.N_IDENT) { return false; };
|
||||
let s: *sym = scopelookup(c.cur, id.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_FN) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
let d: *node = s.decl;
|
||||
if (d.kind != nkind.N_FNDECL) { return false; };
|
||||
let synth: *node = newnode(nkind.N_TFN, "", 0, 0);
|
||||
synth.lhs = d.lhs;
|
||||
synth.list = d.list;
|
||||
let du: *node = resolvealias(c, unwrapbang(dst));
|
||||
if (du == nil) { return false; };
|
||||
if (du.kind == nkind.N_TPTR) { return addrfnptrmatches(c, du, synth); };
|
||||
if (du.kind == nkind.N_TTAGGED) {
|
||||
let nmatch: i32 = 0;
|
||||
let v: *node = du.list;
|
||||
for (v != nil) {
|
||||
if (addrfnptrmatches(c, v, synth)) { nmatch = nmatch + 1; };
|
||||
v = v.next;
|
||||
};
|
||||
return nmatch == 1;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// isassignable — AST-level approximation of C check.c
|
||||
// type_assignable. Returns true when we know the assignment is
|
||||
// OK, false only when we're confident it isn't, and "skip" (true)
|
||||
@@ -3141,6 +3252,31 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #206: two pointers whose referents both resolve to fn types are
|
||||
// NOMINALLY assignable only when structurally equal — and that case
|
||||
// already returned true via typeeqast at the top. Reaching here
|
||||
// means the fn signatures differ, or a bare structural `*fn` value
|
||||
// is being laundered into a `*alias` slot: confidently NOT
|
||||
// assignable, mirror of cstage's nominal type_assignable (harec
|
||||
// types.c:1039-1066). The direct `&fn` adopt-the-alias case is
|
||||
// handled at the assignment caller sites via assignableaddrfn, NOT
|
||||
// here. Without this the lenient catch-all below silently accepted
|
||||
// the laundering shape.
|
||||
if (du.kind == nkind.N_TPTR) {
|
||||
if (su.kind == nkind.N_TPTR) {
|
||||
let dref: *node = resolvealias(c, unwrapbang(du.lhs));
|
||||
let sref: *node = resolvealias(c, unwrapbang(su.lhs));
|
||||
if (dref != nil) {
|
||||
if (sref != nil) {
|
||||
if (dref.kind == nkind.N_TFN) {
|
||||
if (sref.kind == nkind.N_TFN) {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Anything else: don't claim confidence.
|
||||
*confident = false;
|
||||
return true;
|
||||
@@ -3399,8 +3535,11 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
if (est != nil) {
|
||||
if (!isassignable(c, elemtn, est, &conf2)) {
|
||||
if (conf2) {
|
||||
errnotassign(c, elemtn, est, "let");
|
||||
return;
|
||||
// #206: direct `&fn` array element.
|
||||
if (!assignableaddrfn(c, elemtn, ev)) {
|
||||
errnotassign(c, elemtn, est, "let");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -3413,6 +3552,8 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
};
|
||||
let conf: bool = false;
|
||||
let ok: bool = isassignable(c, n.lhs, src, &conf);
|
||||
// #206: direct `&fn` → `*alias` / `(*alias | void)` slot.
|
||||
if (!ok) { if (assignableaddrfn(c, n.lhs, n.rhs)) { ok = true; }; };
|
||||
if (!conf) { return; };
|
||||
if (!ok) { errnotassign(c, n.lhs, src, "let"); };
|
||||
};
|
||||
@@ -3430,6 +3571,8 @@ fn checkretassign(c: *checker, n: *node) void = {
|
||||
if (src == nil) { return; };
|
||||
let conf: bool = false;
|
||||
let ok: bool = isassignable(c, c.fnret, src, &conf);
|
||||
// #206: direct `&fn` returned into a `*alias` / `(*alias | void)`.
|
||||
if (!ok) { if (assignableaddrfn(c, c.fnret, n.lhs)) { ok = true; }; };
|
||||
if (!conf) { return; };
|
||||
if (!ok) { errnotassign(c, c.fnret, src, "return"); };
|
||||
};
|
||||
|
||||
@@ -10797,9 +10797,27 @@ fn typeeqast(a: *node, b: *node) bool = {
|
||||
};
|
||||
return pb == nil;
|
||||
};
|
||||
// Conservative: anything else (struct/tagged/tuple/array) fails
|
||||
// the cheap check. Selfhost code doesn't currently rely on
|
||||
// equality at these shapes for the targeted checks.
|
||||
// #206: tuple structural equality — mirror of cstage type.c:261-268
|
||||
// (TY_TUPLE). Needed since a tuple-RETURN fn pointer compares its
|
||||
// N_TFN return node (aa.lhs) here; without it `*fn(x)(a,b)` never
|
||||
// proves structurally equal to itself, so the #206 punt-tightening
|
||||
// would confidently reject a bare-&fn into a structural `*fn(...)`
|
||||
// slot (test 766 fn_tuple_return). Elements are N_TPARAM-wrapped
|
||||
// (parse.ww:302-318), so compare each link's .lhs.
|
||||
if (k == nkind.N_TTUPLE) {
|
||||
let pa: *node = aa.list;
|
||||
let pb: *node = bb.list;
|
||||
for (pa != nil) {
|
||||
if (pb == nil) { return false; };
|
||||
if (!typeeqast(pa.lhs, pb.lhs)) { return false; };
|
||||
pa = pa.next;
|
||||
pb = pb.next;
|
||||
};
|
||||
return pb == nil;
|
||||
};
|
||||
// Conservative: anything else (struct/tagged/array) fails the
|
||||
// cheap check. Selfhost code doesn't currently rely on equality
|
||||
// at these shapes for the targeted checks.
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -12069,6 +12087,35 @@ fn unoptype(c: *checker, e: *node) *node = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #206: `&fn` must type as `*fn(...)` — a pointer to the fn's
|
||||
// full signature — not `*<rettype>`. exprtype's N_IDENT arm
|
||||
// returns a fn decl's lhs (the return type), so the generic
|
||||
// `*opt` below would mistype `&myread` as `*i32`; a `*fn`
|
||||
// laundered into a `*alias` slot then slips past the nominal
|
||||
// pointer-fn reject in isassignable. Synthesize the N_TFN from
|
||||
// the fn decl (N_FNDECL and N_TFN share parseparams' param
|
||||
// shape) so the address-of carries the signature. Mirrors
|
||||
// cstage, where a fn ident already types as TY_FN
|
||||
// (cmd/wcc/check.c:668), so `&fn` is `*fn` natively.
|
||||
if (e.lhs != nil) {
|
||||
if (e.lhs.kind == nkind.N_IDENT) {
|
||||
let fs: *sym = scopelookup(c.cur, e.lhs.str);
|
||||
if (fs != nil) {
|
||||
if (fs.skind == skind.SK_FN) {
|
||||
if (fs.decl != nil) {
|
||||
if (fs.decl.kind == nkind.N_FNDECL) {
|
||||
let synth: *node = newnode(nkind.N_TFN, "", 0, 0);
|
||||
synth.lhs = fs.decl.lhs;
|
||||
synth.list = fs.decl.list;
|
||||
let pf: *node = newnode(nkind.N_TPTR, "", 0, 0);
|
||||
pf.lhs = synth;
|
||||
return pf;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// opt nil → propagation from inherent-IDENT bail (5-lite-b
|
||||
// #34). Generic &expr widens to *opt; without opt we can't
|
||||
// synthesize the pointer node.
|
||||
@@ -13020,6 +13067,70 @@ fn isstrtname(t: *node) bool = {
|
||||
return streq(t.str, "str");
|
||||
};
|
||||
|
||||
// addrfnptrmatches — true iff `ptr` (after alias-resolve) is a
|
||||
// pointer whose referent resolves to a fn type structurally equal to
|
||||
// `synth` (a synthetic N_TFN built from a fn decl's ret + params).
|
||||
// Mirror of cstage addrfn_ptr_matches (cmd/wcc/check.c, project #206);
|
||||
// reuses typeeqast — the same structural-fn comparator cstage uses via
|
||||
// type_eq(TY_FN,TY_FN) — for symmetry.
|
||||
fn addrfnptrmatches(c: *checker, ptr: *node, synth: *node) bool = {
|
||||
if (ptr == nil) { return false; };
|
||||
let pu: *node = resolvealias(c, unwrapbang(ptr));
|
||||
if (pu == nil) { return false; };
|
||||
if (pu.kind != nkind.N_TPTR) { return false; };
|
||||
let ref: *node = resolvealias(c, unwrapbang(pu.lhs));
|
||||
if (ref == nil) { return false; };
|
||||
if (ref.kind != nkind.N_TFN) { return false; };
|
||||
return typeeqast(synth, ref);
|
||||
};
|
||||
|
||||
// assignableaddrfn — project #206 Option C gate. Mirror of cstage
|
||||
// assignable_addrfn (cmd/wcc/check.c). A bare `&fn` types structurally
|
||||
// as `*fn(...)`, nominally distinct from a `*alias` fn-pointer slot;
|
||||
// isassignable stays nominal (the pointer-fn arm below confidently
|
||||
// rejects a laundered `*fn` value, like harec types.c:1039-1066). This
|
||||
// admits only the shape harec adopts via its address-of hint (harec
|
||||
// check.c:3594-3626): a DIRECT `&`-of-fn-ident whose signature
|
||||
// structurally matches the destination's pointed-to fn alias, or the
|
||||
// single matching ptr-to-fn variant of a tagged dst (>=2 same-sig
|
||||
// variants → ambiguous, reject). Lives at the assignment-boundary
|
||||
// caller sites — not in exprtype — because the alias identity is
|
||||
// nominal-lossy once typed and the direct-&fn shape survives only on
|
||||
// the rhs node. N_FNDECL and N_TFN share parseparams' param-node shape
|
||||
// (lib/ww/parse/decl.ww + parse.ww), so a synthetic N_TFN over the fn
|
||||
// decl's lhs/list compares correctly under typeeqast.
|
||||
fn assignableaddrfn(c: *checker, dst: *node, rhs: *node) bool = {
|
||||
if (dst == nil) { return false; };
|
||||
if (rhs == nil) { return false; };
|
||||
if (rhs.kind != nkind.N_UN) { return false; };
|
||||
if (rhs.op != tkind.TK_AMP) { return false; };
|
||||
let id: *node = rhs.lhs;
|
||||
if (id == nil) { return false; };
|
||||
if (id.kind != nkind.N_IDENT) { return false; };
|
||||
let s: *sym = scopelookup(c.cur, id.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != skind.SK_FN) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
let d: *node = s.decl;
|
||||
if (d.kind != nkind.N_FNDECL) { return false; };
|
||||
let synth: *node = newnode(nkind.N_TFN, "", 0, 0);
|
||||
synth.lhs = d.lhs;
|
||||
synth.list = d.list;
|
||||
let du: *node = resolvealias(c, unwrapbang(dst));
|
||||
if (du == nil) { return false; };
|
||||
if (du.kind == nkind.N_TPTR) { return addrfnptrmatches(c, du, synth); };
|
||||
if (du.kind == nkind.N_TTAGGED) {
|
||||
let nmatch: i32 = 0;
|
||||
let v: *node = du.list;
|
||||
for (v != nil) {
|
||||
if (addrfnptrmatches(c, v, synth)) { nmatch = nmatch + 1; };
|
||||
v = v.next;
|
||||
};
|
||||
return nmatch == 1;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// isassignable — AST-level approximation of C check.c
|
||||
// type_assignable. Returns true when we know the assignment is
|
||||
// OK, false only when we're confident it isn't, and "skip" (true)
|
||||
@@ -13171,6 +13282,31 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #206: two pointers whose referents both resolve to fn types are
|
||||
// NOMINALLY assignable only when structurally equal — and that case
|
||||
// already returned true via typeeqast at the top. Reaching here
|
||||
// means the fn signatures differ, or a bare structural `*fn` value
|
||||
// is being laundered into a `*alias` slot: confidently NOT
|
||||
// assignable, mirror of cstage's nominal type_assignable (harec
|
||||
// types.c:1039-1066). The direct `&fn` adopt-the-alias case is
|
||||
// handled at the assignment caller sites via assignableaddrfn, NOT
|
||||
// here. Without this the lenient catch-all below silently accepted
|
||||
// the laundering shape.
|
||||
if (du.kind == nkind.N_TPTR) {
|
||||
if (su.kind == nkind.N_TPTR) {
|
||||
let dref: *node = resolvealias(c, unwrapbang(du.lhs));
|
||||
let sref: *node = resolvealias(c, unwrapbang(su.lhs));
|
||||
if (dref != nil) {
|
||||
if (sref != nil) {
|
||||
if (dref.kind == nkind.N_TFN) {
|
||||
if (sref.kind == nkind.N_TFN) {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Anything else: don't claim confidence.
|
||||
*confident = false;
|
||||
return true;
|
||||
@@ -13429,8 +13565,11 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
if (est != nil) {
|
||||
if (!isassignable(c, elemtn, est, &conf2)) {
|
||||
if (conf2) {
|
||||
errnotassign(c, elemtn, est, "let");
|
||||
return;
|
||||
// #206: direct `&fn` array element.
|
||||
if (!assignableaddrfn(c, elemtn, ev)) {
|
||||
errnotassign(c, elemtn, est, "let");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -13443,6 +13582,8 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
};
|
||||
let conf: bool = false;
|
||||
let ok: bool = isassignable(c, n.lhs, src, &conf);
|
||||
// #206: direct `&fn` → `*alias` / `(*alias | void)` slot.
|
||||
if (!ok) { if (assignableaddrfn(c, n.lhs, n.rhs)) { ok = true; }; };
|
||||
if (!conf) { return; };
|
||||
if (!ok) { errnotassign(c, n.lhs, src, "let"); };
|
||||
};
|
||||
@@ -13460,6 +13601,8 @@ fn checkretassign(c: *checker, n: *node) void = {
|
||||
if (src == nil) { return; };
|
||||
let conf: bool = false;
|
||||
let ok: bool = isassignable(c, c.fnret, src, &conf);
|
||||
// #206: direct `&fn` returned into a `*alias` / `(*alias | void)`.
|
||||
if (!ok) { if (assignableaddrfn(c, c.fnret, n.lhs)) { ok = true; }; };
|
||||
if (!conf) { return; };
|
||||
if (!ok) { errnotassign(c, c.fnret, src, "return"); };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user