diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 780ba4d..9ff828a 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -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[]` 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[]` substitutions during body lowering. + // `args[]` 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); diff --git a/tests/expected/159-pack-generic-ret.txt b/tests/expected/159-pack-generic-ret.txt index b748e2d..efc7c1a 100644 --- a/tests/expected/159-pack-generic-ret.txt +++ b/tests/expected/159-pack-generic-ret.txt @@ -1 +1 @@ -0 0 +42 99