feat: comptime-cursor indexing of a named-tuple VALUE (GAP 1)

`tup[i]` where `i` folds to a compile-time integer (an `inline for`
cursor or a literal) now reads the i-th tuple field with its CONCRETE
type instead of failing with "cannot index a value of type '(…)'".

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` of the i-th field). A genuinely
runtime index into a tuple value still falls through to the existing
"cannot index a value of type" error (no single element type). A
comptime index out of range gets a dedicated loud diagnostic.

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.

Locked by examples/comptime/0652-comptime-tuple-cursor-index.sx.
This commit is contained in:
agra
2026-06-26 16:17:16 +03:00
parent 291f21e1b5
commit fee86adf2c
5 changed files with 78 additions and 0 deletions

View File

@@ -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