fix: coerce array/vector literal elements to element type (issue 0168)
[N]?T arrays were corrupted: a positional literal .{ null, 7 } stored
bare T/null elements into {T,i1} optional slots because array elements
were never coerced (getStructFields is empty for an array, so the
i<struct_fields.len field-coercion gate never fired). A present element
then read back as absent and direct indexing segfaulted.
lowerStructLiteral's positional branch now computes array_elem_ty for
array/vector targets and coerces each element to it; lowerArrayLiteral
generalizes its slice-only coercion to coerce every element via
coerceToType (layout-aware: scalar->{T,i1}, pointer-sentinel->one-word,
array->slice, concrete->protocol). Verified by 3 adversarial reviews,
suite 780/0.
Regression: examples/optionals/0913-optionals-array-of-optionals.sx.
Filed adjacent pre-existing bugs: 0173 (typed .[null,..] element), 0174
(tuple positional-element coercion), 0175 (positional struct literal
variable element zeroed).
This commit is contained in:
@@ -207,16 +207,39 @@ pub fn lowerStructLiteral(self: *Lowering, sl: *const ast.StructLiteral, span: a
|
||||
return result;
|
||||
}
|
||||
|
||||
// Positional literal: use source order
|
||||
// Positional literal: use source order.
|
||||
//
|
||||
// For an ARRAY / VECTOR target the literal `.{ a, b, ... }` has no named
|
||||
// fields — `getStructFields` returns empty for these, so the per-field
|
||||
// coercion below (`i < struct_fields.len`) never fires. Each positional
|
||||
// element must still be coerced to the homogeneous element type, or a
|
||||
// scalar element flowing into an aggregate element slot stores the wrong
|
||||
// shape. Concretely `[N]?T` would store a bare `T`/`null` into a `{T,i1}`
|
||||
// slot — corrupting the array (a present element reads back as absent;
|
||||
// indexing it segfaults). Issue 0168. `coerceToType` is a no-op when the
|
||||
// element already matches (the common `[N]i64`/`[N]Struct` case).
|
||||
const array_elem_ty: TypeId = if (!ty.isBuiltin()) switch (self.module.types.get(ty)) {
|
||||
.array, .vector => self.getElementType(ty),
|
||||
else => .unresolved,
|
||||
} else .unresolved;
|
||||
|
||||
var fields = std.ArrayList(Ref).empty;
|
||||
defer fields.deinit(self.alloc);
|
||||
|
||||
for (sl.field_inits, 0..) |fi, i| {
|
||||
const saved_tt = self.target_type;
|
||||
if (array_elem_ty != .unresolved) self.target_type = array_elem_ty;
|
||||
var val = self.lowerExpr(fi.value);
|
||||
self.target_type = saved_tt;
|
||||
// Coerce field value to match struct field type
|
||||
if (i < struct_fields.len) {
|
||||
const src_ty = self.inferExprType(fi.value);
|
||||
val = self.coerceToType(val, src_ty, struct_fields[i].ty);
|
||||
} else if (array_elem_ty != .unresolved) {
|
||||
const src_ty = self.builder.getRefType(val);
|
||||
if (src_ty != array_elem_ty) {
|
||||
val = self.coerceToType(val, src_ty, array_elem_ty);
|
||||
}
|
||||
}
|
||||
fields.append(self.alloc, val) catch unreachable;
|
||||
}
|
||||
@@ -1529,22 +1552,23 @@ pub fn lowerArrayLiteral(self: *Lowering, al: *const ast.ArrayLiteral) Ref {
|
||||
self.target_type = elem_ty;
|
||||
var val = self.lowerExpr(elem);
|
||||
self.target_type = old_tt;
|
||||
// A nested `.[...]` element at a slice element type lowers to an
|
||||
// aggregate array `[N]U` (lowerArrayLiteral always yields an array
|
||||
// value); materialize it into a `[]U` slice so the element is a real
|
||||
// {ptr,len} header rather than a raw array the callee would read its
|
||||
// header off of. This per-element coercion recurses with
|
||||
// the literal nesting, so `[][]T` and deeper coerce at every level.
|
||||
if (!elem_ty.isBuiltin()) {
|
||||
const ei = self.module.types.get(elem_ty);
|
||||
if (ei == .slice) {
|
||||
const val_ty = self.builder.getRefType(val);
|
||||
if (!val_ty.isBuiltin()) {
|
||||
const vi = self.module.types.get(val_ty);
|
||||
if (vi == .array and vi.array.element == ei.slice.element) {
|
||||
val = self.coerceToType(val, val_ty, elem_ty);
|
||||
}
|
||||
}
|
||||
// Coerce each element to the declared element type. Setting
|
||||
// `target_type` above steers literal lowering, but the actual
|
||||
// wrap/erase (scalar → optional `{T,i1}`, array → slice header,
|
||||
// concrete → protocol, etc.) lives in `coerceToType`. Without this,
|
||||
// an `[N]?T` literal stores bare `T`/`null` elements into a slot whose
|
||||
// stride is the optional's `{T,i1}` size — corrupting the aggregate
|
||||
// (a present element reads back as absent; indexing segfaults).
|
||||
// Issue 0168.
|
||||
//
|
||||
// `coerceToType` classifies a same-type element as `.no_op`/`.none`
|
||||
// and returns `val` unchanged, so this is a no-op for elements already
|
||||
// at `elem_ty` (the common `[N]i64`/`[N]Struct` case). The earlier
|
||||
// slice special-case is now subsumed by this general coercion.
|
||||
if (elem_ty != .unresolved) {
|
||||
const val_ty = self.builder.getRefType(val);
|
||||
if (val_ty != elem_ty) {
|
||||
val = self.coerceToType(val, val_ty, elem_ty);
|
||||
}
|
||||
}
|
||||
elems.append(self.alloc, val) catch unreachable;
|
||||
|
||||
Reference in New Issue
Block a user