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" {...@@ -1317,9 +1317,9 @@ test "EnumSet non-exhaustive" {
1317}1317}
13181318
1319pub fn EnumIndexer(comptime E: type) type {1319pub fn EnumIndexer(comptime E: type) type {
1320 // Assumes that the enum fields are sorted in ascending order (optimistic).1320 // n log n for `std.mem.sortUnstable` call below.
1321 // Unsorted enums may require the user to manually increase the quota.1321 const fields_len = @typeInfo(E).@"enum".fields.len;
1322 @setEvalBranchQuota(3 * @typeInfo(E).@"enum".fields.len + eval_branch_quota_cushion);1322 @setEvalBranchQuota(3 * fields_len * std.math.log2(@max(fields_len, 1)) + eval_branch_quota_cushion);
13231323
1324 if (!@typeInfo(E).@"enum".is_exhaustive) {1324 if (!@typeInfo(E).@"enum".is_exhaustive) {
1325 const BackingInt = @typeInfo(E).@"enum".tag_type;1325 const BackingInt = @typeInfo(E).@"enum".tag_type;
...@@ -1354,10 +1354,6 @@ pub fn EnumIndexer(comptime E: type) type {...@@ -1354,10 +1354,6 @@ pub fn EnumIndexer(comptime E: type) type {
1354 };1354 };
1355 }1355 }
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
1361 if (fields_len == 0) {1357 if (fields_len == 0) {
1362 return struct {1358 return struct {
1363 pub const Key = E;1359 pub const Key = E;
...@@ -1373,22 +1369,17 @@ pub fn EnumIndexer(comptime E: type) type {...@@ -1373,22 +1369,17 @@ pub fn EnumIndexer(comptime E: type) type {
1373 };1369 };
1374 }1370 }
13751371
1376 const min = fields[0].value;1372 var fields: [fields_len]EnumField = @typeInfo(E).@"enum".fields[0..].*;
1377 const max = fields[fields.len - 1].value;
1378
1379 const SortContext = struct {
1380 fields: []EnumField,
13811373
1382 pub fn lessThan(comptime ctx: @This(), comptime a: usize, comptime b: usize) bool {1374 std.mem.sortUnstable(EnumField, &fields, {}, struct {
1383 return ctx.fields[a].value < ctx.fields[b].value;1375 fn lessThan(ctx: void, lhs: EnumField, rhs: EnumField) bool {
1376 ctx;
1377 return lhs.value < rhs.value;
1384 }1378 }
1379 }.lessThan);
13851380
1386 pub fn swap(comptime ctx: @This(), comptime a: usize, comptime b: usize) void {1381 const min = fields[0].value;
1387 return std.mem.swap(EnumField, &ctx.fields[a], &ctx.fields[b]);1382 const max = fields[fields_len - 1].value;
1388 }
1389 };
1390 std.sort.insertionContext(0, fields_len, SortContext{ .fields = &fields });
1391
1392 if (max - min == fields.len - 1) {1383 if (max - min == fields.len - 1) {
1393 return struct {1384 return struct {
1394 pub const Key = E;1385 pub const Key = E;
...@@ -1538,6 +1529,29 @@ test "EnumIndexer empty" {...@@ -1538,6 +1529,29 @@ test "EnumIndexer empty" {
1538 try testing.expectEqual(0, Indexer.count);1529 try testing.expectEqual(0, Indexer.count);
1539}1530}
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
1541test values {1555test values {
1542 const E = enum {1556 const E = enum {
1543 X,1557 X,