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;
|
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;
|
var target_fields: ?[]const TypeId = null;
|
||||||
if (!has_spread) {
|
if (self.target_type) |tt| {
|
||||||
if (self.target_type) |tt| {
|
if (!tt.isBuiltin()) {
|
||||||
if (!tt.isBuiltin()) {
|
const tinfo = self.module.types.get(tt);
|
||||||
const tinfo = self.module.types.get(tt);
|
if (tinfo == .tuple and (has_spread or tinfo.tuple.fields.len == tl.elements.len)) {
|
||||||
if (tinfo == .tuple and tinfo.tuple.fields.len == tl.elements.len) {
|
target_fields = tinfo.tuple.fields;
|
||||||
target_fields = tinfo.tuple.fields;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const saved_target = self.target_type;
|
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.
|
// Pack-spread element → splice its per-element values as fields.
|
||||||
if (elem.value.data == .spread_expr) {
|
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);
|
defer self.alloc.free(refs);
|
||||||
for (refs) |r| {
|
// Element AST nodes (for protocol-erasure lvalue/name fallback)
|
||||||
elems.append(self.alloc, r) catch unreachable;
|
// when the spread is a bare pack name.
|
||||||
field_type_ids.append(self.alloc, self.builder.getRefType(r)) catch unreachable;
|
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;
|
name_ids.append(self.alloc, self.module.types.internString("")) catch unreachable;
|
||||||
|
out_idx += 1;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -5087,7 +5108,7 @@ pub const Lowering = struct {
|
|||||||
_ = self.lowerExpr(elem.value); // surfaces the spread_expr diagnostic
|
_ = self.lowerExpr(elem.value); // surfaces the spread_expr diagnostic
|
||||||
continue;
|
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;
|
self.target_type = field_ty;
|
||||||
var val = self.lowerExpr(elem.value);
|
var val = self.lowerExpr(elem.value);
|
||||||
self.target_type = saved_target;
|
self.target_type = saved_target;
|
||||||
@@ -5103,6 +5124,7 @@ pub const Lowering = struct {
|
|||||||
} else {
|
} else {
|
||||||
name_ids.append(self.alloc, self.module.types.internString("")) catch unreachable;
|
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
|
// 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.
|
/// 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 {
|
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);
|
const dst_info = self.module.types.get(dst_ty);
|
||||||
if (dst_info != .@"struct") return operand;
|
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