From 9137f4158db4b5339dfea9c1f504d52b4f7f6581 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 27 May 2026 17:26:27 +0300 Subject: [PATCH] ffi M5.A.next.3a.C: $args[$i] in fn-pointer type literals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `resolveFunctionTypeWithBindings` so `function_type_expr` in a binding-aware context — local var annotations, return types, nested type expressions — recursively resolves through the active pack bindings. Without this, the fall-through to `type_bridge.resolveAstType` lost pack context and the new `pack_index_type_expr` arm spammed the "outside pack-aware context" diagnostic (the function still worked by accident thanks to the `.s64` fallback). Plumbing: - `resolveTypeWithBindings` adds a `function_type_expr` case in both the bindings-active branch and the fallthrough switch (the same shape as `closure_type_expr`). - `resolveFunctionTypeWithBindings` recursively resolves each param + return type with bindings, then calls `functionTypeCC` with the AST's calling convention. `examples/167-pack-type-fnptr.sx` exercises the pattern step 5's trampoline needs: fp : (*void, $args[0]) -> $args[1] = double_s64; return fp(null, args[0]); Output: 14 (= 7*2 via the typed fn-pointer). 207/207 example tests + `zig build test` green. --- examples/167-pack-type-fnptr.sx | 30 +++++++++++++++++++++++++ src/ir/lower.zig | 26 +++++++++++++++++++++ tests/expected/167-pack-type-fnptr.exit | 1 + tests/expected/167-pack-type-fnptr.txt | 1 + 4 files changed, 58 insertions(+) create mode 100644 examples/167-pack-type-fnptr.sx create mode 100644 tests/expected/167-pack-type-fnptr.exit create mode 100644 tests/expected/167-pack-type-fnptr.txt diff --git a/examples/167-pack-type-fnptr.sx b/examples/167-pack-type-fnptr.sx new file mode 100644 index 0000000..dcd14e8 --- /dev/null +++ b/examples/167-pack-type-fnptr.sx @@ -0,0 +1,30 @@ +// Variadic heterogeneous type packs — step 3: `$args[$i]` in +// fn-pointer type literals. +// +// `(*void, $args[0]) -> $args[1]` is the shape step 5's generic +// `Into(Block) for Closure(..$args) -> $R` body needs for its +// trampoline: +// typed_fn : (*void, $args[0], $args[1], ...) -> $R = +// xx block_self.sx_fn; +// — the trampoline's invoke slot is typed against the pack +// positions to bridge the Block ABI to the sx closure. +// +// This test exercises the same plumbing on a smaller scale: a +// local var with a fn-pointer type whose param + return types +// both interpolate through `$args[$i]`. + +#import "modules/std.sx"; + +// Two concrete handlers that match the per-mono fn-pointer shape. +double_s64 :: (env: *void, x: s64) -> s64 => x * 2; + +via_fnptr :: (..$args) -> $args[1] { + fp : (*void, $args[0]) -> $args[1] = double_s64; + return fp(null, args[0]); +} + +main :: () -> s32 { + n := via_fnptr(7, 0); // (s64, s64) → fp : (*void, s64) -> s64 + print("{}\n", n); + return 0; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 2d10e2a..4c54221 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -9828,6 +9828,9 @@ pub const Lowering = struct { .closure_type_expr => |ct| { return self.resolveClosureTypeWithBindings(&ct); }, + .function_type_expr => |ft| { + return self.resolveFunctionTypeWithBindings(&ft); + }, else => {}, } } @@ -9865,6 +9868,9 @@ pub const Lowering = struct { .closure_type_expr => |ct| { return self.resolveClosureTypeWithBindings(&ct); }, + .function_type_expr => |ft| { + return self.resolveFunctionTypeWithBindings(&ft); + }, else => {}, } // Check type aliases before falling through to type_bridge @@ -9904,6 +9910,26 @@ pub const Lowering = struct { return self.module.types.closureType(param_ids.items, ret_ty); } + /// Resolve a `(Params...) -> Ret` function type expression with the + /// active type/pack bindings applied. Mirrors + /// `resolveClosureTypeWithBindings` but for `function_type_expr`. + /// Unlocks `$args[$i]` in fn-pointer type literals like + /// `fp : (*void, $args[0]) -> $args[1] = ...` — used in step 5's + /// generic trampoline body. + fn resolveFunctionTypeWithBindings(self: *Lowering, ft: *const ast.FunctionTypeExpr) TypeId { + var param_ids = std.ArrayList(TypeId).empty; + defer param_ids.deinit(self.alloc); + for (ft.param_types) |pt| { + param_ids.append(self.alloc, self.resolveTypeWithBindings(pt)) catch return .void; + } + const ret_ty = if (ft.return_type) |rt| self.resolveTypeWithBindings(rt) else .void; + const cc: types.TypeInfo.CallConv = switch (ft.call_conv) { + .default => .default, + .c => .c, + }; + return self.module.types.functionTypeCC(param_ids.items, ret_ty, cc); + } + /// Resolve a .call node that represents a type constructor (e.g., List(T), Vector(N, T)). fn resolveTypeCallWithBindings(self: *Lowering, cl: *const ast.Call) TypeId { const callee_name: []const u8 = switch (cl.callee.data) { diff --git a/tests/expected/167-pack-type-fnptr.exit b/tests/expected/167-pack-type-fnptr.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/167-pack-type-fnptr.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/167-pack-type-fnptr.txt b/tests/expected/167-pack-type-fnptr.txt new file mode 100644 index 0000000..8351c19 --- /dev/null +++ b/tests/expected/167-pack-type-fnptr.txt @@ -0,0 +1 @@ +14