From 72731f97ee805a792ef9cb317d0ab7a05403b782 Mon Sep 17 00:00:00 2001 From: agra Date: Fri, 29 May 2026 20:07:41 +0300 Subject: [PATCH] =?UTF-8?q?lang=202.3:=20tuple=20materialization=20from=20?= =?UTF-8?q?a=20pack=20=E2=80=94=20(..xs)=20/=20(..xs.method)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A `spread_expr` element inside a tuple literal now expands the pack into the tuple's fields: `(..xs.get)` ≈ `(xs[0].get(), …, xs[N-1].get())` (Decision 2 — a pack is stored by materializing a tuple). lowerTupleLiteral detects a pack-spread element via packSpreadRefs and splices the per-element Refs as fields (typed via getRefType); for Box(T) the materialized tuple is heterogeneous. A spread whose operand isn't a pack falls through to the existing spread_expr diagnostic (tuple-value spread not yet handled). When any element is a spread, field-count ≠ element-count, so the contextual target-tuple alignment is skipped (field types inferred from the expanded refs). examples/198-pack-tuple-materialize.sx. --- examples/198-pack-tuple-materialize.sx | 25 +++++++++++++ src/ir/lower.zig | 35 ++++++++++++++++--- .../expected/198-pack-tuple-materialize.exit | 1 + tests/expected/198-pack-tuple-materialize.txt | 2 ++ 4 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 examples/198-pack-tuple-materialize.sx create mode 100644 tests/expected/198-pack-tuple-materialize.exit create mode 100644 tests/expected/198-pack-tuple-materialize.txt diff --git a/examples/198-pack-tuple-materialize.sx b/examples/198-pack-tuple-materialize.sx new file mode 100644 index 0000000..3bcf603 --- /dev/null +++ b/examples/198-pack-tuple-materialize.sx @@ -0,0 +1,25 @@ +// Feature 1 — materialize a tuple from a pack via `(..xs.method)` (Decision 2: +// a pack is stored by materializing a tuple). `(..xs.get)` projects `get` over +// the pack and collects the results into a real tuple value, which can then be +// stored, indexed, and (for `Box(T)`) is heterogeneous per position. + +#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; } + +snapshot :: (..xs: Box) -> void { + t := (..xs.get); // tuple (s64, string) materialized from the pack + print("0={} 1={}\n", t.0, t.1); +} + +main :: () -> s32 { + snapshot(IntCell.{ v = 42 }, StrCell.{ s = "hi" }); + snapshot(StrCell.{ s = "x" }, IntCell.{ v = 7 }); // order swapped → (string, s64) + 0; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index c42fca6..57951ca 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -4703,18 +4703,43 @@ pub const Lowering = struct { // ambient scalar `target_type` (e.g. the enclosing fn's int return // type) can't narrow an element below its field width. Otherwise each // element's type is inferred independently. + // A pack-spread element `(..xs)` / `(..xs.method)` expands to N fields, + // so element-count ≠ field-count and a contextual target tuple can't be + // aligned by index — infer field types from the expanded refs instead. + var has_spread = false; + for (tl.elements) |elem| { + if (elem.value.data == .spread_expr) has_spread = true; + } + var target_fields: ?[]const TypeId = null; - if (self.target_type) |tt| { - if (!tt.isBuiltin()) { - const tinfo = self.module.types.get(tt); - if (tinfo == .tuple and tinfo.tuple.fields.len == tl.elements.len) { - target_fields = tinfo.tuple.fields; + if (!has_spread) { + if (self.target_type) |tt| { + if (!tt.isBuiltin()) { + const tinfo = self.module.types.get(tt); + if (tinfo == .tuple and tinfo.tuple.fields.len == tl.elements.len) { + target_fields = tinfo.tuple.fields; + } } } } const saved_target = self.target_type; for (tl.elements, 0..) |elem, i| { + // Pack-spread element → splice its per-element values as fields. + if (elem.value.data == .spread_expr) { + if (self.packSpreadRefs(elem.value.data.spread_expr.operand, elem.value.span)) |refs| { + defer self.alloc.free(refs); + for (refs) |r| { + elems.append(self.alloc, r) catch unreachable; + field_type_ids.append(self.alloc, self.builder.getRefType(r)) catch unreachable; + name_ids.append(self.alloc, self.module.types.internString("")) catch unreachable; + } + continue; + } + // Not a pack spread (e.g. tuple-value spread) — not yet handled. + _ = self.lowerExpr(elem.value); // surfaces the spread_expr diagnostic + continue; + } const field_ty = if (target_fields) |tf| tf[i] else self.inferExprType(elem.value); self.target_type = field_ty; var val = self.lowerExpr(elem.value); diff --git a/tests/expected/198-pack-tuple-materialize.exit b/tests/expected/198-pack-tuple-materialize.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/198-pack-tuple-materialize.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/198-pack-tuple-materialize.txt b/tests/expected/198-pack-tuple-materialize.txt new file mode 100644 index 0000000..5409d7c --- /dev/null +++ b/tests/expected/198-pack-tuple-materialize.txt @@ -0,0 +1,2 @@ +0=42 1=hi +0=x 1=7