ffi M5.A.next.3a.C: $args[$i] in fn-pointer type literals

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.
This commit is contained in:
agra
2026-05-27 17:26:27 +03:00
parent 3df58febb6
commit 9137f4158d
4 changed files with 58 additions and 0 deletions

View File

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

View File

@@ -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) {

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@
14