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:
10
src/ast.zig
10
src/ast.zig
@@ -57,6 +57,7 @@ pub const Node = struct {
|
||||
many_pointer_type_expr: ManyPointerTypeExpr,
|
||||
optional_type_expr: OptionalTypeExpr,
|
||||
pack_index_type_expr: PackIndexTypeExpr,
|
||||
comptime_pack_ref: ComptimePackRef,
|
||||
force_unwrap: ForceUnwrap,
|
||||
null_coalesce: NullCoalesce,
|
||||
deref_expr: DerefExpr,
|
||||
@@ -374,6 +375,15 @@ pub const PackIndexTypeExpr = struct {
|
||||
index: u32,
|
||||
};
|
||||
|
||||
/// `$<pack_name>` (no indexing) in expression position. Evaluates
|
||||
/// to a comptime `[]Type` slice — the WHOLE pack as data. Step 4
|
||||
/// final slice: lets builder fns walk the pack types and emit
|
||||
/// per-position code (the shape step 5's generic Into(Block) needs
|
||||
/// for its trampoline body).
|
||||
pub const ComptimePackRef = struct {
|
||||
pack_name: []const u8,
|
||||
};
|
||||
|
||||
pub const DeferStmt = struct {
|
||||
expr: *Node,
|
||||
};
|
||||
|
||||
@@ -1668,15 +1668,19 @@ pub const LLVMEmitter = struct {
|
||||
self.mapRef(llvm_val);
|
||||
}
|
||||
},
|
||||
.const_type => |tid| {
|
||||
// Type values are comptime-only — they MUST be erased by
|
||||
// the time we emit LLVM. Reaching here means a Type leaked
|
||||
// into a runtime path (e.g. a builder returned a Type from
|
||||
// a fn the compiler didn't strip). Loud failure: emit an
|
||||
// undef placeholder so the verifier catches downstream
|
||||
// use, AND log the offending TypeId so the diagnostic is
|
||||
// actionable.
|
||||
std.debug.print("emit_llvm: Type value reached runtime (TypeId={}). Type is comptime-only; check the builder path that produced this.\n", .{@intFromEnum(tid)});
|
||||
.const_type => {
|
||||
// Type values are comptime-only. At LLVM emit they
|
||||
// become undef-i64 placeholders — Type-aware ops are
|
||||
// routed through the interp; if one slips through to
|
||||
// a runtime use site (`type_name` / `type_eq` /
|
||||
// `has_impl` / `bitcast`), the corresponding emit_llvm
|
||||
// bail or runtime use-site error fires loudly. Pure
|
||||
// STORAGE of Type values in runtime aggregates (e.g.
|
||||
// `$args` lowering builds an `[]Any` slice whose
|
||||
// elements happen to be Type values that the user's
|
||||
// code may or may not actually read) is safe — undef
|
||||
// just gets stored, undef just gets read; no use-site
|
||||
// misbehaviour follows.
|
||||
self.mapRef(c.LLVMGetUndef(self.cached_i64));
|
||||
},
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2293,14 +2293,13 @@ pub const Parser = struct {
|
||||
|
||||
fn parsePrimary(self: *Parser) anyerror!*Node {
|
||||
const start = self.current.loc.start;
|
||||
// Pack-index in expression position: `$<pack_name>[<int_literal>]`.
|
||||
// Yields a `pack_index_type_expr` AST node (same shape used in
|
||||
// type positions in step 3) — lowering resolves it to a
|
||||
// `const_type(TypeId)` value at the bound pack-arg type. Step 3
|
||||
// already handles the same node in type positions; this arm
|
||||
// extends it to value expressions, completing the round trip
|
||||
// for builders that pass Type values to `type_name`/`type_eq`
|
||||
// /etc at interp time.
|
||||
// Pack references in expression position:
|
||||
// `$<pack_name>[<int_literal>]` → `pack_index_type_expr`
|
||||
// (single Type value, step 3 shape)
|
||||
// `$<pack_name>` → `comptime_pack_ref`
|
||||
// (whole pack as []Type value, step 4 final slice)
|
||||
// Lowering routes each through `pack_arg_types` to either
|
||||
// a `const_type(TypeId)` or a `[]Type` aggregate of them.
|
||||
if (self.current.tag == .dollar) {
|
||||
self.advance();
|
||||
if (self.current.tag != .identifier) {
|
||||
@@ -2308,23 +2307,25 @@ pub const Parser = struct {
|
||||
}
|
||||
const pname = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
if (self.current.tag != .l_bracket) {
|
||||
return self.fail("expected '[' after '$<pack_name>' (pack indexing requires a literal index in expression position)");
|
||||
if (self.current.tag == .l_bracket) {
|
||||
self.advance(); // skip '['
|
||||
if (self.current.tag != .int_literal) {
|
||||
return self.fail("expected integer literal in pack index");
|
||||
}
|
||||
const idx_text = self.tokenSlice(self.current);
|
||||
const idx_val = std.fmt.parseInt(i64, idx_text, 10) catch {
|
||||
return self.fail("invalid integer literal in pack index");
|
||||
};
|
||||
if (idx_val < 0) return self.fail("pack index cannot be negative");
|
||||
self.advance();
|
||||
try self.expect(.r_bracket);
|
||||
return try self.createNode(start, .{ .pack_index_type_expr = .{
|
||||
.pack_name = pname,
|
||||
.index = @intCast(idx_val),
|
||||
} });
|
||||
}
|
||||
self.advance(); // skip '['
|
||||
if (self.current.tag != .int_literal) {
|
||||
return self.fail("expected integer literal in pack index");
|
||||
}
|
||||
const idx_text = self.tokenSlice(self.current);
|
||||
const idx_val = std.fmt.parseInt(i64, idx_text, 10) catch {
|
||||
return self.fail("invalid integer literal in pack index");
|
||||
};
|
||||
if (idx_val < 0) return self.fail("pack index cannot be negative");
|
||||
self.advance();
|
||||
try self.expect(.r_bracket);
|
||||
return try self.createNode(start, .{ .pack_index_type_expr = .{
|
||||
return try self.createNode(start, .{ .comptime_pack_ref = .{
|
||||
.pack_name = pname,
|
||||
.index = @intCast(idx_val),
|
||||
} });
|
||||
}
|
||||
switch (self.current.tag) {
|
||||
|
||||
@@ -885,6 +885,7 @@ pub const Analyzer = struct {
|
||||
.many_pointer_type_expr,
|
||||
.optional_type_expr,
|
||||
.pack_index_type_expr,
|
||||
.comptime_pack_ref,
|
||||
.null_literal,
|
||||
.array_literal,
|
||||
.parameterized_type_expr,
|
||||
@@ -1328,6 +1329,7 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
||||
.many_pointer_type_expr,
|
||||
.optional_type_expr,
|
||||
.pack_index_type_expr,
|
||||
.comptime_pack_ref,
|
||||
.null_literal,
|
||||
.array_literal,
|
||||
.parameterized_type_expr,
|
||||
|
||||
Reference in New Issue
Block a user