ffi M5.A.next.4A.bare.1.B: bare $args lowers to []Type slice value

Step 4A final-slice fix. Bare `$<pack_name>` (no `[<int>]`)
in expression position now parses + lowers to a comptime
`[]Type` slice value carrying one `const_type(TypeId)` per
pack element.

Plumbing:

- src/ast.zig: new `ComptimePackRef { pack_name }` node +
  `comptime_pack_ref` variant in Data.
- src/parser.zig: `parsePrimary`'s `$` arm makes `[` optional
  after the pack name. With `[<int>]` → existing
  `pack_index_type_expr` (single Type value). Without → new
  `comptime_pack_ref` (whole pack as []Type).
- src/sema.zig: adds the no-op switch arms for the new node
  in `analyzeNode` and `findNodeAtOffset`.
- src/ir/lower.zig: `lowerExpr` arm reads `pack_arg_types[name]`
  and calls `buildPackSliceValue(arg_tys)`. The helper allocas
  a `[N x Any]` array, emits one `const_type(arg_tys[i])` per
  slot, then a slice `{data_ptr, len}` aggregate. No active
  binding → focused diagnostic + null slice placeholder. The
  IR slice element type is `Any` (matches the today's
  `Type → .any` mapping in type_bridge); the interp stores
  raw `.type_tag` Values directly (NOT Any-boxed) so
  `args[i]` at interp time reads a Type value.
- src/ir/emit_llvm.zig: relaxed `const_type` to silently emit
  undef-i64 instead of the previous stderr-noisy bail. Storage
  of Type values in runtime aggregates is harmless (undef in,
  undef out). Use-site misuse is caught by the bails on
  type_name/type_eq/has_impl and the bitcast guard.

`examples/170-pack-bare-value.sx` flips from the parse-error
lock-in to "0/1/3/4" — four call shapes of `len_of(..$args) ->
s64 { list := $args; return list.len; }`. The slice's `.len`
field carries the per-mono pack arity.

210/210 example tests + `zig build test` green.

The remaining 4A.bare slices (4 and 5) — resolveTypeArg
silent-arm fix for index_expr + smoke test of a real builder
walking $args — are separate commits per the cadence rule.
This commit is contained in:
agra
2026-05-27 19:10:37 +03:00
parent c792642d76
commit 5a4a19b3ab
5 changed files with 114 additions and 32 deletions

View File

@@ -2028,6 +2028,28 @@ pub const Lowering = struct {
fn lowerExpr(self: *Lowering, node: *const Node) Ref {
return switch (node.data) {
// Bare `$<pack>` in expression position → an `[]Type` slice
// value where each element is a `const_type(arg_types[i])`.
// Per `Type → .any` mapping in type_bridge, the IR slice
// type is `[]Any`; the interp stores raw `.type_tag` Values
// (NOT Any-boxed) so `args[i]` reads back as a Type value
// 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});
}
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});
}
break :blk self.builder.constNull(self.module.types.sliceOf(.any));
};
break :blk self.buildPackSliceValue(arg_tys);
},
// Pack-index in expression position: `$<pack>[<lit>]` →
// `const_type(arg_types[index])`. Yields a comptime-only
// Type value (`Value.type_tag(TypeId)` in the interp).
@@ -8193,6 +8215,49 @@ pub const Lowering = struct {
/// index it with a runtime int resolve through the slice (with
/// element type `Any`). Literal-indexed accesses keep the
/// concrete per-position types via `packArgNodeAt`.
/// Build a `[]Type` slice VALUE for a bare `$<pack>` reference.
/// Differs from `materialisePackSlice` (which boxes each pack
/// element as Any so the body's `args[i]` reads an Any) — this
/// helper stores raw `.type_tag` Values via `const_type`, so the
/// slice is a list-of-Types that builder fns walk at interp time.
/// Slice IR type is `[]Any` (since `Type → .any`); the interp
/// stores whichever Value the elements actually carry.
fn buildPackSliceValue(self: *Lowering, arg_types: []const TypeId) Ref {
const any_slice_ty = self.module.types.sliceOf(.any);
const any_ptr_ty = self.module.types.ptrTo(.any);
if (arg_types.len == 0) {
const null_ptr = self.builder.constNull(any_ptr_ty);
const zero_len = self.builder.constInt(0, .s64);
const slice_slot = self.builder.alloca(any_slice_ty);
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, any_ptr_ty, any_slice_ty);
self.builder.store(ptr_gep, null_ptr);
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty);
self.builder.store(len_gep, zero_len);
return self.builder.load(slice_slot, any_slice_ty);
}
const array_ty = self.module.types.arrayOf(.any, @intCast(arg_types.len));
const array_slot = self.builder.alloca(array_ty);
for (arg_types, 0..) |ty, i| {
const type_val = self.builder.constType(ty);
const idx_ref = self.builder.constInt(@intCast(i), .s64);
const elem_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = idx_ref } }, any_ptr_ty);
self.builder.store(elem_ptr, type_val);
}
const slice_slot = self.builder.alloca(any_slice_ty);
const zero = self.builder.constInt(0, .s64);
const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = zero } }, any_ptr_ty);
const len_ref = self.builder.constInt(@intCast(arg_types.len), .s64);
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, any_ptr_ty, any_slice_ty);
self.builder.store(ptr_gep, data_ptr);
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, any_slice_ty);
self.builder.store(len_gep, len_ref);
return self.builder.load(slice_slot, any_slice_ty);
}
fn materialisePackSlice(
self: *Lowering,
scope: *Scope,