From 165b621ab368525c8eae58453646aea8ab1437c7 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 27 May 2026 21:58:33 +0300 Subject: [PATCH] =?UTF-8?q?ffi=20M5.A.next.5.2.B:=20generic=20Into(Block)?= =?UTF-8?q?=20impl=20=E2=80=94=20make-green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the generic `impl Into(Block) for Closure(..$args) -> $R` in `library/modules/std/objc_block.sx` alongside the existing hand-rolled `Closure() -> void` and `Closure(bool) -> void` impls. The convert body is a single `#insert build_block_convert($args, $R);` — per-call-shape monomorphisation re-runs the builder so each closure shape gets its dedicated nested `callconv(.c)` trampoline + Block literal. The impl-mono path threads pack types through `pack_bindings[args]` and the single-type return through `type_bindings[R]`. Both need to be visible to the body's `$args` / `$R` expression-position references — the existing lowering only consulted `pack_arg_types` (set by pack-fn mono, not by tryPackImplMatch). Two small extensions: - `lowerExpr`'s `.comptime_pack_ref` arm now consults `pack_arg_types` → `pack_bindings` → `type_bindings` in order, treating a `type_bindings` hit as a single `const_type(T)` value rather than the slice form. - `resolveTypeArg` grows a `.comptime_pack_ref` arm that maps the same name through `type_bindings` so type-arg positions (e.g. inside `type_name(...)` in the builder body) resolve the bound single Type. - `type_bridge.isTypeShapedAstNode` lists `comptime_pack_ref` and `pack_index_type_expr` as type-shaped so `buildTypeBindings`'s strategy-1 explicit-arg path picks them up when calling a `$T: Type`-generic fn. `examples/177-generic-into-block.sx` flips green: a `Closure(s64, s64) -> void` (no hand-rolled impl) is converted through the generic impl, its block invoked via a typed `callconv(.c)` fn-pointer, and the closure's side effects land in the host globals. Hand-rolled impls remain for `()` and `(bool)` shapes; 5.3 deletes those once a focused test covers their behaviour through the generic path. Suite at 217/217. --- library/modules/std/objc_block.sx | 14 +++++++++ src/ir/lower.zig | 52 ++++++++++++++++++++++++------- src/ir/type_bridge.zig | 2 ++ 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/library/modules/std/objc_block.sx b/library/modules/std/objc_block.sx index 0d0f3cc..3c0b031 100644 --- a/library/modules/std/objc_block.sx +++ b/library/modules/std/objc_block.sx @@ -108,6 +108,20 @@ impl Into(Block) for Closure(bool) -> void { } } +// Generic impl: covers any closure shape not handled by the +// hand-rolled per-signature impls above. The compiler +// monomorphises this body per call shape; inside each mono, +// `$args` is the bound pack of arg types and `$R` is the bound +// return type. The `#insert` evaluates `build_block_convert` at +// comptime and substitutes the resulting source — a nested +// `callconv(.c)` trampoline + the Block literal that points its +// `invoke` slot at it. +impl Into(Block) for Closure(..$args) -> $R { + convert :: (self: Closure(..$args) -> $R) -> Block { + #insert build_block_convert($args, $R); + } +} + // Comptime builder for the generic `Into(Block) for Closure(..$args) // -> $R` impl body. Receives the closure's per-position pack types // (`args`, a comptime `[]Type`) and the closure's return type diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 25513b3..dae0986 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -2058,19 +2058,37 @@ pub const Lowering = struct { // directly. Step 4 final slice — lets builder fns walk the // whole pack at interp time. .comptime_pack_ref => |cpr| blk: { - const pat = self.pack_arg_types orelse { - if (self.diagnostics) |diags| { - diags.addFmt(.err, node.span, "pack reference ${s} used outside an active pack binding", .{cpr.pack_name}); + // `$` is overloaded in expression position: + // - Inside a pack-fn mono (or a `tryPackImplMatch` + // impl mono), `name` is a pack binding → slice of + // element types (`[]Type` lowered as `[]Any`). + // - Inside an impl mono whose impl pattern bound a + // single-type generic (`$R: Type` in + // `Closure(..$args) -> $R`), `name` is in + // `type_bindings` → single `const_type(R)` value. + // Pack arg types are checked first (the slice form), + // then pack_bindings (the impl-mono mirror), then + // type_bindings (single-type binding); only if all + // miss is it a real "outside an active binding" error. + if (self.pack_arg_types) |pat| { + if (pat.get(cpr.pack_name)) |arg_tys| { + break :blk self.buildPackSliceValue(arg_tys); } - break :blk self.builder.constNull(self.module.types.sliceOf(.any)); - }; - const arg_tys = pat.get(cpr.pack_name) orelse { - if (self.diagnostics) |diags| { - diags.addFmt(.err, node.span, "pack reference ${s} has no active binding", .{cpr.pack_name}); + } + if (self.pack_bindings) |pb| { + if (pb.get(cpr.pack_name)) |arg_tys| { + break :blk self.buildPackSliceValue(arg_tys); } - break :blk self.builder.constNull(self.module.types.sliceOf(.any)); - }; - break :blk self.buildPackSliceValue(arg_tys); + } + if (self.type_bindings) |tb| { + if (tb.get(cpr.pack_name)) |ty| { + break :blk self.builder.constType(ty); + } + } + if (self.diagnostics) |diags| { + diags.addFmt(.err, node.span, "pack reference ${s} used outside an active pack binding", .{cpr.pack_name}); + } + break :blk self.builder.constNull(self.module.types.sliceOf(.any)); }, // Pack-index in expression position: `$[]` → // `const_type(arg_types[index])`. Yields a comptime-only @@ -9099,6 +9117,18 @@ pub const Lowering = struct { } return .void; } + // Bare `$` in a type-arg position. Single-type generic + // bindings (`$R: Type` in `Closure(..$args) -> $R`) live in + // `type_bindings`; if the name is bound there, return the + // bound TypeId directly. Pack bindings would otherwise resolve + // to a slice value, not a single Type — the caller (e.g. + // `type_name(...)`) expects a single arg. + if (node.data == .comptime_pack_ref) { + const cpr = node.data.comptime_pack_ref; + if (self.type_bindings) |tb| { + if (tb.get(cpr.pack_name)) |ty| return ty; + } + } switch (node.data) { .identifier => |id| { // Check type bindings first (from generic monomorphization) diff --git a/src/ir/type_bridge.zig b/src/ir/type_bridge.zig index f52beff..bc4141f 100644 --- a/src/ir/type_bridge.zig +++ b/src/ir/type_bridge.zig @@ -357,6 +357,8 @@ pub fn isTypeShapedAstNode(node: *const Node, table: *TypeTable) bool { .closure_type_expr, .tuple_type_expr, .parameterized_type_expr, + .pack_index_type_expr, + .comptime_pack_ref, => true, .identifier => |id| table.findByName(table.internString(id.name)) != null, .tuple_literal => |tl| blk: {