authorgravatar for bratishkaerik@getgoogleoff.meEric Joldasov <bratishkaerik@getgoogleoff.me> 2023-11-17 23:36:44+06:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2023-11-18 16:25:55+02:00
log4e212f16506b88d934d9a0c559fa6b2e0e88574e
tree5593aa70f985b759e7633e08d3e4bcd2642513f8
parent7b99189f191df47c400693f324128d26804ee84d

std.enums: allow non-exhaustive enums in EnumIndexer and make `count` comptime_int instead of usize

Seems like this restriction was actual when Ziglang had extern enums, but now it's not neccessary and can be lifted. It was present since original PR which introduced std.enums, https://www.github.com/ziglang/zig/pull/8171. See also: https://ziggit.dev/t/catching-invalid-enum-value-errors/2206/11 * Make `count` comptime_int instead of usize With previous type, creating EnumIndexer for enum(usize) and enum(isize) would cause compile error since `count` could not store maxInt(usize) + 1. Now it can store it and reflects len field from std.builtin.Type.Array (most common use case of count field inside std.enums functions is creating arrays). Signed-off-by: Eric Joldasov <bratishkaerik@getgoogleoff.me>

1 files changed, 91 insertions(+), 13 deletions(-)

lib/std/enums.zig+91-13
......@@ -1267,7 +1267,7 @@ pub fn IndexedArray(comptime I: type, comptime V: type, comptime Ext: ?fn (type)
12671267/// /// The key type which this indexer converts to indices
12681268/// pub const Key: type,
12691269/// /// The number of indexes in the dense mapping
1270/// pub const count: usize,
1270/// pub const count: comptime_int,
12711271/// /// Converts from a key to an index
12721272/// pub fn indexOf(Key) usize;
12731273/// /// Converts from an index to a key
......@@ -1278,8 +1278,8 @@ pub fn ensureIndexer(comptime T: type) void {
12781278 comptime {
12791279 if (!@hasDecl(T, "Key")) @compileError("Indexer must have decl Key: type.");
12801280 if (@TypeOf(T.Key) != type) @compileError("Indexer.Key must be a type.");
1281 if (!@hasDecl(T, "count")) @compileError("Indexer must have decl count: usize.");
1282 if (@TypeOf(T.count) != usize) @compileError("Indexer.count must be a usize.");
1281 if (!@hasDecl(T, "count")) @compileError("Indexer must have decl count: comptime_int.");
1282 if (@TypeOf(T.count) != comptime_int) @compileError("Indexer.count must be a comptime_int.");
12831283 if (!@hasDecl(T, "indexOf")) @compileError("Indexer.indexOf must be a fn (Key) usize.");
12841284 if (@TypeOf(T.indexOf) != fn (T.Key) usize) @compileError("Indexer must have decl indexOf: fn (Key) usize.");
12851285 if (!@hasDecl(T, "keyForIndex")) @compileError("Indexer must have decl keyForIndex: fn (usize) Key.");
......@@ -1290,7 +1290,7 @@ pub fn ensureIndexer(comptime T: type) void {
12901290test "std.enums.ensureIndexer" {
12911291 ensureIndexer(struct {
12921292 pub const Key = u32;
1293 pub const count: usize = 8;
1293 pub const count: comptime_int = 8;
12941294 pub fn indexOf(k: Key) usize {
12951295 return @as(usize, @intCast(k));
12961296 }
......@@ -1302,7 +1302,36 @@ test "std.enums.ensureIndexer" {
13021302
13031303pub fn EnumIndexer(comptime E: type) type {
13041304 if (!@typeInfo(E).Enum.is_exhaustive) {
1305 @compileError("Cannot create an enum indexer for a non-exhaustive enum.");
1305 const BackingInt = @typeInfo(E).Enum.tag_type;
1306 if (@bitSizeOf(BackingInt) > @bitSizeOf(usize))
1307 @compileError("Cannot create an enum indexer for a given non-exhaustive enum, tag_type is larger than usize.");
1308
1309 return struct {
1310 pub const Key: type = E;
1311
1312 const backing_int_sign = @typeInfo(BackingInt).Int.signedness;
1313 const min_value = std.math.minInt(BackingInt);
1314 const max_value = std.math.maxInt(BackingInt);
1315
1316 const RangeType = std.meta.Int(.unsigned, @bitSizeOf(BackingInt));
1317 pub const count: comptime_int = std.math.maxInt(RangeType) + 1;
1318
1319 pub fn indexOf(e: E) usize {
1320 if (backing_int_sign == .unsigned)
1321 return @intFromEnum(e);
1322
1323 return if (@intFromEnum(e) < 0)
1324 @intCast(@intFromEnum(e) - min_value)
1325 else
1326 @as(RangeType, -min_value) + @as(RangeType, @intCast(@intFromEnum(e)));
1327 }
1328 pub fn keyForIndex(i: usize) E {
1329 if (backing_int_sign == .unsigned)
1330 return @enumFromInt(i);
1331
1332 return @enumFromInt(@as(std.meta.Int(.signed, @bitSizeOf(RangeType) + 1), @intCast(i)) + min_value);
1333 }
1334 };
13061335 }
13071336
13081337 const const_fields = std.meta.fields(E);
......@@ -1312,7 +1341,7 @@ pub fn EnumIndexer(comptime E: type) type {
13121341 if (fields_len == 0) {
13131342 return struct {
13141343 pub const Key = E;
1315 pub const count: usize = 0;
1344 pub const count: comptime_int = 0;
13161345 pub fn indexOf(e: E) usize {
13171346 _ = e;
13181347 unreachable;
......@@ -1343,7 +1372,7 @@ pub fn EnumIndexer(comptime E: type) type {
13431372 if (max - min == fields.len - 1) {
13441373 return struct {
13451374 pub const Key = E;
1346 pub const count = fields_len;
1375 pub const count: comptime_int = fields_len;
13471376 pub fn indexOf(e: E) usize {
13481377 return @as(usize, @intCast(@intFromEnum(e) - min));
13491378 }
......@@ -1361,7 +1390,7 @@ pub fn EnumIndexer(comptime E: type) type {
13611390
13621391 return struct {
13631392 pub const Key = E;
1364 pub const count = fields_len;
1393 pub const count: comptime_int = fields_len;
13651394 pub fn indexOf(e: E) usize {
13661395 for (keys, 0..) |k, i| {
13671396 if (k == e) return i;
......@@ -1374,12 +1403,61 @@ pub fn EnumIndexer(comptime E: type) type {
13741403 };
13751404}
13761405
1406test "EnumIndexer non-exhaustive" {
1407 const backing_ints = [_]type{
1408 i1,
1409 i2,
1410 i3,
1411 i4,
1412 i8,
1413 i16,
1414 std.meta.Int(.signed, @bitSizeOf(isize) - 1),
1415 isize,
1416 u1,
1417 u2,
1418 u3,
1419 u4,
1420 u16,
1421 std.meta.Int(.unsigned, @bitSizeOf(usize) - 1),
1422 usize,
1423 };
1424 inline for (backing_ints) |BackingInt| {
1425 const E = enum(BackingInt) {
1426 number_zero_tag = 0,
1427 _,
1428 };
1429 const Indexer = EnumIndexer(E);
1430 ensureIndexer(Indexer);
1431
1432 const min_tag: E = @enumFromInt(std.math.minInt(BackingInt));
1433 const max_tag: E = @enumFromInt(std.math.maxInt(BackingInt));
1434
1435 const RangedType = std.meta.Int(.unsigned, @bitSizeOf(BackingInt));
1436 const max_index: comptime_int = std.math.maxInt(RangedType);
1437 const number_zero_tag_index: usize = switch (@typeInfo(BackingInt).Int.signedness) {
1438 .unsigned => 0,
1439 .signed => std.math.divCeil(comptime_int, max_index, 2) catch unreachable,
1440 };
1441
1442 try testing.expectEqual(E, Indexer.Key);
1443 try testing.expectEqual(max_index + 1, Indexer.count);
1444
1445 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(min_tag));
1446 try testing.expectEqual(number_zero_tag_index, Indexer.indexOf(E.number_zero_tag));
1447 try testing.expectEqual(@as(usize, max_index), Indexer.indexOf(max_tag));
1448
1449 try testing.expectEqual(min_tag, Indexer.keyForIndex(0));
1450 try testing.expectEqual(E.number_zero_tag, Indexer.keyForIndex(number_zero_tag_index));
1451 try testing.expectEqual(max_tag, Indexer.keyForIndex(max_index));
1452 }
1453}
1454
13771455test "std.enums.EnumIndexer dense zeroed" {
13781456 const E = enum(u2) { b = 1, a = 0, c = 2 };
13791457 const Indexer = EnumIndexer(E);
13801458 ensureIndexer(Indexer);
13811459 try testing.expectEqual(E, Indexer.Key);
1382 try testing.expectEqual(@as(usize, 3), Indexer.count);
1460 try testing.expectEqual(3, Indexer.count);
13831461
13841462 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));
13851463 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));
......@@ -1395,7 +1473,7 @@ test "std.enums.EnumIndexer dense positive" {
13951473 const Indexer = EnumIndexer(E);
13961474 ensureIndexer(Indexer);
13971475 try testing.expectEqual(E, Indexer.Key);
1398 try testing.expectEqual(@as(usize, 3), Indexer.count);
1476 try testing.expectEqual(3, Indexer.count);
13991477
14001478 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));
14011479 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));
......@@ -1411,7 +1489,7 @@ test "std.enums.EnumIndexer dense negative" {
14111489 const Indexer = EnumIndexer(E);
14121490 ensureIndexer(Indexer);
14131491 try testing.expectEqual(E, Indexer.Key);
1414 try testing.expectEqual(@as(usize, 3), Indexer.count);
1492 try testing.expectEqual(3, Indexer.count);
14151493
14161494 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));
14171495 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));
......@@ -1427,7 +1505,7 @@ test "std.enums.EnumIndexer sparse" {
14271505 const Indexer = EnumIndexer(E);
14281506 ensureIndexer(Indexer);
14291507 try testing.expectEqual(E, Indexer.Key);
1430 try testing.expectEqual(@as(usize, 3), Indexer.count);
1508 try testing.expectEqual(3, Indexer.count);
14311509
14321510 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));
14331511 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));
......@@ -1443,5 +1521,5 @@ test "std.enums.EnumIndexer empty" {
14431521 const Indexer = EnumIndexer(E);
14441522 ensureIndexer(Indexer);
14451523 try testing.expectEqual(E, Indexer.Key);
1446 try testing.expectEqual(@as(usize, 0), Indexer.count);
1524 try testing.expectEqual(0, Indexer.count);
14471525}