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:
agra
2026-06-01 17:56:51 +03:00
parent 497d450ba7
commit 49dc622234
4 changed files with 53 additions and 0 deletions

View File

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