mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 14:23:09 +00:00
Remove strict verifyContext invocation from hash_map implementation. (#22370)
Fixes #19640 Co-authored-by: Andrew Kelley <andrew@ziglang.org>
This commit is contained in:
parent
0bf44c3093
commit
745d3ed0ac
@ -116,232 +116,6 @@ pub const StringIndexAdapter = struct {
|
|||||||
|
|
||||||
pub const default_max_load_percentage = 80;
|
pub const default_max_load_percentage = 80;
|
||||||
|
|
||||||
/// This function issues a compile error with a helpful message if there
|
|
||||||
/// is a problem with the provided context type. A context must have the following
|
|
||||||
/// member functions:
|
|
||||||
/// - hash(self, PseudoKey) Hash
|
|
||||||
/// - eql(self, PseudoKey, Key) bool
|
|
||||||
///
|
|
||||||
/// If you are passing a context to a *Adapted function, PseudoKey is the type
|
|
||||||
/// of the key parameter. Otherwise, when creating a HashMap or HashMapUnmanaged
|
|
||||||
/// type, PseudoKey = Key = K.
|
|
||||||
pub fn verifyContext(
|
|
||||||
comptime RawContext: type,
|
|
||||||
comptime PseudoKey: type,
|
|
||||||
comptime Key: type,
|
|
||||||
comptime Hash: type,
|
|
||||||
comptime is_array: bool,
|
|
||||||
) void {
|
|
||||||
comptime {
|
|
||||||
var allow_const_ptr = false;
|
|
||||||
var allow_mutable_ptr = false;
|
|
||||||
// Context is the actual namespace type. RawContext may be a pointer to Context.
|
|
||||||
var Context = RawContext;
|
|
||||||
// Make sure the context is a namespace type which may have member functions
|
|
||||||
switch (@typeInfo(Context)) {
|
|
||||||
.@"struct", .@"union", .@"enum" => {},
|
|
||||||
// Special-case .@"opaque" for a better error message
|
|
||||||
.@"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."),
|
|
||||||
.pointer => |ptr| {
|
|
||||||
if (ptr.size != .One) {
|
|
||||||
@compileError("Hash context must be a type with hash and eql member functions. Cannot use " ++ @typeName(Context) ++ " because it is not a single pointer.");
|
|
||||||
}
|
|
||||||
Context = ptr.child;
|
|
||||||
allow_const_ptr = true;
|
|
||||||
allow_mutable_ptr = !ptr.is_const;
|
|
||||||
switch (@typeInfo(Context)) {
|
|
||||||
.@"struct", .@"union", .@"enum", .@"opaque" => {},
|
|
||||||
else => @compileError("Hash context must be a type with hash and eql member functions. Cannot use " ++ @typeName(Context)),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => @compileError("Hash context must be a type with hash and eql member functions. Cannot use " ++ @typeName(Context)),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of multiple errors so we can report them all.
|
|
||||||
var errors: []const u8 = "";
|
|
||||||
|
|
||||||
// Put common errors here, they will only be evaluated
|
|
||||||
// if the error is actually triggered.
|
|
||||||
const lazy = struct {
|
|
||||||
const prefix = "\n ";
|
|
||||||
const deep_prefix = prefix ++ " ";
|
|
||||||
const hash_signature = "fn (self, " ++ @typeName(PseudoKey) ++ ") " ++ @typeName(Hash);
|
|
||||||
const index_param = if (is_array) ", b_index: usize" else "";
|
|
||||||
const eql_signature = "fn (self, " ++ @typeName(PseudoKey) ++ ", " ++
|
|
||||||
@typeName(Key) ++ index_param ++ ") bool";
|
|
||||||
const err_invalid_hash_signature = prefix ++ @typeName(Context) ++ ".hash must be " ++ hash_signature ++
|
|
||||||
deep_prefix ++ "but is actually " ++ @typeName(@TypeOf(Context.hash));
|
|
||||||
const err_invalid_eql_signature = prefix ++ @typeName(Context) ++ ".eql must be " ++ eql_signature ++
|
|
||||||
deep_prefix ++ "but is actually " ++ @typeName(@TypeOf(Context.eql));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Verify Context.hash(self, PseudoKey) => Hash
|
|
||||||
if (@hasDecl(Context, "hash")) {
|
|
||||||
const hash = Context.hash;
|
|
||||||
const info = @typeInfo(@TypeOf(hash));
|
|
||||||
if (info == .@"fn") {
|
|
||||||
const func = info.@"fn";
|
|
||||||
if (func.params.len != 2) {
|
|
||||||
errors = errors ++ lazy.err_invalid_hash_signature;
|
|
||||||
} else {
|
|
||||||
var emitted_signature = false;
|
|
||||||
if (func.params[0].type) |Self| {
|
|
||||||
if (Self == Context) {
|
|
||||||
// pass, this is always fine.
|
|
||||||
} else if (Self == *const Context) {
|
|
||||||
if (!allow_const_ptr) {
|
|
||||||
if (!emitted_signature) {
|
|
||||||
errors = errors ++ lazy.err_invalid_hash_signature;
|
|
||||||
emitted_signature = true;
|
|
||||||
}
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ ", but is " ++ @typeName(Self);
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be a pointer because it is passed by value.";
|
|
||||||
}
|
|
||||||
} else if (Self == *Context) {
|
|
||||||
if (!allow_mutable_ptr) {
|
|
||||||
if (!emitted_signature) {
|
|
||||||
errors = errors ++ lazy.err_invalid_hash_signature;
|
|
||||||
emitted_signature = true;
|
|
||||||
}
|
|
||||||
if (!allow_const_ptr) {
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ ", but is " ++ @typeName(Self);
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be a pointer because it is passed by value.";
|
|
||||||
} else {
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ " or " ++ @typeName(*const Context) ++ ", but is " ++ @typeName(Self);
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be non-const because it is passed by const pointer.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!emitted_signature) {
|
|
||||||
errors = errors ++ lazy.err_invalid_hash_signature;
|
|
||||||
emitted_signature = true;
|
|
||||||
}
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context);
|
|
||||||
if (allow_const_ptr) {
|
|
||||||
errors = errors ++ " or " ++ @typeName(*const Context);
|
|
||||||
if (allow_mutable_ptr) {
|
|
||||||
errors = errors ++ " or " ++ @typeName(*Context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errors = errors ++ ", but is " ++ @typeName(Self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (func.params[1].type != null and func.params[1].type.? != PseudoKey) {
|
|
||||||
if (!emitted_signature) {
|
|
||||||
errors = errors ++ lazy.err_invalid_hash_signature;
|
|
||||||
emitted_signature = true;
|
|
||||||
}
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "Second parameter must be " ++ @typeName(PseudoKey) ++ ", but is " ++ @typeName(func.params[1].type.?);
|
|
||||||
}
|
|
||||||
if (func.return_type != null and func.return_type.? != Hash) {
|
|
||||||
if (!emitted_signature) {
|
|
||||||
errors = errors ++ lazy.err_invalid_hash_signature;
|
|
||||||
emitted_signature = true;
|
|
||||||
}
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "Return type must be " ++ @typeName(Hash) ++ ", but was " ++ @typeName(func.return_type.?);
|
|
||||||
}
|
|
||||||
// If any of these are generic (null), we cannot verify them.
|
|
||||||
// The call sites check the return type, but cannot check the
|
|
||||||
// parameters. This may cause compile errors with generic hash/eql functions.
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors = errors ++ lazy.err_invalid_hash_signature;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors = errors ++ lazy.prefix ++ @typeName(Context) ++ " must declare a pub hash function with signature " ++ lazy.hash_signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify Context.eql(self, PseudoKey, Key) => bool
|
|
||||||
if (@hasDecl(Context, "eql")) {
|
|
||||||
const eql = Context.eql;
|
|
||||||
const info = @typeInfo(@TypeOf(eql));
|
|
||||||
if (info == .@"fn") {
|
|
||||||
const func = info.@"fn";
|
|
||||||
const args_len = if (is_array) 4 else 3;
|
|
||||||
if (func.params.len != args_len) {
|
|
||||||
errors = errors ++ lazy.err_invalid_eql_signature;
|
|
||||||
} else {
|
|
||||||
var emitted_signature = false;
|
|
||||||
if (func.params[0].type) |Self| {
|
|
||||||
if (Self == Context) {
|
|
||||||
// pass, this is always fine.
|
|
||||||
} else if (Self == *const Context) {
|
|
||||||
if (!allow_const_ptr) {
|
|
||||||
if (!emitted_signature) {
|
|
||||||
errors = errors ++ lazy.err_invalid_eql_signature;
|
|
||||||
emitted_signature = true;
|
|
||||||
}
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ ", but is " ++ @typeName(Self);
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be a pointer because it is passed by value.";
|
|
||||||
}
|
|
||||||
} else if (Self == *Context) {
|
|
||||||
if (!allow_mutable_ptr) {
|
|
||||||
if (!emitted_signature) {
|
|
||||||
errors = errors ++ lazy.err_invalid_eql_signature;
|
|
||||||
emitted_signature = true;
|
|
||||||
}
|
|
||||||
if (!allow_const_ptr) {
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ ", but is " ++ @typeName(Self);
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be a pointer because it is passed by value.";
|
|
||||||
} else {
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context) ++ " or " ++ @typeName(*const Context) ++ ", but is " ++ @typeName(Self);
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "Note: Cannot be non-const because it is passed by const pointer.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!emitted_signature) {
|
|
||||||
errors = errors ++ lazy.err_invalid_eql_signature;
|
|
||||||
emitted_signature = true;
|
|
||||||
}
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "First parameter must be " ++ @typeName(Context);
|
|
||||||
if (allow_const_ptr) {
|
|
||||||
errors = errors ++ " or " ++ @typeName(*const Context);
|
|
||||||
if (allow_mutable_ptr) {
|
|
||||||
errors = errors ++ " or " ++ @typeName(*Context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errors = errors ++ ", but is " ++ @typeName(Self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (func.params[1].type.? != PseudoKey) {
|
|
||||||
if (!emitted_signature) {
|
|
||||||
errors = errors ++ lazy.err_invalid_eql_signature;
|
|
||||||
emitted_signature = true;
|
|
||||||
}
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "Second parameter must be " ++ @typeName(PseudoKey) ++ ", but is " ++ @typeName(func.params[1].type.?);
|
|
||||||
}
|
|
||||||
if (func.params[2].type.? != Key) {
|
|
||||||
if (!emitted_signature) {
|
|
||||||
errors = errors ++ lazy.err_invalid_eql_signature;
|
|
||||||
emitted_signature = true;
|
|
||||||
}
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "Third parameter must be " ++ @typeName(Key) ++ ", but is " ++ @typeName(func.params[2].type.?);
|
|
||||||
}
|
|
||||||
if (func.return_type.? != bool) {
|
|
||||||
if (!emitted_signature) {
|
|
||||||
errors = errors ++ lazy.err_invalid_eql_signature;
|
|
||||||
emitted_signature = true;
|
|
||||||
}
|
|
||||||
errors = errors ++ lazy.deep_prefix ++ "Return type must be bool, but was " ++ @typeName(func.return_type.?);
|
|
||||||
}
|
|
||||||
// If any of these are generic (null), we cannot verify them.
|
|
||||||
// The call sites check the return type, but cannot check the
|
|
||||||
// parameters. This may cause compile errors with generic hash/eql functions.
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors = errors ++ lazy.err_invalid_eql_signature;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors = errors ++ lazy.prefix ++ @typeName(Context) ++ " must declare a pub eql function with signature " ++ lazy.eql_signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.len != 0) {
|
|
||||||
// errors begins with a newline (from lazy.prefix)
|
|
||||||
@compileError("Problems found with hash context type " ++ @typeName(Context) ++ ":" ++ errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// General purpose hash table.
|
/// General purpose hash table.
|
||||||
/// No order is guaranteed and any modification invalidates live iterators.
|
/// No order is guaranteed and any modification invalidates live iterators.
|
||||||
/// It provides fast operations (lookup, insertion, deletion) with quite high
|
/// It provides fast operations (lookup, insertion, deletion) with quite high
|
||||||
@ -368,10 +142,6 @@ pub fn HashMap(
|
|||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
|
|
||||||
comptime {
|
|
||||||
verifyContext(Context, K, K, u64, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The type of the unmanaged hash map underlying this wrapper
|
/// The type of the unmanaged hash map underlying this wrapper
|
||||||
pub const Unmanaged = HashMapUnmanaged(K, V, Context, max_load_percentage);
|
pub const Unmanaged = HashMapUnmanaged(K, V, Context, max_load_percentage);
|
||||||
/// An entry, containing pointers to a key and value stored in the map
|
/// An entry, containing pointers to a key and value stored in the map
|
||||||
@ -734,10 +504,6 @@ pub fn HashMapUnmanaged(
|
|||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
comptime {
|
|
||||||
verifyContext(Context, K, K, u64, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is actually a midway pointer to the single buffer containing
|
// This is actually a midway pointer to the single buffer containing
|
||||||
// a `Header` field, the `Metadata`s and `Entry`s.
|
// a `Header` field, the `Metadata`s and `Entry`s.
|
||||||
// At `-@sizeOf(Header)` is the Header field.
|
// At `-@sizeOf(Header)` is the Header field.
|
||||||
@ -1097,7 +863,7 @@ pub fn HashMapUnmanaged(
|
|||||||
pub fn putAssumeCapacityNoClobberContext(self: *Self, key: K, value: V, ctx: Context) void {
|
pub fn putAssumeCapacityNoClobberContext(self: *Self, key: K, value: V, ctx: Context) void {
|
||||||
assert(!self.containsContext(key, ctx));
|
assert(!self.containsContext(key, ctx));
|
||||||
|
|
||||||
const hash = ctx.hash(key);
|
const hash: Hash = ctx.hash(key);
|
||||||
const mask = self.capacity() - 1;
|
const mask = self.capacity() - 1;
|
||||||
var idx: usize = @truncate(hash & mask);
|
var idx: usize = @truncate(hash & mask);
|
||||||
|
|
||||||
@ -1188,8 +954,6 @@ pub fn HashMapUnmanaged(
|
|||||||
|
|
||||||
/// Find the index containing the data for the given key.
|
/// Find the index containing the data for the given key.
|
||||||
fn getIndex(self: Self, key: anytype, ctx: anytype) ?usize {
|
fn getIndex(self: Self, key: anytype, ctx: anytype) ?usize {
|
||||||
comptime verifyContext(@TypeOf(ctx), @TypeOf(key), K, Hash, false);
|
|
||||||
|
|
||||||
if (self.size == 0) {
|
if (self.size == 0) {
|
||||||
// We use cold instead of unlikely to force a jump to this case,
|
// We use cold instead of unlikely to force a jump to this case,
|
||||||
// no matter the weight of the opposing side.
|
// no matter the weight of the opposing side.
|
||||||
@ -1199,12 +963,8 @@ pub fn HashMapUnmanaged(
|
|||||||
|
|
||||||
// If you get a compile error on this line, it means that your generic hash
|
// If you get a compile error on this line, it means that your generic hash
|
||||||
// function is invalid for these parameters.
|
// function is invalid for these parameters.
|
||||||
const hash = ctx.hash(key);
|
const hash: Hash = ctx.hash(key);
|
||||||
// verifyContext can't verify the return type of generic hash functions,
|
|
||||||
// so we need to double-check it here.
|
|
||||||
if (@TypeOf(hash) != Hash) {
|
|
||||||
@compileError("Context " ++ @typeName(@TypeOf(ctx)) ++ " has a generic hash function that returns the wrong type! " ++ @typeName(Hash) ++ " was expected, but found " ++ @typeName(@TypeOf(hash)));
|
|
||||||
}
|
|
||||||
const mask = self.capacity() - 1;
|
const mask = self.capacity() - 1;
|
||||||
const fingerprint = Metadata.takeFingerprint(hash);
|
const fingerprint = Metadata.takeFingerprint(hash);
|
||||||
// Don't loop indefinitely when there are no empty slots.
|
// Don't loop indefinitely when there are no empty slots.
|
||||||
@ -1215,15 +975,8 @@ pub fn HashMapUnmanaged(
|
|||||||
while (!metadata[0].isFree() and limit != 0) {
|
while (!metadata[0].isFree() and limit != 0) {
|
||||||
if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
|
if (metadata[0].isUsed() and metadata[0].fingerprint == fingerprint) {
|
||||||
const test_key = &self.keys()[idx];
|
const test_key = &self.keys()[idx];
|
||||||
// If you get a compile error on this line, it means that your generic eql
|
|
||||||
// function is invalid for these parameters.
|
if (ctx.eql(key, test_key.*)) {
|
||||||
const eql = ctx.eql(key, test_key.*);
|
|
||||||
// verifyContext can't verify the return type of generic eql functions,
|
|
||||||
// so we need to double-check it here.
|
|
||||||
if (@TypeOf(eql) != bool) {
|
|
||||||
@compileError("Context " ++ @typeName(@TypeOf(ctx)) ++ " has a generic eql function that returns the wrong type! bool was expected, but found " ++ @typeName(@TypeOf(eql)));
|
|
||||||
}
|
|
||||||
if (eql) {
|
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1378,16 +1131,11 @@ pub fn HashMapUnmanaged(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
pub fn getOrPutAssumeCapacityAdapted(self: *Self, key: anytype, ctx: anytype) GetOrPutResult {
|
pub fn getOrPutAssumeCapacityAdapted(self: *Self, key: anytype, ctx: anytype) GetOrPutResult {
|
||||||
comptime verifyContext(@TypeOf(ctx), @TypeOf(key), K, Hash, false);
|
|
||||||
|
|
||||||
// If you get a compile error on this line, it means that your generic hash
|
// If you get a compile error on this line, it means that your generic hash
|
||||||
// function is invalid for these parameters.
|
// function is invalid for these parameters.
|
||||||
const hash = ctx.hash(key);
|
const hash: Hash = ctx.hash(key);
|
||||||
// verifyContext can't verify the return type of generic hash functions,
|
|
||||||
// so we need to double-check it here.
|
|
||||||
if (@TypeOf(hash) != Hash) {
|
|
||||||
@compileError("Context " ++ @typeName(@TypeOf(ctx)) ++ " has a generic hash function that returns the wrong type! " ++ @typeName(Hash) ++ " was expected, but found " ++ @typeName(@TypeOf(hash)));
|
|
||||||
}
|
|
||||||
const mask = self.capacity() - 1;
|
const mask = self.capacity() - 1;
|
||||||
const fingerprint = Metadata.takeFingerprint(hash);
|
const fingerprint = Metadata.takeFingerprint(hash);
|
||||||
var limit = self.capacity();
|
var limit = self.capacity();
|
||||||
@ -1400,13 +1148,8 @@ pub fn HashMapUnmanaged(
|
|||||||
const test_key = &self.keys()[idx];
|
const test_key = &self.keys()[idx];
|
||||||
// If you get a compile error on this line, it means that your generic eql
|
// If you get a compile error on this line, it means that your generic eql
|
||||||
// function is invalid for these parameters.
|
// function is invalid for these parameters.
|
||||||
const eql = ctx.eql(key, test_key.*);
|
|
||||||
// verifyContext can't verify the return type of generic eql functions,
|
if (ctx.eql(key, test_key.*)) {
|
||||||
// so we need to double-check it here.
|
|
||||||
if (@TypeOf(eql) != bool) {
|
|
||||||
@compileError("Context " ++ @typeName(@TypeOf(ctx)) ++ " has a generic eql function that returns the wrong type! bool was expected, but found " ++ @typeName(@TypeOf(eql)));
|
|
||||||
}
|
|
||||||
if (eql) {
|
|
||||||
return GetOrPutResult{
|
return GetOrPutResult{
|
||||||
.key_ptr = test_key,
|
.key_ptr = test_key,
|
||||||
.value_ptr = &self.values()[idx],
|
.value_ptr = &self.values()[idx],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user