ComptimeStringMap: return a regular struct and optimize

this patch renames ComptimeStringMap to StaticStringMap, makes it
accept only a single type parameter, and return a known struct type
instead of an anonymous struct.  initial motivation for these changes
was to reduce the 'very long type names' issue described here
https://github.com/ziglang/zig/pull/19682.

this breaks the previous API.  users will now need to write:
`const map = std.StaticStringMap(T).initComptime(kvs_list);`

* move `kvs_list` param from type param to an `initComptime()` param
* new public methods
  * `keys()`, `values()` helpers
  * `init(allocator)`, `deinit(allocator)` for runtime data
  * `getLongestPrefix(str)`, `getLongestPrefixIndex(str)` - i'm not sure
     these belong but have left in for now incase they are deemed useful
* performance notes:
  * i posted some benchmarking results here:
    https://github.com/travisstaloch/comptime-string-map-revised/issues/1
  * i noticed a speedup reducing the size of the struct from 48 to 32
    bytes and thus use u32s instead of usize for all length fields
  * i noticed speedup storing KVs as a struct of arrays
  * latest benchmark shows these wall_time improvements for
    debug/safe/small/fast builds: -6.6% / -10.2% / -19.1% / -8.9%. full
    output in link above.
This commit is contained in:
Travis Staloch 2024-04-20 23:14:39 -07:00 committed by Andrew Kelley
parent fefdbca6e6
commit 8af59d1f98
25 changed files with 608 additions and 387 deletions

View File

@ -222,7 +222,7 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/lib/std/c/linux.zig"
"${CMAKE_SOURCE_DIR}/lib/std/child_process.zig"
"${CMAKE_SOURCE_DIR}/lib/std/coff.zig"
"${CMAKE_SOURCE_DIR}/lib/std/comptime_string_map.zig"
"${CMAKE_SOURCE_DIR}/lib/std/static_string_map.zig"
"${CMAKE_SOURCE_DIR}/lib/std/crypto.zig"
"${CMAKE_SOURCE_DIR}/lib/std/crypto/blake3.zig"
"${CMAKE_SOURCE_DIR}/lib/std/crypto/siphash.zig"

View File

