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

40
examples/for-list.sx Normal file
View File

@@ -0,0 +1,40 @@
// `for` over a `List(T)` (a `{ items: [*]T, len, cap }` struct): value capture,
// by-ref capture (mutates in place), iterating a `*List`, and a by-ref capture
// used as a value-receiver method call (auto-deref).
#import "modules/std.sx";
Box :: struct {
v: s64;
boxed :: (self: Box) -> s64 { self.v; } // value receiver
}
sum_ptr :: (xs: *List(s64)) -> s64 {
total : s64 = 0;
for xs: (n) { total = total + n; } // iterate through a *List
total;
}
main :: () -> s32 {
xs := List(s64).{};
xs.append(10);
xs.append(20);
xs.append(30);
s : s64 = 0;
for xs: (n) { s = s + n; } // value capture
print("sum {}\n", s); // 60
for xs: (*n) { n.* = n + 100; } // by-ref: writes back
s = 0;
for xs: (n) { s = s + n; }
print("sum2 {}\n", s); // 360
print("via ptr {}\n", sum_ptr(@xs)); // 360
bs := List(Box).{};
bs.append(.{ v = 7 });
bt : s64 = 0;
for bs: (*b) { bt = bt + b.boxed(); } // *Box receiver, value-self method
print("boxes {}\n", bt); // 7
0;
}

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);
}
}
}
}
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,4 @@
sum 60
sum2 360
via ptr 360
boxes 7