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:
@@ -322,6 +322,58 @@ def_cast_fits(Type *t, u64 v)
|
||||
return ext == v;
|
||||
}
|
||||
|
||||
/* addrfn_ptr_matches — true iff ptr is a pointer whose referent
|
||||
* (after one NAMED peel) is a fn type structurally equal to fnty. */
|
||||
static int
|
||||
addrfn_ptr_matches(Type *ptr, Type *fnty)
|
||||
{
|
||||
if (ptr == NULL || ptr->kind != TY_PTR) return 0;
|
||||
Type *ref = ptr->sub;
|
||||
if (ref && ref->kind == TY_NAMED) ref = ref->under;
|
||||
if (ref == NULL || ref->kind != TY_FN) return 0;
|
||||
return type_eq(ref, fnty);
|
||||
}
|
||||
|
||||
/* assignable_addrfn — project #206. A bare `&fn` types structurally
|
||||
* as `*fn(...)`, which is nominally distinct from a `*alias`
|
||||
* fn-pointer slot; type_assignable stays fully nominal (preserving
|
||||
* harec's nominal pointer rule, ref/harec/src/types.c:1039-1066) so
|
||||
* any materialized `*fn` value laundered into a `*alias` is rejected.
|
||||
* This admits the one shape harec accepts via its address-of hint
|
||||
* (ref/harec/src/check.c:3594-3626 adopts the alias when the operand
|
||||
* dealiases to the hint's referent): a DIRECT `&`-of-fn-ident whose
|
||||
* signature structurally matches the destination's pointed-to fn
|
||||
* alias, or — for a tagged `(*alias | void)` destination — the single
|
||||
* ptr-to-fn variant it matches (>=2 same-signature variants is
|
||||
* ambiguous → reject, never silently pick). Lives at the assignment
|
||||
* boundary, not in cexpr, because ww's tinfo is nominal-lossy and
|
||||
* cexpr is hint-free (the alias identity is unrecoverable post-typing);
|
||||
* the caller-site rhs node is the only place the direct-&fn shape
|
||||
* survives. "Direct" is strict: the gate fires only when the rhs IS
|
||||
* the address-of node, never on `&fn` nested in a larger expr. */
|
||||
static int
|
||||
assignable_addrfn(Checker *c, Type *dst, Node *rhs)
|
||||
{
|
||||
if (dst == NULL || rhs == NULL) return 0;
|
||||
if (rhs->kind != N_UN || rhs->op != TK_AMP) return 0;
|
||||
Node *id = rhs->lhs;
|
||||
if (id == NULL || id->kind != N_IDENT || id->str == NULL) return 0;
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, id->str);
|
||||
if (s == NULL || s->kind != SK_FN) return 0;
|
||||
Type *fnty = s->type;
|
||||
if (fnty == NULL || fnty->kind != TY_FN) return 0;
|
||||
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
|
||||
if (du == NULL) return 0;
|
||||
if (du->kind == TY_PTR) return addrfn_ptr_matches(du, fnty);
|
||||
if (du->kind == TY_TAGGED) {
|
||||
int n = 0;
|
||||
for (Tparam *p = du->params; p; p = p->next)
|
||||
if (addrfn_ptr_matches(p->type, fnty)) n++;
|
||||
return n == 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* arrlit_init_fits — #130: accept-if-fits for `let/def A: [N]T = [..]`
|
||||
* where the whole-array type_assignable failed (bare-int elements
|
||||
* synthesize [N]i32 via type_default, losing the literal flavor that
|
||||
@@ -360,7 +412,8 @@ arrlit_init_fits(Checker *c, Type *dt, Node *rhs)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!type_assignable(et, e->type)) return 0;
|
||||
if (!type_assignable(et, e->type) && !assignable_addrfn(c, et, e))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -1415,7 +1468,8 @@ cexpr(Checker *c, Node *n)
|
||||
err(c, a->pos,
|
||||
"spread arg must be the last");
|
||||
} else if (elem != ty_err && at != ty_err) {
|
||||
if (!type_assignable(elem, at))
|
||||
if (!type_assignable(elem, at) &&
|
||||
!assignable_addrfn(c, elem, a))
|
||||
err(c, a->pos,
|
||||
"variadic arg: %s not assignable to %s",
|
||||
type_name(c->a, at),
|
||||
@@ -1423,7 +1477,8 @@ cexpr(Checker *c, Node *n)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!type_assignable(p->type, at) && at != ty_err && p->type != ty_err)
|
||||
if (!type_assignable(p->type, at) && at != ty_err && p->type != ty_err
|
||||
&& !assignable_addrfn(c, p->type, a))
|
||||
err(c, a->pos, "argument type %s not assignable to %s",
|
||||
type_name(c->a, at), type_name(c->a, p->type));
|
||||
p = p->next;
|
||||
@@ -1449,7 +1504,8 @@ cexpr(Checker *c, Node *n)
|
||||
}
|
||||
Type *l = cexpr(c, n->lhs);
|
||||
Type *r = cexpr(c, n->rhs);
|
||||
if (l != ty_err && r != ty_err && !type_assignable(l, r))
|
||||
if (l != ty_err && r != ty_err && !type_assignable(l, r) &&
|
||||
!assignable_addrfn(c, l, n->rhs))
|
||||
err(c, n->pos, "cannot assign %s to %s",
|
||||
type_name(c->a, r), type_name(c->a, l));
|
||||
return n->type = l;
|
||||
@@ -1483,7 +1539,8 @@ cexpr(Checker *c, Node *n)
|
||||
err(c, f->pos, "no field '%s' in %s",
|
||||
f->str, type_name(c->a, t));
|
||||
else if (vt != ty_err &&
|
||||
!type_assignable(match->type, vt))
|
||||
!type_assignable(match->type, vt) &&
|
||||
!assignable_addrfn(c, match->type, f->lhs))
|
||||
err(c, f->pos, "field %s: %s not assignable to %s",
|
||||
f->str, type_name(c->a, vt),
|
||||
type_name(c->a, match->type));
|
||||
@@ -1837,7 +1894,8 @@ clet(Checker *c, Node *n)
|
||||
}
|
||||
}
|
||||
if (declared && initt && initt != ty_err && !has_arr_repeat &&
|
||||
!type_assignable(declared, initt))
|
||||
!type_assignable(declared, initt) &&
|
||||
!assignable_addrfn(c, declared, n->rhs))
|
||||
err(c, n->pos, "init %s not assignable to declared %s",
|
||||
type_name(c->a, initt), type_name(c->a, declared));
|
||||
/* #104 fold-2: `let x: f32 = 1.0` — narrow the init literal to f32. */
|
||||
@@ -1877,7 +1935,8 @@ cstmt(Checker *c, Node *n)
|
||||
if (c->ret == ty_void && n->lhs)
|
||||
err(c, n->pos, "return value in void function");
|
||||
else if (c->ret != ty_void && rt != ty_err && c->ret != ty_err
|
||||
&& !type_assignable(c->ret, rt))
|
||||
&& !type_assignable(c->ret, rt)
|
||||
&& !assignable_addrfn(c, c->ret, n->lhs))
|
||||
err(c, n->pos, "return %s not assignable to %s",
|
||||
type_name(c->a, rt), type_name(c->a, c->ret));
|
||||
/* #104 fold-2: `fn g() f32 = { return 1.0; }` — narrow to f32. */
|
||||
|
||||
Reference in New Issue
Block a user