diff --git a/src/ast.zig b/src/ast.zig index 67b36d5..643c886 100644 --- a/src/ast.zig +++ b/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, }; +/// `$` (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, }; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 8f5bc39..3dada61 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -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)); }, diff --git a/src/ir/lower.zig b/src/ir/lower.zig index afcd52f..3f25c96 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -2028,6 +2028,28 @@ pub const Lowering = struct { fn lowerExpr(self: *Lowering, node: *const Node) Ref { return switch (node.data) { + // Bare `$` 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: `$[]` → // `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 `$` 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, diff --git a/src/parser.zig b/src/parser.zig index 6d1d957..cc99132 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -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: `$[]`. - // 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_index_type_expr` + // (single Type value, step 3 shape) + // `$` → `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 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) { diff --git a/src/sema.zig b/src/sema.zig index 410ef64..5a7d174 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -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,