lang 2.4: value-position pack projection xs.value + mixed-tuple type fix
`xs.<method>` over a constrained pack projects a (zero-arg) protocol method across every element into a tuple: `xs.get` ≈ `(xs[0].get(), …, xs[N-1].get())`. lowerFieldAccess intercepts `xs.<m>` on a pack base (where <m> is a protocol method) and synthesizes/lowers `xs[i].<m>()` per element into a tuple_init. For a parameterised `Box(T)` the projected tuple is heterogeneous (each element returns its own T). examples/196-pack-value-projection.sx. Surfaced and fixed a pre-existing bug: inferExprType didn't handle tuple field access (`t.0` / `t.x`), so a mixed-size tuple like `(42, "hi")` inferred the string field as s64 — the wrong type then drove a bad `print` pack mangle and coerced the string to i64 (garbage). Added the tuple arm (numeric + named). Regression: a `(s64, string)` case in examples/190-tuple-values.sx.
This commit is contained in:
@@ -45,5 +45,11 @@ main :: () -> s32 {
|
||||
print("rep {} {}\n", r.0, r.5);
|
||||
print("mem {}\n", 3 in (1, 2, 3));
|
||||
print("lex {}\n", (1, 2) < (1, 3));
|
||||
|
||||
// Mixed-size fields: a tuple with both an s64 and a string (16-byte fat
|
||||
// pointer). Field types are tracked per-position, so reading each back is
|
||||
// typed correctly (s64 prints as a number, string as text).
|
||||
mixed := (42, "hi");
|
||||
print("mixed {} {}\n", mixed.0, mixed.1);
|
||||
0;
|
||||
}
|
||||
|
||||
28
examples/196-pack-value-projection.sx
Normal file
28
examples/196-pack-value-projection.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// Feature 1 — value-position pack projection: `xs.<method>` projects a
|
||||
// (zero-arg) protocol method over every element into a TUPLE of the per-element
|
||||
// results. For a parameterised `Box(T)`, each element's method returns its own
|
||||
// `T`, so the projected tuple is heterogeneous.
|
||||
//
|
||||
// xs.get ≈ (xs[0].get(), xs[1].get())
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: protocol(T: Type) {
|
||||
get :: () -> T;
|
||||
}
|
||||
|
||||
IntCell :: struct { v: s64; }
|
||||
StrCell :: struct { s: string; }
|
||||
impl Box(s64) for IntCell { get :: (self: *IntCell) -> s64 => self.v; }
|
||||
impl Box(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
|
||||
|
||||
show :: (..xs: Box) -> void {
|
||||
vals := xs.get; // tuple (s64, string)
|
||||
print("0={} 1={}\n", vals.0, vals.1);
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
show(IntCell.{ v = 42 }, StrCell.{ s = "hi" });
|
||||
show(StrCell.{ s = "x" }, IntCell.{ v = 7 }); // order swapped → (string, s64)
|
||||
0;
|
||||
}
|
||||
@@ -3976,6 +3976,19 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// Pack value projection: `xs.<m>` where `<m>` is a (zero-arg) method of
|
||||
// the pack's constraint protocol projects it over every element →
|
||||
// a tuple `(xs[0].<m>(), …, xs[N-1].<m>())`. (`xs.len` handled above.)
|
||||
if (self.pack_constraint) |pcon| {
|
||||
if (fa.object.data == .identifier) {
|
||||
if (pcon.get(fa.object.data.identifier.name)) |proto| {
|
||||
if (self.lookupProtocolField(proto, fa.field) != null) {
|
||||
return self.lowerPackValueProjection(fa.object.data.identifier.name, fa.field, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interface-only enforcement (Decision): a member access on a
|
||||
// constrained pack element `xs[i].<m>` may only name a method of the
|
||||
// constraint protocol — not an arbitrary concrete field. Checked here,
|
||||
@@ -4078,6 +4091,41 @@ pub const Lowering = struct {
|
||||
return self.lowerFieldAccessOnType(obj, obj_ty, fa.field, span);
|
||||
}
|
||||
|
||||
/// 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);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
return self.builder.emit(.{ .tuple_init = .{ .fields = owned } }, tuple_ty);
|
||||
}
|
||||
|
||||
/// Lower a struct-level constant value (e.g., Phys.GRAVITY).
|
||||
fn lowerStructConstant(self: *Lowering, info: StructConstInfo) Ref {
|
||||
const val_node = info.value;
|
||||
@@ -12582,6 +12630,20 @@ pub const Lowering = struct {
|
||||
const elem = info.vector.element;
|
||||
return if (is_opt_chain) self.optionalOfFlattened(elem) else elem;
|
||||
}
|
||||
// Tuple field access: numeric `t.0` or named `t.x`.
|
||||
if (info == .tuple) {
|
||||
const tup = info.tuple;
|
||||
if (std.fmt.parseInt(usize, fa.field, 10)) |idx| {
|
||||
if (idx < tup.fields.len)
|
||||
return if (is_opt_chain) self.optionalOfFlattened(tup.fields[idx]) else tup.fields[idx];
|
||||
} else |_| {}
|
||||
if (tup.names) |names| {
|
||||
for (names, 0..) |nm, i| {
|
||||
if (nm == field_name_id and i < tup.fields.len)
|
||||
return if (is_opt_chain) self.optionalOfFlattened(tup.fields[i]) else tup.fields[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check struct fields
|
||||
const fields = self.getStructFields(obj_ty);
|
||||
for (fields) |f| {
|
||||
|
||||
@@ -9,3 +9,4 @@ concat 1 4
|
||||
rep 1 2
|
||||
mem true
|
||||
lex true
|
||||
mixed 42 hi
|
||||
|
||||
1
tests/expected/196-pack-value-projection.exit
Normal file
1
tests/expected/196-pack-value-projection.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
2
tests/expected/196-pack-value-projection.txt
Normal file
2
tests/expected/196-pack-value-projection.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
0=42 1=hi
|
||||
0=x 1=7
|
||||
Reference in New Issue
Block a user