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)...@@ -1267,7 +1267,7 @@ pub fn IndexedArray(comptime I: type, comptime V: type, comptime Ext: ?fn (type)
1267/// /// The key type which this indexer converts to indices1267/// /// The key type which this indexer converts to indices
1268/// pub const Key: type,1268/// pub const Key: type,
1269/// /// The number of indexes in the dense mapping1269/// /// The number of indexes in the dense mapping
1270/// pub const count: usize,1270/// pub const count: comptime_int,
1271/// /// Converts from a key to an index1271/// /// Converts from a key to an index
1272/// pub fn indexOf(Key) usize;1272/// pub fn indexOf(Key) usize;
1273/// /// Converts from an index to a key1273/// /// Converts from an index to a key
...@@ -1278,8 +1278,8 @@ pub fn ensureIndexer(comptime T: type) void {...@@ -1278,8 +1278,8 @@ pub fn ensureIndexer(comptime T: type) void {
1278 comptime {1278 comptime {
1279 if (!@hasDecl(T, "Key")) @compileError("Indexer must have decl Key: type.");1279 if (!@hasDecl(T, "Key")) @compileError("Indexer must have decl Key: type.");
1280 if (@TypeOf(T.Key) != type) @compileError("Indexer.Key must be a type.");1280 if (@TypeOf(T.Key) != type) @compileError("Indexer.Key must be a type.");
1281 if (!@hasDecl(T, "count")) @compileError("Indexer must have decl count: usize.");1281 if (!@hasDecl(T, "count")) @compileError("Indexer must have decl count: comptime_int.");
1282 if (@TypeOf(T.count) != usize) @compileError("Indexer.count must be a usize.");1282 if (@TypeOf(T.count) != comptime_int) @compileError("Indexer.count must be a comptime_int.");
1283 if (!@hasDecl(T, "indexOf")) @compileError("Indexer.indexOf must be a fn (Key) usize.");1283 if (!@hasDecl(T, "indexOf")) @compileError("Indexer.indexOf must be a fn (Key) usize.");
1284 if (@TypeOf(T.indexOf) != fn (T.Key) usize) @compileError("Indexer must have decl indexOf: fn (Key) usize.");1284 if (@TypeOf(T.indexOf) != fn (T.Key) usize) @compileError("Indexer must have decl indexOf: fn (Key) usize.");
1285 if (!@hasDecl(T, "keyForIndex")) @compileError("Indexer must have decl keyForIndex: fn (usize) Key.");1285 if (!@hasDecl(T, "keyForIndex")) @compileError("Indexer must have decl keyForIndex: fn (usize) Key.");
...@@ -1290,7 +1290,7 @@ pub fn ensureIndexer(comptime T: type) void {...@@ -1290,7 +1290,7 @@ pub fn ensureIndexer(comptime T: type) void {
1290test "std.enums.ensureIndexer" {1290test "std.enums.ensureIndexer" {
1291 ensureIndexer(struct {1291 ensureIndexer(struct {
1292 pub const Key = u32;1292 pub const Key = u32;
1293 pub const count: usize = 8;1293 pub const count: comptime_int = 8;
1294 pub fn indexOf(k: Key) usize {1294 pub fn indexOf(k: Key) usize {
1295 return @as(usize, @intCast(k));1295 return @as(usize, @intCast(k));
1296 }1296 }
...@@ -1302,7 +1302,36 @@ test "std.enums.ensureIndexer" {...@@ -1302,7 +1302,36 @@ test "std.enums.ensureIndexer" {
13021302
1303pub fn EnumIndexer(comptime E: type) type {1303pub fn EnumIndexer(comptime E: type) type {
1304 if (!@typeInfo(E).Enum.is_exhaustive) {1304 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 };
1306 }1335 }
13071336
1308 const const_fields = std.meta.fields(E);1337 const const_fields = std.meta.fields(E);
...@@ -1312,7 +1341,7 @@ pub fn EnumIndexer(comptime E: type) type {...@@ -1312,7 +1341,7 @@ pub fn EnumIndexer(comptime E: type) type {
1312 if (fields_len == 0) {1341 if (fields_len == 0) {
1313 return struct {1342 return struct {
1314 pub const Key = E;1343 pub const Key = E;
1315 pub const count: usize = 0;1344 pub const count: comptime_int = 0;
1316 pub fn indexOf(e: E) usize {1345 pub fn indexOf(e: E) usize {
1317 _ = e;1346 _ = e;
1318 unreachable;1347 unreachable;
...@@ -1343,7 +1372,7 @@ pub fn EnumIndexer(comptime E: type) type {...@@ -1343,7 +1372,7 @@ pub fn EnumIndexer(comptime E: type) type {
1343 if (max - min == fields.len - 1) {1372 if (max - min == fields.len - 1) {
1344 return struct {1373 return struct {
1345 pub const Key = E;1374 pub const Key = E;
1346 pub const count = fields_len;1375 pub const count: comptime_int = fields_len;
1347 pub fn indexOf(e: E) usize {1376 pub fn indexOf(e: E) usize {
1348 return @as(usize, @intCast(@intFromEnum(e) - min));1377 return @as(usize, @intCast(@intFromEnum(e) - min));
1349 }1378 }
...@@ -1361,7 +1390,7 @@ pub fn EnumIndexer(comptime E: type) type {...@@ -1361,7 +1390,7 @@ pub fn EnumIndexer(comptime E: type) type {
13611390
1362 return struct {1391 return struct {
1363 pub const Key = E;1392 pub const Key = E;
1364 pub const count = fields_len;1393 pub const count: comptime_int = fields_len;
1365 pub fn indexOf(e: E) usize {1394 pub fn indexOf(e: E) usize {
1366 for (keys, 0..) |k, i| {1395 for (keys, 0..) |k, i| {
1367 if (k == e) return i;1396 if (k == e) return i;
...@@ -1374,12 +1403,61 @@ pub fn EnumIndexer(comptime E: type) type {...@@ -1374,12 +1403,61 @@ pub fn EnumIndexer(comptime E: type) type {
1374 };1403 };
1375}1404}
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
1377test "std.enums.EnumIndexer dense zeroed" {1455test "std.enums.EnumIndexer dense zeroed" {
1378 const E = enum(u2) { b = 1, a = 0, c = 2 };1456 const E = enum(u2) { b = 1, a = 0, c = 2 };
1379 const Indexer = EnumIndexer(E);1457 const Indexer = EnumIndexer(E);
1380 ensureIndexer(Indexer);1458 ensureIndexer(Indexer);
1381 try testing.expectEqual(E, Indexer.Key);1459 try testing.expectEqual(E, Indexer.Key);
1382 try testing.expectEqual(@as(usize, 3), Indexer.count);1460 try testing.expectEqual(3, Indexer.count);
13831461
1384 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));1462 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));
1385 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));1463 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));
...@@ -1395,7 +1473,7 @@ test "std.enums.EnumIndexer dense positive" {...@@ -1395,7 +1473,7 @@ test "std.enums.EnumIndexer dense positive" {
1395 const Indexer = EnumIndexer(E);1473 const Indexer = EnumIndexer(E);
1396 ensureIndexer(Indexer);1474 ensureIndexer(Indexer);
1397 try testing.expectEqual(E, Indexer.Key);1475 try testing.expectEqual(E, Indexer.Key);
1398 try testing.expectEqual(@as(usize, 3), Indexer.count);1476 try testing.expectEqual(3, Indexer.count);
13991477
1400 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));1478 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));
1401 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));1479 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));
...@@ -1411,7 +1489,7 @@ test "std.enums.EnumIndexer dense negative" {...@@ -1411,7 +1489,7 @@ test "std.enums.EnumIndexer dense negative" {
1411 const Indexer = EnumIndexer(E);1489 const Indexer = EnumIndexer(E);
1412 ensureIndexer(Indexer);1490 ensureIndexer(Indexer);
1413 try testing.expectEqual(E, Indexer.Key);1491 try testing.expectEqual(E, Indexer.Key);
1414 try testing.expectEqual(@as(usize, 3), Indexer.count);1492 try testing.expectEqual(3, Indexer.count);
14151493
1416 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));1494 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));
1417 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));1495 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));
...@@ -1427,7 +1505,7 @@ test "std.enums.EnumIndexer sparse" {...@@ -1427,7 +1505,7 @@ test "std.enums.EnumIndexer sparse" {
1427 const Indexer = EnumIndexer(E);1505 const Indexer = EnumIndexer(E);
1428 ensureIndexer(Indexer);1506 ensureIndexer(Indexer);
1429 try testing.expectEqual(E, Indexer.Key);1507 try testing.expectEqual(E, Indexer.Key);
1430 try testing.expectEqual(@as(usize, 3), Indexer.count);1508 try testing.expectEqual(3, Indexer.count);
14311509
1432 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));1510 try testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a));
1433 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));1511 try testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b));
...@@ -1443,5 +1521,5 @@ test "std.enums.EnumIndexer empty" {...@@ -1443,5 +1521,5 @@ test "std.enums.EnumIndexer empty" {
1443 const Indexer = EnumIndexer(E);1521 const Indexer = EnumIndexer(E);
1444 ensureIndexer(Indexer);1522 ensureIndexer(Indexer);
1445 try testing.expectEqual(E, Indexer.Key);1523 try testing.expectEqual(E, Indexer.Key);
1446 try testing.expectEqual(@as(usize, 0), Indexer.count);1524 try testing.expectEqual(0, Indexer.count);
1447}1525}