From 39172acbfb707f6bc567998f47cfbe26bcbb7040 Mon Sep 17 00:00:00 2001 From: foxnne Date: Sat, 17 Oct 2026 10:56:39 -0500 Subject: [PATCH] zig: settings and command schemas read 0.17's @typeInfo The schema builders in sdk/src/settings.zig and sdk/src/Command.zig loop over @typeInfo's field_names/field_types/field_attrs and take a cell's default with FieldAttributes.defaultValue, in place of the ZIG017 stand-in that rebuilt 0.16's field records (sdk/src/zig017_fields.zig, deleted). The plain payload structs reuse @typeInfo's own name slice. fingerprint.zig gains the test the plan asked for: both hashes of a fixed set of types, one of each kind they look into, equal the values Zig 0.16 computed with the file as it was on main. Co-Authored-By: Claude Opus 5.5 --- sdk/src/Command.zig | 45 +++++------ sdk/src/fingerprint.zig | 33 ++++++++ sdk/src/settings.zig | 159 +++++++++++++++++++------------------- sdk/src/zig017_fields.zig | 31 -------- 4 files changed, 132 insertions(+), 136 deletions(-) delete mode 100644 sdk/src/zig017_fields.zig diff --git a/sdk/src/Command.zig b/sdk/src/Command.zig index f911790d4..68996c812 100644 --- a/sdk/src/Command.zig +++ b/sdk/src/Command.zig @@ -222,24 +222,24 @@ pub fn Params(comptime T: type) type { }; } -fn buildParams(comptime T: type) [std.meta.fieldNames(T).len]Param { - const fields = @import("zig017_fields.zig").fields(T); - var out: [fields.len]Param = undefined; - for (fields, 0..) |f, i| { - if (!settings.isCell(f.type)) @compileError( - "sdk.Command.Params: field '" ++ f.name ++ "' of " ++ @typeName(T) ++ - " must be an argument cell — `" ++ f.name ++ ": sdk.Command.Arg(" ++ - @typeName(f.type) ++ ", .{ .description = \"…\" })`. Every parameter needs a description.", +fn buildParams(comptime T: type) [@typeInfo(T).@"struct".field_names.len]Param { + const info = @typeInfo(T).@"struct"; + var out: [info.field_names.len]Param = undefined; + for (info.field_names, info.field_types, info.field_attrs, 0..) |f_name, Cell, cell_attrs, i| { + if (!settings.isCell(Cell)) @compileError( + "sdk.Command.Params: field '" ++ f_name ++ "' of " ++ @typeName(T) ++ + " must be an argument cell — `" ++ f_name ++ ": sdk.Command.Arg(" ++ + @typeName(Cell) ++ ", .{ .description = \"…\" })`. Every parameter needs a description.", ); - const opts = f.type.setting_options; - const default: ?f.type.Payload = if (f.defaultValue()) |cell| cell.v else null; + const opts = Cell.setting_options; + const default: ?Cell.Payload = if (cell_attrs.defaultValue(Cell)) |cell| cell.v else null; out[i] = .{ - .key = f.name, - .label = opts.name orelse settings.deriveLabel(f.name), + .key = f_name, + .label = opts.name orelse settings.deriveLabel(f_name), .description = opts.description, - .kind = settings.kindFor(f.type.Payload, opts), + .kind = settings.kindFor(Cell.Payload, opts), .required = default == null, - .default = if (default) |d| defaultZon(f.type.Payload, d) else null, + .default = if (default) |d| defaultZon(Cell.Payload, d) else null, }; } return out; @@ -261,23 +261,20 @@ fn defaultZon(comptime P: type, comptime value: P) ?[]const u8 { /// `T`'s fields with bare payload types, each keeping its cell's default if it has one. fn Plain(comptime T: type) type { - const fields = @import("zig017_fields.zig").fields(T); - var names: [fields.len][:0]const u8 = undefined; - var types: [fields.len]type = undefined; - var attrs: [fields.len]std.lang.Type.Struct.FieldAttributes = undefined; - for (fields, 0..) |f, i| { - const P = f.type.Payload; - names[i] = f.name; + const info = @typeInfo(T).@"struct"; + var types: [info.field_names.len]type = undefined; + var attrs: [info.field_names.len]std.lang.Type.Struct.FieldAttributes = undefined; + for (info.field_types, info.field_attrs, 0..) |Cell, cell_attrs, i| { + const P = Cell.Payload; types[i] = P; - attrs[i] = if (f.defaultValue()) |cell| blk: { + attrs[i] = if (cell_attrs.defaultValue(Cell)) |cell| blk: { const payload_default: P = cell.v; break :blk .{ .default_value_ptr = @ptrCast(&payload_default) }; } else .{}; } - const frozen_names = names; const frozen_types = types; const frozen_attrs = attrs; - return @Struct(.auto, null, &frozen_names, &frozen_types, &frozen_attrs); + return @Struct(.auto, null, info.field_names, &frozen_types, &frozen_attrs); } test "Params: descriptors, defaults and parsing" { diff --git a/sdk/src/fingerprint.zig b/sdk/src/fingerprint.zig index 27c11a37a..b30c159fa 100644 --- a/sdk/src/fingerprint.zig +++ b/sdk/src/fingerprint.zig @@ -331,3 +331,36 @@ test "fingerprint catches function-pointer signature changes" { const v2 = comptime hashAll(seed, .{V2}, 6); try std.testing.expect(v1 != v2); } + +test "both hashes are the ones Zig 0.16 gave for the same types" { + // Computed by Zig 0.16 with this file as it was before 0.17 made `@typeInfo` struct-of-arrays + // (fizzy `main` at 7b538aa): the 0.17 loops hash the same facts in the same order. A type of + // every kind the hashes look into, at a depth that reaches them all. + const Tag = enum(u8) { a, b, c }; + const Inner = extern struct { x: i16, y: f32 }; + const Packed = packed struct { lo: u4, hi: u12 }; + const Choice = union(enum) { none, int: i64, pair: [2]u16 }; + const Opaque = opaque {}; + const Probe = struct { + tag: Tag, + inner: Inner, + bits: Packed, + choice: Choice, + maybe: ?*const Inner, + many: [3]u16, + name: []const u8, + raw: [*]u8, + len: usize, + delta: isize, + ratio: f64, + on: bool, + hook: *const fn (state: *Opaque, n: u32, s: []const u8) callconv(.c) bool, + plain: *const fn (f32) void, + result: ?*const fn (?*anyopaque) callconv(.c) u64, + }; + // Fixed widths only: `hashAll` folds in sizes, which this keeps the same on every target. + const Fixed = extern struct { a: u8, b: u32, c: i64, d: [4]u16, e: f32, t: Tag }; + @setEvalBranchQuota(1_000_000); + try std.testing.expectEqual(@as(u64, 0x2ae9f3848d7d3d9f), comptime hashAllShape(seed, .{ Probe, Fixed }, 6)); + try std.testing.expectEqual(@as(u64, 0xc0b59f8f6303fb84), comptime hashAll(seed, .{Fixed}, 6)); +} diff --git a/sdk/src/settings.zig b/sdk/src/settings.zig index 4a4b4f77e..189a00127 100644 --- a/sdk/src/settings.zig +++ b/sdk/src/settings.zig @@ -185,10 +185,10 @@ fn intBounds(comptime IntT: type) struct { min: i64, max: i64 } { } fn enumChoices(comptime EnumT: type) []const []const u8 { - const enum_fields = @import("zig017_fields.zig").enumFields(EnumT); - comptime var names: [enum_fields.len][]const u8 = undefined; - inline for (enum_fields, 0..) |f, i| names[i] = f.name; - const frozen = names; + const names = @typeInfo(EnumT).@"enum".field_names; + comptime var out: [names.len][]const u8 = undefined; + inline for (names, 0..) |name, i| out[i] = name; + const frozen = out; return &frozen; } @@ -219,17 +219,17 @@ pub fn kindFor(comptime T: type, comptime opts: Options) Kind { }; } -fn buildSettings(comptime T: type) [std.meta.fieldNames(T).len]Setting { - const struct_fields = @import("zig017_fields.zig").fields(T); - var out: [struct_fields.len]Setting = undefined; - inline for (struct_fields, 0..) |f, i| { - requireCell(f.type, f.name, T); - const opts = f.type.setting_options; +fn buildSettings(comptime T: type) [@typeInfo(T).@"struct".field_names.len]Setting { + const info = @typeInfo(T).@"struct"; + var out: [info.field_names.len]Setting = undefined; + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { + requireCell(Cell, f_name, T); + const opts = Cell.setting_options; out[i] = .{ - .key = f.name, - .label = opts.name orelse deriveLabel(f.name), + .key = f_name, + .label = opts.name orelse deriveLabel(f_name), .description = opts.description, - .kind = kindFor(f.type.Payload, opts), + .kind = kindFor(Cell.Payload, opts), .secret = opts.secret, }; } @@ -241,27 +241,24 @@ fn buildSettings(comptime T: type) [std.meta.fieldNames(T).len]Setting { /// diff against defaults — works on this, which is why the on-disk format never learned that /// cells exist. fn Plain(comptime T: type) type { - const cell_fields = @import("zig017_fields.zig").fields(T); - var names: [cell_fields.len][:0]const u8 = undefined; - var types: [cell_fields.len]type = undefined; - var attrs: [cell_fields.len]std.lang.Type.Struct.FieldAttributes = undefined; - inline for (cell_fields, 0..) |f, i| { - requireCell(f.type, f.name, T); - const P = f.type.Payload; - const cell_default = f.defaultValue() orelse @compileError( - "sdk.settings.Schema: field '" ++ f.name ++ "' of " ++ @typeName(T) ++ + const info = @typeInfo(T).@"struct"; + var types: [info.field_names.len]type = undefined; + var attrs: [info.field_names.len]std.lang.Type.Struct.FieldAttributes = undefined; + inline for (info.field_names, info.field_types, info.field_attrs, 0..) |f_name, Cell, cell_attrs, i| { + requireCell(Cell, f_name, T); + const P = Cell.Payload; + const cell_default = cell_attrs.defaultValue(Cell) orelse @compileError( + "sdk.settings.Schema: field '" ++ f_name ++ "' of " ++ @typeName(T) ++ " needs a default value (`= .init()`) — required so settings.zon only " ++ "has to record what differs from it", ); const payload_default: P = cell_default.v; - names[i] = f.name; types[i] = P; attrs[i] = .{ .default_value_ptr = @ptrCast(&payload_default) }; } - const frozen_names = names; const frozen_types = types; const frozen_attrs = attrs; - return @Struct(.auto, null, &frozen_names, &frozen_types, &frozen_attrs); + return @Struct(.auto, null, info.field_names, &frozen_types, &frozen_attrs); } /// Type-erased read/write of a `Schema(T).Value` for fizzy's generic settings UI. @@ -312,7 +309,7 @@ pub const SettingsSchema = struct { /// non-default-only persistence (see `diffSerialize`, R12 in plans/PLUGIN_MANIFEST_PLAN.md) diffs /// every value against. pub fn Schema(comptime T: type) type { - const struct_fields = @import("zig017_fields.zig").fields(T); + const info = @typeInfo(T).@"struct"; const built_settings = buildSettings(T); return struct { @@ -327,7 +324,7 @@ pub fn Schema(comptime T: type) type { /// Payload type of field `i`. fn PayloadOf(comptime i: usize) type { - return struct_fields[i].type.Payload; + return info.field_types[i].Payload; } /// Live payload of a field, by name — cells are transparent to everything in here. @@ -337,12 +334,12 @@ pub fn Schema(comptime T: type) type { fn toPayloads(v: T) Payloads { var out: Payloads = undefined; - inline for (struct_fields) |f| @field(out, f.name) = @field(v, f.name).v; + inline for (info.field_names) |f_name| @field(out, f_name) = @field(v, f_name).v; return out; } fn fromPayloads(out: *T, p: Payloads) void { - inline for (struct_fields) |f| @field(out.*, f.name).v = @field(p, f.name); + inline for (info.field_names) |f_name| @field(out.*, f_name).v = @field(p, f_name); } pub fn load(host: anytype, id: []const u8, out: *T) void { @@ -373,9 +370,9 @@ pub fn Schema(comptime T: type) type { fn isAllDefault(value: T) bool { const p = toPayloads(value); - inline for (struct_fields) |f| { - const P = f.type.Payload; - if (!fieldEqual(P, @field(p, f.name), @field(default_payloads, f.name))) return false; + inline for (info.field_names, info.field_types) |f_name, Cell| { + const P = Cell.Payload; + if (!fieldEqual(P, @field(p, f_name), @field(default_payloads, f_name))) return false; } return true; } @@ -390,11 +387,11 @@ pub fn Schema(comptime T: type) type { var aw: std.Io.Writer.Allocating = .init(gpa); errdefer aw.deinit(); try aw.writer.writeAll(".{\n"); - inline for (struct_fields) |f| { - const P = f.type.Payload; - if (!fieldEqual(P, @field(p, f.name), @field(default_payloads, f.name))) { - try aw.writer.print(" .{f} = ", .{std.zig.fmtId(f.name)}); - try std.zon.stringify.serialize(@field(p, f.name), .{}, &aw.writer); + inline for (info.field_names, info.field_types) |f_name, Cell| { + const P = Cell.Payload; + if (!fieldEqual(P, @field(p, f_name), @field(default_payloads, f_name))) { + try aw.writer.print(" .{f} = ", .{std.zig.fmtId(f_name)}); + try std.zon.stringify.serialize(@field(p, f_name), .{}, &aw.writer); try aw.writer.writeAll(",\n"); } } @@ -440,9 +437,9 @@ pub fn Schema(comptime T: type) type { /// wholesale would hand a string literal to the allocator. Payloads that aren't strings /// or don't contain any need no release at all. fn freeOwned(gpa: std.mem.Allocator, value: T) void { - inline for (struct_fields) |f| { - if (comptime typeTagFor(f.type.Payload) == .string) { - if (fieldIsOwned(f.name, value)) gpa.free(@field(value, f.name).v); + inline for (info.field_names, info.field_types) |f_name, Cell| { + if (comptime typeTagFor(Cell.Payload) == .string) { + if (fieldIsOwned(f_name, value)) gpa.free(@field(value, f_name).v); } } } @@ -479,11 +476,11 @@ pub fn Schema(comptime T: type) type { }; // The parse's strings are in `arena`. One that is not the declared default is copied // out, so it is this schema's to free (`freeOwned`); a default stays the default. - inline for (struct_fields) |f| { - if (comptime typeTagFor(f.type.Payload) == .string) { - const parsed_str = @field(parsed, f.name); - if (parsed_str.ptr != @field(default_payloads, f.name).ptr) { - @field(parsed, f.name) = gpa.dupe(u8, parsed_str) catch |err| { + inline for (info.field_names, info.field_types) |f_name, Cell| { + if (comptime typeTagFor(Cell.Payload) == .string) { + const parsed_str = @field(parsed, f_name); + if (parsed_str.ptr != @field(default_payloads, f_name).ptr) { + @field(parsed, f_name) = gpa.dupe(u8, parsed_str) catch |err| { dvui.log.warn("sdk.settings: out of memory keeping settings: {s}", .{@errorName(err)}); return; }; @@ -500,10 +497,10 @@ pub fn Schema(comptime T: type) type { fn getBool(ptr: *anyopaque, field_index: usize) bool { const v = asValue(ptr); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { if (i == field_index) { - if (@typeInfo(f.type.Payload) != .bool) return false; - return payloadPtr(v, f.name).*; + if (@typeInfo(Cell.Payload) != .bool) return false; + return payloadPtr(v, f_name).*; } } return false; @@ -511,9 +508,9 @@ pub fn Schema(comptime T: type) type { fn setBool(ptr: *anyopaque, field_index: usize, b: bool) void { const v = asValue(ptr); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { if (i == field_index) { - if (@typeInfo(f.type.Payload) == .bool) payloadPtr(v, f.name).* = b; + if (@typeInfo(Cell.Payload) == .bool) payloadPtr(v, f_name).* = b; return; } } @@ -521,10 +518,10 @@ pub fn Schema(comptime T: type) type { fn getInt(ptr: *anyopaque, field_index: usize) i64 { const v = asValue(ptr); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { if (i == field_index) { - if (@typeInfo(f.type.Payload) != .int) return 0; - return @intCast(payloadPtr(v, f.name).*); + if (@typeInfo(Cell.Payload) != .int) return 0; + return @intCast(payloadPtr(v, f_name).*); } } return 0; @@ -532,11 +529,11 @@ pub fn Schema(comptime T: type) type { fn setInt(ptr: *anyopaque, field_index: usize, n: i64) void { const v = asValue(ptr); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { if (i == field_index) { - if (@typeInfo(f.type.Payload) == .int) { - const p = payloadPtr(v, f.name); - p.* = std.math.cast(f.type.Payload, n) orelse p.*; + if (@typeInfo(Cell.Payload) == .int) { + const p = payloadPtr(v, f_name); + p.* = std.math.cast(Cell.Payload, n) orelse p.*; } return; } @@ -545,10 +542,10 @@ pub fn Schema(comptime T: type) type { fn getFloat(ptr: *anyopaque, field_index: usize) f64 { const v = asValue(ptr); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { if (i == field_index) { - if (@typeInfo(f.type.Payload) != .float) return 0; - return @floatCast(payloadPtr(v, f.name).*); + if (@typeInfo(Cell.Payload) != .float) return 0; + return @floatCast(payloadPtr(v, f_name).*); } } return 0; @@ -556,9 +553,9 @@ pub fn Schema(comptime T: type) type { fn setFloat(ptr: *anyopaque, field_index: usize, n: f64) void { const v = asValue(ptr); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { if (i == field_index) { - if (@typeInfo(f.type.Payload) == .float) payloadPtr(v, f.name).* = @floatCast(n); + if (@typeInfo(Cell.Payload) == .float) payloadPtr(v, f_name).* = @floatCast(n); return; } } @@ -566,15 +563,15 @@ pub fn Schema(comptime T: type) type { fn getEnumIndex(ptr: *anyopaque, field_index: usize) usize { const v = asValue(ptr); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { if (i == field_index) { - const P = f.type.Payload; + const P = Cell.Payload; if (@typeInfo(P) != .@"enum") return 0; // Declaration-order position, matching `enumChoices`' `choices` list and // `setEnumIndex`'s `std.meta.tags(P)[choice_index]` — not the enum's raw // backing integer, which for an explicit-value enum like `TabSize` // (`@"2" = 2, @"4" = 4, @"8" = 8`) is out of range for `choices.len`. - const current = payloadPtr(v, f.name).*; + const current = payloadPtr(v, f_name).*; const tags = std.meta.tags(P); inline for (tags, 0..) |t, ti| { if (t == current) return ti; @@ -587,12 +584,12 @@ pub fn Schema(comptime T: type) type { fn setEnumIndex(ptr: *anyopaque, field_index: usize, choice_index: usize) void { const v = asValue(ptr); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { if (i == field_index) { - const P = f.type.Payload; + const P = Cell.Payload; if (@typeInfo(P) == .@"enum") { const tags = std.meta.tags(P); - if (choice_index < tags.len) payloadPtr(v, f.name).* = tags[choice_index]; + if (choice_index < tags.len) payloadPtr(v, f_name).* = tags[choice_index]; } return; } @@ -601,9 +598,9 @@ pub fn Schema(comptime T: type) type { fn getString(ptr: *anyopaque, field_index: usize) []const u8 { const v = asValue(ptr); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { if (i == field_index) { - if (comptime typeTagFor(f.type.Payload) == .string) return payloadPtr(v, f.name).*; + if (comptime typeTagFor(Cell.Payload) == .string) return payloadPtr(v, f_name).*; return ""; } } @@ -616,15 +613,15 @@ pub fn Schema(comptime T: type) type { fn setString(ptr: *anyopaque, field_index: usize, s: []const u8) void { const v = asValue(ptr); const gpa = runtime.allocator(); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { if (i == field_index) { - if (comptime typeTagFor(f.type.Payload) == .string) { + if (comptime typeTagFor(Cell.Payload) == .string) { const copy = gpa.dupe(u8, s) catch |err| { - dvui.log.warn("sdk.settings: failed to set '{s}': {s}", .{ f.name, @errorName(err) }); + dvui.log.warn("sdk.settings: failed to set '{s}': {s}", .{ f_name, @errorName(err) }); return; }; - if (fieldIsOwned(f.name, v.*)) gpa.free(@field(v.*, f.name).v); - payloadPtr(v, f.name).* = copy; + if (fieldIsOwned(f_name, v.*)) gpa.free(@field(v.*, f_name).v); + payloadPtr(v, f_name).* = copy; } return; } @@ -633,10 +630,10 @@ pub fn Schema(comptime T: type) type { fn getZonText(ptr: *anyopaque, field_index: usize, arena: std.mem.Allocator) []const u8 { const v = asValue(ptr); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, 0..) |f_name, i| { if (i == field_index) { var aw: std.Io.Writer.Allocating = .init(arena); - std.zon.stringify.serialize(payloadPtr(v, f.name).*, .{}, &aw.writer) catch { + std.zon.stringify.serialize(payloadPtr(v, f_name).*, .{}, &aw.writer) catch { aw.deinit(); return ""; }; @@ -652,9 +649,9 @@ pub fn Schema(comptime T: type) type { fn setZonText(ptr: *anyopaque, field_index: usize, text: []const u8) bool { const v = asValue(ptr); const gpa = runtime.allocator(); - inline for (struct_fields, 0..) |f, i| { + inline for (info.field_names, info.field_types, 0..) |f_name, Cell, i| { if (i == field_index) { - const P = f.type.Payload; + const P = Cell.Payload; const text_z = gpa.dupeSentinel(u8, text, 0) catch return false; defer gpa.free(text_z); var arena: std.heap.ArenaAllocator = .init(gpa); @@ -665,9 +662,9 @@ pub fn Schema(comptime T: type) type { // parse's arena, the schema's to free. Other payloads are plain values. if (comptime typeTagFor(P) == .string) { parsed = gpa.dupe(u8, parsed) catch return false; - if (fieldIsOwned(f.name, v.*)) gpa.free(@field(v.*, f.name).v); + if (fieldIsOwned(f_name, v.*)) gpa.free(@field(v.*, f_name).v); } - payloadPtr(v, f.name).* = parsed; + payloadPtr(v, f_name).* = parsed; return true; } } diff --git a/sdk/src/zig017_fields.zig b/sdk/src/zig017_fields.zig deleted file mode 100644 index 83fa11e32..000000000 --- a/sdk/src/zig017_fields.zig +++ /dev/null @@ -1,31 +0,0 @@ -//! ZIG017: 0.16-shaped field records rebuilt from 0.17's struct-of-arrays `@typeInfo`. -const std = @import("std"); - -pub const Field = struct { - name: [:0]const u8, - type: type, - default_value_ptr: ?*const anyopaque, - - pub inline fn defaultValue(comptime f: Field) ?f.type { - const dp: *const f.type = @ptrCast(@alignCast(f.default_value_ptr orelse return null)); - return dp.*; - } -}; - -pub fn fields(comptime T: type) []const Field { - const s = @typeInfo(T).@"struct"; - var out: [s.field_names.len]Field = undefined; - for (s.field_names, s.field_types, s.field_attrs, 0..) |n, t, a, i| out[i] = .{ .name = n, .type = t, .default_value_ptr = a.default_value_ptr }; - const final = out; - return &final; -} - -pub const EnumField = struct { name: [:0]const u8, value: comptime_int }; - -pub fn enumFields(comptime E: type) []const EnumField { - const e = @typeInfo(E).@"enum"; - var out: [e.field_names.len]EnumField = undefined; - for (e.field_names, e.field_values, 0..) |n, v, i| out[i] = .{ .name = n, .value = v }; - const final = out; - return &final; -}