diff --git a/examples/comptime/0652-comptime-tuple-cursor-index.sx b/examples/comptime/0652-comptime-tuple-cursor-index.sx new file mode 100644 index 00000000..4660c628 --- /dev/null +++ b/examples/comptime/0652-comptime-tuple-cursor-index.sx @@ -0,0 +1,40 @@ +// Comptime-cursor indexing of a named-tuple VALUE: `tup[i]` where `i` is an +// `inline for` cursor (or a literal) reads the i-th tuple field with its +// CONCRETE type — not a type-erased `Any`. This is the read side `race` needs: +// pull the i-th `*Task(T_i)` handle out of a named-tuple param keeping its real +// type, so `field`/method access on it resolves. A tuple's elements are +// heterogeneous, so there is no runtime element-indexing op — a comptime index +// lowers exactly like the `.N` field-access path (a `structGet`). A runtime +// index into a tuple value remains an error (no single element type). +// +// (GAP 1 of PLAN-RACE.) +#import "modules/std.sx"; + +Box :: struct ($R: Type) { value: R; } + +// Read each handle with its concrete type via the `inline for` cursor. +show_all :: (tup: $T) { + inline for 0..field_count(T) (i) { + h := tup[i]; // concrete `*Box(T_i)`, not `Any` + print("[{}] = {}\n", i, h.value); // field access resolves + } +} + +// Literal-index form, positional tuple. +first_two :: (tup: $T) -> i64 { + a := tup[0]; + b := tup[1]; + return a.value + b.value; +} + +main :: () -> i32 { + ba : Box(i64) = .{ value = 7 }; + bb : Box(bool) = .{ value = true }; + bc : Box(f64) = .{ value = 2.5 }; + show_all(.(a = @ba, b = @bb, c = @bc)); // named tuple of *Box(..) + + p0 : Box(i64) = .{ value = 10 }; + p1 : Box(i64) = .{ value = 32 }; + print("sum = {}\n", first_two(.(@p0, @p1))); // positional, literal index + return 0; +} diff --git a/examples/comptime/expected/0652-comptime-tuple-cursor-index.exit b/examples/comptime/expected/0652-comptime-tuple-cursor-index.exit new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/examples/comptime/expected/0652-comptime-tuple-cursor-index.exit @@ -0,0 +1 @@ +0 diff --git a/examples/comptime/expected/0652-comptime-tuple-cursor-index.stderr b/examples/comptime/expected/0652-comptime-tuple-cursor-index.stderr new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/comptime/expected/0652-comptime-tuple-cursor-index.stderr @@ -0,0 +1 @@ + diff --git a/examples/comptime/expected/0652-comptime-tuple-cursor-index.stdout b/examples/comptime/expected/0652-comptime-tuple-cursor-index.stdout new file mode 100644 index 00000000..48679b65 --- /dev/null +++ b/examples/comptime/expected/0652-comptime-tuple-cursor-index.stdout @@ -0,0 +1,4 @@ +[0] = 7 +[1] = true +[2] = 2.500000 +sum = 42 diff --git a/src/ir/lower/expr.zig b/src/ir/lower/expr.zig index 3437bcb1..8e99b820 100644 --- a/src/ir/lower/expr.zig +++ b/src/ir/lower/expr.zig @@ -1798,6 +1798,38 @@ pub fn lowerIndexExpr(self: *Lowering, ie: *const ast.IndexExpr) Ref { } // Infer element type from the object's slice/array type const obj_ty = self.inferExprType(ie.object); + // Comptime-constant index into a tuple VALUE — `tup[i]` where `i` folds + // to a compile-time integer (an `inline for` cursor or a literal). A tuple + // has heterogeneous element types, so there is no runtime element-indexing + // op; treat it exactly like the `.N` field-access path (a `structGet` of + // the i-th field), yielding the field's CONCRETE type. This is what the + // `race`/reflection loops need: read the i-th `*Task(T_i)` from a + // named-tuple param with its real type, not a type-erased `Any`. A + // *runtime* index into a tuple value falls through to the generic guard + // below ("cannot index a value of type '(...)'") — there is no single + // element type to index by at runtime. + if (!obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .tuple) { + const tinfo = self.module.types.get(obj_ty).tuple; + if (self.comptimeIndexOf(ie.index)) |ci| { + if (ci >= 0 and @as(usize, @intCast(ci)) < tinfo.fields.len) { + const fi: u32 = @intCast(ci); + const obj = self.lowerExpr(ie.object); + return self.builder.structGet(obj, fi, tinfo.fields[fi]); + } + // Comptime index is out of range — diagnose loudly rather than + // letting it fall through to the generic "cannot index" message, + // which would obscure the real cause (a bad constant index). + if (self.diagnostics) |d| { + d.addFmt(.err, ie.index.span, "tuple index {} out of bounds — tuple '{s}' has {} field{s}", .{ + ci, + self.formatTypeName(obj_ty), + tinfo.fields.len, + if (tinfo.fields.len == 1) "" else "s", + }); + } + return self.builder.constInt(0, .i64); // placeholder — hasErrors() aborts before codegen + } + } // Optional-chain index: `opt?.xs[i]`. The `?.` makes the object an // optional whose child is the (array/slice/many-ptr) field — so the index // applies inside the chain's some-branch and the whole expression is