Created
May 14, 2021 03:23
-
-
Save SpexGuy/3b7591204f1ce16f12387ec98c2be939 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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