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:
agra
2026-05-30 03:11:55 +03:00
parent e395a08331
commit 87ee3d3e65
4 changed files with 79 additions and 13 deletions

View File

@@ -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;