lang: xx <pack> materializes a comptime pack into a runtime slice (issue 0053)

xx args with a slice target now bridges a comptime pack to a runtime slice:
[]Any boxes each element to Any; []P xx-erases each to the protocol (reusing
the slice-of-protocol erasure from 0052). New lowerPackToSlice; the unary-op
arm intercepts xx <pack> before the pack-as-value diagnostic. This is the
working forward to a runtime []Any/[]P helper -- log_count(xx args) -> 3 --
so the 2.7 pack-as-value diagnostics now suggest xx <name> for the call case.

examples/204-pack-xx-to-slice.sx (both []Any and []P paths); 203 help text
updated. issue 0053 FIXED. 239 examples + unit green.
This commit is contained in:
agra
2026-05-30 02:17:55 +03:00
parent 8a875d354c
commit 82b46bc412
6 changed files with 134 additions and 18 deletions

View File

@@ -2347,6 +2347,19 @@ pub const Lowering = struct {
.binary_op => |bop| self.lowerBinaryOp(&bop),
.unary_op => |uop| blk: {
// `xx <pack>` with a slice target materializes the comptime
// pack into a runtime `[]elem` (issue 0053). Must run before the
// operand is lowered (a bare pack name otherwise hits the
// pack-as-value error).
if (uop.op == .xx and uop.operand.data == .identifier and self.isPackName(uop.operand.data.identifier.name)) {
const pname = uop.operand.data.identifier.name;
if (self.target_type) |tt| {
if (!tt.isBuiltin() and self.module.types.get(tt) == .slice) {
break :blk self.lowerPackToSlice(pname, tt);
}
}
break :blk self.diagPackAsValue(pname, node.span, .generic);
}
// address_of(index_expr) → emit index_gep (pointer to element) instead of index_get + addr_of
if (uop.op == .address_of and uop.operand.data == .index_expr) {
const ie = &uop.operand.data.index_expr;
@@ -4916,10 +4929,10 @@ pub const Lowering = struct {
const id = d.addFmtId(.err, span, "pack '{s}' has no runtime value — a pack is comptime-only and can't be used as a value here", .{name});
switch (kind) {
.storage => d.addHelpFmt(id, span, null, "to store it, materialize a tuple: `(..{s})`", .{name}),
.call_arg => d.addHelpFmt(id, span, null, "to pass it on at runtime, declare `{s}` as a slice variadic `..{s}: []P` (a protocol slice) instead of a pack `..{s}: P`", .{ name, name, name }),
.call_arg => d.addHelpFmt(id, span, null, "to pass it to a `[]Any`/`[]P` parameter, materialize it with `xx {s}`", .{name}),
.return_value => d.addHelpFmt(id, span, null, "to return it, return a tuple `(..{s})` and make the return type that tuple", .{name}),
.runtime_iter => d.addHelpFmt(id, span, null, "to iterate at comptime use `inline for 0..{s}.len (i)`; for a runtime loop declare it as `..{s}: []P` (a protocol slice) instead of a pack", .{ name, name }),
.generic => d.addHelpFmt(id, span, null, "materialize a tuple `(..{s})` to store it, or declare `{s}` as a slice variadic `..{s}: []P` for runtime use instead of a pack `..{s}: P`", .{ name, name, name, name }),
.generic => d.addHelpFmt(id, span, null, "materialize a tuple `(..{s})` to store it, or `xx {s}` to convert it to an expected `[]Any`/`[]P` slice", .{ name, name }),
}
}
return self.emitPlaceholder(name);
@@ -4931,6 +4944,48 @@ pub const Lowering = struct {
return ppc.contains(name);
}
/// `xx <pack>` with a slice target: materialize the comptime pack into a
/// runtime `[]elem` by lowering each element node and boxing (`[]Any`) or
/// `xx`-erasing (`[]P`) it into a stack `[N]elem`, then return the slice.
/// This is the explicit pack→slice bridge (issue 0053).
fn lowerPackToSlice(self: *Lowering, pack_name: []const u8, slice_ty: TypeId) Ref {
const arg_nodes = (self.pack_arg_nodes orelse return self.builder.constInt(0, .unresolved)).get(pack_name) orelse
return self.builder.constInt(0, .unresolved);
const elem_ty = self.module.types.get(slice_ty).slice.element;
const is_any = elem_ty == .any;
const elem_is_protocol = blk: {
if (elem_ty.isBuiltin()) break :blk false;
const ei = self.module.types.get(elem_ty);
break :blk ei == .@"struct" and ei.@"struct".is_protocol;
};
const slice_slot = self.builder.alloca(slice_ty);
const ptr_gep = self.builder.structGepTyped(slice_slot, 0, self.module.types.ptrTo(elem_ty), slice_ty);
const len_gep = self.builder.structGepTyped(slice_slot, 1, .s64, slice_ty);
if (arg_nodes.len == 0) {
self.builder.store(ptr_gep, self.builder.constNull(self.module.types.ptrTo(elem_ty)));
self.builder.store(len_gep, self.builder.constInt(0, .s64));
return self.builder.load(slice_slot, slice_ty);
}
const array_ty = self.module.types.arrayOf(elem_ty, @intCast(arg_nodes.len));
const array_slot = self.builder.alloca(array_ty);
for (arg_nodes, 0..) |arg, i| {
var val = self.lowerExpr(arg);
var source_ty = self.inferExprType(arg);
if (source_ty == .unresolved) source_ty = self.builder.getRefType(val);
if (is_any) {
if (source_ty != .any) val = self.builder.boxAny(val, source_ty);
} else if (elem_is_protocol) {
if (source_ty != elem_ty) val = self.buildProtocolErasure(val, arg, source_ty, elem_ty);
}
const ep = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = self.builder.constInt(@intCast(i), .s64) } }, self.module.types.ptrTo(elem_ty));
self.builder.store(ep, val);
}
const data_ptr = self.builder.emit(.{ .index_gep = .{ .lhs = array_slot, .rhs = self.builder.constInt(0, .s64) } }, self.module.types.ptrTo(elem_ty));
self.builder.store(ptr_gep, data_ptr);
self.builder.store(len_gep, self.builder.constInt(@intCast(arg_nodes.len), .s64));
return self.builder.load(slice_slot, slice_ty);
}
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);