ffi M5.A.next.2a.B: pack typed indexing — args[$i] substitutes call arg

Pack-fn bodies that index the pack via `args[<int_literal>]`
now resolve to the i-th call-site argument's lowered value
directly, propagating the call arg's concrete type instead
of the boxed `Any` that the `[]Any` slice path returns.

New plumbing in `src/ir/lower.zig`:

- `pack_arg_nodes: ?std.StringHashMap([]const *const Node)` on
  Lowering. Maps a pack param name (e.g. "args") to the slice
  of call-site arg AST nodes.
- `lowerComptimeCall` populates the map when the variadic
  param is heterogeneous (`is_variadic AND is_comptime`, i.e.
  the `..$args` form). Plain `args: ..Any` keeps the existing
  `[]Any` slice path so stdlib's `format`/`print` continue
  unchanged. The map is saved/restored across nested calls
  mirroring `comptime_param_nodes`.
- `packArgNodeAt(ie)` returns the call-arg node when an
  index_expr matches `<pack_name>[<comptime_int_literal>]`
  with the index in range; null otherwise (fall through to
  standard slice indexing for runtime indices or non-pack
  bases).
- `lowerIndexExpr` checks `packArgNodeAt` first; on a hit it
  lowers the call arg node directly. `inferExprType`'s
  `index_expr` arm does the parallel check so AST-level type
  inference (e.g., for field-access type checking) sees the
  concrete call-arg type.

`examples/156-pack-typed-index.sx` flips from
"field 'x' not found on type 'Any'" to `7` — `args[0].x` now
resolves through the concrete `Point` type instead of Any.

Out of scope (deferred): non-literal comptime indices
(`args[$i]` where `$i` is an arbitrary comptime expression);
`$args[$i]` in type positions (step 3); per-mono mangling
(monomorphisation stays inline-only).

195/195 example tests + `zig build test` green.
This commit is contained in:
agra
2026-05-27 13:55:19 +03:00
parent 223ec3d0b3
commit cd367847a9
3 changed files with 71 additions and 2 deletions

View File

