Files
sx/issues/0196-named-tuple-alias-loses-structure.md
agra dea96bd66a docs: PLAN-RACE (race over M:1 tasks) + file issue 0196
Plan the `race` async deliverable (roadmap A1): a structured first-wins over
the M:1 `Task` layer, returning a comptime-synthesized tagged-union mirroring
the input named tuple's labels. Identifies the one net-new compiler primitive
needed — `pointee($P: Type) -> Type` (project `*Task(A)` → `A`); everything else
(tuple reflection, union synthesis, the suspending runtime) is already in place.

Issue 0196: a named-tuple type ALIAS (`NT :: Tuple(a: i64, b: bool)`) loses its
structure — field access and reflection both fail on the alias, while the inline
and `$T`-type-parameter forms work. Not on the race critical path (race reflects
a tuple type parameter), filed for tracking.
2026-06-26 12:34:13 +03:00

3.3 KiB

Issue 0196 — a named-tuple type alias (NT :: Tuple(a: i64, b: bool)) loses its structure

Status: OPEN. Found while building race (does NOT block it — race reflects a tuple type PARAMETER $T, which works; this is the narrower named alias path). Filed so the inconsistency is tracked.

Symptom

Binding a named tuple type to a :: alias and then using the alias drops the tuple's field structure. Both field access and comptime reflection fail on the alias, even though the identical inline type and a type-parameter $T bound to the same type both work.

form field_count / field_name field access x.a
inline Tuple(a: i64, b: bool) ✓ works (fc=2, name0=a) n/a
type param ($T: Type)Tuple(a: i64, b: bool) ✓ works (fc=2, name0=a) n/a
alias NT :: Tuple(a: i64, b: bool) error: unresolved type: 'NT' error: field 'a' not found on type 'NT'

Reproduction

#import "modules/std.sx";

NT :: Tuple(a: i64, b: bool);

main :: () -> i32 {
    // (1) field access through the alias fails:
    x : NT = .(a = 1, b = true);
    print("x.a={} x.b={}\n", x.a, x.b);     // error: field 'a' not found on type 'NT'

    // (2) reflection through the alias fails:
    print("fc={}\n", field_count(NT));      // error: unresolved type: 'NT'
    return 0;
}

Contrast — both of these work today:

// inline (see examples/comptime/0646-comptime-field-reflect-tuple-array.sx):
field_count(Tuple(a: i64, b: bool));               // 2
field_name(Tuple(a: i64, b: bool), 0);             // "a"

// type parameter:
count :: ($T: Type) -> i64 { return field_count(T); }
count(Tuple(a: i64, b: bool));                     // 2

Notes / investigation prompt

A :: alias of a named tuple type (NT :: Tuple(a: i64, b: bool)) doesn't behave like the inline named tuple: x : NT then x.a reports "field 'a' not found on type 'NT'", and field_count(NT) / field_name(NT, i) report "unresolved type: 'NT'". The inline form and a generic $T type parameter bound to the same Tuple(...) both work, so the named-tuple TypeId is correct — the alias binding is where the structure (or the resolvability) is lost.

First determine whether this is intended (tuples are structural / non-nominal per specs.md §3.5, so perhaps a :: alias is meant to be spelled differently, e.g. a type-fn NT :: () -> Type { ... }) or a genuine bug in how a :: type-alias binds a structural tuple type. If it's a bug: the alias likely registers a forward/opaque type entry that never resolves to the underlying TupleInfo (hence both "unresolved type" in reflection and "field not found" in member access). Check the type-alias decl lowering path (where Name :: <type-expr> binds) and whether a structural tuple type-expr is resolved + carried vs. left as an unresolved nominal placeholder.

Also check the POSITIONAL case (PT :: Tuple(i64, bool)x : PT = .(1, true), x.0, field_count(PT)) to see whether the breakage is named-tuple-specific or all tuple aliases.

Why it doesn't block race

race is RaceResult :: ($T: Type) -> Type over a tuple type parameter (the named tuple arrives as the inferred $T of the race(tasks) call), and reflection on a $T tuple parameter works (verified). The alias form is a convenience that race does not depend on.