lang: for-loop over List(T); deref a *T method receiver

The collection for-loop now iterates a List(T)-like struct ({ items: [*]T, len, … }) — and a *List — by viewing it as items[0..len]. So 'for legal: (m)' / 'for pieces: (*p)' work like iterating a slice, with by-ref captures writing back into the backing.

fixupMethodReceiver also derefs a *T receiver when the method takes T by value, so a 'for xs: (*x)' capture can call value-self methods (x.method()). Regression: examples/for-list.sx.
This commit is contained in:
agra
2026-05-31 11:13:57 +03:00
parent 6b5edc77b4
commit 5c9d8c23ca
4 changed files with 102 additions and 5 deletions

View File

@@ -3346,6 +3346,34 @@ pub const Lowering = struct {
return self.builder.constInt(0, .void);
}
/// View a `List(T)`-like struct (`{ items: [*]T, len, … }`) as its backing
/// `items` pointer + element type + `len`, so `for list: (x)` iterates the
/// elements. Null for anything that isn't such a struct.
fn listView(self: *Lowering, value: Ref, ty: TypeId) ?struct { data: Ref, data_ty: TypeId, len: Ref } {
if (ty.isBuiltin()) return null;
const info = self.module.types.get(ty);
if (info != .@"struct") return null;
const items_id = self.module.types.internString("items");
const len_id = self.module.types.internString("len");
var items_idx: ?u32 = null;
var items_ty: TypeId = .unresolved;
var len_idx: ?u32 = null;
for (info.@"struct".fields, 0..) |f, i| {
if (f.name == items_id and !f.ty.isBuiltin() and self.module.types.get(f.ty) == .many_pointer) {
items_idx = @intCast(i);
items_ty = f.ty;
} else if (f.name == len_id) {
len_idx = @intCast(i);
}
}
if (items_idx == null or len_idx == null) return null;
return .{
.data = self.builder.emit(.{ .struct_get = .{ .base = value, .field_index = items_idx.? } }, items_ty),
.data_ty = items_ty,
.len = self.builder.emit(.{ .struct_get = .{ .base = value, .field_index = len_idx.? } }, .s64),
};
}
fn lowerFor(self: *Lowering, fe: *const ast.ForExpr) Ref {
if (fe.range_end) |end_node| {
if (fe.is_inline) return self.lowerInlineRangeFor(fe, end_node);
@@ -3357,11 +3385,27 @@ pub const Lowering = struct {
return self.diagPackAsValue(fe.iterable.data.identifier.name, fe.iterable.span, .runtime_iter);
}
// Lower iterable
const iterable = self.lowerExpr(fe.iterable);
// Lower iterable + resolve its static type.
var iterable = self.lowerExpr(fe.iterable);
var iterable_ty = self.inferExprType(fe.iterable);
// Get length
const len = self.builder.emit(.{ .length = .{ .operand = iterable } }, .s64);
// `*List` / `*[]T` etc. — deref to the collection value.
const ptr_info = if (iterable_ty.isBuiltin()) null else self.module.types.get(iterable_ty);
if (ptr_info != null and ptr_info.? == .pointer) {
iterable = self.builder.load(iterable, ptr_info.?.pointer.pointee);
iterable_ty = ptr_info.?.pointer.pointee;
}
// A `List(T)`-like struct iterates its `items[0..len]`; arrays/slices
// use their intrinsic length.
var len: Ref = undefined;
if (self.listView(iterable, iterable_ty)) |lv| {
iterable = lv.data;
iterable_ty = lv.data_ty;
len = lv.len;
} else {
len = self.builder.emit(.{ .length = .{ .operand = iterable } }, .s64);
}
// Create index variable
const idx_slot = self.builder.alloca(.s64);
@@ -3387,7 +3431,6 @@ pub const Lowering = struct {
// 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 bind_ty = if (fe.capture_by_ref) self.module.types.ptrTo(elem_ty) else elem_ty;
const elem = if (fe.capture_by_ref) blk: {
@@ -4188,6 +4231,15 @@ pub const Lowering = struct {
self.builder.store(slot, method_args.items[0]);
method_args.items[0] = slot;
}
} else {
// Method expects a value `T` but the receiver is a `*T` (e.g. a
// `for xs: (*x)` by-ref capture) — deref to pass the value.
if (!obj_ty.isBuiltin()) {
const oi = self.module.types.get(obj_ty);
if (oi == .pointer and oi.pointer.pointee == first_param_ty) {
method_args.items[0] = self.builder.load(method_args.items[0], first_param_ty);
}
}
}
}
}