feat: comptime tuple-element L-values + named-tuple-literal binding (GAP 2)

Completes comptime-cursor tuple indexing (started by the read path in
fee86adf) and unblocks the `race` runtime synthesis. Five enablers:

1. Named-tuple-literal type inference preserves element NAMES. A
   `.(a = x, b = y)` passed DIRECTLY as a `$T` argument inferred to a
   tuple with `.names = null`, so `field_name(T, i)` reflected "" and a
   `make_enum` over those labels collided on the empty name. The typer
   now mirrors `lowerTupleLiteral`'s name capture.

2. `inferExprType` resolves a comptime-constant tuple index to the i-th
   field's CONCRETE type (the inference sibling of the fee86adf read
   path), so `tup[i].field` / methods / comparisons on it resolve.

3. Tuple-element L-VALUES by comptime index — `tup[i] = v`,
   `tup[i].f = v`, `@tup[i]` — lower to a typed `structGep` of field i
   across all four paths (`lowerAssignment`, the multi-assign store,
   `lowerExprAsPtr`, and address-of-index). Previously each emitted an
   `index_gep` with a `ptrTo(.unresolved)` element type (a tuple has no
   uniform element) that panicked at LLVM emit. An out-of-range comptime
   index now diagnoses loudly on every path instead of falling through to
   that panic.

4. A user generic `($X..) -> Type` call is recognized as type-shaped
   (`isTypeReturningCallNode`), so it can bind a `$E: Type` parameter —
   e.g. `make_variant(RaceResult(T), i, …)`. The static
   `isTypeShapedAstNode` only knew the type-returning builtins
   (field_type/pointee/type_of).

Locked by examples/comptime/0652 (read, fee86adf) and 0653 (store +
address-of + element-pointer field store).
This commit is contained in:
agra
2026-06-26 18:06:55 +03:00
parent fee86adf2c
commit 6a97628749
7 changed files with 209 additions and 6 deletions

View File

@@ -0,0 +1,35 @@
// Comptime-cursor tuple-element L-VALUES: writing a named-tuple element by a
// comptime-constant index — the store/address-of siblings of 0652's read path.
// A tuple is heterogeneous, so each element L-value is a typed `structGep` of the
// i-th field (not a uniform `index_gep`): `tup[i] = v` (direct store), a field
// store through an element pointer (`tup[i].f = v`), and `@tup[i]` (address-of).
// These are what the `race` runtime needs to register a waiter on the i-th task
// handle (`tasks[i].waiter = …`). An out-of-range comptime index is a loud
// compile error on every one of these paths (no silent `ptrTo(unresolved)` panic).
#import "modules/std.sx";
Box :: struct ($R: Type) { value: R; }
main :: () -> i32 {
// Direct element store by literal index.
t := .(a = 1, b = 2, c = 3);
t[0] = 100;
t[2] = 300;
print("t = ({}, {}, {})\n", t.a, t.b, t.c);
// Address-of an element, write through the pointer.
p := @t[1];
p.* = 200;
print("t.b via @t[1] = {}\n", t.b);
// Field store THROUGH an element pointer — `tup[i].field = v` — the exact
// L-value shape `race` uses to register a waiter (`tasks[i].waiter = …`): the
// i-th element is a `*Box`, and `.value` writes through it to the pointee.
ba : Box(i64) = .{ value = 0 };
bb : Box(bool) = .{ value = false };
handles := .(x = @ba, y = @bb);
handles[0].value = 7;
handles[1].value = true;
print("ba.value = {}, bb.value = {}\n", ba.value, bb.value);
return 0;
}