fix: resolve closure/fn-pointer struct-field call types (issue 0201)
Calling a closure or function-pointer value stored in a struct data field (`box.run(args)`) typed the call as 'unresolved': value returns marshalled as garbage, failable fields could not be try/catch-ed. Lowering already dispatched these (call_closure / call_indirect); only CallResolver.plan lacked a field-access arm. Add a closure/fn-pointer field arm to plan (before the instance-method check, mirroring lowering's precedence — a closure-typed field shadows a same-named method) and extend the lowering closure-field arm to also handle bare .function fields via call_indirect. Lock: examples/closures/0315-closures-struct-field-call.sx.
This commit is contained in:
@@ -270,6 +270,47 @@ pub const CallResolver = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Struct field holding a CLOSURE value, called directly
|
||||
// (`box.run(args)` where `run: Closure(..) -> R`). Mirrors the
|
||||
// lowering dispatch (call.zig closure-field arm) which runs in the
|
||||
// value-receiver path BEFORE instance-method dispatch — so a
|
||||
// closure-typed field shadows a same-named method, exactly as
|
||||
// lowering binds it. Without this the call typed as `.unresolved`
|
||||
// (issue 0201): value returns marshalled as garbage, failable
|
||||
// returns couldn't be `try`/`catch`-ed. Lowering owns the dispatch;
|
||||
// plan only needs the field's `.ret` so typing matches.
|
||||
{
|
||||
var fld_recv = recv_ty;
|
||||
if (!fld_recv.isBuiltin()) {
|
||||
const ri = self.l.module.types.get(fld_recv);
|
||||
if (ri == .pointer) fld_recv = ri.pointer.pointee;
|
||||
}
|
||||
if (!fld_recv.isBuiltin()) {
|
||||
const ri = self.l.module.types.get(fld_recv);
|
||||
if (ri == .@"struct") {
|
||||
const field_name_id = self.l.module.types.internString(cfa.field);
|
||||
for (ri.@"struct".fields) |f| {
|
||||
if (f.name == field_name_id and !f.ty.isBuiltin()) {
|
||||
const fti = self.l.module.types.get(f.ty);
|
||||
if (fti == .closure) return .{
|
||||
.kind = .closure,
|
||||
.return_type = fti.closure.ret,
|
||||
.target = .{ .named = cfa.field },
|
||||
};
|
||||
// Bare function-pointer field (`fp: (T) -> R`),
|
||||
// symmetric with the bare-identifier fn-pointer
|
||||
// path above — call via `call_indirect`.
|
||||
if (fti == .function) return .{
|
||||
.kind = .fn_pointer,
|
||||
.return_type = fti.function.ret,
|
||||
.target = .{ .named = cfa.field },
|
||||
.prepends_ctx = self.l.implicit_ctx_enabled and fti.function.call_conv != .c,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Instance method call: obj.method(args) → StructName.method.
|
||||
{
|
||||
var obj_ty = recv_ty;
|
||||
|
||||
@@ -990,6 +990,28 @@ pub fn lowerCall(self: *Lowering, c_in: *const ast.Call) Ref {
|
||||
} else self.alloc.dupe(Ref, args.items) catch unreachable;
|
||||
return self.builder.emit(.{ .call_closure = .{ .callee = closure_val, .args = owned } }, fti.closure.ret);
|
||||
}
|
||||
// Bare function-pointer field (`fp: (T) -> R`, no env) —
|
||||
// load the field value and call it via `call_indirect`,
|
||||
// mirroring the bare-identifier / global fn-pointer paths
|
||||
// (ctx prepend gated on the fn-ptr's own ABI).
|
||||
if (fti == .function) {
|
||||
var agg = obj;
|
||||
const oi = self.module.types.get(obj_ty);
|
||||
if (oi == .pointer) {
|
||||
agg = self.builder.load(obj, oi.pointer.pointee);
|
||||
}
|
||||
const fp_val = self.builder.structGet(agg, @intCast(fi), f.ty);
|
||||
// Coerce user args to the fn-ptr's param types (issue 0186).
|
||||
coerceClosureCallArgs(self, args.items, fti.function.params);
|
||||
var final_args = std.ArrayList(Ref).empty;
|
||||
defer final_args.deinit(self.alloc);
|
||||
if (self.fnPtrTypeWantsCtx(f.ty)) {
|
||||
final_args.append(self.alloc, self.current_ctx_ref) catch unreachable;
|
||||
}
|
||||
final_args.appendSlice(self.alloc, args.items) catch unreachable;
|
||||
const owned = self.alloc.dupe(Ref, final_args.items) catch unreachable;
|
||||
return self.builder.emit(.{ .call_indirect = .{ .callee = fp_val, .args = owned } }, fti.function.ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user