@ -47,7 +47,7 @@ pub const Standard = enum {
/// Working Draft for ISO C23 with GNU extensions
gnu23,
const NameMap = std.ComptimeStringMap(Standard, .{
const NameMap = std.StaticStringMap(Standard).initComptime(.{
.{ "c89", .c89 }, .{ "c90", .c89 }, .{ "iso9899:1990", .c89 },
.{ "iso9899:199409", .iso9899 }, .{ "gnu89", .gnu89 }, .{ "gnu90", .gnu89 },
.{ "c99", .c99 }, .{ "iso9899:1999", .c99 }, .{ "c9x", .c99 },

View File

@ -1709,7 +1709,7 @@ fn expandFuncMacro(
}
if (!pp.comp.langopts.standard.atLeast(.c23)) break :res not_found;
const attrs = std.ComptimeStringMap([]const u8, .{
const attrs = std.StaticStringMap([]const u8).initComptime(.{
.{ "deprecated", "201904L\n" },
.{ "fallthrough", "201904L\n" },
.{ "maybe_unused", "201904L\n" },

View File

@ -872,7 +872,7 @@ pub const Token = struct {
};
}
const all_kws = std.ComptimeStringMap(Id, .{
const all_kws = std.StaticStringMap(Id).initComptime(.{
.{ "auto", auto: {
@setEvalBranchQuota(3000);
break :auto .keyword_auto;

View File

@ -240,7 +240,7 @@ pub const ErrorDetails = struct {
// see https://github.com/ziglang/zig/issues/15395
_: u26 = 0,
pub const strings = std.ComptimeStringMap([]const u8, .{
pub const strings = std.StaticStringMap([]const u8).initComptime(.{
.{ "number", "number" },
.{ "number_expression", "number expression" },
.{ "string_literal", "quoted string literal" },

View File

@ -47,7 +47,10 @@ pub const Resource = enum {
fontdir_num,
manifest_num,
const map = std.ComptimeStringMapWithEql(Resource, .{
const map = std.StaticStringMapWithEql(
Resource,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "ACCELERATORS", .accelerators },
.{ "BITMAP", .bitmap },
.{ "CURSOR", .cursor },
@ -67,7 +70,7 @@ pub const Resource = enum {
.{ "TOOLBAR", .toolbar },
.{ "VERSIONINFO", .versioninfo },
.{ "VXD", .vxd },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
pub fn fromString(bytes: SourceBytes) Resource {
const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(bytes);
@ -157,20 +160,26 @@ pub const OptionalStatements = enum {
menu,
style,
pub const map = std.ComptimeStringMapWithEql(OptionalStatements, .{
pub const map = std.StaticStringMapWithEql(
OptionalStatements,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "CHARACTERISTICS", .characteristics },
.{ "LANGUAGE", .language },
.{ "VERSION", .version },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
pub const dialog_map = std.ComptimeStringMapWithEql(OptionalStatements, .{
pub const dialog_map = std.StaticStringMapWithEql(
OptionalStatements,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "CAPTION", .caption },
.{ "CLASS", .class },
.{ "EXSTYLE", .exstyle },
.{ "FONT", .font },
.{ "MENU", .menu },
.{ "STYLE", .style },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
};
pub const Control = enum {
@ -197,7 +206,10 @@ pub const Control = enum {
state3,
userbutton,
pub const map = std.ComptimeStringMapWithEql(Control, .{
pub const map = std.StaticStringMapWithEql(
Control,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "AUTO3STATE", .auto3state },
.{ "AUTOCHECKBOX", .autocheckbox },
.{ "AUTORADIOBUTTON", .autoradiobutton },
@ -220,7 +232,7 @@ pub const Control = enum {
.{ "SCROLLBAR", .scrollbar },
.{ "STATE3", .state3 },
.{ "USERBUTTON", .userbutton },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
pub fn hasTextParam(control: Control) bool {
switch (control) {
@ -231,14 +243,17 @@ pub const Control = enum {
};
pub const ControlClass = struct {
pub const map = std.ComptimeStringMapWithEql(res.ControlClass, .{
pub const map = std.StaticStringMapWithEql(
res.ControlClass,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "BUTTON", .button },
.{ "EDIT", .edit },
.{ "STATIC", .static },
.{ "LISTBOX", .listbox },
.{ "SCROLLBAR", .scrollbar },
.{ "COMBOBOX", .combobox },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
/// Like `map.get` but works on WTF16 strings, for use with parsed
/// string literals ("BUTTON", or even "\x42UTTON")
@ -280,10 +295,13 @@ pub const MenuItem = enum {
menuitem,
popup,
pub const map = std.ComptimeStringMapWithEql(MenuItem, .{
pub const map = std.StaticStringMapWithEql(
MenuItem,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "MENUITEM", .menuitem },
.{ "POPUP", .popup },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
pub fn isSeparator(bytes: []const u8) bool {
return std.ascii.eqlIgnoreCase(bytes, "SEPARATOR");
@ -297,14 +315,17 @@ pub const MenuItem = enum {
menubarbreak,
menubreak,
pub const map = std.ComptimeStringMapWithEql(Option, .{
pub const map = std.StaticStringMapWithEql(
Option,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "CHECKED", .checked },
.{ "GRAYED", .grayed },
.{ "HELP", .help },
.{ "INACTIVE", .inactive },
.{ "MENUBARBREAK", .menubarbreak },
.{ "MENUBREAK", .menubreak },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
};
};
@ -312,10 +333,13 @@ pub const ToolbarButton = enum {
button,
separator,
pub const map = std.ComptimeStringMapWithEql(ToolbarButton, .{
pub const map = std.StaticStringMapWithEql(
ToolbarButton,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "BUTTON", .button },
.{ "SEPARATOR", .separator },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
};
pub const VersionInfo = enum {
@ -327,7 +351,10 @@ pub const VersionInfo = enum {
file_type,
file_subtype,
pub const map = std.ComptimeStringMapWithEql(VersionInfo, .{
pub const map = std.StaticStringMapWithEql(
VersionInfo,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "FILEVERSION", .file_version },
.{ "PRODUCTVERSION", .product_version },
.{ "FILEFLAGSMASK", .file_flags_mask },
@ -335,17 +362,20 @@ pub const VersionInfo = enum {
.{ "FILEOS", .file_os },
.{ "FILETYPE", .file_type },
.{ "FILESUBTYPE", .file_subtype },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
};
pub const VersionBlock = enum {
block,
value,
pub const map = std.ComptimeStringMapWithEql(VersionBlock, .{
pub const map = std.StaticStringMapWithEql(
VersionBlock,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "BLOCK", .block },
.{ "VALUE", .value },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
};
/// Keywords that are be the first token in a statement and (if so) dictate how the rest
@ -356,12 +386,15 @@ pub const TopLevelKeywords = enum {
characteristics,
stringtable,
pub const map = std.ComptimeStringMapWithEql(TopLevelKeywords, .{
pub const map = std.StaticStringMapWithEql(
TopLevelKeywords,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "LANGUAGE", .language },
.{ "VERSION", .version },
.{ "CHARACTERISTICS", .characteristics },
.{ "STRINGTABLE", .stringtable },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
};
pub const CommonResourceAttributes = enum {
@ -375,7 +408,10 @@ pub const CommonResourceAttributes = enum {
shared,
nonshared,
pub const map = std.ComptimeStringMapWithEql(CommonResourceAttributes, .{
pub const map = std.StaticStringMapWithEql(
CommonResourceAttributes,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "PRELOAD", .preload },
.{ "LOADONCALL", .loadoncall },
.{ "FIXED", .fixed },
@ -385,7 +421,7 @@ pub const CommonResourceAttributes = enum {
.{ "IMPURE", .impure },
.{ "SHARED", .shared },
.{ "NONSHARED", .nonshared },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
};
pub const AcceleratorTypeAndOptions = enum {
@ -396,12 +432,15 @@ pub const AcceleratorTypeAndOptions = enum {
shift,
control,
pub const map = std.ComptimeStringMapWithEql(AcceleratorTypeAndOptions, .{
pub const map = std.StaticStringMapWithEql(
AcceleratorTypeAndOptions,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "VIRTKEY", .virtkey },
.{ "ASCII", .ascii },
.{ "NOINVERT", .noinvert },
.{ "ALT", .alt },
.{ "SHIFT", .shift },
.{ "CONTROL", .control },
}, std.comptime_string_map.eqlAsciiIgnoreCase);
});
};

View File

@ -1,320 +0,0 @@
const std = @import("std.zig");
const mem = std.mem;
/// Comptime string map optimized for small sets of disparate string keys.
/// Works by separating the keys by length at comptime and only checking strings of
/// equal length at runtime.
///
/// `kvs_list` expects a list of `struct { []const u8, V }` (key-value pair) tuples.
/// You can pass `struct { []const u8 }` (only keys) tuples if `V` is `void`.
pub fn ComptimeStringMap(
comptime V: type,
comptime kvs_list: anytype,
) type {
return ComptimeStringMapWithEql(V, kvs_list, defaultEql);
}
/// Like `std.mem.eql`, but takes advantage of the fact that the lengths
/// of `a` and `b` are known to be equal.
pub fn defaultEql(a: []const u8, b: []const u8) bool {
if (a.ptr == b.ptr) return true;
for (a, b) |a_elem, b_elem| {
if (a_elem != b_elem) return false;
}
return true;
}
/// Like `std.ascii.eqlIgnoreCase` but takes advantage of the fact that
/// the lengths of `a` and `b` are known to be equal.
pub fn eqlAsciiIgnoreCase(a: []const u8, b: []const u8) bool {
if (a.ptr == b.ptr) return true;
for (a, b) |a_c, b_c| {
if (std.ascii.toLower(a_c) != std.ascii.toLower(b_c)) return false;
}
return true;
}
/// ComptimeStringMap, but accepts an equality function (`eql`).
/// The `eql` function is only called to determine the equality
/// of equal length strings. Any strings that are not equal length
/// are never compared using the `eql` function.
pub fn ComptimeStringMapWithEql(
comptime V: type,
comptime kvs_list: anytype,
comptime eql: fn (a: []const u8, b: []const u8) bool,
) type {
const empty_list = kvs_list.len == 0;
const precomputed = blk: {
@setEvalBranchQuota(1500);
const KV = struct {
key: []const u8,
value: V,
};
if (empty_list)
break :blk .{};
var sorted_kvs: [kvs_list.len]KV = undefined;
for (kvs_list, 0..) |kv, i| {
if (V != void) {
sorted_kvs[i] = .{ .key = kv.@"0", .value = kv.@"1" };
} else {
sorted_kvs[i] = .{ .key = kv.@"0", .value = {} };
}
}
const SortContext = struct {
kvs: []KV,
pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
return ctx.kvs[a].key.len < ctx.kvs[b].key.len;
}
pub fn swap(ctx: @This(), a: usize, b: usize) void {
return std.mem.swap(KV, &ctx.kvs[a], &ctx.kvs[b]);
}
};
mem.sortUnstableContext(0, sorted_kvs.len, SortContext{ .kvs = &sorted_kvs });
const min_len = sorted_kvs[0].key.len;
const max_len = sorted_kvs[sorted_kvs.len - 1].key.len;
var len_indexes: [max_len + 1]usize = undefined;
var len: usize = 0;
var i: usize = 0;
while (len <= max_len) : (len += 1) {
// find the first keyword len == len
while (len > sorted_kvs[i].key.len) {
i += 1;
}
len_indexes[len] = i;
}
break :blk .{
.min_len = min_len,
.max_len = max_len,
.sorted_kvs = sorted_kvs,
.len_indexes = len_indexes,
};
};
return struct {
/// Array of `struct { key: []const u8, value: V }` where `value` is `void{}` if `V` is `void`.
/// Sorted by `key` length.
pub const kvs = precomputed.sorted_kvs;
/// Checks if the map has a value for the key.
pub fn has(str: []const u8) bool {
return get(str) != null;
}
/// Returns the value for the key if any, else null.
pub fn get(str: []const u8) ?V {
if (empty_list)
return null;
return precomputed.sorted_kvs[getIndex(str) orelse return null].value;
}
pub fn getIndex(str: []const u8) ?usize {
if (empty_list)
return null;
if (str.len < precomputed.min_len or str.len > precomputed.max_len)
return null;
var i = precomputed.len_indexes[str.len];
while (true) {
const kv = precomputed.sorted_kvs[i];
if (kv.key.len != str.len)
return null;
if (eql(kv.key, str))
return i;
i += 1;
if (i >= precomputed.sorted_kvs.len)
return null;
}
}
};
}
const TestEnum = enum {
A,
B,
C,
D,
E,
};
test "list literal of list literals" {
const map = ComptimeStringMap(TestEnum, .{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
});
try testMap(map);
// Default comparison is case sensitive
try std.testing.expect(null == map.get("NOTHING"));
}
test "array of structs" {
const KV = struct { []const u8, TestEnum };
const map = ComptimeStringMap(TestEnum, [_]KV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
});
try testMap(map);
}
test "slice of structs" {
const KV = struct { []const u8, TestEnum };
const slice: []const KV = &[_]KV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
};
const map = ComptimeStringMap(TestEnum, slice);
try testMap(map);
}
fn testMap(comptime map: anytype) !void {
try std.testing.expectEqual(TestEnum.A, map.get("have").?);
try std.testing.expectEqual(TestEnum.B, map.get("nothing").?);
try std.testing.expect(null == map.get("missing"));
try std.testing.expectEqual(TestEnum.D, map.get("these").?);
try std.testing.expectEqual(TestEnum.E, map.get("samelen").?);
try std.testing.expect(!map.has("missing"));
try std.testing.expect(map.has("these"));
try std.testing.expect(null == map.get(""));
try std.testing.expect(null == map.get("averylongstringthathasnomatches"));
}
test "void value type, slice of structs" {
const KV = struct { []const u8 };
const slice: []const KV = &[_]KV{
.{"these"},
.{"have"},
.{"nothing"},
.{"incommon"},
.{"samelen"},
};
const map = ComptimeStringMap(void, slice);
try testSet(map);
// Default comparison is case sensitive
try std.testing.expect(null == map.get("NOTHING"));
}
test "void value type, list literal of list literals" {
const map = ComptimeStringMap(void, .{
.{"these"},
.{"have"},
.{"nothing"},
.{"incommon"},
.{"samelen"},
});
try testSet(map);
}
fn testSet(comptime map: anytype) !void {
try std.testing.expectEqual({}, map.get("have").?);
try std.testing.expectEqual({}, map.get("nothing").?);
try std.testing.expect(null == map.get("missing"));
try std.testing.expectEqual({}, map.get("these").?);
try std.testing.expectEqual({}, map.get("samelen").?);
try std.testing.expect(!map.has("missing"));
try std.testing.expect(map.has("these"));
try std.testing.expect(null == map.get(""));
try std.testing.expect(null == map.get("averylongstringthathasnomatches"));
}
test "ComptimeStringMapWithEql" {
const map = ComptimeStringMapWithEql(TestEnum, .{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
}, eqlAsciiIgnoreCase);
try testMap(map);
try std.testing.expectEqual(TestEnum.A, map.get("HAVE").?);
try std.testing.expectEqual(TestEnum.E, map.get("SameLen").?);
try std.testing.expect(null == map.get("SameLength"));
try std.testing.expect(map.has("ThESe"));
}
test "empty" {
const m1 = ComptimeStringMap(usize, .{});
try std.testing.expect(null == m1.get("anything"));
const m2 = ComptimeStringMapWithEql(usize, .{}, eqlAsciiIgnoreCase);
try std.testing.expect(null == m2.get("anything"));
}
test "redundant entries" {
const map = ComptimeStringMap(TestEnum, .{
.{ "redundant", .D },
.{ "theNeedle", .A },
.{ "redundant", .B },
.{ "re" ++ "dundant", .C },
.{ "redun" ++ "dant", .E },
});
// No promises about which one you get:
try std.testing.expect(null != map.get("redundant"));
// Default map is not case sensitive:
try std.testing.expect(null == map.get("REDUNDANT"));
try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
}
test "redundant insensitive" {
const map = ComptimeStringMapWithEql(TestEnum, .{
.{ "redundant", .D },
.{ "theNeedle", .A },
.{ "redundanT", .B },
.{ "RE" ++ "dundant", .C },
.{ "redun" ++ "DANT", .E },
}, eqlAsciiIgnoreCase);
// No promises about which result you'll get ...
try std.testing.expect(null != map.get("REDUNDANT"));
try std.testing.expect(null != map.get("ReDuNdAnT"));
try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
}
test "comptime-only value" {
const map = std.ComptimeStringMap(type, .{
.{ "a", struct {
pub const foo = 1;
} },
.{ "b", struct {
pub const foo = 2;
} },
.{ "c", struct {
pub const foo = 3;
} },
});
try std.testing.expect(map.get("a").?.foo == 1);
try std.testing.expect(map.get("b").?.foo == 2);
try std.testing.expect(map.get("c").?.foo == 3);
try std.testing.expect(map.get("d") == null);
}

View File

@ -19,7 +19,7 @@ pub const Algorithm = enum {
md5WithRSAEncryption,
curveEd25519,
pub const map = std.ComptimeStringMap(Algorithm, .{
pub const map = std.StaticStringMap(Algorithm).initComptime(.{
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
@ -52,7 +52,7 @@ pub const AlgorithmCategory = enum {
X9_62_id_ecPublicKey,
curveEd25519,
pub const map = std.ComptimeStringMap(AlgorithmCategory, .{
pub const map = std.StaticStringMap(AlgorithmCategory).initComptime(.{
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
.{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
@ -73,7 +73,7 @@ pub const Attribute = enum {
pkcs9_emailAddress,
domainComponent,
pub const map = std.ComptimeStringMap(Attribute, .{
pub const map = std.StaticStringMap(Attribute).initComptime(.{
.{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
.{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
.{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
@ -94,7 +94,7 @@ pub const NamedCurve = enum {
secp521r1,
X9_62_prime256v1,
pub const map = std.ComptimeStringMap(NamedCurve, .{
pub const map = std.StaticStringMap(NamedCurve).initComptime(.{
.{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },
.{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x23 }, .secp521r1 },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
@ -130,7 +130,7 @@ pub const ExtensionId = enum {
netscape_cert_type,
netscape_comment,
pub const map = std.ComptimeStringMap(ExtensionId, .{
pub const map = std.StaticStringMap(ExtensionId).initComptime(.{
.{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
.{ &[_]u8{ 0x55, 0x1D, 0x01 }, .authority_key_identifier },
.{ &[_]u8{ 0x55, 0x1D, 0x07 }, .subject_alt_name },

View File

@ -1641,7 +1641,7 @@ test "walker" {
// iteration order of walker is undefined, so need lookup maps to check against
const expected_paths = std.ComptimeStringMap(void, .{
const expected_paths = std.StaticStringMap(void).initComptime(.{
.{"dir1"},
.{"dir2"},
.{"dir3"},
@ -1651,7 +1651,7 @@ test "walker" {
.{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"},
});
const expected_basenames = std.ComptimeStringMap(void, .{
const expected_basenames = std.StaticStringMap(void).initComptime(.{
.{"dir1"},
.{"dir2"},
.{"dir3"},
@ -1661,8 +1661,8 @@ test "walker" {
.{"subsub1"},
});
for (expected_paths.kvs) |kv| {
try tmp.dir.makePath(kv.key);
for (expected_paths.keys()) |key| {
try tmp.dir.makePath(key);
}
var walker = try tmp.dir.walk(testing.allocator);

View File

@ -1570,7 +1570,7 @@ pub const RequestOptions = struct {
};
fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {
const protocol_map = std.ComptimeStringMap(Connection.Protocol, .{
const protocol_map = std.StaticStringMap(Connection.Protocol).initComptime(.{
.{ "http", .plain },
.{ "ws", .plain },
.{ "https", .tls },

View File

@ -19,7 +19,7 @@ pub const isTag = @compileError("deprecated; use 'tagged_value == @field(E, tag_
/// Returns the variant of an enum type, `T`, which is named `str`, or `null` if no such variant exists.
pub fn stringToEnum(comptime T: type, str: []const u8) ?T {
// Using ComptimeStringMap here is more performant, but it will start to take too
// Using StaticStringMap here is more performant, but it will start to take too
// long to compile if the enum is large enough, due to the current limits of comptime
// performance when doing things like constructing lookup maps at comptime.
// TODO The '100' here is arbitrary and should be increased when possible:
@ -34,7 +34,7 @@ pub fn stringToEnum(comptime T: type, str: []const u8) ?T {
}
break :build_kvs kvs_array[0..];
};
const map = std.ComptimeStringMap(T, kvs);
const map = std.StaticStringMap(T).initComptime(kvs);
return map.get(str);
} else {
inline for (@typeInfo(T).Enum.fields) |enumField| {

View File

@ -0,0 +1,502 @@
const std = @import("std.zig");
const mem = std.mem;
/// Static string map optimized for small sets of disparate string keys.
/// Works by separating the keys by length at initialization and only checking
/// strings of equal length at runtime.
pub fn StaticStringMap(comptime V: type) type {
return StaticStringMapWithEql(V, defaultEql);
}
/// Like `std.mem.eql`, but takes advantage of the fact that the lengths
/// of `a` and `b` are known to be equal.
pub fn defaultEql(a: []const u8, b: []const u8) bool {
if (a.ptr == b.ptr) return true;
for (a, b) |a_elem, b_elem| {
if (a_elem != b_elem) return false;
}
return true;
}
/// Like `std.ascii.eqlIgnoreCase` but takes advantage of the fact that
/// the lengths of `a` and `b` are known to be equal.
pub fn eqlAsciiIgnoreCase(a: []const u8, b: []const u8) bool {
if (a.ptr == b.ptr) return true;
for (a, b) |a_c, b_c| {
if (std.ascii.toLower(a_c) != std.ascii.toLower(b_c)) return false;
}
return true;
}
/// StaticStringMap, but accepts an equality function (`eql`).
/// The `eql` function is only called to determine the equality
/// of equal length strings. Any strings that are not equal length
/// are never compared using the `eql` function.
pub fn StaticStringMapWithEql(
comptime V: type,
comptime eql: fn (a: []const u8, b: []const u8) bool,
) type {
return struct {
kvs: *const KVs = &empty_kvs,
len_indexes: [*]const u32 = &empty_len_indexes,
len_indexes_len: u32 = 0,
min_len: u32 = std.math.maxInt(u32),
max_len: u32 = 0,
pub const KV = struct {
key: []const u8,
value: V,
};
const Self = @This();
const KVs = struct {
keys: [*]const []const u8,
values: [*]const V,
len: u32,
};
const empty_kvs = KVs{
.keys = &empty_keys,
.values = &empty_vals,
.len = 0,
};
const empty_len_indexes = [0]u32{};
const empty_keys = [0][]const u8{};
const empty_vals = [0]V{};
/// Returns a map backed by static, comptime allocated memory.
///
/// `kvs_list` must be either a list of `struct { []const u8, V }`
/// (key-value pair) tuples, or a list of `struct { []const u8 }`
/// (only keys) tuples if `V` is `void`.
pub inline fn initComptime(comptime kvs_list: anytype) Self {
comptime {
@setEvalBranchQuota(30 * kvs_list.len);
var self = Self{};
if (kvs_list.len == 0)
return self;
var sorted_keys: [kvs_list.len][]const u8 = undefined;
var sorted_vals: [kvs_list.len]V = undefined;
self.initSortedKVs(kvs_list, &sorted_keys, &sorted_vals);
const final_keys = sorted_keys;
const final_vals = sorted_vals;
self.kvs = &.{
.keys = &final_keys,
.values = &final_vals,
.len = @intCast(kvs_list.len),
};
var len_indexes: [self.max_len + 1]u32 = undefined;
self.initLenIndexes(&len_indexes);
const final_len_indexes = len_indexes;
self.len_indexes = &final_len_indexes;
self.len_indexes_len = @intCast(len_indexes.len);
return self;
}
}
/// Returns a map backed by memory allocated with `allocator`.
///
/// Handles `kvs_list` the same way as `initComptime()`.
pub fn init(kvs_list: anytype, allocator: mem.Allocator) !Self {
var self = Self{};
if (kvs_list.len == 0)
return self;
const sorted_keys = try allocator.alloc([]const u8, kvs_list.len);
errdefer allocator.free(sorted_keys);
const sorted_vals = try allocator.alloc(V, kvs_list.len);
errdefer allocator.free(sorted_vals);
const kvs = try allocator.create(KVs);
errdefer allocator.destroy(kvs);
self.initSortedKVs(kvs_list, sorted_keys, sorted_vals);
kvs.* = .{
.keys = sorted_keys.ptr,
.values = sorted_vals.ptr,
.len = kvs_list.len,
};
self.kvs = kvs;
const len_indexes = try allocator.alloc(u32, self.max_len + 1);
self.initLenIndexes(len_indexes);
self.len_indexes = len_indexes.ptr;
self.len_indexes_len = @intCast(len_indexes.len);
return self;
}
/// this method should only be used with init() and not with initComptime().
pub fn deinit(self: Self, allocator: mem.Allocator) void {
allocator.free(self.len_indexes[0..self.len_indexes_len]);
allocator.free(self.kvs.keys[0..self.kvs.len]);
allocator.free(self.kvs.values[0..self.kvs.len]);
allocator.destroy(self.kvs);
}
const SortContext = struct {
keys: [][]const u8,
vals: []V,
pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
return ctx.keys[a].len < ctx.keys[b].len;
}
pub fn swap(ctx: @This(), a: usize, b: usize) void {
std.mem.swap([]const u8, &ctx.keys[a], &ctx.keys[b]);
std.mem.swap(V, &ctx.vals[a], &ctx.vals[b]);
}
};
fn initSortedKVs(
self: *Self,
kvs_list: anytype,
sorted_keys: [][]const u8,
sorted_vals: []V,
) void {
for (kvs_list, 0..) |kv, i| {
sorted_keys[i] = kv.@"0";
sorted_vals[i] = if (V == void) {} else kv.@"1";
self.min_len = @intCast(@min(self.min_len, kv.@"0".len));
self.max_len = @intCast(@max(self.max_len, kv.@"0".len));
}
mem.sortUnstableContext(0, sorted_keys.len, SortContext{
.keys = sorted_keys,
.vals = sorted_vals,
});
}
fn initLenIndexes(self: Self, len_indexes: []u32) void {
var len: usize = 0;
var i: u32 = 0;
while (len <= self.max_len) : (len += 1) {
// find the first keyword len == len
while (len > self.kvs.keys[i].len) {
i += 1;
}
len_indexes[len] = i;
}
}
/// Checks if the map has a value for the key.
pub fn has(self: Self, str: []const u8) bool {
return self.get(str) != null;
}
/// Returns the value for the key if any, else null.
pub fn get(self: Self, str: []const u8) ?V {
if (self.kvs.len == 0)
return null;
return self.kvs.values[self.getIndex(str) orelse return null];
}
pub fn getIndex(self: Self, str: []const u8) ?usize {
const kvs = self.kvs.*;
if (kvs.len == 0)
return null;
if (str.len < self.min_len or str.len > self.max_len)
return null;
var i = self.len_indexes[str.len];
while (true) {
const key = kvs.keys[i];
if (key.len != str.len)
return null;
if (eql(key, str))
return i;
i += 1;
if (i >= kvs.len)
return null;
}
}
/// Returns the longest key, value pair where key is a prefix of `str`
/// else null.
pub fn getLongestPrefix(self: Self, str: []const u8) ?KV {
if (self.kvs.len == 0)
return null;
const i = self.getLongestPrefixIndex(str) orelse return null;
const kvs = self.kvs.*;
return .{
.key = kvs.keys[i],
.value = kvs.values[i],
};
}
pub fn getLongestPrefixIndex(self: Self, str: []const u8) ?usize {
if (self.kvs.len == 0)
return null;
if (str.len < self.min_len)
return null;
var len = @min(self.max_len, str.len);
while (len >= self.min_len) : (len -= 1) {
if (self.getIndex(str[0..len])) |i|
return i;
}
return null;
}
pub fn keys(self: Self) []const []const u8 {
const kvs = self.kvs.*;
return kvs.keys[0..kvs.len];
}
pub fn values(self: Self) []const V {
const kvs = self.kvs.*;
return kvs.values[0..kvs.len];
}
};
}
const TestEnum = enum { A, B, C, D, E };
const TestMap = StaticStringMap(TestEnum);
const TestKV = struct { []const u8, TestEnum };
const TestMapVoid = StaticStringMap(void);
const TestKVVoid = struct { []const u8 };
const TestMapWithEql = StaticStringMapWithEql(TestEnum, eqlAsciiIgnoreCase);
const testing = std.testing;
const test_alloc = testing.allocator;
test "list literal of list literals" {
const slice = [_]TestKV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
};
const map = TestMap.initComptime(slice);
try testMap(map);
// Default comparison is case sensitive
try testing.expect(null == map.get("NOTHING"));
// runtime init(), deinit()
const map_rt = try TestMap.init(slice, test_alloc);
defer map_rt.deinit(test_alloc);
try testMap(map_rt);
// Default comparison is case sensitive
try testing.expect(null == map_rt.get("NOTHING"));
}
test "array of structs" {
const slice = [_]TestKV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
};
try testMap(TestMap.initComptime(slice));
}
test "slice of structs" {
const slice = [_]TestKV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
};
try testMap(TestMap.initComptime(slice));
}
fn testMap(map: anytype) !void {
try testing.expectEqual(TestEnum.A, map.get("have").?);
try testing.expectEqual(TestEnum.B, map.get("nothing").?);
try testing.expect(null == map.get("missing"));
try testing.expectEqual(TestEnum.D, map.get("these").?);
try testing.expectEqual(TestEnum.E, map.get("samelen").?);
try testing.expect(!map.has("missing"));
try testing.expect(map.has("these"));
try testing.expect(null == map.get(""));
try testing.expect(null == map.get("averylongstringthathasnomatches"));
}
test "void value type, slice of structs" {
const slice = [_]TestKVVoid{
.{"these"},
.{"have"},
.{"nothing"},
.{"incommon"},
.{"samelen"},
};
const map = TestMapVoid.initComptime(slice);
try testSet(map);
// Default comparison is case sensitive
try testing.expect(null == map.get("NOTHING"));
}
test "void value type, list literal of list literals" {
const slice = [_]TestKVVoid{
.{"these"},
.{"have"},
.{"nothing"},
.{"incommon"},
.{"samelen"},
};
try testSet(TestMapVoid.initComptime(slice));
}
fn testSet(map: TestMapVoid) !void {
try testing.expectEqual({}, map.get("have").?);
try testing.expectEqual({}, map.get("nothing").?);
try testing.expect(null == map.get("missing"));
try testing.expectEqual({}, map.get("these").?);
try testing.expectEqual({}, map.get("samelen").?);
try testing.expect(!map.has("missing"));
try testing.expect(map.has("these"));
try testing.expect(null == map.get(""));
try testing.expect(null == map.get("averylongstringthathasnomatches"));
}
fn testStaticStringMapWithEql(map: TestMapWithEql) !void {
try testMap(map);
try testing.expectEqual(TestEnum.A, map.get("HAVE").?);
try testing.expectEqual(TestEnum.E, map.get("SameLen").?);
try testing.expect(null == map.get("SameLength"));
try testing.expect(map.has("ThESe"));
}
test "StaticStringMapWithEql" {
const slice = [_]TestKV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
};
try testStaticStringMapWithEql(TestMapWithEql.initComptime(slice));
}
test "empty" {
const m1 = StaticStringMap(usize).initComptime(.{});
try testing.expect(null == m1.get("anything"));
const m2 = StaticStringMapWithEql(usize, eqlAsciiIgnoreCase).initComptime(.{});
try testing.expect(null == m2.get("anything"));
const m3 = try StaticStringMap(usize).init(.{}, test_alloc);
try testing.expect(null == m3.get("anything"));
const m4 = try StaticStringMapWithEql(usize, eqlAsciiIgnoreCase).init(.{}, test_alloc);
try testing.expect(null == m4.get("anything"));
}
test "redundant entries" {
const slice = [_]TestKV{
.{ "redundant", .D },
.{ "theNeedle", .A },
.{ "redundant", .B },
.{ "re" ++ "dundant", .C },
.{ "redun" ++ "dant", .E },
};
const map = TestMap.initComptime(slice);
// No promises about which one you get:
try testing.expect(null != map.get("redundant"));
// Default map is not case sensitive:
try testing.expect(null == map.get("REDUNDANT"));
try testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
}
test "redundant insensitive" {
const slice = [_]TestKV{
.{ "redundant", .D },
.{ "theNeedle", .A },
.{ "redundanT", .B },
.{ "RE" ++ "dundant", .C },
.{ "redun" ++ "DANT", .E },
};
const map = TestMapWithEql.initComptime(slice);
// No promises about which result you'll get ...
try testing.expect(null != map.get("REDUNDANT"));
try testing.expect(null != map.get("ReDuNdAnT"));
try testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
}
test "comptime-only value" {
const map = StaticStringMap(type).initComptime(.{
.{ "a", struct {
pub const foo = 1;
} },
.{ "b", struct {
pub const foo = 2;
} },
.{ "c", struct {
pub const foo = 3;
} },
});
try testing.expect(map.get("a").?.foo == 1);
try testing.expect(map.get("b").?.foo == 2);
try testing.expect(map.get("c").?.foo == 3);
try testing.expect(map.get("d") == null);
}
test "getLongestPrefix" {
const slice = [_]TestKV{
.{ "a", .A },
.{ "aa", .B },
.{ "aaa", .C },
.{ "aaaa", .D },
};
const map = TestMap.initComptime(slice);
try testing.expectEqual(null, map.getLongestPrefix(""));
try testing.expectEqual(null, map.getLongestPrefix("bar"));
try testing.expectEqualStrings("aaaa", map.getLongestPrefix("aaaabar").?.key);
try testing.expectEqualStrings("aaa", map.getLongestPrefix("aaabar").?.key);
}
test "getLongestPrefix2" {
const slice = [_]struct { []const u8, u8 }{
.{ "one", 1 },
.{ "two", 2 },
.{ "three", 3 },
.{ "four", 4 },
.{ "five", 5 },
.{ "six", 6 },
.{ "seven", 7 },
.{ "eight", 8 },
.{ "nine", 9 },
};
const map = StaticStringMap(u8).initComptime(slice);
try testing.expectEqual(1, map.get("one"));
try testing.expectEqual(null, map.get("o"));
try testing.expectEqual(null, map.get("onexxx"));
try testing.expectEqual(9, map.get("nine"));
try testing.expectEqual(null, map.get("n"));
try testing.expectEqual(null, map.get("ninexxx"));
try testing.expectEqual(null, map.get("xxx"));
try testing.expectEqual(1, map.getLongestPrefix("one").?.value);
try testing.expectEqual(1, map.getLongestPrefix("onexxx").?.value);
try testing.expectEqual(null, map.getLongestPrefix("o"));
try testing.expectEqual(null, map.getLongestPrefix("on"));
try testing.expectEqual(9, map.getLongestPrefix("nine").?.value);
try testing.expectEqual(9, map.getLongestPrefix("ninexxx").?.value);
try testing.expectEqual(null, map.getLongestPrefix("n"));
try testing.expectEqual(null, map.getLongestPrefix("xxx"));
}
test "long kvs_list doesn't exceed @setEvalBranchQuota" {
_ = TestMapVoid.initComptime([1]TestKVVoid{.{"x"}} ** 1_000);
}

View File

@ -16,8 +16,8 @@ pub const BufMap = @import("buf_map.zig").BufMap;
pub const BufSet = @import("buf_set.zig").BufSet;
/// Deprecated: use `process.Child`.
pub const ChildProcess = @import("child_process.zig").ChildProcess;
pub const ComptimeStringMap = comptime_string_map.ComptimeStringMap;
pub const ComptimeStringMapWithEql = comptime_string_map.ComptimeStringMapWithEql;
pub const StaticStringMap = static_string_map.StaticStringMap;
pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;
pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;
pub const DynLib = @import("dynamic_library.zig").DynLib;
pub const DynamicBitSet = bit_set.DynamicBitSet;
@ -62,7 +62,7 @@ pub const builtin = @import("builtin.zig");
pub const c = @import("c.zig");
pub const coff = @import("coff.zig");
pub const compress = @import("compress.zig");
pub const comptime_string_map = @import("comptime_string_map.zig");
pub const static_string_map = @import("static_string_map.zig");
pub const crypto = @import("crypto.zig");
pub const debug = @import("debug.zig");
pub const dwarf = @import("dwarf.zig");

View File

@ -10125,7 +10125,7 @@ fn calleeExpr(
}
}
const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{
const primitive_instrs = std.StaticStringMap(Zir.Inst.Ref).initComptime(.{
.{ "anyerror", .anyerror_type },
.{ "anyframe", .anyframe_type },
.{ "anyopaque", .anyopaque_type },
@ -10173,14 +10173,14 @@ const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{
comptime {
// These checks ensure that std.zig.primitives stays in sync with the primitive->Zir map.
const primitives = std.zig.primitives;
for (primitive_instrs.kvs) |kv| {
if (!primitives.isPrimitive(kv.key)) {
@compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(kv.value) ++ "'");
for (primitive_instrs.keys(), primitive_instrs.values()) |key, value| {
if (!primitives.isPrimitive(key)) {
@compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(value) ++ "'");
}
}
for (primitives.names.kvs) |kv| {
if (primitive_instrs.get(kv.key) == null) {
@compileError("std.zig.primitives entry '" ++ kv.key ++ "' does not have a corresponding Zir instr");
for (primitives.names.keys()) |key| {
if (primitive_instrs.get(key) == null) {
@compileError("std.zig.primitives entry '" ++ key ++ "' does not have a corresponding Zir instr");
}
}
}

View File

@ -160,7 +160,7 @@ param_count: ?u8,
pub const list = list: {
@setEvalBranchQuota(3000);
break :list std.ComptimeStringMap(@This(), .{
break :list std.StaticStringMap(@This()).initComptime(.{
.{
"@addWithOverflow",
.{

View File

@ -2,7 +2,7 @@ const std = @import("std");
/// Set of primitive type and value names.
/// Does not include `_` or integer type names.
pub const names = std.ComptimeStringMap(void, .{
pub const names = std.StaticStringMap(void).initComptime(.{
.{"anyerror"},
.{"anyframe"},
.{"anyopaque"},

View File

@ -2886,11 +2886,11 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote
// If we read the whole thing, we have to do further checks.
const longest_keyword_or_primitive_len = comptime blk: {
var longest = 0;
for (primitives.names.kvs) |kv| {
if (kv.key.len > longest) longest = kv.key.len;
for (primitives.names.keys()) |key| {
if (key.len > longest) longest = key.len;
}
for (std.zig.Token.keywords.kvs) |kv| {
if (kv.key.len > longest) longest = kv.key.len;
for (std.zig.Token.keywords.keys()) |key| {
if (key.len > longest) longest = key.len;
}
break :blk longest;
};

View File

@ -9,7 +9,7 @@ pub const Token = struct {
end: usize,
};
pub const keywords = std.ComptimeStringMap(Tag, .{
pub const keywords = std.StaticStringMap(Tag).initComptime(.{
.{ "addrspace", .keyword_addrspace },
.{ "align", .keyword_align },
.{ "allowzero", .keyword_allowzero },

View File

@ -265,7 +265,7 @@ pub const CRTFile = struct {
/// Supported languages for "zig clang -x <lang>".
/// Loosely based on llvm-project/clang/include/clang/Driver/Types.def
pub const LangToExt = std.ComptimeStringMap(FileExt, .{
pub const LangToExt = std.StaticStringMap(FileExt).initComptime(.{
.{ "c", .c },
.{ "c-header", .h },
.{ "c++", .cpp },

View File

@ -116,7 +116,7 @@ const ValueRenderLocation = enum {
const BuiltinInfo = enum { none, bits };
const reserved_idents = std.ComptimeStringMap(void, .{
const reserved_idents = std.StaticStringMap(void).initComptime(.{
// C language
.{ "alignas", {
@setEvalBranchQuota(4000);

View File

@ -244,7 +244,7 @@ pub const Feature = struct {
}
};
pub const known_features = std.ComptimeStringMap(Feature.Tag, .{
pub const known_features = std.StaticStringMap(Feature.Tag).initComptime(.{
.{ "atomics", .atomics },
.{ "bulk-memory", .bulk_memory },
.{ "exception-handling", .exception_handling },

View File

@ -671,7 +671,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co
return addTopLevelDecl(c, var_name, node);
}
const builtin_typedef_map = std.ComptimeStringMap([]const u8, .{
const builtin_typedef_map = std.StaticStringMap([]const u8).initComptime(.{
.{ "uint8_t", "u8" },
.{ "int8_t", "i8" },
.{ "uint16_t", "u16" },

View File

@ -993,7 +993,7 @@ const TestManifest = struct {
config_map: std.StringHashMap([]const u8),
trailing_bytes: []const u8 = "",
const valid_keys = std.ComptimeStringMap(void, .{
const valid_keys = std.StaticStringMap(void).initComptime(.{
.{ "is_test", {} },
.{ "output_mode", {} },
.{ "target", {} },

View File

@ -45,7 +45,7 @@ const OperandKindMap = std.ArrayHashMap(StringPair, OperandKind, StringPairConte
/// Khronos made it so that these names are not defined explicitly, so
/// we need to hardcode it (like they did).
/// See https://github.com/KhronosGroup/SPIRV-Registry/
const set_names = std.ComptimeStringMap([]const u8, .{
const set_names = std.StaticStringMap([]const u8).initComptime(.{
.{ "opencl.std.100", "OpenCL.std" },
.{ "glsl.std.450", "GLSL.std.450" },
.{ "opencl.debuginfo.100", "OpenCL.DebugInfo.100" },

View File

@ -9,7 +9,7 @@ const fmt = std.fmt;
const zig = std.zig;
const fs = std.fs;
const stdlib_renames = std.ComptimeStringMap([]const u8, .{
const stdlib_renames = std.StaticStringMap([]const u8).initComptime(.{
// Most 64-bit archs.
.{ "newfstatat", "fstatat64" },
// POWER.