From a922814ba34af12f52ae474712a07b578bfdde4d Mon Sep 17 00:00:00 2001 From: agra Date: Sat, 30 May 2026 02:45:46 +0300 Subject: [PATCH] lang F1 4.2: (..F(Ts)) per-element type application in pack-shaped fields packTypeElems now handles a parameterized spread operand F(Ts): for each pack element T_i it temporarily binds the pack name to T_i and resolves F(T_i), yielding (VL(T0), VL(T1), ...). Combined with parameterized-protocol value types, the canonical Combined struct field sources: (..VL(Ts)) now resolves to a tuple of real protocol values. End-to-end (examples/207): instantiate Combined(s64, s64, string), whole-store c.sources = (xx IntCell, xx StrCell), and per-element dispatch c.sources.0.get() / c.sources.1.get() all work. 242 examples + unit green. --- examples/207-combined-pack-field.sx | 29 +++++++++++++++++++ src/ir/lower.zig | 31 ++++++++++++++++++++- tests/expected/207-combined-pack-field.exit | 1 + tests/expected/207-combined-pack-field.txt | 1 + 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 examples/207-combined-pack-field.sx create mode 100644 tests/expected/207-combined-pack-field.exit create mode 100644 tests/expected/207-combined-pack-field.txt diff --git a/examples/207-combined-pack-field.sx b/examples/207-combined-pack-field.sx new file mode 100644 index 0000000..f4288c6 --- /dev/null +++ b/examples/207-combined-pack-field.sx @@ -0,0 +1,29 @@ +// Phase 4.2 — the canonical `Combined` struct's storage layer: a generic +// struct whose field is a pack of PARAMETERIZED-protocol values, +// `sources: (..VL(Ts))` → `(VL(T0), VL(T1), …)`. Each `VL(Ti)` is a real +// 16-byte protocol value (issue: parameterized-protocol value types), and +// `(..VL(Ts))` applies `VL` per pack element. Instantiate + whole-tuple store +// of `xx`-erased values + per-element method dispatch all work. + +#import "modules/std.sx"; + +VL :: protocol(T: Type) { get :: () -> T; } +IntCell :: struct { v: s64; } +StrCell :: struct { s: string; } +impl VL(s64) for IntCell { get :: (self: *IntCell) -> s64 => self.v; } +impl VL(string) for StrCell { get :: (self: *StrCell) -> string => self.s; } + +Combined :: struct($R: Type, ..$Ts: []Type) { + sources: (..VL(Ts)); // (VL(T0), VL(T1), …) — tuple of protocol values + value: $R; +} + +main :: () -> s32 { + // Combined(s64, s64, string): R=s64, Ts=[s64, string], + // sources: (VL(s64), VL(string)). + c : Combined(s64, s64, string) = ---; + c.sources = (xx IntCell.{ v = 10 }, xx StrCell.{ s = "hi" }); + c.value = 99; + print("{} {} {}\n", c.sources.0.get(), c.sources.1.get(), c.value); // 10 hi 99 + 0; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 182455c..17025ba 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -11221,7 +11221,36 @@ pub const Lowering = struct { /// `impl P(args) for elem` in `param_impl_map`). Null when not a pack spread. /// Caller owns the returned slice. fn packTypeElems(self: *Lowering, operand: *const Node) ?[]TypeId { - if (self.pack_arg_types == null) return null; + const pat = self.pack_arg_types orelse return null; + // `..F(Ts)` — apply a parameterized type `F` to each pack element: + // `(..VL(Ts))` → `(VL(T0), VL(T1), …)`. Per element, temporarily bind + // the pack name to that single element type and resolve `F(elem)`. + if (operand.data == .parameterized_type_expr) { + const pt = operand.data.parameterized_type_expr; + var pack_name_p: []const u8 = ""; + for (pt.args) |a| { + const nm = switch (a.data) { + .identifier => |id| id.name, + .type_expr => |te| te.name, + else => continue, + }; + if (pat.contains(nm)) { + pack_name_p = nm; + break; + } + } + if (pack_name_p.len == 0) return null; + const elems = pat.get(pack_name_p) orelse return null; + if (self.type_bindings == null) return null; + var out = std.ArrayList(TypeId).empty; + for (elems) |ti| { + const had = self.type_bindings.?.get(pack_name_p); + self.type_bindings.?.put(pack_name_p, ti) catch {}; + out.append(self.alloc, self.resolveTypeWithBindings(operand)) catch return null; + if (had) |h| self.type_bindings.?.put(pack_name_p, h) catch {} else _ = self.type_bindings.?.remove(pack_name_p); + } + return out.toOwnedSlice(self.alloc) catch null; + } // In type position `xs` / `xs.T` parse to a (possibly dotted) type_expr // name; `field_access` covers any value-shaped form. var pack_name: []const u8 = ""; diff --git a/tests/expected/207-combined-pack-field.exit b/tests/expected/207-combined-pack-field.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/207-combined-pack-field.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/207-combined-pack-field.txt b/tests/expected/207-combined-pack-field.txt new file mode 100644 index 0000000..4b2d2e4 --- /dev/null +++ b/tests/expected/207-combined-pack-field.txt @@ -0,0 +1 @@ +10 hi 99