diff --git a/lib/std/bit_set.zig b/lib/std/bit_set.zig index 29ad0d7963c9..80cdd5c79c09 100644 --- a/lib/std/bit_set.zig +++ b/lib/std/bit_set.zig @@ -176,7 +176,7 @@ pub fn IntegerBitSet(comptime size: u16) type { /// The default options (.{}) will iterate indices of set bits in /// ascending order. Modifications to the underlying bit set may /// or may not be observed by the iterator. - pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options.direction) { + pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options) { return .{ .bits_remain = switch (options.kind) { .set => self.mask, @@ -185,7 +185,11 @@ pub fn IntegerBitSet(comptime size: u16) type { }; } - fn Iterator(comptime direction: IteratorOptions.Direction) type { + pub fn Iterator(comptime options: IteratorOptions) type { + return SingleWordIterator(options.direction); + } + + fn SingleWordIterator(comptime direction: IteratorOptions.Direction) type { return struct { const IterSelf = @This(); // all bits which have not yet been iterated over @@ -425,8 +429,12 @@ pub fn ArrayBitSet(comptime MaskIntType: type, comptime size: usize) type { /// The default options (.{}) will iterate indices of set bits in /// ascending order. Modifications to the underlying bit set may /// or may not be observed by the iterator. - pub fn iterator(self: *const Self, comptime options: IteratorOptions) BitSetIterator(MaskInt, options) { - return BitSetIterator(MaskInt, options).init(&self.masks, last_item_mask); + pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options) { + return Iterator(options).init(&self.masks, last_item_mask); + } + + pub fn Iterator(comptime options: IteratorOptions) type { + return BitSetIterator(MaskInt, options); } fn maskBit(index: usize) MaskInt { @@ -700,11 +708,15 @@ pub const DynamicBitSetUnmanaged = struct { /// ascending order. Modifications to the underlying bit set may /// or may not be observed by the iterator. Resizing the underlying /// bit set invalidates the iterator. - pub fn iterator(self: *const Self, comptime options: IteratorOptions) BitSetIterator(MaskInt, options) { + pub fn iterator(self: *const Self, comptime options: IteratorOptions) Iterator(options) { const num_masks = numMasks(self.bit_length); const padding_bits = num_masks * @bitSizeOf(MaskInt) - self.bit_length; const last_item_mask = (~@as(MaskInt, 0)) >> @intCast(ShiftInt, padding_bits); - return BitSetIterator(MaskInt, options).init(self.masks[0..num_masks], last_item_mask); + return Iterator(options).init(self.masks[0..num_masks], last_item_mask); + } + + pub fn Iterator(comptime options: IteratorOptions) type { + return BitSetIterator(MaskInt, options); } fn maskBit(index: usize) MaskInt { @@ -858,9 +870,11 @@ pub const DynamicBitSet = struct { /// ascending order. Modifications to the underlying bit set may /// or may not be observed by the iterator. Resizing the underlying /// bit set invalidates the iterator. - pub fn iterator(self: *Self, comptime options: IteratorOptions) BitSetIterator(MaskInt, options) { + pub fn iterator(self: *Self, comptime options: IteratorOptions) Iterator(options) { return self.unmanaged.iterator(options); } + + pub const Iterator = DynamicBitSetUnmanaged.Iterator; }; /// Options for configuring an iterator over a bit set diff --git a/lib/std/enums.zig b/lib/std/enums.zig new file mode 100644 index 000000000000..bddda38c9f95 --- /dev/null +++ b/lib/std/enums.zig @@ -0,0 +1,1281 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! This module contains utilities and data structures for working with enums. + +const std = @import("std.zig"); +const assert = std.debug.assert; +const testing = std.testing; +const EnumField = std.builtin.TypeInfo.EnumField; + +/// Returns a struct with a field matching each unique named enum element. +/// If the enum is extern and has multiple names for the same value, only +/// the first name is used. Each field is of type Data and has the provided +/// default, which may be undefined. +pub fn EnumFieldStruct(comptime E: type, comptime Data: type, comptime field_default: ?Data) type { + const StructField = std.builtin.TypeInfo.StructField; + var fields: []const StructField = &[_]StructField{}; + for (uniqueFields(E)) |field, i| { + fields = fields ++ &[_]StructField{.{ + .name = field.name, + .field_type = Data, + .default_value = field_default, + .is_comptime = false, + .alignment = if (@sizeOf(Data) > 0) @alignOf(Data) else 0, + }}; + } + return @Type(.{ .Struct = .{ + .layout = .Auto, + .fields = fields, + .decls = &[_]std.builtin.TypeInfo.Declaration{}, + .is_tuple = false, + }}); +} + +/// Looks up the supplied fields in the given enum type. +/// Uses only the field names, field values are ignored. +/// The result array is in the same order as the input. +pub fn valuesFromFields(comptime E: type, comptime fields: []const EnumField) []const E { + comptime { + var result: [fields.len]E = undefined; + for (fields) |f, i| { + result[i] = @field(E, f.name); + } + return &result; + } +} + +test "std.enums.valuesFromFields" { + const E = extern enum { a, b, c, d = 0 }; + const fields = valuesFromFields(E, &[_]EnumField{ + .{ .name = "b", .value = undefined }, + .{ .name = "a", .value = undefined }, + .{ .name = "a", .value = undefined }, + .{ .name = "d", .value = undefined }, + }); + testing.expectEqual(E.b, fields[0]); + testing.expectEqual(E.a, fields[1]); + testing.expectEqual(E.d, fields[2]); // a == d + testing.expectEqual(E.d, fields[3]); +} + +/// Returns the set of all named values in the given enum, in +/// declaration order. +pub fn values(comptime E: type) []const E { + return comptime valuesFromFields(E, @typeInfo(E).Enum.fields); +} + +test "std.enum.values" { + const E = extern enum { a, b, c, d = 0 }; + testing.expectEqualSlices(E, &.{.a, .b, .c, .d}, values(E)); +} + +/// Returns the set of all unique named values in the given enum, in +/// declaration order. For repeated values in extern enums, only the +/// first name for each value is included. +pub fn uniqueValues(comptime E: type) []const E { + return comptime valuesFromFields(E, uniqueFields(E)); +} + +test "std.enum.uniqueValues" { + const E = extern enum { a, b, c, d = 0, e, f = 3 }; + testing.expectEqualSlices(E, &.{.a, .b, .c, .f}, uniqueValues(E)); + + const F = enum { a, b, c }; + testing.expectEqualSlices(F, &.{.a, .b, .c}, uniqueValues(F)); +} + +/// Returns the set of all unique field values in the given enum, in +/// declaration order. For repeated values in extern enums, only the +/// first name for each value is included. +pub fn uniqueFields(comptime E: type) []const EnumField { + comptime { + const info = @typeInfo(E).Enum; + const raw_fields = info.fields; + // Only extern enums can contain duplicates, + // so fast path other types. + if (info.layout != .Extern) { + return raw_fields; + } + + var unique_fields: []const EnumField = &[_]EnumField{}; + outer: + for (raw_fields) |candidate| { + for (unique_fields) |u| { + if (u.value == candidate.value) + continue :outer; + } + unique_fields = unique_fields ++ &[_]EnumField{candidate}; + } + + return unique_fields; + } +} + +/// Determines the length of a direct-mapped enum array, indexed by +/// @intCast(usize, @enumToInt(enum_value)). The enum must be exhaustive. +/// If the enum contains any fields with values that cannot be represented +/// by usize, a compile error is issued. The max_unused_slots parameter limits +/// the total number of items which have no matching enum key (holes in the enum +/// numbering). So for example, if an enum has values 1, 2, 5, and 6, max_unused_slots +/// must be at least 3, to allow unused slots 0, 3, and 4. +fn directEnumArrayLen(comptime E: type, comptime max_unused_slots: comptime_int) comptime_int { + const info = @typeInfo(E).Enum; + if (!info.is_exhaustive) { + @compileError("Cannot create direct array of non-exhaustive enum "++@typeName(E)); + } + + var max_value: comptime_int = -1; + const max_usize: comptime_int = ~@as(usize, 0); + const fields = uniqueFields(E); + for (fields) |f| { + if (f.value < 0) { + @compileError("Cannot create a direct enum array for "++@typeName(E)++", field ."++f.name++" has a negative value."); + } + if (f.value > max_value) { + if (f.value > max_usize) { + @compileError("Cannot create a direct enum array for "++@typeName(E)++", field ."++f.name++" is larger than the max value of usize."); + } + max_value = f.value; + } + } + + const unused_slots = max_value + 1 - fields.len; + if (unused_slots > max_unused_slots) { + const unused_str = std.fmt.comptimePrint("{d}", .{unused_slots}); + const allowed_str = std.fmt.comptimePrint("{d}", .{max_unused_slots}); + @compileError("Cannot create a direct enum array for "++@typeName(E)++". It would have "++unused_str++" unused slots, but only "++allowed_str++" are allowed."); + } + + return max_value + 1; +} + +/// Initializes an array of Data which can be indexed by +/// @intCast(usize, @enumToInt(enum_value)). The enum must be exhaustive. +/// If the enum contains any fields with values that cannot be represented +/// by usize, a compile error is issued. The max_unused_slots parameter limits +/// the total number of items which have no matching enum key (holes in the enum +/// numbering). So for example, if an enum has values 1, 2, 5, and 6, max_unused_slots +/// must be at least 3, to allow unused slots 0, 3, and 4. +/// The init_values parameter must be a struct with field names that match the enum values. +/// If the enum has multiple fields with the same value, the name of the first one must +/// be used. +pub fn directEnumArray( + comptime E: type, + comptime Data: type, + comptime max_unused_slots: comptime_int, + init_values: EnumFieldStruct(E, Data, null), +) [directEnumArrayLen(E, max_unused_slots)]Data { + return directEnumArrayDefault(E, Data, null, max_unused_slots, init_values); +} + +test "std.enums.directEnumArray" { + const E = enum(i4) { a = 4, b = 6, c = 2 }; + var runtime_false: bool = false; + const array = directEnumArray(E, bool, 4, .{ + .a = true, + .b = runtime_false, + .c = true, + }); + + testing.expectEqual([7]bool, @TypeOf(array)); + testing.expectEqual(true, array[4]); + testing.expectEqual(false, array[6]); + testing.expectEqual(true, array[2]); +} + +/// Initializes an array of Data which can be indexed by +/// @intCast(usize, @enumToInt(enum_value)). The enum must be exhaustive. +/// If the enum contains any fields with values that cannot be represented +/// by usize, a compile error is issued. The max_unused_slots parameter limits +/// the total number of items which have no matching enum key (holes in the enum +/// numbering). So for example, if an enum has values 1, 2, 5, and 6, max_unused_slots +/// must be at least 3, to allow unused slots 0, 3, and 4. +/// The init_values parameter must be a struct with field names that match the enum values. +/// If the enum has multiple fields with the same value, the name of the first one must +/// be used. +pub fn directEnumArrayDefault( + comptime E: type, + comptime Data: type, + comptime default: ?Data, + comptime max_unused_slots: comptime_int, + init_values: EnumFieldStruct(E, Data, default), +) [directEnumArrayLen(E, max_unused_slots)]Data { + const len = comptime directEnumArrayLen(E, max_unused_slots); + var result: [len]Data = if (default) |d| [_]Data{d} ** len else undefined; + inline for (@typeInfo(@TypeOf(init_values)).Struct.fields) |f, i| { + const enum_value = @field(E, f.name); + const index = @intCast(usize, @enumToInt(enum_value)); + result[index] = @field(init_values, f.name); + } + return result; +} + +test "std.enums.directEnumArrayDefault" { + const E = enum(i4) { a = 4, b = 6, c = 2 }; + var runtime_false: bool = false; + const array = directEnumArrayDefault(E, bool, false, 4, .{ + .a = true, + .b = runtime_false, + }); + + testing.expectEqual([7]bool, @TypeOf(array)); + testing.expectEqual(true, array[4]); + testing.expectEqual(false, array[6]); + testing.expectEqual(false, array[2]); +} + +/// Cast an enum literal, value, or string to the enum value of type E +/// with the same name. +pub fn nameCast(comptime E: type, comptime value: anytype) E { + comptime { + const V = @TypeOf(value); + if (V == E) return value; + var name: ?[]const u8 = switch (@typeInfo(V)) { + .EnumLiteral, .Enum => @tagName(value), + .Pointer => if (std.meta.trait.isZigString(V)) value else null, + else => null, + }; + if (name) |n| { + if (@hasField(E, n)) { + return @field(E, n); + } + @compileError("Enum "++@typeName(E)++" has no field named "++n); + } + @compileError("Cannot cast from "++@typeName(@TypeOf(value))++" to "++@typeName(E)); + } +} + +test "std.enums.nameCast" { + const A = enum { a = 0, b = 1 }; + const B = enum { a = 1, b = 0 }; + testing.expectEqual(A.a, nameCast(A, .a)); + testing.expectEqual(A.a, nameCast(A, A.a)); + testing.expectEqual(A.a, nameCast(A, B.a)); + testing.expectEqual(A.a, nameCast(A, "a")); + testing.expectEqual(A.a, nameCast(A, @as(*const[1]u8, "a"))); + testing.expectEqual(A.a, nameCast(A, @as([:0]const u8, "a"))); + testing.expectEqual(A.a, nameCast(A, @as([]const u8, "a"))); + + testing.expectEqual(B.a, nameCast(B, .a)); + testing.expectEqual(B.a, nameCast(B, A.a)); + testing.expectEqual(B.a, nameCast(B, B.a)); + testing.expectEqual(B.a, nameCast(B, "a")); + + testing.expectEqual(B.b, nameCast(B, .b)); + testing.expectEqual(B.b, nameCast(B, A.b)); + testing.expectEqual(B.b, nameCast(B, B.b)); + testing.expectEqual(B.b, nameCast(B, "b")); +} + +/// A set of enum elements, backed by a bitfield. If the enum +/// is not dense, a mapping will be constructed from enum values +/// to dense indices. This type does no dynamic allocation and +/// can be copied by value. +pub fn EnumSet(comptime E: type) type { + const mixin = struct { + fn EnumSetExt(comptime Self: type) type { + const Indexer = Self.Indexer; + return struct { + /// Initializes the set using a struct of bools + pub fn init(init_values: EnumFieldStruct(E, bool, false)) Self { + var result = Self{}; + comptime var i: usize = 0; + inline while (i < Self.len) : (i += 1) { + comptime const key = Indexer.keyForIndex(i); + comptime const tag = @tagName(key); + if (@field(init_values, tag)) { + result.bits.set(i); + } + } + return result; + } + }; + } + }; + return IndexedSet(EnumIndexer(E), mixin.EnumSetExt); +} + +/// A map keyed by an enum, backed by a bitfield and a dense array. +/// If the enum is not dense, a mapping will be constructed from +/// enum values to dense indices. This type does no dynamic +/// allocation and can be copied by value. +pub fn EnumMap(comptime E: type, comptime V: type) type { + const mixin = struct { + fn EnumMapExt(comptime Self: type) type { + const Indexer = Self.Indexer; + return struct { + /// Initializes the map using a sparse struct of optionals + pub fn init(init_values: EnumFieldStruct(E, ?V, @as(?V, null))) Self { + var result = Self{}; + comptime var i: usize = 0; + inline while (i < Self.len) : (i += 1) { + comptime const key = Indexer.keyForIndex(i); + comptime const tag = @tagName(key); + if (@field(init_values, tag)) |*v| { + result.bits.set(i); + result.values[i] = v.*; + } + } + return result; + } + /// Initializes a full mapping with all keys set to value. + /// Consider using EnumArray instead if the map will remain full. + pub fn initFull(value: V) Self { + var result = Self{ + .bits = Self.BitSet.initFull(), + .values = undefined, + }; + std.mem.set(V, &result.values, value); + return result; + } + /// Initializes a full mapping with supplied values. + /// Consider using EnumArray instead if the map will remain full. + pub fn initFullWith(init_values: EnumFieldStruct(E, V, @as(?V, null))) Self { + return initFullWithDefault(@as(?V, null), init_values); + } + /// Initializes a full mapping with a provided default. + /// Consider using EnumArray instead if the map will remain full. + pub fn initFullWithDefault(comptime default: ?V, init_values: EnumFieldStruct(E, V, default)) Self { + var result = Self{ + .bits = Self.BitSet.initFull(), + .values = undefined, + }; + comptime var i: usize = 0; + inline while (i < Self.len) : (i += 1) { + comptime const key = Indexer.keyForIndex(i); + comptime const tag = @tagName(key); + result.values[i] = @field(init_values, tag); + } + return result; + } + }; + } + }; + return IndexedMap(EnumIndexer(E), V, mixin.EnumMapExt); +} + +/// An array keyed by an enum, backed by a dense array. +/// If the enum is not dense, a mapping will be constructed from +/// enum values to dense indices. This type does no dynamic +/// allocation and can be copied by value. +pub fn EnumArray(comptime E: type, comptime V: type) type { + const mixin = struct { + fn EnumArrayExt(comptime Self: type) type { + const Indexer = Self.Indexer; + return struct { + /// Initializes all values in the enum array + pub fn init(init_values: EnumFieldStruct(E, V, @as(?V, null))) Self { + return initDefault(@as(?V, null), init_values); + } + + /// Initializes values in the enum array, with the specified default. + pub fn initDefault(comptime default: ?V, init_values: EnumFieldStruct(E, V, default)) Self { + var result = Self{ .values = undefined }; + comptime var i: usize = 0; + inline while (i < Self.len) : (i += 1) { + const key = comptime Indexer.keyForIndex(i); + const tag = @tagName(key); + result.values[i] = @field(init_values, tag); + } + return result; + } + }; + } + }; + return IndexedArray(EnumIndexer(E), V, mixin.EnumArrayExt); +} + +/// Pass this function as the Ext parameter to Indexed* if you +/// do not want to attach any extensions. This parameter was +/// originally an optional, but optional generic functions +/// seem to be broken at the moment. +/// TODO: Once #8169 is fixed, consider switching this param +/// back to an optional. +pub fn NoExtension(comptime Self: type) type { + return NoExt; +} +const NoExt = struct{}; + +/// A set type with an Indexer mapping from keys to indices. +/// Presence or absence is stored as a dense bitfield. This +/// type does no allocation and can be copied by value. +pub fn IndexedSet(comptime I: type, comptime Ext: fn(type)type) type { + comptime ensureIndexer(I); + return struct { + const Self = @This(); + + pub usingnamespace Ext(Self); + + /// The indexing rules for converting between keys and indices. + pub const Indexer = I; + /// The element type for this set. + pub const Key = Indexer.Key; + + const BitSet = std.StaticBitSet(Indexer.count); + + /// The maximum number of items in this set. + pub const len = Indexer.count; + + bits: BitSet = BitSet.initEmpty(), + + /// Returns a set containing all possible keys. + pub fn initFull() Self { + return .{ .bits = BitSet.initFull() }; + } + + /// Returns the number of keys in the set. + pub fn count(self: Self) usize { + return self.bits.count(); + } + + /// Checks if a key is in the set. + pub fn contains(self: Self, key: Key) bool { + return self.bits.isSet(Indexer.indexOf(key)); + } + + /// Puts a key in the set. + pub fn insert(self: *Self, key: Key) void { + self.bits.set(Indexer.indexOf(key)); + } + + /// Removes a key from the set. + pub fn remove(self: *Self, key: Key) void { + self.bits.unset(Indexer.indexOf(key)); + } + + /// Changes the presence of a key in the set to match the passed bool. + pub fn setPresent(self: *Self, key: Key, present: bool) void { + self.bits.setValue(Indexer.indexOf(key), present); + } + + /// Toggles the presence of a key in the set. If the key is in + /// the set, removes it. Otherwise adds it. + pub fn toggle(self: *Self, key: Key) void { + self.bits.toggle(Indexer.indexOf(key)); + } + + /// Toggles the presence of all keys in the passed set. + pub fn toggleSet(self: *Self, other: Self) void { + self.bits.toggleSet(other.bits); + } + + /// Toggles all possible keys in the set. + pub fn toggleAll(self: *Self) void { + self.bits.toggleAll(); + } + + /// Adds all keys in the passed set to this set. + pub fn setUnion(self: *Self, other: Self) void { + self.bits.setUnion(other.bits); + } + + /// Removes all keys which are not in the passed set. + pub fn setIntersection(self: *Self, other: Self) void { + self.bits.setIntersection(other.bits); + } + + /// Returns an iterator over this set, which iterates in + /// index order. Modifications to the set during iteration + /// may or may not be observed by the iterator, but will + /// not invalidate it. + pub fn iterator(self: *Self) Iterator { + return .{ .inner = self.bits.iterator(.{}) }; + } + + pub const Iterator = struct { + inner: BitSet.Iterator(.{}), + + pub fn next(self: *Iterator) ?Key { + return if (self.inner.next()) |index| + Indexer.keyForIndex(index) + else null; + } + }; + }; +} + +/// A map from keys to values, using an index lookup. Uses a +/// bitfield to track presence and a dense array of values. +/// This type does no allocation and can be copied by value. +pub fn IndexedMap(comptime I: type, comptime V: type, comptime Ext: fn(type)type) type { + comptime ensureIndexer(I); + return struct { + const Self = @This(); + + pub usingnamespace Ext(Self); + + /// The index mapping for this map + pub const Indexer = I; + /// The key type used to index this map + pub const Key = Indexer.Key; + /// The value type stored in this map + pub const Value = V; + /// The number of possible keys in the map + pub const len = Indexer.count; + + const BitSet = std.StaticBitSet(Indexer.count); + + /// Bits determining whether items are in the map + bits: BitSet = BitSet.initEmpty(), + /// Values of items in the map. If the associated + /// bit is zero, the value is undefined. + values: [Indexer.count]Value = undefined, + + /// The number of items in the map. + pub fn count(self: Self) usize { + return self.bits.count(); + } + + /// Checks if the map contains an item. + pub fn contains(self: Self, key: Key) bool { + return self.bits.isSet(Indexer.indexOf(key)); + } + + /// Gets the value associated with a key. + /// If the key is not in the map, returns null. + pub fn get(self: Self, key: Key) ?Value { + const index = Indexer.indexOf(key); + return if (self.bits.isSet(index)) self.values[index] else null; + } + + /// Gets the value associated with a key, which must + /// exist in the map. + pub fn getAssertContains(self: Self, key: Key) Value { + const index = Indexer.indexOf(key); + assert(self.bits.isSet(index)); + return self.values[index]; + } + + /// Gets the address of the value associated with a key. + /// If the key is not in the map, returns null. + pub fn getPtr(self: *Self, key: Key) ?*Value { + const index = Indexer.indexOf(key); + return if (self.bits.isSet(index)) &self.values[index] else null; + } + + /// Gets the address of the const value associated with a key. + /// If the key is not in the map, returns null. + pub fn getPtrConst(self: *const Self, key: Key) ?*const Value { + const index = Indexer.indexOf(key); + return if (self.bits.isSet(index)) &self.values[index] else null; + } + + /// Gets the address of the value associated with a key. + /// The key must be present in the map. + pub fn getPtrAssertContains(self: *Self, key: Key) *Value { + const index = Indexer.indexOf(key); + assert(self.bits.isSet(index)); + return &self.values[index]; + } + + /// Adds the key to the map with the supplied value. + /// If the key is already in the map, overwrites the value. + pub fn put(self: *Self, key: Key, value: Value) void { + const index = Indexer.indexOf(key); + self.bits.set(index); + self.values[index] = value; + } + + /// Adds the key to the map with an undefined value. + /// If the key is already in the map, the value becomes undefined. + /// A pointer to the value is returned, which should be + /// used to initialize the value. + pub fn putUninitialized(self: *Self, key: Key) *Value { + const index = Indexer.indexOf(key); + self.bits.set(index); + self.values[index] = undefined; + return &self.values[index]; + } + + /// Sets the value associated with the key in the map, + /// and returns the old value. If the key was not in + /// the map, returns null. + pub fn fetchPut(self: *Self, key: Key, value: Value) ?Value { + const index = Indexer.indexOf(key); + const result: ?Value = if (self.bits.isSet(index)) self.values[index] else null; + self.bits.set(index); + self.values[index] = value; + return result; + } + + /// Removes a key from the map. If the key was not in the map, + /// does nothing. + pub fn remove(self: *Self, key: Key) void { + const index = Indexer.indexOf(key); + self.bits.unset(index); + self.values[index] = undefined; + } + + /// Removes a key from the map, and returns the old value. + /// If the key was not in the map, returns null. + pub fn fetchRemove(self: *Self, key: Key) ?Value { + const index = Indexer.indexOf(key); + const result: ?Value = if (self.bits.isSet(index)) self.values[index] else null; + self.bits.unset(index); + self.values[index] = undefined; + return result; + } + + /// Returns an iterator over the map, which visits items in index order. + /// Modifications to the underlying map may or may not be observed by + /// the iterator, but will not invalidate it. + pub fn iterator(self: *Self) Iterator { + return .{ + .inner = self.bits.iterator(.{}), + .values = &self.values, + }; + } + + /// An entry in the map. + pub const Entry = struct { + /// The key associated with this entry. + /// Modifying this key will not change the map. + key: Key, + + /// A pointer to the value in the map associated + /// with this key. Modifications through this + /// pointer will modify the underlying data. + value: *Value, + }; + + pub const Iterator = struct { + inner: BitSet.Iterator(.{}), + values: *[Indexer.count]Value, + + pub fn next(self: *Iterator) ?Entry { + return if (self.inner.next()) |index| + Entry{ + .key = Indexer.keyForIndex(index), + .value = &self.values[index], + } + else null; + } + }; + }; +} + +/// A dense array of values, using an indexed lookup. +/// This type does no allocation and can be copied by value. +pub fn IndexedArray(comptime I: type, comptime V: type, comptime Ext: fn(type)type) type { + comptime ensureIndexer(I); + return struct { + const Self = @This(); + + pub usingnamespace Ext(Self); + + /// The index mapping for this map + pub const Indexer = I; + /// The key type used to index this map + pub const Key = Indexer.Key; + /// The value type stored in this map + pub const Value = V; + /// The number of possible keys in the map + pub const len = Indexer.count; + + values: [Indexer.count]Value, + + pub fn initUndefined() Self { + return Self{ .values = undefined }; + } + + pub fn initFill(v: Value) Self { + var self: Self = undefined; + std.mem.set(Value, &self.values, v); + return self; + } + + /// Returns the value in the array associated with a key. + pub fn get(self: Self, key: Key) Value { + return self.values[Indexer.indexOf(key)]; + } + + /// Returns a pointer to the slot in the array associated with a key. + pub fn getPtr(self: *Self, key: Key) *Value { + return &self.values[Indexer.indexOf(key)]; + } + + /// Returns a const pointer to the slot in the array associated with a key. + pub fn getPtrConst(self: *const Self, key: Key) *const Value { + return &self.values[Indexer.indexOf(key)]; + } + + /// Sets the value in the slot associated with a key. + pub fn set(self: *Self, key: Key, value: Value) void { + self.values[Indexer.indexOf(key)] = value; + } + + /// Iterates over the items in the array, in index order. + pub fn iterator(self: *Self) Iterator { + return .{ + .values = &self.values, + }; + } + + /// An entry in the array. + pub const Entry = struct { + /// The key associated with this entry. + /// Modifying this key will not change the array. + key: Key, + + /// A pointer to the value in the array associated + /// with this key. Modifications through this + /// pointer will modify the underlying data. + value: *Value, + }; + + pub const Iterator = struct { + index: usize = 0, + values: *[Indexer.count]Value, + + pub fn next(self: *Iterator) ?Entry { + const index = self.index; + if (index < Indexer.count) { + self.index += 1; + return Entry{ + .key = Indexer.keyForIndex(index), + .value = &self.values[index], + }; + } + return null; + } + }; + }; +} + +/// Verifies that a type is a valid Indexer, providing a helpful +/// compile error if not. An Indexer maps a comptime known set +/// of keys to a dense set of zero-based indices. +/// The indexer interface must look like this: +/// ``` +/// struct { +/// /// The key type which this indexer converts to indices +/// pub const Key: type, +/// /// The number of indexes in the dense mapping +/// pub const count: usize, +/// /// Converts from a key to an index +/// pub fn indexOf(Key) usize; +/// /// Converts from an index to a key +/// pub fn keyForIndex(usize) Key; +/// } +/// ``` +pub fn ensureIndexer(comptime T: type) void { + comptime { + if (!@hasDecl(T, "Key")) @compileError("Indexer must have decl Key: type."); + if (@TypeOf(T.Key) != type) @compileError("Indexer.Key must be a type."); + if (!@hasDecl(T, "count")) @compileError("Indexer must have decl count: usize."); + if (@TypeOf(T.count) != usize) @compileError("Indexer.count must be a usize."); + if (!@hasDecl(T, "indexOf")) @compileError("Indexer.indexOf must be a fn(Key)usize."); + if (@TypeOf(T.indexOf) != fn(T.Key)usize) @compileError("Indexer must have decl indexOf: fn(Key)usize."); + if (!@hasDecl(T, "keyForIndex")) @compileError("Indexer must have decl keyForIndex: fn(usize)Key."); + if (@TypeOf(T.keyForIndex) != fn(usize)T.Key) @compileError("Indexer.keyForIndex must be a fn(usize)Key."); + } +} + +test "std.enums.ensureIndexer" { + ensureIndexer(struct { + pub const Key = u32; + pub const count: usize = 8; + pub fn indexOf(k: Key) usize { + return @intCast(usize, k); + } + pub fn keyForIndex(index: usize) Key { + return @intCast(Key, index); + } + }); +} + +fn ascByValue(ctx: void, comptime a: EnumField, comptime b: EnumField) bool { + return a.value < b.value; +} +pub fn EnumIndexer(comptime E: type) type { + if (!@typeInfo(E).Enum.is_exhaustive) { + @compileError("Cannot create an enum indexer for a non-exhaustive enum."); + } + + const const_fields = uniqueFields(E); + var fields = const_fields[0..const_fields.len].*; + if (fields.len == 0) { + return struct { + pub const Key = E; + pub const count: usize = 0; + pub fn indexOf(e: E) usize { unreachable; } + pub fn keyForIndex(i: usize) E { unreachable; } + }; + } + std.sort.sort(EnumField, &fields, {}, ascByValue); + const min = fields[0].value; + const max = fields[fields.len-1].value; + if (max - min == fields.len-1) { + return struct { + pub const Key = E; + pub const count = fields.len; + pub fn indexOf(e: E) usize { + return @intCast(usize, @enumToInt(e) - min); + } + pub fn keyForIndex(i: usize) E { + // TODO fix addition semantics. This calculation + // gives up some safety to avoid artificially limiting + // the range of signed enum values to max_isize. + const enum_value = if (min < 0) @bitCast(isize, i) +% min else i + min; + return @intToEnum(E, @intCast(std.meta.Tag(E), enum_value)); + } + }; + } + + const keys = valuesFromFields(E, &fields); + + return struct { + pub const Key = E; + pub const count = fields.len; + pub fn indexOf(e: E) usize { + for (keys) |k, i| { + if (k == e) return i; + } + unreachable; + } + pub fn keyForIndex(i: usize) E { + return keys[i]; + } + }; +} + +test "std.enums.EnumIndexer dense zeroed" { + const E = enum{ b = 1, a = 0, c = 2 }; + const Indexer = EnumIndexer(E); + ensureIndexer(Indexer); + testing.expectEqual(E, Indexer.Key); + testing.expectEqual(@as(usize, 3), Indexer.count); + + testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); + testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); + testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); + + testing.expectEqual(E.a, Indexer.keyForIndex(0)); + testing.expectEqual(E.b, Indexer.keyForIndex(1)); + testing.expectEqual(E.c, Indexer.keyForIndex(2)); +} + +test "std.enums.EnumIndexer dense positive" { + const E = enum(u4) { c = 6, a = 4, b = 5 }; + const Indexer = EnumIndexer(E); + ensureIndexer(Indexer); + testing.expectEqual(E, Indexer.Key); + testing.expectEqual(@as(usize, 3), Indexer.count); + + testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); + testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); + testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); + + testing.expectEqual(E.a, Indexer.keyForIndex(0)); + testing.expectEqual(E.b, Indexer.keyForIndex(1)); + testing.expectEqual(E.c, Indexer.keyForIndex(2)); +} + +test "std.enums.EnumIndexer dense negative" { + const E = enum(i4) { a = -6, c = -4, b = -5 }; + const Indexer = EnumIndexer(E); + ensureIndexer(Indexer); + testing.expectEqual(E, Indexer.Key); + testing.expectEqual(@as(usize, 3), Indexer.count); + + testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); + testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); + testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); + + testing.expectEqual(E.a, Indexer.keyForIndex(0)); + testing.expectEqual(E.b, Indexer.keyForIndex(1)); + testing.expectEqual(E.c, Indexer.keyForIndex(2)); +} + +test "std.enums.EnumIndexer sparse" { + const E = enum(i4) { a = -2, c = 6, b = 4 }; + const Indexer = EnumIndexer(E); + ensureIndexer(Indexer); + testing.expectEqual(E, Indexer.Key); + testing.expectEqual(@as(usize, 3), Indexer.count); + + testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); + testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); + testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); + + testing.expectEqual(E.a, Indexer.keyForIndex(0)); + testing.expectEqual(E.b, Indexer.keyForIndex(1)); + testing.expectEqual(E.c, Indexer.keyForIndex(2)); +} + +test "std.enums.EnumIndexer repeats" { + const E = extern enum{ a = -2, c = 6, b = 4, b2 = 4 }; + const Indexer = EnumIndexer(E); + ensureIndexer(Indexer); + testing.expectEqual(E, Indexer.Key); + testing.expectEqual(@as(usize, 3), Indexer.count); + + testing.expectEqual(@as(usize, 0), Indexer.indexOf(.a)); + testing.expectEqual(@as(usize, 1), Indexer.indexOf(.b)); + testing.expectEqual(@as(usize, 2), Indexer.indexOf(.c)); + + testing.expectEqual(E.a, Indexer.keyForIndex(0)); + testing.expectEqual(E.b, Indexer.keyForIndex(1)); + testing.expectEqual(E.c, Indexer.keyForIndex(2)); +} + +test "std.enums.EnumSet" { + const E = extern enum { a, b, c, d, e = 0 }; + const Set = EnumSet(E); + testing.expectEqual(E, Set.Key); + testing.expectEqual(EnumIndexer(E), Set.Indexer); + testing.expectEqual(@as(usize, 4), Set.len); + + // Empty sets + const empty = Set{}; + comptime testing.expect(empty.count() == 0); + + var empty_b = Set.init(.{}); + testing.expect(empty_b.count() == 0); + + const empty_c = comptime Set.init(.{}); + comptime testing.expect(empty_c.count() == 0); + + const full = Set.initFull(); + testing.expect(full.count() == Set.len); + + const full_b = comptime Set.initFull(); + comptime testing.expect(full_b.count() == Set.len); + + testing.expectEqual(false, empty.contains(.a)); + testing.expectEqual(false, empty.contains(.b)); + testing.expectEqual(false, empty.contains(.c)); + testing.expectEqual(false, empty.contains(.d)); + testing.expectEqual(false, empty.contains(.e)); + { + var iter = empty_b.iterator(); + testing.expectEqual(@as(?E, null), iter.next()); + } + + var mut = Set.init(.{ + .a=true, .c=true, + }); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(false, mut.contains(.b)); + testing.expectEqual(true, mut.contains(.c)); + testing.expectEqual(false, mut.contains(.d)); + testing.expectEqual(true, mut.contains(.e)); // aliases a + { + var it = mut.iterator(); + testing.expectEqual(@as(?E, .a), it.next()); + testing.expectEqual(@as(?E, .c), it.next()); + testing.expectEqual(@as(?E, null), it.next()); + } + + mut.toggleAll(); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(false, mut.contains(.a)); + testing.expectEqual(true, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(true, mut.contains(.d)); + testing.expectEqual(false, mut.contains(.e)); // aliases a + { + var it = mut.iterator(); + testing.expectEqual(@as(?E, .b), it.next()); + testing.expectEqual(@as(?E, .d), it.next()); + testing.expectEqual(@as(?E, null), it.next()); + } + + mut.toggleSet(Set.init(.{ .a=true, .b=true })); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(false, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(true, mut.contains(.d)); + testing.expectEqual(true, mut.contains(.e)); // aliases a + + mut.setUnion(Set.init(.{ .a=true, .b=true })); + testing.expectEqual(@as(usize, 3), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(true, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(true, mut.contains(.d)); + + mut.remove(.c); + mut.remove(.b); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(false, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(true, mut.contains(.d)); + + mut.setIntersection(Set.init(.{ .a=true, .b=true })); + testing.expectEqual(@as(usize, 1), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(false, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(false, mut.contains(.d)); + + mut.insert(.a); + mut.insert(.b); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(true, mut.contains(.a)); + testing.expectEqual(true, mut.contains(.b)); + testing.expectEqual(false, mut.contains(.c)); + testing.expectEqual(false, mut.contains(.d)); + + mut.setPresent(.a, false); + mut.toggle(.b); + mut.toggle(.c); + mut.setPresent(.d, true); + testing.expectEqual(@as(usize, 2), mut.count()); + testing.expectEqual(false, mut.contains(.a)); + testing.expectEqual(false, mut.contains(.b)); + testing.expectEqual(true, mut.contains(.c)); + testing.expectEqual(true, mut.contains(.d)); +} + +test "std.enums.EnumArray void" { + const E = extern enum { a, b, c, d, e = 0 }; + const ArrayVoid = EnumArray(E, void); + testing.expectEqual(E, ArrayVoid.Key); + testing.expectEqual(EnumIndexer(E), ArrayVoid.Indexer); + testing.expectEqual(void, ArrayVoid.Value); + testing.expectEqual(@as(usize, 4), ArrayVoid.len); + + const undef = ArrayVoid.initUndefined(); + var inst = ArrayVoid.initFill({}); + const inst2 = ArrayVoid.init(.{ .a = {}, .b = {}, .c = {}, .d = {} }); + const inst3 = ArrayVoid.initDefault({}, .{}); + + _ = inst.get(.a); + _ = inst.getPtr(.b); + _ = inst.getPtrConst(.c); + inst.set(.a, {}); + + var it = inst.iterator(); + testing.expectEqual(E.a, it.next().?.key); + testing.expectEqual(E.b, it.next().?.key); + testing.expectEqual(E.c, it.next().?.key); + testing.expectEqual(E.d, it.next().?.key); + testing.expect(it.next() == null); +} + +test "std.enums.EnumArray sized" { + const E = extern enum { a, b, c, d, e = 0 }; + const Array = EnumArray(E, usize); + testing.expectEqual(E, Array.Key); + testing.expectEqual(EnumIndexer(E), Array.Indexer); + testing.expectEqual(usize, Array.Value); + testing.expectEqual(@as(usize, 4), Array.len); + + const undef = Array.initUndefined(); + var inst = Array.initFill(5); + const inst2 = Array.init(.{ .a = 1, .b = 2, .c = 3, .d = 4 }); + const inst3 = Array.initDefault(6, .{.b = 4, .c = 2}); + + testing.expectEqual(@as(usize, 5), inst.get(.a)); + testing.expectEqual(@as(usize, 5), inst.get(.b)); + testing.expectEqual(@as(usize, 5), inst.get(.c)); + testing.expectEqual(@as(usize, 5), inst.get(.d)); + + testing.expectEqual(@as(usize, 1), inst2.get(.a)); + testing.expectEqual(@as(usize, 2), inst2.get(.b)); + testing.expectEqual(@as(usize, 3), inst2.get(.c)); + testing.expectEqual(@as(usize, 4), inst2.get(.d)); + + testing.expectEqual(@as(usize, 6), inst3.get(.a)); + testing.expectEqual(@as(usize, 4), inst3.get(.b)); + testing.expectEqual(@as(usize, 2), inst3.get(.c)); + testing.expectEqual(@as(usize, 6), inst3.get(.d)); + + testing.expectEqual(&inst.values[0], inst.getPtr(.a)); + testing.expectEqual(&inst.values[1], inst.getPtr(.b)); + testing.expectEqual(&inst.values[2], inst.getPtr(.c)); + testing.expectEqual(&inst.values[3], inst.getPtr(.d)); + + testing.expectEqual(@as(*const usize, &inst.values[0]), inst.getPtrConst(.a)); + testing.expectEqual(@as(*const usize, &inst.values[1]), inst.getPtrConst(.b)); + testing.expectEqual(@as(*const usize, &inst.values[2]), inst.getPtrConst(.c)); + testing.expectEqual(@as(*const usize, &inst.values[3]), inst.getPtrConst(.d)); + + inst.set(.c, 8); + testing.expectEqual(@as(usize, 5), inst.get(.a)); + testing.expectEqual(@as(usize, 5), inst.get(.b)); + testing.expectEqual(@as(usize, 8), inst.get(.c)); + testing.expectEqual(@as(usize, 5), inst.get(.d)); + + var it = inst.iterator(); + const Entry = Array.Entry; + testing.expectEqual(@as(?Entry, Entry{ + .key = .a, + .value = &inst.values[0], + }), it.next()); + testing.expectEqual(@as(?Entry, Entry{ + .key = .b, + .value = &inst.values[1], + }), it.next()); + testing.expectEqual(@as(?Entry, Entry{ + .key = .c, + .value = &inst.values[2], + }), it.next()); + testing.expectEqual(@as(?Entry, Entry{ + .key = .d, + .value = &inst.values[3], + }), it.next()); + testing.expectEqual(@as(?Entry, null), it.next()); +} + +test "std.enums.EnumMap void" { + const E = extern enum { a, b, c, d, e = 0 }; + const Map = EnumMap(E, void); + testing.expectEqual(E, Map.Key); + testing.expectEqual(EnumIndexer(E), Map.Indexer); + testing.expectEqual(void, Map.Value); + testing.expectEqual(@as(usize, 4), Map.len); + + const b = Map.initFull({}); + testing.expectEqual(@as(usize, 4), b.count()); + + const c = Map.initFullWith(.{ .a = {}, .b = {}, .c = {}, .d = {} }); + testing.expectEqual(@as(usize, 4), c.count()); + + const d = Map.initFullWithDefault({}, .{ .b = {} }); + testing.expectEqual(@as(usize, 4), d.count()); + + var a = Map.init(.{ .b = {}, .d = {} }); + testing.expectEqual(@as(usize, 2), a.count()); + testing.expectEqual(false, a.contains(.a)); + testing.expectEqual(true, a.contains(.b)); + testing.expectEqual(false, a.contains(.c)); + testing.expectEqual(true, a.contains(.d)); + testing.expect(a.get(.a) == null); + testing.expect(a.get(.b) != null); + testing.expect(a.get(.c) == null); + testing.expect(a.get(.d) != null); + testing.expect(a.getPtr(.a) == null); + testing.expect(a.getPtr(.b) != null); + testing.expect(a.getPtr(.c) == null); + testing.expect(a.getPtr(.d) != null); + testing.expect(a.getPtrConst(.a) == null); + testing.expect(a.getPtrConst(.b) != null); + testing.expect(a.getPtrConst(.c) == null); + testing.expect(a.getPtrConst(.d) != null); + _ = a.getPtrAssertContains(.b); + _ = a.getAssertContains(.d); + + a.put(.a, {}); + a.put(.a, {}); + a.putUninitialized(.c).* = {}; + a.putUninitialized(.c).* = {}; + + testing.expectEqual(@as(usize, 4), a.count()); + testing.expect(a.get(.a) != null); + testing.expect(a.get(.b) != null); + testing.expect(a.get(.c) != null); + testing.expect(a.get(.d) != null); + + a.remove(.a); + _ = a.fetchRemove(.c); + + var iter = a.iterator(); + const Entry = Map.Entry; + testing.expectEqual(E.b, iter.next().?.key); + testing.expectEqual(E.d, iter.next().?.key); + testing.expect(iter.next() == null); +} + +test "std.enums.EnumMap sized" { + const E = extern enum { a, b, c, d, e = 0 }; + const Map = EnumMap(E, usize); + testing.expectEqual(E, Map.Key); + testing.expectEqual(EnumIndexer(E), Map.Indexer); + testing.expectEqual(usize, Map.Value); + testing.expectEqual(@as(usize, 4), Map.len); + + const b = Map.initFull(5); + testing.expectEqual(@as(usize, 4), b.count()); + testing.expect(b.contains(.a)); + testing.expect(b.contains(.b)); + testing.expect(b.contains(.c)); + testing.expect(b.contains(.d)); + testing.expectEqual(@as(?usize, 5), b.get(.a)); + testing.expectEqual(@as(?usize, 5), b.get(.b)); + testing.expectEqual(@as(?usize, 5), b.get(.c)); + testing.expectEqual(@as(?usize, 5), b.get(.d)); + + const c = Map.initFullWith(.{ .a = 1, .b = 2, .c = 3, .d = 4 }); + testing.expectEqual(@as(usize, 4), c.count()); + testing.expect(c.contains(.a)); + testing.expect(c.contains(.b)); + testing.expect(c.contains(.c)); + testing.expect(c.contains(.d)); + testing.expectEqual(@as(?usize, 1), c.get(.a)); + testing.expectEqual(@as(?usize, 2), c.get(.b)); + testing.expectEqual(@as(?usize, 3), c.get(.c)); + testing.expectEqual(@as(?usize, 4), c.get(.d)); + + const d = Map.initFullWithDefault(6, .{ .b = 2, .c = 4 }); + testing.expectEqual(@as(usize, 4), d.count()); + testing.expect(d.contains(.a)); + testing.expect(d.contains(.b)); + testing.expect(d.contains(.c)); + testing.expect(d.contains(.d)); + testing.expectEqual(@as(?usize, 6), d.get(.a)); + testing.expectEqual(@as(?usize, 2), d.get(.b)); + testing.expectEqual(@as(?usize, 4), d.get(.c)); + testing.expectEqual(@as(?usize, 6), d.get(.d)); + + var a = Map.init(.{ .b = 2, .d = 4 }); + testing.expectEqual(@as(usize, 2), a.count()); + testing.expectEqual(false, a.contains(.a)); + testing.expectEqual(true, a.contains(.b)); + testing.expectEqual(false, a.contains(.c)); + testing.expectEqual(true, a.contains(.d)); + + testing.expectEqual(@as(?usize, null), a.get(.a)); + testing.expectEqual(@as(?usize, 2), a.get(.b)); + testing.expectEqual(@as(?usize, null), a.get(.c)); + testing.expectEqual(@as(?usize, 4), a.get(.d)); + + testing.expectEqual(@as(?*usize, null), a.getPtr(.a)); + testing.expectEqual(@as(?*usize, &a.values[1]), a.getPtr(.b)); + testing.expectEqual(@as(?*usize, null), a.getPtr(.c)); + testing.expectEqual(@as(?*usize, &a.values[3]), a.getPtr(.d)); + + testing.expectEqual(@as(?*const usize, null), a.getPtrConst(.a)); + testing.expectEqual(@as(?*const usize, &a.values[1]), a.getPtrConst(.b)); + testing.expectEqual(@as(?*const usize, null), a.getPtrConst(.c)); + testing.expectEqual(@as(?*const usize, &a.values[3]), a.getPtrConst(.d)); + + testing.expectEqual(@as(*const usize, &a.values[1]), a.getPtrAssertContains(.b)); + testing.expectEqual(@as(*const usize, &a.values[3]), a.getPtrAssertContains(.d)); + testing.expectEqual(@as(usize, 2), a.getAssertContains(.b)); + testing.expectEqual(@as(usize, 4), a.getAssertContains(.d)); + + a.put(.a, 3); + a.put(.a, 5); + a.putUninitialized(.c).* = 7; + a.putUninitialized(.c).* = 9; + + testing.expectEqual(@as(usize, 4), a.count()); + testing.expectEqual(@as(?usize, 5), a.get(.a)); + testing.expectEqual(@as(?usize, 2), a.get(.b)); + testing.expectEqual(@as(?usize, 9), a.get(.c)); + testing.expectEqual(@as(?usize, 4), a.get(.d)); + + a.remove(.a); + testing.expectEqual(@as(?usize, null), a.fetchRemove(.a)); + testing.expectEqual(@as(?usize, 9), a.fetchRemove(.c)); + a.remove(.c); + + var iter = a.iterator(); + const Entry = Map.Entry; + testing.expectEqual(@as(?Entry, Entry{ + .key = .b, .value = &a.values[1], + }), iter.next()); + testing.expectEqual(@as(?Entry, Entry{ + .key = .d, .value = &a.values[3], + }), iter.next()); + testing.expectEqual(@as(?Entry, null), iter.next()); +} diff --git a/lib/std/meta/trait.zig b/lib/std/meta/trait.zig index e67f9b9bc4aa..481bfe212b34 100644 --- a/lib/std/meta/trait.zig +++ b/lib/std/meta/trait.zig @@ -408,6 +408,84 @@ test "std.meta.trait.isTuple" { testing.expect(isTuple(@TypeOf(t3))); } +/// Returns true if the passed type will coerce to []const u8. +/// Any of the following are considered strings: +/// ``` +/// []const u8, [:S]const u8, *const [N]u8, *const [N:S]u8, +/// []u8, [:S]u8, *[:S]u8, *[N:S]u8. +/// ``` +/// These types are not considered strings: +/// ``` +/// u8, [N]u8, [*]const u8, [*:0]const u8, +/// [*]const [N]u8, []const u16, []const i8, +/// *const u8, ?[]const u8, ?*const [N]u8. +/// ``` +pub fn isZigString(comptime T: type) bool { + comptime { + // Only pointer types can be strings, no optionals + const info = @typeInfo(T); + if (info != .Pointer) return false; + + const ptr = &info.Pointer; + // Check for CV qualifiers that would prevent coerction to []const u8 + if (ptr.is_volatile or ptr.is_allowzero) return false; + + // If it's already a slice, simple check. + if (ptr.size == .Slice) { + return ptr.child == u8; + } + + // Otherwise check if it's an array type that coerces to slice. + if (ptr.size == .One) { + const child = @typeInfo(ptr.child); + if (child == .Array) { + const arr = &child.Array; + return arr.child == u8; + } + } + + return false; + } +} + +test "std.meta.trait.isZigString" { + testing.expect(isZigString([]const u8)); + testing.expect(isZigString([]u8)); + testing.expect(isZigString([:0]const u8)); + testing.expect(isZigString([:0]u8)); + testing.expect(isZigString([:5]const u8)); + testing.expect(isZigString([:5]u8)); + testing.expect(isZigString(*const [0]u8)); + testing.expect(isZigString(*[0]u8)); + testing.expect(isZigString(*const [0:0]u8)); + testing.expect(isZigString(*[0:0]u8)); + testing.expect(isZigString(*const [0:5]u8)); + testing.expect(isZigString(*[0:5]u8)); + testing.expect(isZigString(*const [10]u8)); + testing.expect(isZigString(*[10]u8)); + testing.expect(isZigString(*const [10:0]u8)); + testing.expect(isZigString(*[10:0]u8)); + testing.expect(isZigString(*const [10:5]u8)); + testing.expect(isZigString(*[10:5]u8)); + + testing.expect(!isZigString(u8)); + testing.expect(!isZigString([4]u8)); + testing.expect(!isZigString([4:0]u8)); + testing.expect(!isZigString([*]const u8)); + testing.expect(!isZigString([*]const [4]u8)); + testing.expect(!isZigString([*c]const u8)); + testing.expect(!isZigString([*c]const [4]u8)); + testing.expect(!isZigString([*:0]const u8)); + testing.expect(!isZigString([*:0]const u8)); + testing.expect(!isZigString(*[]const u8)); + testing.expect(!isZigString(?[]const u8)); + testing.expect(!isZigString(?*const [4]u8)); + testing.expect(!isZigString([]allowzero u8)); + testing.expect(!isZigString([]volatile u8)); + testing.expect(!isZigString(*allowzero [4]u8)); + testing.expect(!isZigString(*volatile [4]u8)); +} + pub fn hasDecls(comptime T: type, comptime names: anytype) bool { inline for (names) |name| { if (!@hasDecl(T, name)) diff --git a/lib/std/std.zig b/lib/std/std.zig index a7e5bcb68278..82249af1572d 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -20,6 +20,9 @@ pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringM pub const DynLib = @import("dynamic_library.zig").DynLib; pub const DynamicBitSet = bit_set.DynamicBitSet; pub const DynamicBitSetUnmanaged = bit_set.DynamicBitSetUnmanaged; +pub const EnumArray = enums.EnumArray; +pub const EnumMap = enums.EnumMap; +pub const EnumSet = enums.EnumSet; pub const HashMap = hash_map.HashMap; pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; pub const MultiArrayList = @import("multi_array_list.zig").MultiArrayList; @@ -54,6 +57,7 @@ pub const cstr = @import("cstr.zig"); pub const debug = @import("debug.zig"); pub const dwarf = @import("dwarf.zig"); pub const elf = @import("elf.zig"); +pub const enums = @import("enums.zig"); pub const event = @import("event.zig"); pub const fifo = @import("fifo.zig"); pub const fmt = @import("fmt.zig");