lang F1 6: (..sources) materializes a pack into a protocol-typed tuple field
lowerTupleLiteral now coerces/erases each spliced spread element to the contextual target tuple field type (computed even when a spread is present, indexed by output position). New coerceOrErase: protocol target -> xx-erase via buildProtocolErasure, else coerceToType. So c.sources = (..sources) on a (..VL(Ts)) field erases each concrete pack element to its VL(Ti) slot. examples/210 (build(IntCell, StrCell) -> 10 hi). 245 examples + unit green.
This commit is contained in:
28
examples/210-pack-to-protocol-tuple.sx
Normal file
28
examples/210-pack-to-protocol-tuple.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// Phase 6 — `c.sources = (..sources)`: materialize a pack into a
|
||||
// protocol-typed tuple field, erasing each concrete pack element to the field's
|
||||
// protocol slot. The pack `..sources: VL` holds concrete cells; `(..sources)`
|
||||
// into a `(..VL(Ts))` field `xx`-erases each to its `VL(Ti)` value.
|
||||
|
||||
#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));
|
||||
value: $R;
|
||||
}
|
||||
|
||||
build :: (..sources: VL) -> void {
|
||||
c : Combined(s64, ..sources.T) = ---;
|
||||
c.sources = (..sources); // pack → tuple, per-element erase
|
||||
print("{} {}\n", c.sources.0.get(), c.sources.1.get());
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
build(IntCell.{ v = 10 }, StrCell.{ s = "hi" }); // 10 hi
|
||||
0;
|
||||
}
|
||||
@@ -5058,28 +5058,49 @@ pub const Lowering = struct {
|
||||
if (elem.value.data == .spread_expr) has_spread = true;
|
||||
}
|
||||
|
||||
// Contextual target tuple field types. Without a spread we require
|
||||
// exact arity (existing behavior); with a spread we index positionally
|
||||
// by output position (so `(..sources)` into a `(VL(T0), …)` field coerces
|
||||
// / erases each spliced element to its slot's type).
|
||||
var target_fields: ?[]const TypeId = null;
|
||||
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;
|
||||
}
|
||||
if (self.target_type) |tt| {
|
||||
if (!tt.isBuiltin()) {
|
||||
const tinfo = self.module.types.get(tt);
|
||||
if (tinfo == .tuple and (has_spread or tinfo.tuple.fields.len == tl.elements.len)) {
|
||||
target_fields = tinfo.tuple.fields;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const saved_target = self.target_type;
|
||||
for (tl.elements, 0..) |elem, i| {
|
||||
var out_idx: usize = 0;
|
||||
for (tl.elements) |elem| {
|
||||
// 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| {
|
||||
const sp_operand = elem.value.data.spread_expr.operand;
|
||||
if (self.packSpreadRefs(sp_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;
|
||||
// Element AST nodes (for protocol-erasure lvalue/name fallback)
|
||||
// when the spread is a bare pack name.
|
||||
const elem_nodes: ?[]const *const Node = if (sp_operand.data == .identifier and self.pack_arg_nodes != null)
|
||||
self.pack_arg_nodes.?.get(sp_operand.data.identifier.name)
|
||||
else
|
||||
null;
|
||||
for (refs, 0..) |r, ri| {
|
||||
var val = r;
|
||||
var vty = self.builder.getRefType(r);
|
||||
if (target_fields) |tf| {
|
||||
if (out_idx < tf.len and tf[out_idx] != vty and tf[out_idx] != .void) {
|
||||
const want = tf[out_idx];
|
||||
const node = if (elem_nodes) |ens| (if (ri < ens.len) ens[ri] else elem.value) else elem.value;
|
||||
val = self.coerceOrErase(r, vty, want, node);
|
||||
vty = want;
|
||||
}
|
||||
}
|
||||
elems.append(self.alloc, val) catch unreachable;
|
||||
field_type_ids.append(self.alloc, vty) catch unreachable;
|
||||
name_ids.append(self.alloc, self.module.types.internString("")) catch unreachable;
|
||||
out_idx += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -5087,7 +5108,7 @@ pub const Lowering = struct {
|
||||
_ = self.lowerExpr(elem.value); // surfaces the spread_expr diagnostic
|
||||
continue;
|
||||
}
|
||||
const field_ty = if (target_fields) |tf| tf[i] else self.inferExprType(elem.value);
|
||||
const field_ty = if (target_fields) |tf| (if (out_idx < tf.len) tf[out_idx] else self.inferExprType(elem.value)) else self.inferExprType(elem.value);
|
||||
self.target_type = field_ty;
|
||||
var val = self.lowerExpr(elem.value);
|
||||
self.target_type = saved_target;
|
||||
@@ -5103,6 +5124,7 @@ pub const Lowering = struct {
|
||||
} else {
|
||||
name_ids.append(self.alloc, self.module.types.internString("")) catch unreachable;
|
||||
}
|
||||
out_idx += 1;
|
||||
}
|
||||
|
||||
// Reuse the contextual target tuple type when it drove lowering so the
|
||||
@@ -14018,6 +14040,20 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
/// Build a protocol value from a concrete value via xx conversion.
|
||||
/// Coerce `val` (type `src`) to `dst`: if `dst` is a protocol, `xx`-erase
|
||||
/// the concrete value into it; otherwise fall back to numeric/struct
|
||||
/// coercion. Used to materialize a pack into a protocol-typed tuple field.
|
||||
fn coerceOrErase(self: *Lowering, val: Ref, src: TypeId, dst: TypeId, node: *const Node) Ref {
|
||||
if (src == dst) return val;
|
||||
if (!dst.isBuiltin()) {
|
||||
const di = self.module.types.get(dst);
|
||||
if (di == .@"struct" and di.@"struct".is_protocol) {
|
||||
return self.buildProtocolErasure(val, node, src, dst);
|
||||
}
|
||||
}
|
||||
return self.coerceToType(val, src, dst);
|
||||
}
|
||||
|
||||
fn buildProtocolErasure(self: *Lowering, operand: Ref, operand_node: *const Node, src_ty: TypeId, dst_ty: TypeId) Ref {
|
||||
const dst_info = self.module.types.get(dst_ty);
|
||||
if (dst_info != .@"struct") return operand;
|
||||
|
||||
1
tests/expected/210-pack-to-protocol-tuple.exit
Normal file
1
tests/expected/210-pack-to-protocol-tuple.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
tests/expected/210-pack-to-protocol-tuple.txt
Normal file
1
tests/expected/210-pack-to-protocol-tuple.txt
Normal file
@@ -0,0 +1 @@
|
||||
10 hi
|
||||
Reference in New Issue
Block a user