lang: for-loop by-ref element capture (for xs: (*x))
(*x) binds x to a pointer into the collection (index_gep) instead of a per-element value copy: passing it on (e.g. to a *T param) is zero-copy and mutations write back. In a value position x auto-derefs — a binary-op operand loads the element, a pointer-typed slot keeps the pointer, and an 'if x == {...}' match derefs the pointee for its tag/payload. Arrays GEP through their storage so writes hit the original. Regression test: examples/for-by-ref-capture.sx.
This commit is contained in:
@@ -492,6 +492,9 @@ pub const ForExpr = struct {
|
||||
range_end: ?*Node = null,
|
||||
/// `inline for` — comptime-unrolled (range bounds must be comptime).
|
||||
is_inline: bool = false,
|
||||
/// `for xs: (*x)` — bind `x` to a pointer into the collection (no per-element
|
||||
/// copy) rather than a value copy of each element.
|
||||
capture_by_ref: bool = false,
|
||||
};
|
||||
|
||||
pub const SpreadExpr = struct {
|
||||
|
||||
@@ -38,6 +38,7 @@ const Binding = struct {
|
||||
ref: Ref,
|
||||
ty: TypeId,
|
||||
is_alloca: bool, // true if ref is a pointer that needs load
|
||||
is_ref_capture: bool = false, // `for xs: (*x)` — `ref` is `*elem`; auto-deref in value positions
|
||||
};
|
||||
|
||||
const Scope = struct {
|
||||
@@ -2542,6 +2543,17 @@ pub const Lowering = struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// If `node` names a `for xs: (*x)` by-ref capture (an `*elem`), returns
|
||||
/// the element (pointee) type so a value-position use can auto-deref it.
|
||||
fn refCapturePointee(self: *Lowering, node: *const Node) ?TypeId {
|
||||
if (node.data != .identifier) return null;
|
||||
const scope = self.scope orelse return null;
|
||||
const binding = scope.lookup(node.data.identifier.name) orelse return null;
|
||||
if (!binding.is_ref_capture or binding.ty.isBuiltin()) return null;
|
||||
const info = self.module.types.get(binding.ty);
|
||||
return if (info == .pointer) info.pointer.pointee else null;
|
||||
}
|
||||
|
||||
fn lowerBinaryOp(self: *Lowering, bop: *const ast.BinaryOp) Ref {
|
||||
// Short-circuit: `a and b` → if a then b else false
|
||||
if (bop.op == .and_op) {
|
||||
@@ -2656,10 +2668,15 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
var lhs = self.lowerExpr(bop.lhs);
|
||||
// A `for xs: (*x)` capture is a pointer; in a value position (here, an
|
||||
// operand) it auto-derefs to the element.
|
||||
const lhs_ref_pointee = self.refCapturePointee(bop.lhs);
|
||||
if (lhs_ref_pointee) |p| lhs = self.builder.load(lhs, p);
|
||||
// Set target_type from LHS so enum literals on RHS resolve correctly.
|
||||
// When the LHS isn't statically inferable (e.g. `#objc_call(...)`), use
|
||||
// the lowered operand's concrete type rather than a guess.
|
||||
const lhs_ty = blk: {
|
||||
if (lhs_ref_pointee) |p| break :blk p;
|
||||
const it = self.inferExprType(bop.lhs);
|
||||
break :blk if (it == .unresolved) self.builder.getRefType(lhs) else it;
|
||||
};
|
||||
@@ -2675,6 +2692,8 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
var rhs = self.lowerExpr(bop.rhs);
|
||||
const rhs_ref_pointee = self.refCapturePointee(bop.rhs);
|
||||
if (rhs_ref_pointee) |p| rhs = self.builder.load(rhs, p);
|
||||
self.target_type = saved_tt;
|
||||
// Infer result type from LHS operand (covers float, bool, etc.)
|
||||
var ty = lhs_ty;
|
||||
@@ -2682,7 +2701,7 @@ pub const Lowering = struct {
|
||||
// Promote int×float → float (e.g., s64 * f32 → f32)
|
||||
// Only for scalar int LHS — don't affect vectors or structs.
|
||||
{
|
||||
const rhs_inferred = self.inferExprType(bop.rhs);
|
||||
const rhs_inferred = rhs_ref_pointee orelse self.inferExprType(bop.rhs);
|
||||
const l_int = isInt(ty);
|
||||
const r_float = (rhs_inferred == .f32 or rhs_inferred == .f64);
|
||||
if (l_int and r_float) {
|
||||
@@ -2698,7 +2717,7 @@ pub const Lowering = struct {
|
||||
lhs = self.builder.emit(.{ .optional_unwrap = .{ .operand = lhs } }, ty);
|
||||
}
|
||||
}
|
||||
const rhs_ty = self.inferExprType(bop.rhs);
|
||||
const rhs_ty = rhs_ref_pointee orelse self.inferExprType(bop.rhs);
|
||||
if (!rhs_ty.isBuiltin()) {
|
||||
const rhs_info = self.module.types.get(rhs_ty);
|
||||
if (rhs_info == .optional) {
|
||||
@@ -3365,16 +3384,26 @@ pub const Lowering = struct {
|
||||
// Body
|
||||
self.builder.switchToBlock(body_bb);
|
||||
|
||||
// Bind element — resolve element type from iterable
|
||||
// Bind element — resolve element type from iterable. `for xs: (*x)`
|
||||
// binds a pointer into the collection (no per-element copy); `(x)`
|
||||
// binds a value copy.
|
||||
const iterable_ty = self.inferExprType(fe.iterable);
|
||||
const elem_ty = self.getElementType(iterable_ty);
|
||||
const elem = self.builder.emit(.{ .index_get = .{ .lhs = iterable, .rhs = idx_val } }, elem_ty);
|
||||
const bind_ty = if (fe.capture_by_ref) self.module.types.ptrTo(elem_ty) else elem_ty;
|
||||
const elem = if (fe.capture_by_ref) blk: {
|
||||
// A slice value carries its backing pointer, so GEP on it writes
|
||||
// through. An array is a value — GEP needs its storage (alloca) or
|
||||
// mutations would hit a copy.
|
||||
const is_array = !iterable_ty.isBuiltin() and self.module.types.get(iterable_ty) == .array;
|
||||
const base = if (is_array) (self.getExprAlloca(fe.iterable) orelse iterable) else iterable;
|
||||
break :blk self.builder.emit(.{ .index_gep = .{ .lhs = base, .rhs = idx_val } }, bind_ty);
|
||||
} else self.builder.emit(.{ .index_get = .{ .lhs = iterable, .rhs = idx_val } }, bind_ty);
|
||||
|
||||
var body_scope = Scope.init(self.alloc, self.scope);
|
||||
const old_scope = self.scope;
|
||||
self.scope = &body_scope;
|
||||
|
||||
body_scope.put(fe.capture_name, .{ .ref = elem, .ty = elem_ty, .is_alloca = false });
|
||||
body_scope.put(fe.capture_name, .{ .ref = elem, .ty = bind_ty, .is_alloca = false, .is_ref_capture = fe.capture_by_ref });
|
||||
|
||||
// Bind index if requested
|
||||
if (fe.index_name) |iname| {
|
||||
@@ -3563,10 +3592,20 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
const is_type_match = isTypeCategoryMatch(me);
|
||||
const subject = self.lowerExpr(me.subject);
|
||||
|
||||
// Detect optional subject type
|
||||
const subject_ty = self.inferExprType(me.subject);
|
||||
var subject = self.lowerExpr(me.subject);
|
||||
var subject_ty = self.inferExprType(me.subject);
|
||||
// A pointer subject (e.g. a `for xs: (*x)` element capture) — deref to
|
||||
// the pointed-to union/enum so tag/payload extraction works.
|
||||
if (!subject_ty.isBuiltin()) {
|
||||
const sinfo = self.module.types.get(subject_ty);
|
||||
if (sinfo == .pointer and !sinfo.pointer.pointee.isBuiltin()) {
|
||||
const pinfo = self.module.types.get(sinfo.pointer.pointee);
|
||||
if (pinfo == .tagged_union or pinfo == .@"enum") {
|
||||
subject = self.builder.load(subject, sinfo.pointer.pointee);
|
||||
subject_ty = sinfo.pointer.pointee;
|
||||
}
|
||||
}
|
||||
}
|
||||
const is_optional_match = blk: {
|
||||
if (!subject_ty.isBuiltin()) {
|
||||
const info = self.module.types.get(subject_ty);
|
||||
|
||||
@@ -2864,6 +2864,7 @@ pub const Parser = struct {
|
||||
|
||||
var capture_name: []const u8 = "";
|
||||
var index_name: ?[]const u8 = null;
|
||||
var capture_by_ref = false;
|
||||
|
||||
if (range_end != null) {
|
||||
// Range capture is the optional cursor: `(i)` or nothing.
|
||||
@@ -2875,9 +2876,14 @@ pub const Parser = struct {
|
||||
try self.expect(.r_paren);
|
||||
}
|
||||
} else {
|
||||
// Collection form: `: (capture, index?)`.
|
||||
// Collection form: `: (capture, index?)`. A leading `*` on the
|
||||
// capture (`(*x)`) binds it by pointer into the collection.
|
||||
try self.expect(.colon);
|
||||
try self.expect(.l_paren);
|
||||
if (self.current.tag == .star) {
|
||||
capture_by_ref = true;
|
||||
self.advance();
|
||||
}
|
||||
if (self.current.tag != .identifier) return self.fail("expected capture variable name");
|
||||
capture_name = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
@@ -2898,6 +2904,7 @@ pub const Parser = struct {
|
||||
.capture_name = capture_name,
|
||||
.index_name = index_name,
|
||||
.range_end = range_end,
|
||||
.capture_by_ref = capture_by_ref,
|
||||
} });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user