From 5c9d8c23cac81469f9279c8d522edcdaf3c5ca9a Mon Sep 17 00:00:00 2001 From: agra Date: Sun, 31 May 2026 11:13:57 +0300 Subject: [PATCH] lang: for-loop over List(T); deref a *T method receiver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- examples/for-list.sx | 40 +++++++++++++++++++++++ src/ir/lower.zig | 62 +++++++++++++++++++++++++++++++++--- tests/expected/for-list.exit | 1 + tests/expected/for-list.txt | 4 +++ 4 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 examples/for-list.sx create mode 100644 tests/expected/for-list.exit create mode 100644 tests/expected/for-list.txt diff --git a/examples/for-list.sx b/examples/for-list.sx new file mode 100644 index 0000000..a4822e0 --- /dev/null +++ b/examples/for-list.sx @@ -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; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 62e9d2e..7f89f09 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -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); + } + } } } } diff --git a/tests/expected/for-list.exit b/tests/expected/for-list.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/for-list.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/for-list.txt b/tests/expected/for-list.txt new file mode 100644 index 0000000..4d0cce7 --- /dev/null +++ b/tests/expected/for-list.txt @@ -0,0 +1,4 @@ +sum 60 +sum2 360 +via ptr 360 +boxes 7