@@ -155,6 +155,15 @@ pub const Lowering = struct {
/// and trips LLVM's "Terminator found in the middle of a basic
/// block" verifier.
inline_return_target: ?InlineReturnInfo = null,
/// Active pack-arg-node bindings during a comptime call's body lowering.
/// Maps the pack-param name (e.g. `args`) to the slice of call-site
/// argument AST nodes. `lowerIndexExpr` (and `inferExprType`) check
/// this map when the index expression's base is an identifier matching
/// a pack name AND the index is a comptime int literal — substitutes
/// with the i-th call arg's lowered value so the static type tracks
/// the call arg's real type instead of `Any`. The `[]Any` slice path
/// remains the runtime-indexed fallback for non-literal indices.
pack_arg_nodes: ?std.StringHashMap([]const *const Node) = null,
struct_const_map: std.StringHashMap(StructConstInfo) = std.StringHashMap(StructConstInfo).init(std.heap.page_allocator), // "Struct.CONST" → value info
module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator), // module-level value constants (e.g. AF_INET :s32: 2)
foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames
@@ -4193,6 +4202,15 @@ pub const Lowering = struct {
}
fn lowerIndexExpr(self: *Lowering, ie: *const ast.IndexExpr) Ref {
// Pack-arg substitution: `args[<int_literal>]` inside a body
// whose enclosing comptime call bound `args` as a pack name.
// Lowering the i-th call-site arg directly gives the concrete
// call-arg type — bypasses the `[]Any` slice boxing that would
// otherwise lose the type. Non-literal indices fall through to
// the standard slice indexing path.
if (self.packArgNodeAt(ie)) |arg_node| {
return self.lowerExpr(arg_node);
}
const obj = self.lowerExpr(ie.object);
const idx = self.lowerExpr(ie.index);
// Infer element type from the object's slice/array type
@@ -4201,6 +4219,22 @@ pub const Lowering = struct {
return self.builder.emit(.{ .index_get = .{ .lhs = obj, .rhs = idx } }, elem_ty);
}
/// Returns the call-site arg AST node when `ie` matches
/// `<pack_name>[<comptime_int_literal>]` with the pack name bound
/// in the active `pack_arg_nodes` map and the index in range.
/// Otherwise null — caller falls back to standard slice indexing.
fn packArgNodeAt(self: *Lowering, ie: *const ast.IndexExpr) ?*const Node {
const pan = self.pack_arg_nodes orelse return null;
if (ie.object.data != .identifier) return null;
const arg_nodes = pan.get(ie.object.data.identifier.name) orelse return null;
if (ie.index.data != .int_literal) return null;
const raw: i64 = ie.index.data.int_literal.value;
if (raw < 0) return null;
const i: usize = @intCast(raw);
if (i >= arg_nodes.len) return null;
return arg_nodes[i];
}
fn lowerSliceExpr(self: *Lowering, se: *const ast.SliceExpr) Ref {
const obj = self.lowerExpr(se.object);
const lo = if (se.start) |s| self.lowerExpr(s) else self.builder.constInt(0, .s64);
@@ -7183,11 +7217,26 @@ pub const Lowering = struct {
// Build comptime param substitution map: param_name → call_site AST node
var cpn = std.StringHashMap(*const Node).init(self.alloc);
var call_arg_idx: usize = 0;
// Pack-arg-node registration (step 2 of the variadic heterogeneous
// type packs feature): when the fn declares a pack param, record
// the slice of call-site arg nodes under the pack name so the
// body's `args[$i]` lowering can substitute the i-th arg with
// its concrete-typed value instead of the `[]Any` slice load.
var pack_arg_name: ?[]const u8 = null;
var pack_arg_slice: []const *const Node = &.{};
for (fd.params) |param| {
if (param.is_variadic) {
// Variadic param: pack remaining call args into []Any slice
self.lowerVariadicArgs(param.name, call_node.args, call_arg_idx);
// Only heterogeneous pack form `..$args` (is_comptime AND
// is_variadic) registers for typed indexing. Plain
// `args: ..Any` keeps the existing []Any path so stdlib's
// `format`/`print` continue boxing through Any.
if (param.is_comptime and call_arg_idx <= call_node.args.len) {
pack_arg_name = param.name;
pack_arg_slice = call_node.args[call_arg_idx..];
}
break; // variadic is always the last param
}
if (call_arg_idx >= call_node.args.len) break;
@@ -7227,6 +7276,23 @@ pub const Lowering = struct {
self.comptime_param_nodes = cpn;
defer self.comptime_param_nodes = saved_cpn;
// Install pack-arg-node binding. Mirrors `comptime_param_nodes`:
// each call owns its own map, nested calls shadow. `lowerIndexExpr`
// reads the map for `args[<int_literal>]` substitution.
const saved_pan = self.pack_arg_nodes;
var pan_map: std.StringHashMap([]const *const Node) = undefined;
var pan_installed = false;
if (pack_arg_name) |pn| {
pan_map = std.StringHashMap([]const *const Node).init(self.alloc);
pan_map.put(pn, pack_arg_slice) catch {};
self.pack_arg_nodes = pan_map;
pan_installed = true;
}
defer {
if (pan_installed) pan_map.deinit();
self.pack_arg_nodes = saved_pan;
}
// Lower the body — capture return value for functions with return type
const ret_ty = self.resolveReturnType(fd);
if (ret_ty != .void) {
@@ -11068,6 +11134,9 @@ pub const Lowering = struct {
} });
},
.index_expr => |ie| {
if (self.packArgNodeAt(&ie)) |arg_node| {
return self.inferExprType(arg_node);
}
const obj_ty = self.inferExprType(ie.object);
return self.getElementType(obj_ty);
},

View File

@@ -1 +1 @@
1
0

View File

@@ -1 +1 @@
/Users/agra/projects/sx/examples/156-pack-typed-index.sx:25:30: error: field 'x' not found on type 'Any'
7