use std::collections::BTreeMap;
use std::hash::{Hash, Hasher};
use std::rc::Rc;
use std::sync::Arc;
use rkyv::collections::btree_map::ArchivedBTreeMap;
use rkyv::option::ArchivedOption;
use rkyv::rc::ArchivedRc;
use rkyv::string::ArchivedString;
use rkyv::vec::ArchivedVec;
use crate::hash::default_hasher;
pub trait HashRepr {
const FAITHFUL: bool;
fn hash_repr<H: Hasher>(&self, state: &mut H);
#[inline]
fn hash_slice_repr<H: Hasher>(data: &[Self], state: &mut H)
where
Self: Sized,
{
for element in data {
element.hash_repr(state);
}
}
}
pub fn archived_hash<T>(value: &T) -> Option<u64>
where
T: HashRepr + ?Sized,
{
T::FAITHFUL.then(|| {
let mut hasher = default_hasher();
value.hash_repr(&mut hasher);
hasher.finish()
})
}
#[inline]
fn write_length_prefix<H: Hasher>(state: &mut H, len: usize) {
state.write_usize(len);
}
#[inline]
fn hash_option_discriminant<H: Hasher>(state: &mut H, is_some: bool) {
let probe = if is_some { Some(()) } else { None };
std::mem::discriminant(&probe).hash(state);
}
#[macro_export]
macro_rules! impl_hash_repr_via_hash {
($($ty:ty),* $(,)?) => {$(
impl $crate::dynamic::HashRepr for $ty {
const FAITHFUL: bool = true;
#[inline]
fn hash_repr<H: ::std::hash::Hasher>(&self, state: &mut H) {
::std::hash::Hash::hash(self, state)
}
}
)*};
}
#[macro_export]
macro_rules! impl_hash_repr_via_hash_and_slice {
($($ty:ty),* $(,)?) => {$(
impl $crate::dynamic::HashRepr for $ty {
const FAITHFUL: bool = true;
#[inline]
fn hash_repr<H: ::std::hash::Hasher>(&self, state: &mut H) {
::std::hash::Hash::hash(self, state)
}
#[inline]
fn hash_slice_repr<H: ::std::hash::Hasher>(data: &[Self], state: &mut H) {
::std::hash::Hash::hash_slice(data, state)
}
}
)*};
}
impl_hash_repr_via_hash!(bool, char, ());
impl_hash_repr_via_hash_and_slice!(
i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize,
);
impl_hash_repr_via_hash!(uuid::Uuid);
impl_hash_repr_via_hash!(ArchivedString);
impl<T> HashRepr for ArchivedOption<T>
where
T: HashRepr,
{
const FAITHFUL: bool = T::FAITHFUL;
fn hash_repr<H: Hasher>(&self, state: &mut H) {
match self {
ArchivedOption::None => hash_option_discriminant(state, false),
ArchivedOption::Some(value) => {
hash_option_discriminant(state, true);
value.hash_repr(state);
}
}
}
}
impl<T> HashRepr for ArchivedVec<T>
where
T: HashRepr,
{
const FAITHFUL: bool = T::FAITHFUL;
fn hash_repr<H: Hasher>(&self, state: &mut H) {
write_length_prefix(state, self.len());
T::hash_slice_repr(self.as_slice(), state);
}
}
impl<K, V> HashRepr for ArchivedBTreeMap<K, V>
where
K: HashRepr,
V: HashRepr,
{
const FAITHFUL: bool = K::FAITHFUL && V::FAITHFUL;
fn hash_repr<H: Hasher>(&self, state: &mut H) {
write_length_prefix(state, self.len());
for (key, value) in self.iter() {
key.hash_repr(state);
value.hash_repr(state);
}
}
}
impl<T, F> HashRepr for ArchivedRc<T, F>
where
T: HashRepr + rkyv::ArchivePointee + ?Sized,
{
const FAITHFUL: bool = T::FAITHFUL;
fn hash_repr<H: Hasher>(&self, state: &mut H) {
(**self).hash_repr(state);
}
}
macro_rules! decoded_hash_repr {
($([$($generics:tt)*] $ty:ty $(where $($bound:tt)*)?),* $(,)?) => {$(
impl<$($generics)*> HashRepr for $ty
where
$ty: Hash,
$($($bound)*)?
{
const FAITHFUL: bool = true;
#[inline]
fn hash_repr<H: Hasher>(&self, state: &mut H) {
self.hash(state)
}
}
)*};
}
decoded_hash_repr!(
[] String,
[T] Vec<T>,
[T] Option<T>,
[K, V] BTreeMap<K, V>,
[T: ?Sized] Arc<T>,
[T: ?Sized] Rc<T>,
);
#[cfg(test)]
mod test {
use std::collections::BTreeMap;
use super::{HashRepr, archived_hash};
use crate::DBData;
use crate::hash::default_hash;
use crate::storage::file::to_bytes;
fn check<T>(value: &T)
where
T: DBData + HashRepr,
T::Repr: HashRepr,
{
let bytes = to_bytes(value).unwrap();
let archived = unsafe { rkyv::archived_root::<T>(bytes.as_slice()) };
assert_eq!(
archived_hash(archived),
Some(default_hash(value)),
"the archived form of {value:?} hashes differently from the decoded one",
);
assert_eq!(archived_hash(value), Some(default_hash(value)));
}
#[derive(Default)]
struct CallLog(Vec<Vec<u8>>);
impl std::hash::Hasher for CallLog {
fn finish(&self) -> u64 {
0
}
fn write(&mut self, bytes: &[u8]) {
self.0.push(bytes.to_vec());
}
}
fn check_calls<T>(value: &T)
where
T: DBData + std::hash::Hash,
T::Repr: HashRepr,
{
let bytes = to_bytes(value).unwrap();
let archived = unsafe { rkyv::archived_root::<T>(bytes.as_slice()) };
let mut decoded = CallLog::default();
std::hash::Hash::hash(value, &mut decoded);
let mut archived_calls = CallLog::default();
archived.hash_repr(&mut archived_calls);
assert_eq!(
decoded.0, archived_calls.0,
"hashing the archived form of {value:?} asks the hasher for \
something different from hashing the decoded one",
);
}
#[test]
fn a_sequence_asks_the_hasher_for_what_the_decoded_one_asks_for() {
check_calls(&vec![1i64, 2, 3]);
check_calls(&Vec::<i64>::new());
check_calls(&vec![0u8, 1, 255]);
check_calls(&vec![1i32, -1]);
check_calls(&vec![1u128, 2]);
check_calls(&vec![true, false]);
check_calls(&vec!['a', 'b']);
check_calls(&vec![Some(1i64), None]);
check_calls(&vec![String::from("a"), String::new()]);
check_calls(&vec![vec![1u8], vec![]]);
}
#[test]
fn primitives_and_strings() {
check(&0i64);
check(&i64::MIN);
check(&u128::MAX);
check(&true);
check(&String::new());
check(&"hello".to_string());
}
#[test]
fn options_and_sequences() {
check(&Option::<i64>::None);
check(&Some(7i64));
check(&Some(String::new()));
check(&Vec::<i64>::new());
check(&vec![1i64, 2, 3]);
check(&vec![String::from("a")]);
check(&vec![Some(1i64), None]);
}
#[test]
fn maps_carry_their_length_prefix() {
check(&BTreeMap::<i64, i64>::new());
check(&BTreeMap::from([(1i64, 2i64)]));
check(&BTreeMap::from([(1i64, 2i64), (3, 4)]));
check(&BTreeMap::from([(String::from("a"), vec![1i64])]));
check(&BTreeMap::from([(1i64, BTreeMap::from([(2i64, 3i64)]))]));
}
#[test]
fn tuples_hash_their_fields_in_order() {
use crate::utils::{Tup1, Tup2, Tup3, Tup8};
check(&Tup1::new(1i64));
check(&Tup2::new(1i64, 2u32));
check(&Tup2::new(Some(1i64), Option::<String>::None));
check(&Tup2::new(String::from("a"), vec![1i64, 2]));
check(&Tup3::new(1i64, String::new(), Option::<i64>::None));
check(&Tup8::new(1i64, 2u32, 3i16, 4u8, 5i8, 6u16, 7i32, 8u64));
const { assert!(<Tup2<i64, u32> as HashRepr>::FAITHFUL) };
}
#[test]
fn floats_including_the_awkward_ones() {
use crate::algebra::{F32, F64};
for value in [
f64::NEG_INFINITY,
f64::MIN,
-0.0,
0.0,
f64::MIN_POSITIVE,
f64::MAX,
f64::INFINITY,
f64::NAN,
] {
check(&F64::from(value));
}
for value in [f32::NEG_INFINITY, -0.0, 0.0, f32::INFINITY, f32::NAN] {
check(&F32::from(value));
}
check(&Some(F64::from(f64::NAN)));
check(&vec![F64::from(-0.0), F64::from(0.0)]);
}
#[test]
fn an_unfaithful_archived_form_poisons_what_holds_it() {
use rkyv::collections::btree_map::ArchivedBTreeMap;
use rkyv::option::ArchivedOption;
use rkyv::vec::ArchivedVec;
struct Opaque;
impl HashRepr for Opaque {
const FAITHFUL: bool = false;
fn hash_repr<H: std::hash::Hasher>(&self, _state: &mut H) {}
}
assert_eq!(archived_hash(&Opaque), None);
const { assert!(!<ArchivedOption<Opaque> as HashRepr>::FAITHFUL) };
const { assert!(!<ArchivedVec<Opaque> as HashRepr>::FAITHFUL) };
const { assert!(!<ArchivedBTreeMap<i64, Opaque> as HashRepr>::FAITHFUL) };
const { assert!(!<ArchivedBTreeMap<Opaque, i64> as HashRepr>::FAITHFUL) };
const { assert!(<ArchivedVec<i64> as HashRepr>::FAITHFUL) };
}
}