fix: method on array-index/deref receiver mutates the live place (issue 0145)
A *self method called directly on arr[i] (or a deref place) fell through to an alloca+store-of-value, so the callee mutated a throwaway copy and the live slot was never written. fixupMethodReceiver now takes the real address of .index_expr/.deref_expr receivers via lowerExprAsPtr (normalized to *T), mirroring the explicit-argument path. A comptime-pack index (xs[i] where xs is a pack) is excluded -- a pack has no runtime storage to address -- so it keeps flowing through the general copy path. Regression: examples/0188-types-method-array-index-receiver.sx
This commit is contained in:
@@ -282,15 +282,33 @@ pub fn fixupMethodReceiver(self: *Lowering, method_args: *std.ArrayList(Ref), fu
|
||||
}
|
||||
}
|
||||
}
|
||||
// Field access: obj.field.method() → GEP to field, pass pointer directly.
|
||||
// This avoids copying the struct value (mutations through *T must be visible).
|
||||
if (obj_node.data == .field_access) {
|
||||
const gep_ref = self.lowerExprAsPtr(obj_node);
|
||||
// GEP returns a pointer in LLVM but its IR type is the field value type.
|
||||
// Wrap with addr_of (no-op in LLVM) to set the IR type to *T,
|
||||
// Compound lvalue receiver: obj.field.method() / arr[i].method() /
|
||||
// (*p).method() → take the lvalue's real address so mutations
|
||||
// through *T are visible on the original storage (not a throwaway
|
||||
// copy). Mirrors the explicit-arg path in call.zig.
|
||||
//
|
||||
// Exclude a comptime-pack index (`xs[i]` where `xs` is a pack): a
|
||||
// pack has no runtime storage to address — its element is materialized
|
||||
// at comptime and can't be mutated in place — so it must keep flowing
|
||||
// through the general alloca+store-of-value path below.
|
||||
const is_pack_index = obj_node.data == .index_expr and
|
||||
obj_node.data.index_expr.object.data == .identifier and
|
||||
self.isPackName(obj_node.data.index_expr.object.data.identifier.name);
|
||||
if (!is_pack_index and (obj_node.data == .field_access or obj_node.data == .index_expr or obj_node.data == .deref_expr)) {
|
||||
// `lowerExprAsPtr` yields the lvalue's address, typed either as
|
||||
// `*T` already (index/deref) or as the pointee `T` (a field
|
||||
// "place" ref). Normalize to `*T`: if it's already the pointer
|
||||
// type, pass it directly; if it's the pointee value type, wrap
|
||||
// with addr_of (a no-op in LLVM) to set the IR type to *T,
|
||||
// preventing coerceCallArgs from doing a spurious alloca+store.
|
||||
const ptr_ty = self.module.types.ptrTo(obj_ty);
|
||||
method_args.items[0] = self.builder.emit(.{ .addr_of = .{ .operand = gep_ref } }, ptr_ty);
|
||||
const place = self.lowerExprAsPtr(obj_node);
|
||||
const place_ty = self.builder.getRefType(place);
|
||||
if (place_ty == ptr_ty) {
|
||||
method_args.items[0] = place;
|
||||
} else {
|
||||
method_args.items[0] = self.builder.emit(.{ .addr_of = .{ .operand = place } }, ptr_ty);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// General case: alloca+store the value and pass the alloca pointer
|
||||
|
||||
Reference in New Issue
Block a user