Skip to content

Instantly share code, notes, and snippets.

@SpexGuy
Created May 14, 2021 03:23
Show Gist options
  • Save SpexGuy/3b7591204f1ce16f12387ec98c2be939 to your computer and use it in GitHub Desktop.
Save SpexGuy/3b7591204f1ce16f12387ec98c2be939 to your computer and use it in GitHub Desktop.
/// 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) 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 => {},
// 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, .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 eql_signature = "fn (self, "++@typeName(PseudoKey)++", "++@typeName(Key)++") 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.args.len != 2) {
errors = errors ++ lazy.err_invalid_hash_signature;
} else {
var emitted_signature = false;
if (func.args[0].arg_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.args[1].arg_type != null and func.args[1].arg_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.args[1].arg_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 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;
if (func.args.len != 3) {
errors = errors ++ lazy.err_invalid_eql_signature;
} else {
var emitted_signature = false;
if (func.args[0].arg_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.args[1].arg_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.args[1].arg_type.?);
}
if (func.args[2].arg_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.args[2].arg_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 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);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment