lang 2.3: pack spread into call args (f(..xs) / f(..xs.value))
A pack spread in call-arg position now expands to N positional args: `add2(..xs.get)` ≈ `add2(xs[0].get(), xs[1].get())` — the canonical's `mapper(..sources.value)` shape. The call-arg loop detects a spread whose operand is a pack (`..xs`) or a pack projection (`..xs.method`) and splices the per-element Refs in; a runtime-slice spread (`..arr`) is still left to the slice-variadic path. Factored the per-element synthesis out of lowerPackValueProjection into `lowerPackElems` (used by both projection-to-tuple and spread-to-args), plus a `packSpreadRefs` helper. examples/197-pack-spread-call.sx (2- and 3-arg, mixed element types).
This commit is contained in:
26
examples/197-pack-spread-call.sx
Normal file
26
examples/197-pack-spread-call.sx
Normal file
@@ -0,0 +1,26 @@
|
||||
// Feature 1 — pack spread into a call's positional arguments. `f(..xs.get)`
|
||||
// projects `get` over the pack and spreads the resulting tuple into f's params:
|
||||
// add2(..xs.get) ≈ add2(xs[0].get(), xs[1].get())
|
||||
// The canonical's `mapper(..sources.value)` is this shape.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: protocol(T: Type) {
|
||||
get :: () -> s64;
|
||||
}
|
||||
IntCell :: struct { v: s64; }
|
||||
Dbl :: struct { n: s64; }
|
||||
impl Box(s64) for IntCell { get :: (self: *IntCell) -> s64 => self.v; }
|
||||
impl Box(s64) for Dbl { get :: (self: *Dbl) -> s64 => self.n * 2; }
|
||||
|
||||
add2 :: (a: s64, b: s64) -> s64 { return a + b; }
|
||||
add3 :: (a: s64, b: s64, c: s64) -> s64 { return a + b + c; }
|
||||
|
||||
via2 :: (..xs: Box) -> s64 { return add2(..xs.get); }
|
||||
via3 :: (..xs: Box) -> s64 { return add3(..xs.get); }
|
||||
|
||||
main :: () -> s32 {
|
||||
print("two={}\n", via2(IntCell.{ v = 10 }, Dbl.{ n = 5 })); // 10 + 10 = 20
|
||||
print("three={}\n", via3(Dbl.{ n = 1 }, IntCell.{ v = 2 }, Dbl.{ n = 3 })); // 2 + 2 + 6 = 10
|
||||
0;
|
||||
}
|
||||
@@ -4091,41 +4091,71 @@ pub const Lowering = struct {
|
||||
return self.lowerFieldAccessOnType(obj, obj_ty, fa.field, span);
|
||||
}
|
||||
|
||||
/// Lower each pack element to a Ref: `pack_name[i]` when `method` is null,
|
||||
/// or `pack_name[i].method()` when given. Synthesizes the index/field/call
|
||||
/// AST per element and lowers it (substitution turns `xs[i]` into the
|
||||
/// concrete arg; UFCS dispatches the method). Caller owns the returned slice.
|
||||
fn lowerPackElems(self: *Lowering, pack_name: []const u8, method: ?[]const u8, span: ast.Span) []Ref {
|
||||
const n: u32 = if (self.pack_param_count) |ppc| (ppc.get(pack_name) orelse 0) else 0;
|
||||
var refs = std.ArrayList(Ref).empty;
|
||||
var i: u32 = 0;
|
||||
while (i < n) : (i += 1) {
|
||||
const id_node = self.alloc.create(Node) catch break;
|
||||
id_node.* = .{ .span = span, .data = .{ .identifier = .{ .name = pack_name } } };
|
||||
const idx_node = self.alloc.create(Node) catch break;
|
||||
idx_node.* = .{ .span = span, .data = .{ .int_literal = .{ .value = @intCast(i) } } };
|
||||
const index_node = self.alloc.create(Node) catch break;
|
||||
index_node.* = .{ .span = span, .data = .{ .index_expr = .{ .object = id_node, .index = idx_node } } };
|
||||
var elem_node = index_node;
|
||||
if (method) |m| {
|
||||
const fa_node = self.alloc.create(Node) catch break;
|
||||
fa_node.* = .{ .span = span, .data = .{ .field_access = .{ .object = index_node, .field = m } } };
|
||||
const call_node = self.alloc.create(Node) catch break;
|
||||
call_node.* = .{ .span = span, .data = .{ .call = .{ .callee = fa_node, .args = &.{} } } };
|
||||
elem_node = call_node;
|
||||
}
|
||||
refs.append(self.alloc, self.lowerExpr(elem_node)) catch break;
|
||||
}
|
||||
return refs.toOwnedSlice(self.alloc) catch &.{};
|
||||
}
|
||||
|
||||
/// Value-position pack projection `xs.<method>`: call the (zero-arg)
|
||||
/// protocol method on each element and collect the results into a tuple
|
||||
/// `(xs[0].<method>(), …, xs[N-1].<method>())`. N=0 yields the empty tuple.
|
||||
/// Synthesizes `xs[i].<method>()` per element and lowers it (substitution
|
||||
/// turns `xs[i]` into the concrete arg; UFCS dispatches the method).
|
||||
fn lowerPackValueProjection(self: *Lowering, pack_name: []const u8, method: []const u8, span: ast.Span) Ref {
|
||||
const n: u32 = if (self.pack_param_count) |ppc| (ppc.get(pack_name) orelse 0) else 0;
|
||||
var refs = std.ArrayList(Ref).empty;
|
||||
defer refs.deinit(self.alloc);
|
||||
const refs = self.lowerPackElems(pack_name, method, span);
|
||||
defer self.alloc.free(refs);
|
||||
var tys = std.ArrayList(TypeId).empty;
|
||||
defer tys.deinit(self.alloc);
|
||||
var i: u32 = 0;
|
||||
while (i < n) : (i += 1) {
|
||||
const id_node = self.alloc.create(Node) catch return self.builder.constInt(0, .void);
|
||||
id_node.* = .{ .span = span, .data = .{ .identifier = .{ .name = pack_name } } };
|
||||
const idx_node = self.alloc.create(Node) catch return self.builder.constInt(0, .void);
|
||||
idx_node.* = .{ .span = span, .data = .{ .int_literal = .{ .value = @intCast(i) } } };
|
||||
const index_node = self.alloc.create(Node) catch return self.builder.constInt(0, .void);
|
||||
index_node.* = .{ .span = span, .data = .{ .index_expr = .{ .object = id_node, .index = idx_node } } };
|
||||
const fa_node = self.alloc.create(Node) catch return self.builder.constInt(0, .void);
|
||||
fa_node.* = .{ .span = span, .data = .{ .field_access = .{ .object = index_node, .field = method } } };
|
||||
const call_node = self.alloc.create(Node) catch return self.builder.constInt(0, .void);
|
||||
call_node.* = .{ .span = span, .data = .{ .call = .{ .callee = fa_node, .args = &.{} } } };
|
||||
const r = self.lowerExpr(call_node);
|
||||
refs.append(self.alloc, r) catch return self.builder.constInt(0, .void);
|
||||
tys.append(self.alloc, self.builder.getRefType(r)) catch return self.builder.constInt(0, .void);
|
||||
}
|
||||
for (refs) |r| tys.append(self.alloc, self.builder.getRefType(r)) catch {};
|
||||
const tuple_ty = self.module.types.intern(.{ .tuple = .{
|
||||
.fields = self.alloc.dupe(TypeId, tys.items) catch return self.builder.constInt(0, .void),
|
||||
.names = null,
|
||||
} });
|
||||
const owned = self.alloc.dupe(Ref, refs.items) catch return self.builder.constInt(0, .void);
|
||||
const owned = self.alloc.dupe(Ref, refs) catch return self.builder.constInt(0, .void);
|
||||
return self.builder.emit(.{ .tuple_init = .{ .fields = owned } }, tuple_ty);
|
||||
}
|
||||
|
||||
/// If `operand` is a pack spread — `..xs` (bare pack) or `..xs.method`
|
||||
/// (per-element projection) — return the per-element Refs to splice into a
|
||||
/// call's positional args. Null when it's not a pack spread (e.g. a runtime
|
||||
/// slice `..arr`, handled by the slice-variadic path). Caller owns the slice.
|
||||
fn packSpreadRefs(self: *Lowering, operand: *const Node, span: ast.Span) ?[]Ref {
|
||||
const ppc = self.pack_param_count orelse return null;
|
||||
switch (operand.data) {
|
||||
.identifier => |id| {
|
||||
if (ppc.contains(id.name)) return self.lowerPackElems(id.name, null, span);
|
||||
},
|
||||
.field_access => |fa| {
|
||||
if (fa.object.data == .identifier and ppc.contains(fa.object.data.identifier.name)) {
|
||||
return self.lowerPackElems(fa.object.data.identifier.name, fa.field, span);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Lower a struct-level constant value (e.g., Phys.GRAVITY).
|
||||
fn lowerStructConstant(self: *Lowering, info: StructConstInfo) Ref {
|
||||
const val_node = info.value;
|
||||
@@ -5974,8 +6004,15 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
for (c.args, 0..) |arg, ai| {
|
||||
// Skip spread expressions — they'll be handled by packVariadicCallArgs from AST
|
||||
if (arg.data == .spread_expr) {
|
||||
// Pack spread `..xs` / `..xs.method` → expand to N positional
|
||||
// args here. A runtime-slice spread (`..arr`) is left as a
|
||||
// placeholder for the slice-variadic path (packVariadicCallArgs).
|
||||
if (self.packSpreadRefs(arg.data.spread_expr.operand, arg.span)) |elems| {
|
||||
defer self.alloc.free(elems);
|
||||
for (elems) |e| args.append(self.alloc, e) catch unreachable;
|
||||
continue;
|
||||
}
|
||||
args.append(self.alloc, Ref.none) catch unreachable;
|
||||
continue;
|
||||
}
|
||||
|
||||
1
tests/expected/197-pack-spread-call.exit
Normal file
1
tests/expected/197-pack-spread-call.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
2
tests/expected/197-pack-spread-call.txt
Normal file
2
tests/expected/197-pack-spread-call.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
two=20
|
||||
three=10
|
||||
Reference in New Issue
Block a user