ffi M5.A.next.2b.fu2.B: generic pack-fn return — infer ret_ty from body

Fix for follow-up #2 from step 2b. When a pack-fn declares
`(..\$args) -> \$R` (return type a generic name), the mono now
infers ret_ty from the body's first explicit `return X;` or
falls back to the tail expression of an arrow-form body.

Plumbing in src/ir/lower.zig:

- `inferPackBodyReturnType(body)` walks the body via the
  existing `findReturnValueType` helper (return stmts) and
  falls through to `inferExprType` on the tail expression for
  arrow-form / tail-expr bodies.
- `monomorphizePackFn` now pre-installs `pack_arg_nodes` and
  `pack_param_count` BEFORE resolving the return type so the
  inference can substitute `args[<lit>]` to call-site arg
  AST nodes during type lookup.
- Generic-ret detection: `fd.return_type` AST node is a
  `type_expr` with `is_generic = true`. Concrete returns stay
  on the standard `resolveReturnType` path.

`examples/159-pack-generic-ret.sx` flips from `0 0` (silent-
zero coercion through opaque struct ret_ty) to `42 99`.

198/198 example tests + `zig build test` green.
This commit is contained in:
agra
2026-05-27 16:28:52 +03:00
parent e44ba4b240
commit c917f92509
2 changed files with 53 additions and 15 deletions

View File

@@ -8066,6 +8066,27 @@ pub const Lowering = struct {
return self.builder.constInt(0, .void);
}
/// Infer the return type of a pack-fn body for the generic-`$R`
/// case. Walks the body looking for the first concrete return
/// type: a `return X;` statement's value type, or — failing that —
/// the tail expression of an arrow-form body. Caller must have
/// `pack_arg_nodes` installed so `args[<lit>]` substitutes during
/// inference. Falls back to `.s64` if nothing concrete is found
/// (matches the broader "default to .s64" convention elsewhere).
fn inferPackBodyReturnType(self: *Lowering, body: *const Node) TypeId {
// First try explicit `return X;` — walks past structured
// control flow but stops at nested fn / lambda bodies.
if (self.findReturnValueType(body)) |ty| return ty;
// Arrow-form / tail-expression body: the body IS the value.
// For block bodies whose last stmt is an expression, walk down.
if (body.data == .block) {
const stmts = body.data.block.stmts;
if (stmts.len == 0) return .void;
return self.inferExprType(stmts[stmts.len - 1]);
}
return self.inferExprType(body);
}
/// Per-call-shape monomorphisation entry for pack-fns
/// (`isPackFn(fd) == true`). Computes a mangled name from the
/// call-site arg types, builds the mono if it's not cached, and
@@ -8169,13 +8190,12 @@ pub const Lowering = struct {
self.builder.inst_counter = saved_counter;
}
const ret_ty = self.resolveReturnType(fd);
self.target_type = ret_ty;
const wants_ctx = self.funcWantsImplicitCtx(fd);
// Synthesise pack-param names + AST ident nodes used to bind
// `args[<lit>]` substitutions during body lowering.
// `args[<lit>]` substitutions during body lowering. Built
// BEFORE return-type resolution so the generic-`$R` path can
// pre-install the binding for type inference.
var pack_synth_names = std.ArrayList([]const u8).empty;
defer pack_synth_names.deinit(self.alloc);
var pack_arg_idents = std.ArrayList(*const Node).empty;
@@ -8191,6 +8211,32 @@ pub const Lowering = struct {
pack_arg_idents.append(self.alloc, ident_node) catch return;
}
// Resolve return type. When the declared type is a generic
// name (e.g. `(..$args) -> $R`), `resolveReturnType` would
// return an opaque struct TypeId and the mono's signature
// would be wrong. Pre-install the pack bindings + infer the
// ret type from the body's tail expression / first explicit
// `return X;` instead.
var pre_pan = std.StringHashMap([]const *const Node).init(self.alloc);
defer pre_pan.deinit();
pre_pan.put(pack_name, pack_arg_idents.items) catch return;
var pre_ppc = std.StringHashMap(u32).init(self.alloc);
defer pre_ppc.deinit();
pre_ppc.put(pack_name, @intCast(arg_types.len)) catch return;
self.pack_arg_nodes = pre_pan;
self.pack_param_count = pre_ppc;
const declared_is_generic_ret = blk: {
const rt = fd.return_type orelse break :blk false;
if (rt.data != .type_expr) break :blk false;
break :blk rt.data.type_expr.is_generic;
};
const ret_ty: TypeId = if (declared_is_generic_ret)
self.inferPackBodyReturnType(fd.body)
else
self.resolveReturnType(fd);
self.target_type = ret_ty;
// Param list: ctx (if needed) + fixed prefix + N pack params.
// NOT deinit'd — `params.items` is stored by reference in
// `Function.init` and read back later via `func.params`.
@@ -8248,16 +8294,8 @@ pub const Lowering = struct {
param_idx += 1;
}
// Install pack bindings for the body lowering.
var pan_map = std.StringHashMap([]const *const Node).init(self.alloc);
defer pan_map.deinit();
pan_map.put(pack_name, pack_arg_idents.items) catch return;
self.pack_arg_nodes = pan_map;
var ppc_map = std.StringHashMap(u32).init(self.alloc);
defer ppc_map.deinit();
ppc_map.put(pack_name, @intCast(arg_types.len)) catch return;
self.pack_param_count = ppc_map;
// Pack bindings remain installed from the pre-resolution
// (generic-`$R`) inference step above. No need to reinstall.
if (ret_ty != .void) {
const body_val = self.lowerBlockValue(fd.body);