authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-07-31 10:10:40+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-07-31 22:10:22+01:00
logac1e73e249f8ce06bc7c89d2bdc4359b0399236c
tree8fb4f2d0d148b6da972585034407e9e698039817
parent04d7b491b436f67e51a17ba6fb49de862fa7bb8c

std.enums: fix `EnumIndexer` branch quota

It's quite silly to have this override which nonetheless makes assumptions about the input type. Encode the actual complexity of the sort. Also, simplify the sorting logic, and fix a bug (grab min and max *after* the sort, not *before*!)

1 files changed, 34 insertions(+), 20 deletions(-)

lib/std/enums.zig+34-20
......@@ -1317,9 +1317,9 @@ test "EnumSet non-exhaustive" {
13171317}
13181318
13191319pub fn EnumIndexer(comptime E: type) type {
1320 // Assumes that the enum fields are sorted in ascending order (optimistic).
1321 // Unsorted enums may require the user to manually increase the quota.
1322 @setEvalBranchQuota(3 * @typeInfo(E).@"enum".fields.len + eval_branch_quota_cushion);
1320 // n log n for `std.mem.sortUnstable` call below.
1321 const fields_len = @typeInfo(E).@"enum".fields.len;
1322 @setEvalBranchQuota(3 * fields_len * std.math.log2(@max(fields_len, 1)) + eval_branch_quota_cushion);
13231323
13241324 if (!@typeInfo(E).@"enum".is_exhaustive) {
13251325 const BackingInt = @typeInfo(E).@"enum".tag_type;
......@@ -1354,10 +1354,6 @@ pub fn EnumIndexer(comptime E: type) type {
13541354 };
13551355 }
13561356
1357 const const_fields = @typeInfo(E).@"enum".fields;
1358 var fields = const_fields[0..const_fields.len].*;
1359 const fields_len = fields.len;
1360
13611357 if (fields_len == 0) {
13621358 return struct {
13631359 pub const Key = E;
......@@ -1373,22 +1369,17 @@ pub fn EnumIndexer(comptime E: type) type {
13731369 };
13741370 }
13751371
1376 const min = fields[0].value;
1377 const max = fields[fields.len - 1].value;
1378
1379 const SortContext = struct {
1380 fields: []EnumField,
1372 var fields: [fields_len]EnumField = @typeInfo(E).@"enum".fields[0..].*;
13811373
1382 pub fn lessThan(comptime ctx: @This(), comptime a: usize, comptime b: usize) bool {
1383 return ctx.fields[a].value < ctx.fields[b].value;
1374 std.mem.sortUnstable(EnumField, &fields, {}, struct {
1375 fn lessThan(ctx: void, lhs: EnumField, rhs: EnumField) bool {
1376 ctx;
1377 return lhs.value < rhs.value;
13841378 }
1379 }.lessThan);
13851380
1386 pub fn swap(comptime ctx: @This(), comptime a: usize, comptime b: usize) void {
1387 return std.mem.swap(EnumField, &ctx.fields[a], &ctx.fields[b]);
1388 }
1389 };
1390 std.sort.insertionContext(0, fields_len, SortContext{ .fields = &fields });
1391
1381 const min = fields[0].value;
1382 const max = fields[fields_len - 1].value;
13921383 if (max - min == fields.len - 1) {
13931384 return struct {
13941385 pub const Key = E;
......@@ -1538,6 +1529,29 @@ test "EnumIndexer empty" {
15381529 try testing.expectEqual(0, Indexer.count);
15391530}
15401531
1532test "EnumIndexer large dense unsorted" {
1533 @setEvalBranchQuota(500_000); // many `comptimePrint`s
1534 // Make an enum with 500 fields with values in *descending* order.
1535 const E = @Type(.{ .@"enum" = .{
1536 .tag_type = u32,
1537 .fields = comptime fields: {
1538 var fields: [500]EnumField = undefined;
1539 for (&fields, 0..) |*f, i| f.* = .{
1540 .name = std.fmt.comptimePrint("f{d}", .{i}),
1541 .value = 500 - i,
1542 };
1543 break :fields &fields;
1544 },
1545 .decls = &.{},
1546 .is_exhaustive = true,
1547 } });
1548 const Indexer = EnumIndexer(E);
1549 try testing.expectEqual(E.f0, Indexer.keyForIndex(499));
1550 try testing.expectEqual(E.f499, Indexer.keyForIndex(0));
1551 try testing.expectEqual(499, Indexer.indexOf(.f0));
1552 try testing.expectEqual(0, Indexer.indexOf(.f499));
1553}
1554
15411555test values {
15421556 const E = enum {
15431557 X,