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:
agra
2026-06-28 09:18:30 +03:00
parent 69a6ecfb57
commit 45bd561a0d
7 changed files with 201 additions and 0 deletions

View File

@@ -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;