authorgravatar for twostepted@gmail.comTravis Staloch <twostepted@gmail.com> 2024-04-20 23:14:39-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-04-22 15:31:41-07:00
log8af59d1f98266bd70b3afb44d196bbd151cedf22
tree64b0c48f2b2d222629acbd5698c1f5310fdc708f
parentfefdbca6e62145a20777789961262f15c2bf6cbe

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.

25 files changed, 608 insertions(+), 387 deletions(-)

CMakeLists.txt+1-1
......@@ -222,7 +222,7 @@ set(ZIG_STAGE2_SOURCES
222222 "${CMAKE_SOURCE_DIR}/lib/std/c/linux.zig"
223223 "${CMAKE_SOURCE_DIR}/lib/std/child_process.zig"
224224 "${CMAKE_SOURCE_DIR}/lib/std/coff.zig"
225 "${CMAKE_SOURCE_DIR}/lib/std/comptime_string_map.zig"
225 "${CMAKE_SOURCE_DIR}/lib/std/static_string_map.zig"
226226 "${CMAKE_SOURCE_DIR}/lib/std/crypto.zig"
227227 "${CMAKE_SOURCE_DIR}/lib/std/crypto/blake3.zig"
228228 "${CMAKE_SOURCE_DIR}/lib/std/crypto/siphash.zig"
lib/compiler/aro/aro/LangOpts.zig+1-1
......@@ -47,7 +47,7 @@ pub const Standard = enum {
4747 /// Working Draft for ISO C23 with GNU extensions
4848 gnu23,
4949
50 const NameMap = std.ComptimeStringMap(Standard, .{
50 const NameMap = std.StaticStringMap(Standard).initComptime(.{
5151 .{ "c89", .c89 }, .{ "c90", .c89 }, .{ "iso9899:1990", .c89 },
5252 .{ "iso9899:199409", .iso9899 }, .{ "gnu89", .gnu89 }, .{ "gnu90", .gnu89 },
5353 .{ "c99", .c99 }, .{ "iso9899:1999", .c99 }, .{ "c9x", .c99 },
lib/compiler/aro/aro/Preprocessor.zig+1-1
......@@ -1709,7 +1709,7 @@ fn expandFuncMacro(
17091709 }
17101710 if (!pp.comp.langopts.standard.atLeast(.c23)) break :res not_found;
17111711
1712 const attrs = std.ComptimeStringMap([]const u8, .{
1712 const attrs = std.StaticStringMap([]const u8).initComptime(.{
17131713 .{ "deprecated", "201904L\n" },
17141714 .{ "fallthrough", "201904L\n" },
17151715 .{ "maybe_unused", "201904L\n" },
lib/compiler/aro/aro/Tokenizer.zig+1-1
......@@ -872,7 +872,7 @@ pub const Token = struct {
872872 };
873873 }
874874
875 const all_kws = std.ComptimeStringMap(Id, .{
875 const all_kws = std.StaticStringMap(Id).initComptime(.{
876876 .{ "auto", auto: {
877877 @setEvalBranchQuota(3000);
878878 break :auto .keyword_auto;
lib/compiler/resinator/errors.zig+1-1
......@@ -240,7 +240,7 @@ pub const ErrorDetails = struct {
240240 // see https://github.com/ziglang/zig/issues/15395
241241 _: u26 = 0,
242242
243 pub const strings = std.ComptimeStringMap([]const u8, .{
243 pub const strings = std.StaticStringMap([]const u8).initComptime(.{
244244 .{ "number", "number" },
245245 .{ "number_expression", "number expression" },
246246 .{ "string_literal", "quoted string literal" },
lib/compiler/resinator/rc.zig+65-26
......@@ -47,7 +47,10 @@ pub const Resource = enum {
4747 fontdir_num,
4848 manifest_num,
4949
50 const map = std.ComptimeStringMapWithEql(Resource, .{
50 const map = std.StaticStringMapWithEql(
51 Resource,
52 std.static_string_map.eqlAsciiIgnoreCase,
53 ).initComptime(.{
5154 .{ "ACCELERATORS", .accelerators },
5255 .{ "BITMAP", .bitmap },
5356 .{ "CURSOR", .cursor },
......@@ -67,7 +70,7 @@ pub const Resource = enum {
6770 .{ "TOOLBAR", .toolbar },
6871 .{ "VERSIONINFO", .versioninfo },
6972 .{ "VXD", .vxd },
70 }, std.comptime_string_map.eqlAsciiIgnoreCase);
73 });
7174
7275 pub fn fromString(bytes: SourceBytes) Resource {
7376 const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(bytes);
......@@ -157,20 +160,26 @@ pub const OptionalStatements = enum {
157160 menu,
158161 style,
159162
160 pub const map = std.ComptimeStringMapWithEql(OptionalStatements, .{
163 pub const map = std.StaticStringMapWithEql(
164 OptionalStatements,
165 std.static_string_map.eqlAsciiIgnoreCase,
166 ).initComptime(.{
161167 .{ "CHARACTERISTICS", .characteristics },
162168 .{ "LANGUAGE", .language },
163169 .{ "VERSION", .version },
164 }, std.comptime_string_map.eqlAsciiIgnoreCase);
170 });
165171
166 pub const dialog_map = std.ComptimeStringMapWithEql(OptionalStatements, .{
172 pub const dialog_map = std.StaticStringMapWithEql(
173 OptionalStatements,
174 std.static_string_map.eqlAsciiIgnoreCase,
175 ).initComptime(.{
167176 .{ "CAPTION", .caption },
168177 .{ "CLASS", .class },
169178 .{ "EXSTYLE", .exstyle },
170179 .{ "FONT", .font },
171180 .{ "MENU", .menu },
172181 .{ "STYLE", .style },
173 }, std.comptime_string_map.eqlAsciiIgnoreCase);
182 });
174183};
175184
176185pub const Control = enum {
......@@ -197,7 +206,10 @@ pub const Control = enum {
197206 state3,
198207 userbutton,
199208
200 pub const map = std.ComptimeStringMapWithEql(Control, .{
209 pub const map = std.StaticStringMapWithEql(
210 Control,
211 std.static_string_map.eqlAsciiIgnoreCase,
212 ).initComptime(.{
201213 .{ "AUTO3STATE", .auto3state },
202214 .{ "AUTOCHECKBOX", .autocheckbox },
203215 .{ "AUTORADIOBUTTON", .autoradiobutton },
......@@ -220,7 +232,7 @@ pub const Control = enum {
220232 .{ "SCROLLBAR", .scrollbar },
221233 .{ "STATE3", .state3 },
222234 .{ "USERBUTTON", .userbutton },
223 }, std.comptime_string_map.eqlAsciiIgnoreCase);
235 });
224236
225237 pub fn hasTextParam(control: Control) bool {
226238 switch (control) {
......@@ -231,14 +243,17 @@ pub const Control = enum {
231243};
232244
233245pub const ControlClass = struct {
234 pub const map = std.ComptimeStringMapWithEql(res.ControlClass, .{
246 pub const map = std.StaticStringMapWithEql(
247 res.ControlClass,
248 std.static_string_map.eqlAsciiIgnoreCase,
249 ).initComptime(.{
235250 .{ "BUTTON", .button },
236251 .{ "EDIT", .edit },
237252 .{ "STATIC", .static },
238253 .{ "LISTBOX", .listbox },
239254 .{ "SCROLLBAR", .scrollbar },
240255 .{ "COMBOBOX", .combobox },
241 }, std.comptime_string_map.eqlAsciiIgnoreCase);
256 });
242257
243258 /// Like `map.get` but works on WTF16 strings, for use with parsed
244259 /// string literals ("BUTTON", or even "\x42UTTON")
......@@ -280,10 +295,13 @@ pub const MenuItem = enum {
280295 menuitem,
281296 popup,
282297
283 pub const map = std.ComptimeStringMapWithEql(MenuItem, .{
298 pub const map = std.StaticStringMapWithEql(
299 MenuItem,
300 std.static_string_map.eqlAsciiIgnoreCase,
301 ).initComptime(.{
284302 .{ "MENUITEM", .menuitem },
285303 .{ "POPUP", .popup },
286 }, std.comptime_string_map.eqlAsciiIgnoreCase);
304 });
287305
288306 pub fn isSeparator(bytes: []const u8) bool {
289307 return std.ascii.eqlIgnoreCase(bytes, "SEPARATOR");
......@@ -297,14 +315,17 @@ pub const MenuItem = enum {
297315 menubarbreak,
298316 menubreak,
299317
300 pub const map = std.ComptimeStringMapWithEql(Option, .{
318 pub const map = std.StaticStringMapWithEql(
319 Option,
320 std.static_string_map.eqlAsciiIgnoreCase,
321 ).initComptime(.{
301322 .{ "CHECKED", .checked },
302323 .{ "GRAYED", .grayed },
303324 .{ "HELP", .help },
304325 .{ "INACTIVE", .inactive },
305326 .{ "MENUBARBREAK", .menubarbreak },
306327 .{ "MENUBREAK", .menubreak },
307 }, std.comptime_string_map.eqlAsciiIgnoreCase);
328 });
308329 };
309330};
310331
......@@ -312,10 +333,13 @@ pub const ToolbarButton = enum {
312333 button,
313334 separator,
314335
315 pub const map = std.ComptimeStringMapWithEql(ToolbarButton, .{
336 pub const map = std.StaticStringMapWithEql(
337 ToolbarButton,
338 std.static_string_map.eqlAsciiIgnoreCase,
339 ).initComptime(.{
316340 .{ "BUTTON", .button },
317341 .{ "SEPARATOR", .separator },
318 }, std.comptime_string_map.eqlAsciiIgnoreCase);
342 });
319343};
320344
321345pub const VersionInfo = enum {
......@@ -327,7 +351,10 @@ pub const VersionInfo = enum {
327351 file_type,
328352 file_subtype,
329353
330 pub const map = std.ComptimeStringMapWithEql(VersionInfo, .{
354 pub const map = std.StaticStringMapWithEql(
355 VersionInfo,
356 std.static_string_map.eqlAsciiIgnoreCase,
357 ).initComptime(.{
331358 .{ "FILEVERSION", .file_version },
332359 .{ "PRODUCTVERSION", .product_version },
333360 .{ "FILEFLAGSMASK", .file_flags_mask },
......@@ -335,17 +362,20 @@ pub const VersionInfo = enum {
335362 .{ "FILEOS", .file_os },
336363 .{ "FILETYPE", .file_type },
337364 .{ "FILESUBTYPE", .file_subtype },
338 }, std.comptime_string_map.eqlAsciiIgnoreCase);
365 });
339366};
340367
341368pub const VersionBlock = enum {
342369 block,
343370 value,
344371
345 pub const map = std.ComptimeStringMapWithEql(VersionBlock, .{
372 pub const map = std.StaticStringMapWithEql(
373 VersionBlock,
374 std.static_string_map.eqlAsciiIgnoreCase,
375 ).initComptime(.{
346376 .{ "BLOCK", .block },
347377 .{ "VALUE", .value },
348 }, std.comptime_string_map.eqlAsciiIgnoreCase);
378 });
349379};
350380
351381/// 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 {
356386 characteristics,
357387 stringtable,
358388
359 pub const map = std.ComptimeStringMapWithEql(TopLevelKeywords, .{
389 pub const map = std.StaticStringMapWithEql(
390 TopLevelKeywords,
391 std.static_string_map.eqlAsciiIgnoreCase,
392 ).initComptime(.{
360393 .{ "LANGUAGE", .language },
361394 .{ "VERSION", .version },
362395 .{ "CHARACTERISTICS", .characteristics },
363396 .{ "STRINGTABLE", .stringtable },
364 }, std.comptime_string_map.eqlAsciiIgnoreCase);
397 });
365398};
366399
367400pub const CommonResourceAttributes = enum {
......@@ -375,7 +408,10 @@ pub const CommonResourceAttributes = enum {
375408 shared,
376409 nonshared,
377410
378 pub const map = std.ComptimeStringMapWithEql(CommonResourceAttributes, .{
411 pub const map = std.StaticStringMapWithEql(
412 CommonResourceAttributes,
413 std.static_string_map.eqlAsciiIgnoreCase,
414 ).initComptime(.{
379415 .{ "PRELOAD", .preload },
380416 .{ "LOADONCALL", .loadoncall },
381417 .{ "FIXED", .fixed },
......@@ -385,7 +421,7 @@ pub const CommonResourceAttributes = enum {
385421 .{ "IMPURE", .impure },
386422 .{ "SHARED", .shared },
387423 .{ "NONSHARED", .nonshared },
388 }, std.comptime_string_map.eqlAsciiIgnoreCase);
424 });
389425};
390426
391427pub const AcceleratorTypeAndOptions = enum {
......@@ -396,12 +432,15 @@ pub const AcceleratorTypeAndOptions = enum {
396432 shift,
397433 control,
398434
399 pub const map = std.ComptimeStringMapWithEql(AcceleratorTypeAndOptions, .{
435 pub const map = std.StaticStringMapWithEql(
436 AcceleratorTypeAndOptions,
437 std.static_string_map.eqlAsciiIgnoreCase,
438 ).initComptime(.{
400439 .{ "VIRTKEY", .virtkey },
401440 .{ "ASCII", .ascii },
402441 .{ "NOINVERT", .noinvert },
403442 .{ "ALT", .alt },
404443 .{ "SHIFT", .shift },
405444 .{ "CONTROL", .control },
406 }, std.comptime_string_map.eqlAsciiIgnoreCase);
445 });
407446};
lib/std/comptime_string_map.zig deleted-320
......@@ -1,320 +0,0 @@
1const std = @import("std.zig");
2const mem = std.mem;
3
4/// Comptime string map optimized for small sets of disparate string keys.
5/// Works by separating the keys by length at comptime and only checking strings of
6/// equal length at runtime.
7///
8/// `kvs_list` expects a list of `struct { []const u8, V }` (key-value pair) tuples.
9/// You can pass `struct { []const u8 }` (only keys) tuples if `V` is `void`.
10pub fn ComptimeStringMap(
11 comptime V: type,
12 comptime kvs_list: anytype,
13) type {
14 return ComptimeStringMapWithEql(V, kvs_list, defaultEql);
15}
16
17/// Like `std.mem.eql`, but takes advantage of the fact that the lengths
18/// of `a` and `b` are known to be equal.
19pub fn defaultEql(a: []const u8, b: []const u8) bool {
20 if (a.ptr == b.ptr) return true;
21 for (a, b) |a_elem, b_elem| {
22 if (a_elem != b_elem) return false;
23 }
24 return true;
25}
26
27/// Like `std.ascii.eqlIgnoreCase` but takes advantage of the fact that
28/// the lengths of `a` and `b` are known to be equal.
29pub fn eqlAsciiIgnoreCase(a: []const u8, b: []const u8) bool {
30 if (a.ptr == b.ptr) return true;
31 for (a, b) |a_c, b_c| {
32 if (std.ascii.toLower(a_c) != std.ascii.toLower(b_c)) return false;
33 }
34 return true;
35}
36
37/// ComptimeStringMap, but accepts an equality function (`eql`).
38/// The `eql` function is only called to determine the equality
39/// of equal length strings. Any strings that are not equal length
40/// are never compared using the `eql` function.
41pub fn ComptimeStringMapWithEql(
42 comptime V: type,
43 comptime kvs_list: anytype,
44 comptime eql: fn (a: []const u8, b: []const u8) bool,
45) type {
46 const empty_list = kvs_list.len == 0;
47 const precomputed = blk: {
48 @setEvalBranchQuota(1500);
49 const KV = struct {
50 key: []const u8,
51 value: V,
52 };
53 if (empty_list)
54 break :blk .{};
55 var sorted_kvs: [kvs_list.len]KV = undefined;
56 for (kvs_list, 0..) |kv, i| {
57 if (V != void) {
58 sorted_kvs[i] = .{ .key = kv.@"0", .value = kv.@"1" };
59 } else {
60 sorted_kvs[i] = .{ .key = kv.@"0", .value = {} };
61 }
62 }
63
64 const SortContext = struct {
65 kvs: []KV,
66
67 pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
68 return ctx.kvs[a].key.len < ctx.kvs[b].key.len;
69 }
70
71 pub fn swap(ctx: @This(), a: usize, b: usize) void {
72 return std.mem.swap(KV, &ctx.kvs[a], &ctx.kvs[b]);
73 }
74 };
75 mem.sortUnstableContext(0, sorted_kvs.len, SortContext{ .kvs = &sorted_kvs });
76
77 const min_len = sorted_kvs[0].key.len;
78 const max_len = sorted_kvs[sorted_kvs.len - 1].key.len;
79 var len_indexes: [max_len + 1]usize = undefined;
80 var len: usize = 0;
81 var i: usize = 0;
82 while (len <= max_len) : (len += 1) {
83 // find the first keyword len == len
84 while (len > sorted_kvs[i].key.len) {
85 i += 1;
86 }
87 len_indexes[len] = i;
88 }
89 break :blk .{
90 .min_len = min_len,
91 .max_len = max_len,
92 .sorted_kvs = sorted_kvs,
93 .len_indexes = len_indexes,
94 };
95 };
96
97 return struct {
98 /// Array of `struct { key: []const u8, value: V }` where `value` is `void{}` if `V` is `void`.
99 /// Sorted by `key` length.
100 pub const kvs = precomputed.sorted_kvs;
101
102 /// Checks if the map has a value for the key.
103 pub fn has(str: []const u8) bool {
104 return get(str) != null;
105 }
106
107 /// Returns the value for the key if any, else null.
108 pub fn get(str: []const u8) ?V {
109 if (empty_list)
110 return null;
111
112 return precomputed.sorted_kvs[getIndex(str) orelse return null].value;
113 }
114
115 pub fn getIndex(str: []const u8) ?usize {
116 if (empty_list)
117 return null;
118
119 if (str.len < precomputed.min_len or str.len > precomputed.max_len)
120 return null;
121
122 var i = precomputed.len_indexes[str.len];
123 while (true) {
124 const kv = precomputed.sorted_kvs[i];
125 if (kv.key.len != str.len)
126 return null;
127 if (eql(kv.key, str))
128 return i;
129 i += 1;
130 if (i >= precomputed.sorted_kvs.len)
131 return null;
132 }
133 }
134 };
135}
136
137const TestEnum = enum {
138 A,
139 B,
140 C,
141 D,
142 E,
143};
144
145test "list literal of list literals" {
146 const map = ComptimeStringMap(TestEnum, .{
147 .{ "these", .D },
148 .{ "have", .A },
149 .{ "nothing", .B },
150 .{ "incommon", .C },
151 .{ "samelen", .E },
152 });
153
154 try testMap(map);
155
156 // Default comparison is case sensitive
157 try std.testing.expect(null == map.get("NOTHING"));
158}
159
160test "array of structs" {
161 const KV = struct { []const u8, TestEnum };
162 const map = ComptimeStringMap(TestEnum, [_]KV{
163 .{ "these", .D },
164 .{ "have", .A },
165 .{ "nothing", .B },
166 .{ "incommon", .C },
167 .{ "samelen", .E },
168 });
169
170 try testMap(map);
171}
172
173test "slice of structs" {
174 const KV = struct { []const u8, TestEnum };
175 const slice: []const KV = &[_]KV{
176 .{ "these", .D },
177 .{ "have", .A },
178 .{ "nothing", .B },
179 .{ "incommon", .C },
180 .{ "samelen", .E },
181 };
182 const map = ComptimeStringMap(TestEnum, slice);
183
184 try testMap(map);
185}
186
187fn testMap(comptime map: anytype) !void {
188 try std.testing.expectEqual(TestEnum.A, map.get("have").?);
189 try std.testing.expectEqual(TestEnum.B, map.get("nothing").?);
190 try std.testing.expect(null == map.get("missing"));
191 try std.testing.expectEqual(TestEnum.D, map.get("these").?);
192 try std.testing.expectEqual(TestEnum.E, map.get("samelen").?);
193
194 try std.testing.expect(!map.has("missing"));
195 try std.testing.expect(map.has("these"));
196
197 try std.testing.expect(null == map.get(""));
198 try std.testing.expect(null == map.get("averylongstringthathasnomatches"));
199}
200
201test "void value type, slice of structs" {
202 const KV = struct { []const u8 };
203 const slice: []const KV = &[_]KV{
204 .{"these"},
205 .{"have"},
206 .{"nothing"},
207 .{"incommon"},
208 .{"samelen"},
209 };
210 const map = ComptimeStringMap(void, slice);
211
212 try testSet(map);
213
214 // Default comparison is case sensitive
215 try std.testing.expect(null == map.get("NOTHING"));
216}
217
218test "void value type, list literal of list literals" {
219 const map = ComptimeStringMap(void, .{
220 .{"these"},
221 .{"have"},
222 .{"nothing"},
223 .{"incommon"},
224 .{"samelen"},
225 });
226
227 try testSet(map);
228}
229
230fn testSet(comptime map: anytype) !void {
231 try std.testing.expectEqual({}, map.get("have").?);
232 try std.testing.expectEqual({}, map.get("nothing").?);
233 try std.testing.expect(null == map.get("missing"));
234 try std.testing.expectEqual({}, map.get("these").?);
235 try std.testing.expectEqual({}, map.get("samelen").?);
236
237 try std.testing.expect(!map.has("missing"));
238 try std.testing.expect(map.has("these"));
239
240 try std.testing.expect(null == map.get(""));
241 try std.testing.expect(null == map.get("averylongstringthathasnomatches"));
242}
243
244test "ComptimeStringMapWithEql" {
245 const map = ComptimeStringMapWithEql(TestEnum, .{
246 .{ "these", .D },
247 .{ "have", .A },
248 .{ "nothing", .B },
249 .{ "incommon", .C },
250 .{ "samelen", .E },
251 }, eqlAsciiIgnoreCase);
252
253 try testMap(map);
254 try std.testing.expectEqual(TestEnum.A, map.get("HAVE").?);
255 try std.testing.expectEqual(TestEnum.E, map.get("SameLen").?);
256 try std.testing.expect(null == map.get("SameLength"));
257
258 try std.testing.expect(map.has("ThESe"));
259}
260
261test "empty" {
262 const m1 = ComptimeStringMap(usize, .{});
263 try std.testing.expect(null == m1.get("anything"));
264
265 const m2 = ComptimeStringMapWithEql(usize, .{}, eqlAsciiIgnoreCase);
266 try std.testing.expect(null == m2.get("anything"));
267}
268
269test "redundant entries" {
270 const map = ComptimeStringMap(TestEnum, .{
271 .{ "redundant", .D },
272 .{ "theNeedle", .A },
273 .{ "redundant", .B },
274 .{ "re" ++ "dundant", .C },
275 .{ "redun" ++ "dant", .E },
276 });
277
278 // No promises about which one you get:
279 try std.testing.expect(null != map.get("redundant"));
280
281 // Default map is not case sensitive:
282 try std.testing.expect(null == map.get("REDUNDANT"));
283
284 try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
285}
286
287test "redundant insensitive" {
288 const map = ComptimeStringMapWithEql(TestEnum, .{
289 .{ "redundant", .D },
290 .{ "theNeedle", .A },
291 .{ "redundanT", .B },
292 .{ "RE" ++ "dundant", .C },
293 .{ "redun" ++ "DANT", .E },
294 }, eqlAsciiIgnoreCase);
295
296 // No promises about which result you'll get ...
297 try std.testing.expect(null != map.get("REDUNDANT"));
298 try std.testing.expect(null != map.get("ReDuNdAnT"));
299
300 try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
301}
302
303test "comptime-only value" {
304 const map = std.ComptimeStringMap(type, .{
305 .{ "a", struct {
306 pub const foo = 1;
307 } },
308 .{ "b", struct {
309 pub const foo = 2;
310 } },
311 .{ "c", struct {
312 pub const foo = 3;
313 } },
314 });
315
316 try std.testing.expect(map.get("a").?.foo == 1);
317 try std.testing.expect(map.get("b").?.foo == 2);
318 try std.testing.expect(map.get("c").?.foo == 3);
319 try std.testing.expect(map.get("d") == null);
320}
lib/std/crypto/Certificate.zig+5-5
......@@ -19,7 +19,7 @@ pub const Algorithm = enum {
1919 md5WithRSAEncryption,
2020 curveEd25519,
2121
22 pub const map = std.ComptimeStringMap(Algorithm, .{
22 pub const map = std.StaticStringMap(Algorithm).initComptime(.{
2323 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
2424 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
2525 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
......@@ -52,7 +52,7 @@ pub const AlgorithmCategory = enum {
5252 X9_62_id_ecPublicKey,
5353 curveEd25519,
5454
55 pub const map = std.ComptimeStringMap(AlgorithmCategory, .{
55 pub const map = std.StaticStringMap(AlgorithmCategory).initComptime(.{
5656 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
5757 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
5858 .{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
......@@ -73,7 +73,7 @@ pub const Attribute = enum {
7373 pkcs9_emailAddress,
7474 domainComponent,
7575
76 pub const map = std.ComptimeStringMap(Attribute, .{
76 pub const map = std.StaticStringMap(Attribute).initComptime(.{
7777 .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
7878 .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
7979 .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
......@@ -94,7 +94,7 @@ pub const NamedCurve = enum {
9494 secp521r1,
9595 X9_62_prime256v1,
9696
97 pub const map = std.ComptimeStringMap(NamedCurve, .{
97 pub const map = std.StaticStringMap(NamedCurve).initComptime(.{
9898 .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },
9999 .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x23 }, .secp521r1 },
100100 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
......@@ -130,7 +130,7 @@ pub const ExtensionId = enum {
130130 netscape_cert_type,
131131 netscape_comment,
132132
133 pub const map = std.ComptimeStringMap(ExtensionId, .{
133 pub const map = std.StaticStringMap(ExtensionId).initComptime(.{
134134 .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
135135 .{ &[_]u8{ 0x55, 0x1D, 0x01 }, .authority_key_identifier },
136136 .{ &[_]u8{ 0x55, 0x1D, 0x07 }, .subject_alt_name },
lib/std/fs/test.zig+4-4
......@@ -1641,7 +1641,7 @@ test "walker" {
16411641
16421642 // iteration order of walker is undefined, so need lookup maps to check against
16431643
1644 const expected_paths = std.ComptimeStringMap(void, .{
1644 const expected_paths = std.StaticStringMap(void).initComptime(.{
16451645 .{"dir1"},
16461646 .{"dir2"},
16471647 .{"dir3"},
......@@ -1651,7 +1651,7 @@ test "walker" {
16511651 .{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"},
16521652 });
16531653
1654 const expected_basenames = std.ComptimeStringMap(void, .{
1654 const expected_basenames = std.StaticStringMap(void).initComptime(.{
16551655 .{"dir1"},
16561656 .{"dir2"},
16571657 .{"dir3"},
......@@ -1661,8 +1661,8 @@ test "walker" {
16611661 .{"subsub1"},
16621662 });
16631663
1664 for (expected_paths.kvs) |kv| {
1665 try tmp.dir.makePath(kv.key);
1664 for (expected_paths.keys()) |key| {
1665 try tmp.dir.makePath(key);
16661666 }
16671667
16681668 var walker = try tmp.dir.walk(testing.allocator);
lib/std/http/Client.zig+1-1
......@@ -1570,7 +1570,7 @@ pub const RequestOptions = struct {
15701570};
15711571
15721572fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {
1573 const protocol_map = std.ComptimeStringMap(Connection.Protocol, .{
1573 const protocol_map = std.StaticStringMap(Connection.Protocol).initComptime(.{
15741574 .{ "http", .plain },
15751575 .{ "ws", .plain },
15761576 .{ "https", .tls },
lib/std/meta.zig+2-2
......@@ -19,7 +19,7 @@ pub const isTag = @compileError("deprecated; use 'tagged_value == @field(E, tag_
1919
2020/// Returns the variant of an enum type, `T`, which is named `str`, or `null` if no such variant exists.
2121pub fn stringToEnum(comptime T: type, str: []const u8) ?T {
22 // Using ComptimeStringMap here is more performant, but it will start to take too
22 // Using StaticStringMap here is more performant, but it will start to take too
2323 // long to compile if the enum is large enough, due to the current limits of comptime
2424 // performance when doing things like constructing lookup maps at comptime.
2525 // 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 {
3434 }
3535 break :build_kvs kvs_array[0..];
3636 };
37 const map = std.ComptimeStringMap(T, kvs);
37 const map = std.StaticStringMap(T).initComptime(kvs);
3838 return map.get(str);
3939 } else {
4040 inline for (@typeInfo(T).Enum.fields) |enumField| {
lib/std/static_string_map.zig created+502
......@@ -0,0 +1,502 @@
1const std = @import("std.zig");
2const mem = std.mem;
3
4/// Static string map optimized for small sets of disparate string keys.
5/// Works by separating the keys by length at initialization and only checking
6/// strings of equal length at runtime.
7pub fn StaticStringMap(comptime V: type) type {
8 return StaticStringMapWithEql(V, defaultEql);
9}
10
11/// Like `std.mem.eql`, but takes advantage of the fact that the lengths
12/// of `a` and `b` are known to be equal.
13pub fn defaultEql(a: []const u8, b: []const u8) bool {
14 if (a.ptr == b.ptr) return true;
15 for (a, b) |a_elem, b_elem| {
16 if (a_elem != b_elem) return false;
17 }
18 return true;
19}
20
21/// Like `std.ascii.eqlIgnoreCase` but takes advantage of the fact that
22/// the lengths of `a` and `b` are known to be equal.
23pub fn eqlAsciiIgnoreCase(a: []const u8, b: []const u8) bool {
24 if (a.ptr == b.ptr) return true;
25 for (a, b) |a_c, b_c| {
26 if (std.ascii.toLower(a_c) != std.ascii.toLower(b_c)) return false;
27 }
28 return true;
29}
30
31/// StaticStringMap, but accepts an equality function (`eql`).
32/// The `eql` function is only called to determine the equality
33/// of equal length strings. Any strings that are not equal length
34/// are never compared using the `eql` function.
35pub fn StaticStringMapWithEql(
36 comptime V: type,
37 comptime eql: fn (a: []const u8, b: []const u8) bool,
38) type {
39 return struct {
40 kvs: *const KVs = &empty_kvs,
41 len_indexes: [*]const u32 = &empty_len_indexes,
42 len_indexes_len: u32 = 0,
43 min_len: u32 = std.math.maxInt(u32),
44 max_len: u32 = 0,
45
46 pub const KV = struct {
47 key: []const u8,
48 value: V,
49 };
50
51 const Self = @This();
52 const KVs = struct {
53 keys: [*]const []const u8,
54 values: [*]const V,
55 len: u32,
56 };
57 const empty_kvs = KVs{
58 .keys = &empty_keys,
59 .values = &empty_vals,
60 .len = 0,
61 };
62 const empty_len_indexes = [0]u32{};
63 const empty_keys = [0][]const u8{};
64 const empty_vals = [0]V{};
65
66 /// Returns a map backed by static, comptime allocated memory.
67 ///
68 /// `kvs_list` must be either a list of `struct { []const u8, V }`
69 /// (key-value pair) tuples, or a list of `struct { []const u8 }`
70 /// (only keys) tuples if `V` is `void`.
71 pub inline fn initComptime(comptime kvs_list: anytype) Self {
72 comptime {
73 @setEvalBranchQuota(30 * kvs_list.len);
74 var self = Self{};
75 if (kvs_list.len == 0)
76 return self;
77
78 var sorted_keys: [kvs_list.len][]const u8 = undefined;
79 var sorted_vals: [kvs_list.len]V = undefined;
80
81 self.initSortedKVs(kvs_list, &sorted_keys, &sorted_vals);
82 const final_keys = sorted_keys;
83 const final_vals = sorted_vals;
84 self.kvs = &.{
85 .keys = &final_keys,
86 .values = &final_vals,
87 .len = @intCast(kvs_list.len),
88 };
89
90 var len_indexes: [self.max_len + 1]u32 = undefined;
91 self.initLenIndexes(&len_indexes);
92 const final_len_indexes = len_indexes;
93 self.len_indexes = &final_len_indexes;
94 self.len_indexes_len = @intCast(len_indexes.len);
95 return self;
96 }
97 }
98
99 /// Returns a map backed by memory allocated with `allocator`.
100 ///
101 /// Handles `kvs_list` the same way as `initComptime()`.
102 pub fn init(kvs_list: anytype, allocator: mem.Allocator) !Self {
103 var self = Self{};
104 if (kvs_list.len == 0)
105 return self;
106
107 const sorted_keys = try allocator.alloc([]const u8, kvs_list.len);
108 errdefer allocator.free(sorted_keys);
109 const sorted_vals = try allocator.alloc(V, kvs_list.len);
110 errdefer allocator.free(sorted_vals);
111 const kvs = try allocator.create(KVs);
112 errdefer allocator.destroy(kvs);
113
114 self.initSortedKVs(kvs_list, sorted_keys, sorted_vals);
115 kvs.* = .{
116 .keys = sorted_keys.ptr,
117 .values = sorted_vals.ptr,
118 .len = kvs_list.len,
119 };
120 self.kvs = kvs;
121
122 const len_indexes = try allocator.alloc(u32, self.max_len + 1);
123 self.initLenIndexes(len_indexes);
124 self.len_indexes = len_indexes.ptr;
125 self.len_indexes_len = @intCast(len_indexes.len);
126 return self;
127 }
128
129 /// this method should only be used with init() and not with initComptime().
130 pub fn deinit(self: Self, allocator: mem.Allocator) void {
131 allocator.free(self.len_indexes[0..self.len_indexes_len]);
132 allocator.free(self.kvs.keys[0..self.kvs.len]);
133 allocator.free(self.kvs.values[0..self.kvs.len]);
134 allocator.destroy(self.kvs);
135 }
136
137 const SortContext = struct {
138 keys: [][]const u8,
139 vals: []V,
140
141 pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
142 return ctx.keys[a].len < ctx.keys[b].len;
143 }
144
145 pub fn swap(ctx: @This(), a: usize, b: usize) void {
146 std.mem.swap([]const u8, &ctx.keys[a], &ctx.keys[b]);
147 std.mem.swap(V, &ctx.vals[a], &ctx.vals[b]);
148 }
149 };
150
151 fn initSortedKVs(
152 self: *Self,
153 kvs_list: anytype,
154 sorted_keys: [][]const u8,
155 sorted_vals: []V,
156 ) void {
157 for (kvs_list, 0..) |kv, i| {
158 sorted_keys[i] = kv.@"0";
159 sorted_vals[i] = if (V == void) {} else kv.@"1";
160 self.min_len = @intCast(@min(self.min_len, kv.@"0".len));
161 self.max_len = @intCast(@max(self.max_len, kv.@"0".len));
162 }
163 mem.sortUnstableContext(0, sorted_keys.len, SortContext{
164 .keys = sorted_keys,
165 .vals = sorted_vals,
166 });
167 }
168
169 fn initLenIndexes(self: Self, len_indexes: []u32) void {
170 var len: usize = 0;
171 var i: u32 = 0;
172 while (len <= self.max_len) : (len += 1) {
173 // find the first keyword len == len
174 while (len > self.kvs.keys[i].len) {
175 i += 1;
176 }
177 len_indexes[len] = i;
178 }
179 }
180
181 /// Checks if the map has a value for the key.
182 pub fn has(self: Self, str: []const u8) bool {
183 return self.get(str) != null;
184 }
185
186 /// Returns the value for the key if any, else null.
187 pub fn get(self: Self, str: []const u8) ?V {
188 if (self.kvs.len == 0)
189 return null;
190
191 return self.kvs.values[self.getIndex(str) orelse return null];
192 }
193
194 pub fn getIndex(self: Self, str: []const u8) ?usize {
195 const kvs = self.kvs.*;
196 if (kvs.len == 0)
197 return null;
198
199 if (str.len < self.min_len or str.len > self.max_len)
200 return null;
201
202 var i = self.len_indexes[str.len];
203 while (true) {
204 const key = kvs.keys[i];
205 if (key.len != str.len)
206 return null;
207 if (eql(key, str))
208 return i;
209 i += 1;
210 if (i >= kvs.len)
211 return null;
212 }
213 }
214
215 /// Returns the longest key, value pair where key is a prefix of `str`
216 /// else null.
217 pub fn getLongestPrefix(self: Self, str: []const u8) ?KV {
218 if (self.kvs.len == 0)
219 return null;
220 const i = self.getLongestPrefixIndex(str) orelse return null;
221 const kvs = self.kvs.*;
222 return .{
223 .key = kvs.keys[i],
224 .value = kvs.values[i],
225 };
226 }
227
228 pub fn getLongestPrefixIndex(self: Self, str: []const u8) ?usize {
229 if (self.kvs.len == 0)
230 return null;
231
232 if (str.len < self.min_len)
233 return null;
234
235 var len = @min(self.max_len, str.len);
236 while (len >= self.min_len) : (len -= 1) {
237 if (self.getIndex(str[0..len])) |i|
238 return i;
239 }
240 return null;
241 }
242
243 pub fn keys(self: Self) []const []const u8 {
244 const kvs = self.kvs.*;
245 return kvs.keys[0..kvs.len];
246 }
247
248 pub fn values(self: Self) []const V {
249 const kvs = self.kvs.*;
250 return kvs.values[0..kvs.len];
251 }
252 };
253}
254
255const TestEnum = enum { A, B, C, D, E };
256const TestMap = StaticStringMap(TestEnum);
257const TestKV = struct { []const u8, TestEnum };
258const TestMapVoid = StaticStringMap(void);
259const TestKVVoid = struct { []const u8 };
260const TestMapWithEql = StaticStringMapWithEql(TestEnum, eqlAsciiIgnoreCase);
261const testing = std.testing;
262const test_alloc = testing.allocator;
263
264test "list literal of list literals" {
265 const slice = [_]TestKV{
266 .{ "these", .D },
267 .{ "have", .A },
268 .{ "nothing", .B },
269 .{ "incommon", .C },
270 .{ "samelen", .E },
271 };
272 const map = TestMap.initComptime(slice);
273 try testMap(map);
274 // Default comparison is case sensitive
275 try testing.expect(null == map.get("NOTHING"));
276
277 // runtime init(), deinit()
278 const map_rt = try TestMap.init(slice, test_alloc);
279 defer map_rt.deinit(test_alloc);
280 try testMap(map_rt);
281 // Default comparison is case sensitive
282 try testing.expect(null == map_rt.get("NOTHING"));
283}
284
285test "array of structs" {
286 const slice = [_]TestKV{
287 .{ "these", .D },
288 .{ "have", .A },
289 .{ "nothing", .B },
290 .{ "incommon", .C },
291 .{ "samelen", .E },
292 };
293
294 try testMap(TestMap.initComptime(slice));
295}
296
297test "slice of structs" {
298 const slice = [_]TestKV{
299 .{ "these", .D },
300 .{ "have", .A },
301 .{ "nothing", .B },
302 .{ "incommon", .C },
303 .{ "samelen", .E },
304 };
305
306 try testMap(TestMap.initComptime(slice));
307}
308
309fn testMap(map: anytype) !void {
310 try testing.expectEqual(TestEnum.A, map.get("have").?);
311 try testing.expectEqual(TestEnum.B, map.get("nothing").?);
312 try testing.expect(null == map.get("missing"));
313 try testing.expectEqual(TestEnum.D, map.get("these").?);
314 try testing.expectEqual(TestEnum.E, map.get("samelen").?);
315
316 try testing.expect(!map.has("missing"));
317 try testing.expect(map.has("these"));
318
319 try testing.expect(null == map.get(""));
320 try testing.expect(null == map.get("averylongstringthathasnomatches"));
321}
322
323test "void value type, slice of structs" {
324 const slice = [_]TestKVVoid{
325 .{"these"},
326 .{"have"},
327 .{"nothing"},
328 .{"incommon"},
329 .{"samelen"},
330 };
331 const map = TestMapVoid.initComptime(slice);
332 try testSet(map);
333 // Default comparison is case sensitive
334 try testing.expect(null == map.get("NOTHING"));
335}
336
337test "void value type, list literal of list literals" {
338 const slice = [_]TestKVVoid{
339 .{"these"},
340 .{"have"},
341 .{"nothing"},
342 .{"incommon"},
343 .{"samelen"},
344 };
345
346 try testSet(TestMapVoid.initComptime(slice));
347}
348
349fn testSet(map: TestMapVoid) !void {
350 try testing.expectEqual({}, map.get("have").?);
351 try testing.expectEqual({}, map.get("nothing").?);
352 try testing.expect(null == map.get("missing"));
353 try testing.expectEqual({}, map.get("these").?);
354 try testing.expectEqual({}, map.get("samelen").?);
355
356 try testing.expect(!map.has("missing"));
357 try testing.expect(map.has("these"));
358
359 try testing.expect(null == map.get(""));
360 try testing.expect(null == map.get("averylongstringthathasnomatches"));
361}
362
363fn testStaticStringMapWithEql(map: TestMapWithEql) !void {
364 try testMap(map);
365 try testing.expectEqual(TestEnum.A, map.get("HAVE").?);
366 try testing.expectEqual(TestEnum.E, map.get("SameLen").?);
367 try testing.expect(null == map.get("SameLength"));
368 try testing.expect(map.has("ThESe"));
369}
370
371test "StaticStringMapWithEql" {
372 const slice = [_]TestKV{
373 .{ "these", .D },
374 .{ "have", .A },
375 .{ "nothing", .B },
376 .{ "incommon", .C },
377 .{ "samelen", .E },
378 };
379
380 try testStaticStringMapWithEql(TestMapWithEql.initComptime(slice));
381}
382
383test "empty" {
384 const m1 = StaticStringMap(usize).initComptime(.{});
385 try testing.expect(null == m1.get("anything"));
386
387 const m2 = StaticStringMapWithEql(usize, eqlAsciiIgnoreCase).initComptime(.{});
388 try testing.expect(null == m2.get("anything"));
389
390 const m3 = try StaticStringMap(usize).init(.{}, test_alloc);
391 try testing.expect(null == m3.get("anything"));
392
393 const m4 = try StaticStringMapWithEql(usize, eqlAsciiIgnoreCase).init(.{}, test_alloc);
394 try testing.expect(null == m4.get("anything"));
395}
396
397test "redundant entries" {
398 const slice = [_]TestKV{
399 .{ "redundant", .D },
400 .{ "theNeedle", .A },
401 .{ "redundant", .B },
402 .{ "re" ++ "dundant", .C },
403 .{ "redun" ++ "dant", .E },
404 };
405 const map = TestMap.initComptime(slice);
406
407 // No promises about which one you get:
408 try testing.expect(null != map.get("redundant"));
409
410 // Default map is not case sensitive:
411 try testing.expect(null == map.get("REDUNDANT"));
412
413 try testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
414}
415
416test "redundant insensitive" {
417 const slice = [_]TestKV{
418 .{ "redundant", .D },
419 .{ "theNeedle", .A },
420 .{ "redundanT", .B },
421 .{ "RE" ++ "dundant", .C },
422 .{ "redun" ++ "DANT", .E },
423 };
424
425 const map = TestMapWithEql.initComptime(slice);
426
427 // No promises about which result you'll get ...
428 try testing.expect(null != map.get("REDUNDANT"));
429 try testing.expect(null != map.get("ReDuNdAnT"));
430 try testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
431}
432
433test "comptime-only value" {
434 const map = StaticStringMap(type).initComptime(.{
435 .{ "a", struct {
436 pub const foo = 1;
437 } },
438 .{ "b", struct {
439 pub const foo = 2;
440 } },
441 .{ "c", struct {
442 pub const foo = 3;
443 } },
444 });
445
446 try testing.expect(map.get("a").?.foo == 1);
447 try testing.expect(map.get("b").?.foo == 2);
448 try testing.expect(map.get("c").?.foo == 3);
449 try testing.expect(map.get("d") == null);
450}
451
452test "getLongestPrefix" {
453 const slice = [_]TestKV{
454 .{ "a", .A },
455 .{ "aa", .B },
456 .{ "aaa", .C },
457 .{ "aaaa", .D },
458 };
459
460 const map = TestMap.initComptime(slice);
461
462 try testing.expectEqual(null, map.getLongestPrefix(""));
463 try testing.expectEqual(null, map.getLongestPrefix("bar"));
464 try testing.expectEqualStrings("aaaa", map.getLongestPrefix("aaaabar").?.key);
465 try testing.expectEqualStrings("aaa", map.getLongestPrefix("aaabar").?.key);
466}
467
468test "getLongestPrefix2" {
469 const slice = [_]struct { []const u8, u8 }{
470 .{ "one", 1 },
471 .{ "two", 2 },
472 .{ "three", 3 },
473 .{ "four", 4 },
474 .{ "five", 5 },
475 .{ "six", 6 },
476 .{ "seven", 7 },
477 .{ "eight", 8 },
478 .{ "nine", 9 },
479 };
480 const map = StaticStringMap(u8).initComptime(slice);
481
482 try testing.expectEqual(1, map.get("one"));
483 try testing.expectEqual(null, map.get("o"));
484 try testing.expectEqual(null, map.get("onexxx"));
485 try testing.expectEqual(9, map.get("nine"));
486 try testing.expectEqual(null, map.get("n"));
487 try testing.expectEqual(null, map.get("ninexxx"));
488 try testing.expectEqual(null, map.get("xxx"));
489
490 try testing.expectEqual(1, map.getLongestPrefix("one").?.value);
491 try testing.expectEqual(1, map.getLongestPrefix("onexxx").?.value);
492 try testing.expectEqual(null, map.getLongestPrefix("o"));
493 try testing.expectEqual(null, map.getLongestPrefix("on"));
494 try testing.expectEqual(9, map.getLongestPrefix("nine").?.value);
495 try testing.expectEqual(9, map.getLongestPrefix("ninexxx").?.value);
496 try testing.expectEqual(null, map.getLongestPrefix("n"));
497 try testing.expectEqual(null, map.getLongestPrefix("xxx"));
498}
499
500test "long kvs_list doesn't exceed @setEvalBranchQuota" {
501 _ = TestMapVoid.initComptime([1]TestKVVoid{.{"x"}} ** 1_000);
502}
lib/std/std.zig+3-3
......@@ -16,8 +16,8 @@ pub const BufMap = @import("buf_map.zig").BufMap;
1616pub const BufSet = @import("buf_set.zig").BufSet;
1717/// Deprecated: use `process.Child`.
1818pub const ChildProcess = @import("child_process.zig").ChildProcess;
19pub const ComptimeStringMap = comptime_string_map.ComptimeStringMap;
20pub const ComptimeStringMapWithEql = comptime_string_map.ComptimeStringMapWithEql;
19pub const StaticStringMap = static_string_map.StaticStringMap;
20pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;
2121pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;
2222pub const DynLib = @import("dynamic_library.zig").DynLib;
2323pub const DynamicBitSet = bit_set.DynamicBitSet;
......@@ -62,7 +62,7 @@ pub const builtin = @import("builtin.zig");
6262pub const c = @import("c.zig");
6363pub const coff = @import("coff.zig");
6464pub const compress = @import("compress.zig");
65pub const comptime_string_map = @import("comptime_string_map.zig");
65pub const static_string_map = @import("static_string_map.zig");
6666pub const crypto = @import("crypto.zig");
6767pub const debug = @import("debug.zig");
6868pub const dwarf = @import("dwarf.zig");
lib/std/zig/AstGen.zig+7-7
......@@ -10125,7 +10125,7 @@ fn calleeExpr(
1012510125 }
1012610126}
1012710127
10128const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{
10128const primitive_instrs = std.StaticStringMap(Zir.Inst.Ref).initComptime(.{
1012910129 .{ "anyerror", .anyerror_type },
1013010130 .{ "anyframe", .anyframe_type },
1013110131 .{ "anyopaque", .anyopaque_type },
......@@ -10173,14 +10173,14 @@ const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{
1017310173comptime {
1017410174 // These checks ensure that std.zig.primitives stays in sync with the primitive->Zir map.
1017510175 const primitives = std.zig.primitives;
10176 for (primitive_instrs.kvs) |kv| {
10177 if (!primitives.isPrimitive(kv.key)) {
10178 @compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(kv.value) ++ "'");
10176 for (primitive_instrs.keys(), primitive_instrs.values()) |key, value| {
10177 if (!primitives.isPrimitive(key)) {
10178 @compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(value) ++ "'");
1017910179 }
1018010180 }
10181 for (primitives.names.kvs) |kv| {
10182 if (primitive_instrs.get(kv.key) == null) {
10183 @compileError("std.zig.primitives entry '" ++ kv.key ++ "' does not have a corresponding Zir instr");
10181 for (primitives.names.keys()) |key| {
10182 if (primitive_instrs.get(key) == null) {
10183 @compileError("std.zig.primitives entry '" ++ key ++ "' does not have a corresponding Zir instr");
1018410184 }
1018510185 }
1018610186}
lib/std/zig/BuiltinFn.zig+1-1
......@@ -160,7 +160,7 @@ param_count: ?u8,
160160
161161pub const list = list: {
162162 @setEvalBranchQuota(3000);
163 break :list std.ComptimeStringMap(@This(), .{
163 break :list std.StaticStringMap(@This()).initComptime(.{
164164 .{
165165 "@addWithOverflow",
166166 .{
lib/std/zig/primitives.zig+1-1
......@@ -2,7 +2,7 @@ const std = @import("std");
22
33/// Set of primitive type and value names.
44/// Does not include `_` or integer type names.
5pub const names = std.ComptimeStringMap(void, .{
5pub const names = std.StaticStringMap(void).initComptime(.{
66 .{"anyerror"},
77 .{"anyframe"},
88 .{"anyopaque"},
lib/std/zig/render.zig+4-4
......@@ -2886,11 +2886,11 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote
28862886 // If we read the whole thing, we have to do further checks.
28872887 const longest_keyword_or_primitive_len = comptime blk: {
28882888 var longest = 0;
2889 for (primitives.names.kvs) |kv| {
2890 if (kv.key.len > longest) longest = kv.key.len;
2889 for (primitives.names.keys()) |key| {
2890 if (key.len > longest) longest = key.len;
28912891 }
2892 for (std.zig.Token.keywords.kvs) |kv| {
2893 if (kv.key.len > longest) longest = kv.key.len;
2892 for (std.zig.Token.keywords.keys()) |key| {
2893 if (key.len > longest) longest = key.len;
28942894 }
28952895 break :blk longest;
28962896 };
lib/std/zig/tokenizer.zig+1-1
......@@ -9,7 +9,7 @@ pub const Token = struct {
99 end: usize,
1010 };
1111
12 pub const keywords = std.ComptimeStringMap(Tag, .{
12 pub const keywords = std.StaticStringMap(Tag).initComptime(.{
1313 .{ "addrspace", .keyword_addrspace },
1414 .{ "align", .keyword_align },
1515 .{ "allowzero", .keyword_allowzero },
src/Compilation.zig+1-1
......@@ -265,7 +265,7 @@ pub const CRTFile = struct {
265265
266266/// Supported languages for "zig clang -x <lang>".
267267/// Loosely based on llvm-project/clang/include/clang/Driver/Types.def
268pub const LangToExt = std.ComptimeStringMap(FileExt, .{
268pub const LangToExt = std.StaticStringMap(FileExt).initComptime(.{
269269 .{ "c", .c },
270270 .{ "c-header", .h },
271271 .{ "c++", .cpp },
src/codegen/c.zig+1-1
......@@ -116,7 +116,7 @@ const ValueRenderLocation = enum {
116116
117117const BuiltinInfo = enum { none, bits };
118118
119const reserved_idents = std.ComptimeStringMap(void, .{
119const reserved_idents = std.StaticStringMap(void).initComptime(.{
120120 // C language
121121 .{ "alignas", {
122122 @setEvalBranchQuota(4000);
src/link/Wasm/types.zig+1-1
......@@ -244,7 +244,7 @@ pub const Feature = struct {
244244 }
245245};
246246
247pub const known_features = std.ComptimeStringMap(Feature.Tag, .{
247pub const known_features = std.StaticStringMap(Feature.Tag).initComptime(.{
248248 .{ "atomics", .atomics },
249249 .{ "bulk-memory", .bulk_memory },
250250 .{ "exception-handling", .exception_handling },
src/translate_c.zig+1-1
......@@ -671,7 +671,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co
671671 return addTopLevelDecl(c, var_name, node);
672672}
673673
674const builtin_typedef_map = std.ComptimeStringMap([]const u8, .{
674const builtin_typedef_map = std.StaticStringMap([]const u8).initComptime(.{
675675 .{ "uint8_t", "u8" },
676676 .{ "int8_t", "i8" },
677677 .{ "uint16_t", "u16" },
test/src/Cases.zig+1-1
......@@ -993,7 +993,7 @@ const TestManifest = struct {
993993 config_map: std.StringHashMap([]const u8),
994994 trailing_bytes: []const u8 = "",
995995
996 const valid_keys = std.ComptimeStringMap(void, .{
996 const valid_keys = std.StaticStringMap(void).initComptime(.{
997997 .{ "is_test", {} },
998998 .{ "output_mode", {} },
999999 .{ "target", {} },
tools/gen_spirv_spec.zig+1-1
......@@ -45,7 +45,7 @@ const OperandKindMap = std.ArrayHashMap(StringPair, OperandKind, StringPairConte
4545/// Khronos made it so that these names are not defined explicitly, so
4646/// we need to hardcode it (like they did).
4747/// See https://github.com/KhronosGroup/SPIRV-Registry/
48const set_names = std.ComptimeStringMap([]const u8, .{
48const set_names = std.StaticStringMap([]const u8).initComptime(.{
4949 .{ "opencl.std.100", "OpenCL.std" },
5050 .{ "glsl.std.450", "GLSL.std.450" },
5151 .{ "opencl.debuginfo.100", "OpenCL.DebugInfo.100" },
tools/generate_linux_syscalls.zig+1-1
......@@ -9,7 +9,7 @@ const fmt = std.fmt;
99const zig = std.zig;
1010const fs = std.fs;
1111
12const stdlib_renames = std.ComptimeStringMap([]const u8, .{
12const stdlib_renames = std.StaticStringMap([]const u8).initComptime(.{
1313 // Most 64-bit archs.
1414 .{ "newfstatat", "fstatat64" },
1515 // POWER.