authorgravatar for pat.github@tullmann.orgPat Tullmann <pat.github@tullmann.org> 2023-12-08 19:33:39-08:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2024-01-05 05:09:24+02:00
log04ac028a2cd20ea430dd6a05b956363ec182b6ff
tree4cb9ec2bc8185c90b5bbd7b1ae1adc56823dd808
parent45836678d76014f94d35a2ffea983c21a57426c4

std.CompTimeStringMap*: support empty initialization list

Add tests for empty initialization, and some more corner cases (empty key, very long key, duplicate keys). Fixes #18212

1 files changed, 64 insertions(+), 1 deletions(-)

lib/std/comptime_string_map.zig+64-1
......@@ -43,12 +43,15 @@ pub fn ComptimeStringMapWithEql(
4343 comptime kvs_list: anytype,
4444 comptime eql: fn (a: []const u8, b: []const u8) bool,
4545) type {
46 const precomputed = comptime blk: {
46 const empty_list = kvs_list.len == 0;
47 const precomputed = blk: {
4748 @setEvalBranchQuota(1500);
4849 const KV = struct {
4950 key: []const u8,
5051 value: V,
5152 };
53 if (empty_list)
54 break :blk .{};
5255 var sorted_kvs: [kvs_list.len]KV = undefined;
5356 for (kvs_list, 0..) |kv, i| {
5457 if (V != void) {
......@@ -103,10 +106,16 @@ pub fn ComptimeStringMapWithEql(
103106
104107 /// Returns the value for the key if any, else null.
105108 pub fn get(str: []const u8) ?V {
109 if (empty_list)
110 return null;
111
106112 return precomputed.sorted_kvs[getIndex(str) orelse return null].value;
107113 }
108114
109115 pub fn getIndex(str: []const u8) ?usize {
116 if (empty_list)
117 return null;
118
110119 if (str.len < precomputed.min_len or str.len > precomputed.max_len)
111120 return null;
112121
......@@ -143,6 +152,9 @@ test "ComptimeStringMap list literal of list literals" {
143152 });
144153
145154 try testMap(map);
155
156 // Default comparison is case sensitive
157 try std.testing.expect(null == map.get("NOTHING"));
146158}
147159
148160test "ComptimeStringMap array of structs" {
......@@ -181,6 +193,9 @@ fn testMap(comptime map: anytype) !void {
181193
182194 try std.testing.expect(!map.has("missing"));
183195 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"));
184199}
185200
186201test "ComptimeStringMap void value type, slice of structs" {
......@@ -195,6 +210,9 @@ test "ComptimeStringMap void value type, slice of structs" {
195210 const map = ComptimeStringMap(void, slice);
196211
197212 try testSet(map);
213
214 // Default comparison is case sensitive
215 try std.testing.expect(null == map.get("NOTHING"));
198216}
199217
200218test "ComptimeStringMap void value type, list literal of list literals" {
......@@ -218,6 +236,9 @@ fn testSet(comptime map: anytype) !void {
218236
219237 try std.testing.expect(!map.has("missing"));
220238 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"));
221242}
222243
223244test "ComptimeStringMapWithEql" {
......@@ -236,3 +257,45 @@ test "ComptimeStringMapWithEql" {
236257
237258 try std.testing.expect(map.has("ThESe"));
238259}
260
261test "ComptimeStringMap 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 "ComptimeStringMap 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 "ComptimeStringMap 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}