From 49dc6222349344b7f42338672dd85954efaed64d Mon Sep 17 00:00:00 2001 From: agra Date: Mon, 1 Jun 2026 17:56:51 +0300 Subject: [PATCH] fix(lower): auto-ref compound lvalues passed to *T params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- examples/255-autoref-compound-lvalue.sx | 20 ++++++++++++ src/ir/lower.zig | 31 +++++++++++++++++++ .../expected/255-autoref-compound-lvalue.exit | 1 + .../expected/255-autoref-compound-lvalue.txt | 1 + 4 files changed, 53 insertions(+) create mode 100644 examples/255-autoref-compound-lvalue.sx create mode 100644 tests/expected/255-autoref-compound-lvalue.exit create mode 100644 tests/expected/255-autoref-compound-lvalue.txt diff --git a/examples/255-autoref-compound-lvalue.sx b/examples/255-autoref-compound-lvalue.sx new file mode 100644 index 0000000..ee546a2 --- /dev/null +++ b/examples/255-autoref-compound-lvalue.sx @@ -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; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index d83da96..ff10617 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -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 diff --git a/tests/expected/255-autoref-compound-lvalue.exit b/tests/expected/255-autoref-compound-lvalue.exit new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/tests/expected/255-autoref-compound-lvalue.exit @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/tests/expected/255-autoref-compound-lvalue.txt b/tests/expected/255-autoref-compound-lvalue.txt new file mode 100644 index 0000000..01f9c06 --- /dev/null +++ b/tests/expected/255-autoref-compound-lvalue.txt @@ -0,0 +1 @@ +w.s.v = 42