diff options
Diffstat (limited to 'vendor/libghostty-vt/src/lib/enum.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/lib/enum.zig | 99 |
1 files changed, 56 insertions, 43 deletions
diff --git a/vendor/libghostty-vt/src/lib/enum.zig b/vendor/libghostty-vt/src/lib/enum.zig index 76e05fa9..3c7a3a41 100644 --- a/vendor/libghostty-vt/src/lib/enum.zig +++ b/vendor/libghostty-vt/src/lib/enum.zig @@ -5,58 +5,50 @@ const Target = @import("target.zig").Target; /// if we're targeting C, otherwise a Zig enum with smallest possible /// backing type. /// -/// In all cases, the enum keys will be created in the order given. -/// For C ABI, this means that the order MUST NOT be changed in order -/// to preserve ABI compatibility. You can set a key to null to -/// remove it from the Zig enum while keeping the "hole" in the C enum -/// to preserve ABI compatibility. +/// In all cases, each enum value is its index in `keys`. The order MUST NOT +/// be changed when the integer values are part of an ABI or serialized format. +/// A null key removes that field while preserving its integer hole. /// /// C detection is up to the caller, since there are multiple ways /// to do that. We rely on the `target` parameter to determine whether we /// should create a C compatible enum or a Zig enum. /// -/// For the Zig enum, the enum value is not guaranteed to be stable, so -/// it shouldn't be relied for things like serialization. +/// C enums use `c_int`. Zig enums use the smallest unsigned integer that can +/// represent every key index, including holes. pub fn Enum( target: Target, keys: []const ?[:0]const u8, ) type { - var fields: [keys.len]std.builtin.Type.EnumField = undefined; - var fields_i: usize = 0; - var holes: usize = 0; - for (keys) |key_| { - const key: [:0]const u8 = key_ orelse { - switch (target) { - // For Zig we don't track holes because the enum value - // isn't guaranteed to be stable and we want to use the - // smallest possible backing type. - .zig => {}, - - // For C we must track holes to preserve ABI compatibility - // with subsequent values. - .c => holes += 1, + var names_raw: [keys.len][]const u8 = undefined; + var values_raw: [keys.len]comptime_int = undefined; + const names_actual, const values_actual = kv: { + // Remove null fields while preserving their positions as integer holes. + var to: comptime_int = 0; + for (0..keys.len) |from| { + if (keys[from]) |key| { + names_raw[to] = key; + values_raw[to] = from; + to += 1; } - continue; - }; + } - fields[fields_i] = .{ - .name = key, - .value = fields_i + holes, - }; - fields_i += 1; - } + break :kv .{ names_raw[0..to], values_raw[0..to] }; + }; - // Assigned to var so that the type name is nicer in stack traces. - const Result = @Type(.{ .@"enum" = .{ - .tag_type = switch (target) { - .c => c_int, - .zig => std.math.IntFittingRange(0, fields_i - 1), - }, - .fields = fields[0..fields_i], - .decls = &.{}, - .is_exhaustive = true, - } }); - return Result; + const TagInt = switch (target) { + .c => c_int, + .zig => std.math.IntFittingRange(0, keys.len - 1), + }; + + return @Enum(TagInt, .exhaustive, names_actual, &(values: { + // We have to transform our comptime_int values into the actual int + // we're creating the enum as. + var result_int_values: [values_actual.len]TagInt = undefined; + for (0..values_actual.len) |idx| { + result_int_values[idx] = values_actual[idx]; + } + break :values result_int_values; + })); } test "zig" { @@ -73,7 +65,7 @@ test "c" { try testing.expectEqual(c_int, info.tag_type); } -test "abi by removing a key" { +test "stable values when removing a key" { const testing = std.testing; // C { @@ -88,10 +80,28 @@ test "abi by removing a key" { const T = Enum(.zig, &.{ "a", "b", null, "d" }); const info = @typeInfo(T).@"enum"; try testing.expectEqual(u2, info.tag_type); - try testing.expectEqual(2, @intFromEnum(T.d)); + try testing.expectEqual(3, @intFromEnum(T.d)); } } +test "zig backing integer includes trailing holes" { + const testing = std.testing; + const T = Enum(.zig, &.{ "a", null, null, null, null }); + const info = @typeInfo(T).@"enum"; + try testing.expectEqual(u3, info.tag_type); + try testing.expectEqual(0, @intFromEnum(T.a)); +} + +test "zig values remain stable across multiple holes" { + const testing = std.testing; + const T = Enum(.zig, &.{ null, "b", null, "d", null, "f" }); + const info = @typeInfo(T).@"enum"; + try testing.expectEqual(u3, info.tag_type); + try testing.expectEqual(1, @intFromEnum(T.b)); + try testing.expectEqual(3, @intFromEnum(T.d)); + try testing.expectEqual(5, @intFromEnum(T.f)); +} + /// Verify that for every key in enum T, there is a matching declaration in /// `ghostty.h` with the correct value. This should only ever be called inside a `test` /// because the `ghostty.h` module is only available then. @@ -125,7 +135,10 @@ pub fn checkGhosttyHEnum( if (@hasDecl(c, expected_name)) { std.testing.expectEqual(field.value, @field(c, expected_name)) catch |e| { - std.log.err(@typeName(T) ++ " key " ++ field.name ++ " does not have the same backing int as " ++ expected_name, .{}); + std.log.err( + "{s} key {s} does not have the same backing int as " ++ expected_name, + .{ @typeName(T), field.name }, + ); return e; }; |
