Skip to content

Commit 745d3ed

Browse files
cdelerandrewrk
andauthored
Remove strict verifyContext invocation from hash_map implementation. (#22370)
Fixes #19640 Co-authored-by: Andrew Kelley <[email protected]>
1 parent 0bf44c3 commit 745d3ed

File tree

1 file changed

+9
-266
lines changed

1 file changed

+9
-266
lines changed

lib/std/hash_map.zig

Lines changed: 9 additions & 266 deletions
Original file line numberDiff line numberDiff line change
@@ -116,232 +116,6 @@ pub const StringIndexAdapter = struct {
116116

117117
pub 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.
128-
pub 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],

0 commit comments

Comments
 (0)