green(reify): field_type($T, i) -> Type over the type table

REIFY Phase 2.1. fieldTypeOf (lower/generic.zig, re-exported on Lowering)
returns the i-th member type of T: struct field / tagged-union + union
variant payload (.void for a tagless variant) / tuple element / array +
vector element. Out-of-range and memberless types poison to .unresolved
with a loud diagnostic (never a silent default). Wired into
resolveTypeCallWithBindings (replacing the Phase-2 bail); since it folds
to a TypeId at lower time it composes inside type_eq / type_name / any
type-arg slot.

examples/0616 green: struct fields (name via field_name + type via
field_type), type_eq fold, tagged-union payloads incl. quit -> void.
Suite green (672 examples, 447 unit).

type_info($T) -> TypeInfo (reflect into a value, inverse of reify) is
NOT done — still bails loudly; it's the larger Phase 2.2 step (widen the
TypeInfo data model + comptime value construction). Plan/checkpoint updated.
This commit is contained in:
agra
2026-06-16 19:06:57 +03:00
parent bd139dc09c
commit ac8c689518
7 changed files with 107 additions and 17 deletions

View File

@@ -1894,6 +1894,7 @@ pub const Lowering = struct {
pub const flatFnAuthorAmbiguous = lower_generic.flatFnAuthorAmbiguous;
pub const flatFnAuthorVisible = lower_generic.flatFnAuthorVisible;
pub const resolveTypeCallWithBindings = lower_generic.resolveTypeCallWithBindings;
pub const fieldTypeOf = lower_generic.fieldTypeOf;
pub const resolveParameterizedWithBindings = lower_generic.resolveParameterizedWithBindings;
pub const resolveValueParamArg = lower_generic.resolveValueParamArg;
pub const canonicalIntConstraintName = lower_generic.canonicalIntConstraintName;

View File

@@ -1209,6 +1209,39 @@ pub fn flatFnAuthorVisible(self: *Lowering, name: []const u8, from: []const u8)
}
/// Resolve a .call node that represents a type constructor (e.g., List(T), Vector(N, T)).
/// The `idx`-th member type of `t` for `field_type($T, i)`: a struct field,
/// a tagged-union variant payload (`.void` for a tagless variant), a tuple
/// element, a `union` field, or the element type of an array/vector (index
/// ignored — every element shares it). Out-of-range or a memberless type
/// diagnoses and poisons to `.unresolved` (never a silent default).
pub fn fieldTypeOf(self: *Lowering, t: TypeId, idx: usize, span: ?ast.Span) TypeId {
const oob = struct {
fn err(s: *Lowering, sp: ?ast.Span, i: usize, n: usize) TypeId {
if (s.diagnostics) |d|
d.addFmt(.err, sp, "field_type index {d} out of range ({d} field{s})", .{ i, n, if (n == 1) @as([]const u8, "") else "s" });
return .unresolved;
}
};
if (t.isBuiltin()) {
if (self.diagnostics) |d|
d.addFmt(.err, span, "field_type: '{s}' has no fields", .{self.formatTypeName(t)});
return .unresolved;
}
return switch (self.module.types.get(t)) {
.@"struct" => |s| if (idx < s.fields.len) s.fields[idx].ty else oob.err(self, span, idx, s.fields.len),
.tagged_union => |u| if (idx < u.fields.len) u.fields[idx].ty else oob.err(self, span, idx, u.fields.len),
.@"union" => |u| if (idx < u.fields.len) u.fields[idx].ty else oob.err(self, span, idx, u.fields.len),
.tuple => |tup| if (idx < tup.fields.len) tup.fields[idx] else oob.err(self, span, idx, tup.fields.len),
.array => |a| if (idx < a.length) a.element else oob.err(self, span, idx, a.length),
.vector => |v| if (idx < v.length) v.element else oob.err(self, span, idx, v.length),
else => blk: {
if (self.diagnostics) |d|
d.addFmt(.err, span, "field_type: '{s}' has no indexable fields", .{self.formatTypeName(t)});
break :blk .unresolved;
},
};
}
pub fn resolveTypeCallWithBindings(self: *Lowering, cl: *const ast.Call) TypeId {
// A namespaced callee (`ns.Box(..)`) is an explicit qualified reach and is
// exempt from the bare-head visibility gate; only a plain identifier head
@@ -1230,9 +1263,25 @@ pub fn resolveTypeCallWithBindings(self: *Lowering, cl: *const ast.Call) TypeId
return .unresolved;
}
if (std.mem.eql(u8, callee_name, "field_type")) {
if (self.diagnostics) |d|
d.addFmt(.err, cl.callee.span, "field_type is not yet implemented (REIFY Phase 2)", .{});
return .unresolved;
// field_type($T, i) -> Type — the i-th field / variant-payload /
// element type of `T`. Folds at lower time (it's a `$T: Type` builtin),
// so it composes inside `type_eq` / `type_name` / any type-arg slot.
if (cl.args.len != 2) {
if (self.diagnostics) |d|
d.addFmt(.err, cl.callee.span, "field_type takes a type and an index: field_type($T, i)", .{});
return .unresolved;
}
const t = self.resolveTypeArg(cl.args[0]);
if (t == .unresolved) return .unresolved;
const idx: usize = switch (program_index_mod.foldDimU32(cl.args[1], self, 0)) {
.ok => |n| n,
else => {
if (self.diagnostics) |d|
d.addFmt(.err, cl.args[1].span, "field_type index must be a non-negative compile-time integer", .{});
return .unresolved;
},
};
return self.fieldTypeOf(t, idx, cl.callee.span);
}
// Built-in: Vector(N, T)
if (std.mem.eql(u8, callee_name, "Vector") and cl.args.len == 2) {