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

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