authorgravatar for serj.krotov@gmail.comcdeler <serj.krotov@gmail.com> 2025-01-06 05:37:30-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-01-06 13:37:30+00:00
log745d3ed0ac1ad1ec8c72d1b432f79e40a1917cde
tree5167742df017d354141d9711a6d3323175b7f469
parent0bf44c30934bced6fc8f6451cf418ae40db665e6
signaturebadge-check Signed by PGP key B5690EEEBB952194

Remove strict verifyContext invocation from hash_map implementation. (#22370)

Fixes #19640 Co-authored-by: Andrew Kelley <andrew@ziglang.org>

1 files changed, 9 insertions(+), 266 deletions(-)

lib/std/hash_map.zig+9-266
......@@ -116,232 +116,6 @@ pub const StringIndexAdapter = struct {
116116
117117pub const default_max_load_percentage = 80;
118118
119/// This function issues a compile error with a helpful message if there
120/// is a problem with the provided context type. A context must have the following
121/// member functions:
122/// - hash(self, PseudoKey) Hash
123/// - eql(self, PseudoKey, Key) bool
124///
125/// If you are passing a context to a *Adapted function, PseudoKey is the type
126/// of the key parameter. Otherwise, when creating a HashMap or HashMapUnmanaged
127/// type, PseudoKey = Key = K.
128pub fn verifyContext(
129 comptime RawContext: type,
130 comptime PseudoKey: type,
131 comptime Key: type,
132 comptime Hash: type,
133 comptime is_array: bool,
134) void {
135 comptime {
136 var allow_const_ptr = false;
137 var allow_mutable_ptr = false;
138 // Context is the actual namespace type. RawContext may be a pointer to Context.
139 var Context = RawContext;
140 // Make sure the context is a namespace type which may have member functions
141 switch (@typeInfo(Context)) {
142 .@"struct", .@"union", .@"enum" => {},
143 // Special-case .@"opaque" for a better error message
144 .@"opaque" => @compileError("Hash context must be a type with hash and eql member functions. Cannot use " ++ @typeName(Context) ++ " because it is opaque. Use a pointer instead."),
145 .pointer => |ptr| {
146 if (ptr.size != .One) {
147 @compileError("Hash context must be a type with hash and eql member functions. Cannot use " ++ @typeName(Context) ++ " because it is not a single pointer.");
148 }
149 Context = ptr.child;
150 allow_const_ptr = true;
151 allow_mutable_ptr = !ptr.is_const;
152 switch (@typeInfo(Context)) {
153 .@"struct", .@"union", .@"enum", .@"opaque" => {},
154 else => @compileError("Hash context must be a type with hash and eql member functions. Cannot use " ++ @typeName(Context)),
155 }
156 },
157 else => @compileError("Hash context must be a type with hash and eql member functions. Cannot use " ++ @typeName(Context)),
158 }
159
160 // Keep track of multiple errors so we can report them all.
161 var errors: []const u8 = "";
162
163 // Put common errors here, they will only be evaluated
164 // if the error is actually triggered.
165 const lazy = struct {
166 const prefix = "\n ";
167 const deep_prefix = prefix ++ " ";
168 const hash_signature = "fn (self, " ++ @typeName(PseudoKey) ++ ") " ++ @typeName(Hash);
169 const index_param = if (is_array) ", b_index: usize" else "";
170 const eql_signature = "fn (self, " ++ @typeName(PseudoKey) ++ ", " ++
171 @typeName(Key) ++ index_param ++ ") bool";
172 const err_invalid_hash_signature = prefix ++ @typeName(Context) ++ ".hash must be " ++ hash_signature ++
173 deep_prefix ++ "but is actually " ++ @typeName(@TypeOf(Context.hash));
174 const err_invalid_eql_signature = prefix ++ @typeName(Context) ++ ".eql must be " ++ eql_signature ++
175 deep_prefix ++ "but is actually " ++ @typeName(@TypeOf(Context.eql));
176 };
177
178 // Verify Context.hash(self, PseudoKey) => Hash
179 if (@hasDecl(Context, "hash")) {
180 const hash = Context.hash;
181 const info = @typeInfo(@TypeOf(hash));
182 if (info == .@"fn") {
183 const func = info.@"fn";
184 if (func.params.len != 2) {
185 errors = errors ++ lazy.err_invalid_hash_signature;
186 } else {
187 var emitted_signature = false;
188 if (func.params[0].type) |Self| {
189 if (Self == Context) {
190 // pass, this is always fine.
191 } else if (Self == *const Context) {
192 if (!allow_const_ptr) {
193 if (!emitted_signature) {
194 errors = errors ++ lazy.err_invalid_hash_signature;
195 emitted_signature = true;
196 }
197 errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ ", but is " ++ @typeName(Self);
198 errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be a pointer because it is passed by value.";
199 }
200 } else if (Self == *Context) {
201 if (!allow_mutable_ptr) {
202 if (!emitted_signature) {
203 errors = errors ++ lazy.err_invalid_hash_signature;
204 emitted_signature = true;
205 }
206 if (!allow_const_ptr) {
207 errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ ", but is " ++ @typeName(Self);
208 errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be a pointer because it is passed by value.";
209 } else {
210 errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ " or " ++ @typeName(*const Context) ++ ", but is " ++ @typeName(Self);
211 errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be non-const because it is passed by const pointer.";
212 }
213 }
214 } else {
215 if (!emitted_signature) {
216 errors = errors ++ lazy.err_invalid_hash_signature;
217 emitted_signature = true;
218 }
219 errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context);
220 if (allow_const_ptr) {
221 errors = errors ++ " or " ++ @typeName(*const Context);
222 if (allow_mutable_ptr) {
223 errors = errors ++ " or " ++ @typeName(*Context);
224 }
225 }
226 errors = errors ++ ", but is " ++ @typeName(Self);
227 }
228 }
229 if (func.params[1].type != null and func.params[1].type.? != PseudoKey) {
230 if (!emitted_signature) {
231 errors = errors ++ lazy.err_invalid_hash_signature;
232 emitted_signature = true;
233 }
234 errors = errors ++ lazy.deep_prefix ++ "Second parameter must be " ++ @typeName(PseudoKey) ++ ", but is " ++ @typeName(func.params[1].type.?);
235 }
236 if (func.return_type != null and func.return_type.? != Hash) {
237 if (!emitted_signature) {
238 errors = errors ++ lazy.err_invalid_hash_signature;
239 emitted_signature = true;
240 }
241 errors = errors ++ lazy.deep_prefix ++ "Return type must be " ++ @typeName(Hash) ++ ", but was " ++ @typeName(func.return_type.?);
242 }
243 // If any of these are generic (null), we cannot verify them.
244 // The call sites check the return type, but cannot check the
245 // parameters. This may cause compile errors with generic hash/eql functions.
246 }
247 } else {
248 errors = errors ++ lazy.err_invalid_hash_signature;
249 }
250 } else {
251 errors = errors ++ lazy.prefix ++ @typeName(Context) ++ " must declare a pub hash function with signature " ++ lazy.hash_signature;
252 }
253
254 // Verify Context.eql(self, PseudoKey, Key) => bool
255 if (@hasDecl(Context, "eql")) {
256 const eql = Context.eql;
257 const info = @typeInfo(@TypeOf(eql));
258 if (info == .@"fn") {
259 const func = info.@"fn";
260 const args_len = if (is_array) 4 else 3;
261 if (func.params.len != args_len) {
262 errors = errors ++ lazy.err_invalid_eql_signature;
263 } else {
264 var emitted_signature = false;
265 if (func.params[0].type) |Self| {
266 if (Self == Context) {
267 // pass, this is always fine.
268 } else if (Self == *const Context) {
269 if (!allow_const_ptr) {
270 if (!emitted_signature) {
271 errors = errors ++ lazy.err_invalid_eql_signature;
272 emitted_signature = true;
273 }
274 errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ ", but is " ++ @typeName(Self);
275 errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be a pointer because it is passed by value.";
276 }
277 } else if (Self == *Context) {
278 if (!allow_mutable_ptr) {
279 if (!emitted_signature) {
280 errors = errors ++ lazy.err_invalid_eql_signature;
281 emitted_signature = true;
282 }
283 if (!allow_const_ptr) {
284 errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ ", but is " ++ @typeName(Self);
285 errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be a pointer because it is passed by value.";
286 } else {
287 errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ " or " ++ @typeName(*const Context) ++ ", but is " ++ @typeName(Self);
288 errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be non-const because it is passed by const pointer.";
289 }
290 }
291 } else {
292 if (!emitted_signature) {
293 errors = errors ++ lazy.err_invalid_eql_signature;
294 emitted_signature = true;
295 }
296 errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context);
297 if (allow_const_ptr) {
298 errors = errors ++ " or " ++ @typeName(*const Context);
299 if (allow_mutable_ptr) {
300 errors = errors ++ " or " ++ @typeName(*Context);
301 }
302 }
303 errors = errors ++ ", but is " ++ @typeName(Self);
304 }
305 }
306 if (func.params[1].type.? != PseudoKey) {
307 if (!emitted_signature) {
308 errors = errors ++ lazy.err_invalid_eql_signature;
309 emitted_signature = true;
310 }
311 errors = errors ++ lazy.deep_prefix ++ "Second parameter must be " ++ @typeName(PseudoKey) ++ ", but is " ++ @typeName(func.params[1].type.?);
312 }
313 if (func.params[2].type.? != Key) {
314 if (!emitted_signature) {
315 errors = errors ++ lazy.err_invalid_eql_signature;
316 emitted_signature = true;
317 }
318 errors = errors ++ lazy.deep_prefix ++ "Third parameter must be " ++ @typeName(Key) ++ ", but is " ++ @typeName(func.params[2].type.?);
319 }
320 if (func.return_type.? != bool) {
321 if (!emitted_signature) {
322 errors = errors ++ lazy.err_invalid_eql_signature;
323 emitted_signature = true;
324 }
325 errors = errors ++ lazy.deep_prefix ++ "Return type must be bool, but was " ++ @typeName(func.return_type.?);
326 }
327 // If any of these are generic (null), we cannot verify them.
328 // The call sites check the return type, but cannot check the
329 // parameters. This may cause compile errors with generic hash/eql functions.
330 }
331 } else {
332 errors = errors ++ lazy.err_invalid_eql_signature;
333 }
334 } else {
335 errors = errors ++ lazy.prefix ++ @typeName(Context) ++ " must declare a pub eql function with signature " ++ lazy.eql_signature;
336 }
337
338 if (errors.len != 0) {
339 // errors begins with a newline (from lazy.prefix)
340 @compileError("Problems found with hash context type " ++ @typeName(Context) ++ ":" ++ errors);
341 }
342 }
343}
344
345119/// General purpose hash table.
346120/// No order is guaranteed and any modification invalidates live iterators.
347121/// It provides fast operations (lookup, insertion, deletion) with quite high
......@@ -368,10 +142,6 @@ pub fn HashMap(
368142 allocator: Allocator,
369143 ctx: Context,
370144
371 comptime {
372 verifyContext(Context, K, K, u64, false);
373 }
374
375145 /// The type of the unmanaged hash map underlying this wrapper
376146 pub const Unmanaged = HashMapUnmanaged(K, V, Context, max_load_percentage);
377147 /// An entry, containing pointers to a key and value stored in the map
......@@ -734,10 +504,6 @@ pub fn HashMapUnmanaged(
734504 return struct {
735505 const Self = @This();
736506
737 comptime {
738 verifyContext(Context, K, K, u64, false);
739 }
740
741507 // This is actually a midway pointer to the single buffer containing
742508 // a `Header` field, the `Metadata`s and `Entry`s.
743509 // At `-@sizeOf(Header)` is the Header field.
......@@ -1097,7 +863,7 @@ pub fn HashMapUnmanaged(
1097863 pub fn putAssumeCapacityNoClobberContext(self: *Self, key: K, value: V, ctx: Context) void {
1098864 assert(!self.containsContext(key, ctx));
1099865
1100 const hash = ctx.hash(key);
866 const hash: Hash = ctx.hash(key);
1101867 const mask = self.capacity() - 1;
1102868 var idx: usize = @truncate(hash & mask);
1103869
......@@ -1188,8 +954,6 @@ pub fn HashMapUnmanaged(
1188954
1189955 /// Find the index containing the data for the given key.
1190956 fn getIndex(self: Self, key: anytype, ctx: anytype) ?usize {
1191 comptime verifyContext(@TypeOf(ctx), @TypeOf(key), K, Hash, false);
1192
1193957 if (self.size == 0) {
1194958 // We use cold instead of unlikely to force a jump to this case,
1195959 // no matter the weight of the opposing side.
......@@ -1199,12 +963,8 @@ pub fn HashMapUnmanaged(
1199963
1200964 // If you get a compile error on this line, it means that your generic hash
1201965 // function is invalid for these parameters.
1202 const hash = ctx.hash(key);
1203 // verifyContext can't verify the return type of generic hash functions,
1204 // so we need to double-check it here.
1205 if (@TypeOf(hash) != Hash) {
1206 @compileError("Context " ++ @typeName(@TypeOf(ctx)) ++ " has a generic hash function that returns the wrong type! " ++ @typeName(Hash) ++ " was expected, but found " ++ @typeName(@TypeOf(hash)));
1207 }
966 const hash: Hash = ctx.hash(key);
967
1208968 const mask = self.capacity() - 1;
1209969 const fingerprint = Metadata.takeFingerprint(hash);
1210970 // Don't loop indefinitely when there are no empty slots.
......@@ -1215,15 +975,8 @@ pub fn HashMapUnmanaged(
1215975 while (!metadata[0].isFree() and limit != 0) {
1216976 if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
1217977 const test_key = &self.keys()[idx];
1218 // If you get a compile error on this line, it means that your generic eql
1219 // function is invalid for these parameters.
1220 const eql = ctx.eql(key, test_key.*);
1221 // verifyContext can't verify the return type of generic eql functions,
1222 // so we need to double-check it here.
1223 if (@TypeOf(eql) != bool) {
1224 @compileError("Context " ++ @typeName(@TypeOf(ctx)) ++ " has a generic eql function that returns the wrong type! bool was expected, but found " ++ @typeName(@TypeOf(eql)));
1225 }
1226 if (eql) {
978
979 if (ctx.eql(key, test_key.*)) {
1227980 return idx;
1228981 }
1229982 }
......@@ -1378,16 +1131,11 @@ pub fn HashMapUnmanaged(
13781131 return result;
13791132 }
13801133 pub fn getOrPutAssumeCapacityAdapted(self: *Self, key: anytype, ctx: anytype) GetOrPutResult {
1381 comptime verifyContext(@TypeOf(ctx), @TypeOf(key), K, Hash, false);
13821134
13831135 // If you get a compile error on this line, it means that your generic hash
13841136 // function is invalid for these parameters.
1385 const hash = ctx.hash(key);
1386 // verifyContext can't verify the return type of generic hash functions,
1387 // so we need to double-check it here.
1388 if (@TypeOf(hash) != Hash) {
1389 @compileError("Context " ++ @typeName(@TypeOf(ctx)) ++ " has a generic hash function that returns the wrong type! " ++ @typeName(Hash) ++ " was expected, but found " ++ @typeName(@TypeOf(hash)));
1390 }
1137 const hash: Hash = ctx.hash(key);
1138
13911139 const mask = self.capacity() - 1;
13921140 const fingerprint = Metadata.takeFingerprint(hash);
13931141 var limit = self.capacity();
......@@ -1400,13 +1148,8 @@ pub fn HashMapUnmanaged(
14001148 const test_key = &self.keys()[idx];
14011149 // If you get a compile error on this line, it means that your generic eql
14021150 // function is invalid for these parameters.
1403 const eql = ctx.eql(key, test_key.*);
1404 // verifyContext can't verify the return type of generic eql functions,
1405 // so we need to double-check it here.
1406 if (@TypeOf(eql) != bool) {
1407 @compileError("Context " ++ @typeName(@TypeOf(ctx)) ++ " has a generic eql function that returns the wrong type! bool was expected, but found " ++ @typeName(@TypeOf(eql)));
1408 }
1409 if (eql) {
1151
1152 if (ctx.eql(key, test_key.*)) {
14101153 return GetOrPutResult{
14111154 .key_ptr = test_key,
14121155 .value_ptr = &self.values()[idx],