fix(lower): auto-ref compound lvalues passed to *T params
The implicit address-of that gives `*T` params reference semantics only fired for plain identifiers (`mut(v)`). For a field-access / index / deref lvalue (`make_move(self.board, m)`, `mut(w.s)`), the branch was skipped: the arg was loaded into a temporary and the callee mutated a throwaway copy — silent data loss, with the type check satisfied through the temp so no diagnostic fired. Now compound lvalues auto-ref too: take the real lvalue address via `lowerExprAsPtr`, normalizing the "place" ref to `*T` exactly as `@field_access` does. Mutations through the pointer are now visible to the caller, matching the identifier case. Regression: examples/255-autoref-compound-lvalue.sx.
This commit is contained in:
20
examples/255-autoref-compound-lvalue.sx
Normal file
20
examples/255-autoref-compound-lvalue.sx
Normal file
@@ -0,0 +1,20 @@
|
||||
// Auto-ref of a COMPOUND lvalue (field access / index / deref) passed to a
|
||||
// `*T` parameter must reference the REAL lvalue, not a copy. Regression: a
|
||||
// field-access argument (e.g. `make_move(self.board, m)`) silently passed the
|
||||
// address of a temporary copy, so mutations through the pointer were lost with
|
||||
// no diagnostic. A plain local (`bump(x)`) already auto-ref'd; now compound
|
||||
// lvalues do too. Expected: w.s.v == 42 (the real field was mutated).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
S :: struct { v: s32; }
|
||||
W :: struct { s: S; }
|
||||
|
||||
bump :: (p: *S) { p.v = p.v + 41; }
|
||||
|
||||
main :: () -> s32 {
|
||||
w : W = .{ s = .{ v = 1 } };
|
||||
bump(w.s); // field access, no `@` — auto-refs &w.s
|
||||
print("w.s.v = {}\n", w.s.v); // 42, not 1
|
||||
return 0;
|
||||
}
|
||||
@@ -6784,6 +6784,37 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Implicit address-of for compound lvalues (field access / index /
|
||||
// deref): when the param expects `*T` and the arg is an addressable
|
||||
// lvalue of type `T`, pass the lvalue's real address (GEP) — same
|
||||
// reference semantics as the identifier case above. Without this the
|
||||
// arg would be loaded into a temporary and the callee would mutate a
|
||||
// throwaway copy (silent data loss — e.g. `make_move(self.board, m)`).
|
||||
if (ai < param_types.len and (arg.data == .field_access or arg.data == .index_expr or arg.data == .deref_expr)) {
|
||||
const pt = param_types[ai];
|
||||
if (!pt.isBuiltin()) {
|
||||
const pti = self.module.types.get(pt);
|
||||
if (pti == .pointer and self.inferExprType(arg) == pti.pointer.pointee) {
|
||||
// `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` — exactly
|
||||
// what `@field_access` does.
|
||||
const place = self.lowerExprAsPtr(arg);
|
||||
const place_ty = self.builder.getRefType(place);
|
||||
const ref: ?Ref = if (place_ty == pt)
|
||||
place
|
||||
else if (place_ty == pti.pointer.pointee)
|
||||
self.builder.emit(.{ .addr_of = .{ .operand = place } }, pt)
|
||||
else
|
||||
null;
|
||||
if (ref) |r| {
|
||||
args.append(self.alloc, r) catch unreachable;
|
||||
self.target_type = saved_target;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const val = self.lowerExpr(arg);
|
||||
self.target_type = saved_target;
|
||||
// Passing a `*T` where a `T` value is expected — a by-reference loop
|
||||
|
||||
1
tests/expected/255-autoref-compound-lvalue.exit
Normal file
1
tests/expected/255-autoref-compound-lvalue.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
tests/expected/255-autoref-compound-lvalue.txt
Normal file
1
tests/expected/255-autoref-compound-lvalue.txt
Normal file
@@ -0,0 +1 @@
|
||||
w.s.v = 42
|
||||
Reference in New Issue
Block a user