From a2d78ecef9f14187dd630d205bb2392b4bb99816 Mon Sep 17 00:00:00 2001 From: Joe Neeman Date: Sun, 1 May 2022 15:30:58 -0500 Subject: [PATCH] Use const generics instead of typenum. This has the (desired) side effect of making containers covariant over their input type, so we also add some assertions to ensure that this doesn't regress. Fixes #196. --- CHANGELOG.md | 6 ++++++ Cargo.toml | 4 ++-- src/config.rs | 8 +++----- src/hash/map.rs | 3 +++ src/hash/set.rs | 2 ++ src/lib.rs | 6 +++--- src/nodes/btree.rs | 9 ++++----- src/nodes/hamt.rs | 25 +++++++++++-------------- src/nodes/mod.rs | 8 ++------ src/ord/map.rs | 3 +++ src/ord/set.rs | 2 ++ src/test.rs | 21 +++++++-------------- src/util.rs | 13 +++++++++++++ src/vector/mod.rs | 2 ++ 14 files changed, 63 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edcf429..6700a0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- All container types are now covariant over the stored type. (#196) + ## [15.1.0] - 2022-04-29 ### Added diff --git a/Cargo.toml b/Cargo.toml index 93f581d..9415bf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,8 +35,8 @@ debug = [] [dependencies] typenum = "1.12" -bitmaps = "2" -sized-chunks = "0.6.4" +bitmaps = "3" +sized-chunks = "0.7" rand_core = "0.6" rand_xoshiro = "0.6" quickcheck = { version = "1", optional = true } diff --git a/src/config.rs b/src/config.rs index f861139..6abec59 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,17 +2,15 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -use typenum::*; - /// The branching factor of RRB-trees -pub(crate) type VectorChunkSize = U64; +pub(crate) const VECTOR_CHUNK_SIZE: usize = 64; /// The branching factor of B-trees -pub(crate) type OrdChunkSize = U64; // Must be an even number! +pub(crate) const ORD_CHUNK_SIZE: usize = 64; // Must be an even number! /// The level size of HAMTs, in bits /// Branching factor is 2 ^ HashLevelSize. -pub(crate) type HashLevelSize = U5; +pub(crate) const HASH_LEVEL_SIZE: usize = 5; /// The size of per-instance memory pools if the `pool` feature is enabled. /// This is set to 0, meaning you have to opt in to using a pool by constructing diff --git a/src/hash/map.rs b/src/hash/map.rs index 4bbba40..8645f7a 100644 --- a/src/hash/map.rs +++ b/src/hash/map.rs @@ -2125,6 +2125,9 @@ mod test { use ::proptest::{collection, proptest}; use std::hash::BuildHasherDefault; + assert_covariant!(HashMap in T); + assert_covariant!(HashMap in T); + #[test] fn safe_mutation() { let v1: HashMap = (0..131_072).map(|i| (i, i)).collect::>(); diff --git a/src/hash/set.rs b/src/hash/set.rs index 11ba3a0..0d67207 100644 --- a/src/hash/set.rs +++ b/src/hash/set.rs @@ -1070,6 +1070,8 @@ mod test { use ::proptest::proptest; use std::hash::BuildHasherDefault; + assert_covariant!(HashSet in T); + #[test] fn insert_failing() { let mut set: HashSet> = Default::default(); diff --git a/src/lib.rs b/src/lib.rs index 0444dbd..ed318f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -344,14 +344,14 @@ #[macro_use] extern crate pretty_assertions; +#[macro_use] +mod util; + mod config; mod nodes; mod sort; mod sync; -#[macro_use] -mod util; - #[macro_use] mod ord; pub use crate::ord::map as ordmap; diff --git a/src/nodes/btree.rs b/src/nodes/btree.rs index 84f63fa..0102a18 100644 --- a/src/nodes/btree.rs +++ b/src/nodes/btree.rs @@ -8,16 +8,15 @@ use std::mem; use std::ops::{Bound, RangeBounds}; use sized_chunks::Chunk; -use typenum::{Add1, Unsigned}; -use crate::config::OrdChunkSize as NodeSize; +pub(crate) use crate::config::ORD_CHUNK_SIZE as NODE_SIZE; use crate::util::{Pool, PoolClone, PoolDefault, PoolRef}; use self::Insert::*; use self::InsertAction::*; -pub(crate) const NODE_SIZE: usize = NodeSize::USIZE; const MEDIAN: usize = (NODE_SIZE + 1) >> 1; +const NUM_CHILDREN: usize = NODE_SIZE + 1; pub trait BTreeValue { type Key; @@ -38,8 +37,8 @@ pub trait BTreeValue { } pub(crate) struct Node { - keys: Chunk, - children: Chunk>>, Add1>, + keys: Chunk, + children: Chunk>>, NUM_CHILDREN>, } #[cfg(feature = "pool")] diff --git a/src/nodes/hamt.rs b/src/nodes/hamt.rs index 945068b..9bf75fa 100644 --- a/src/nodes/hamt.rs +++ b/src/nodes/hamt.rs @@ -9,17 +9,14 @@ use std::iter::FusedIterator; use std::slice::{Iter as SliceIter, IterMut as SliceIterMut}; use std::{mem, ptr}; -use bitmaps::Bits; +use bitmaps::{Bits, BitsImpl}; use sized_chunks::sparse_chunk::{Iter as ChunkIter, IterMut as ChunkIterMut, SparseChunk}; -use typenum::{Pow, Unsigned, U2}; -use crate::config::HashLevelSize; use crate::util::{clone_ref, Pool, PoolClone, PoolDefault, PoolRef, Ref}; -pub(crate) type HashWidth = >::Output; -pub(crate) type HashBits = ::Store; // a uint of HASH_SIZE bits -pub(crate) const HASH_SHIFT: usize = HashLevelSize::USIZE; -pub(crate) const HASH_WIDTH: usize = HashWidth::USIZE; +pub(crate) use crate::config::HASH_LEVEL_SIZE as HASH_SHIFT; +pub(crate) const HASH_WIDTH: usize = 2_usize.pow(HASH_SHIFT as u32); +pub(crate) type HashBits = as Bits>::Store; // a uint of HASH_WIDTH bits pub(crate) const HASH_MASK: HashBits = (HASH_WIDTH - 1) as HashBits; pub(crate) fn hash_key(bh: &S, key: &K) -> HashBits { @@ -42,7 +39,7 @@ pub trait HashValue { #[derive(Clone)] pub(crate) struct Node { - data: SparseChunk, HashWidth>, + data: SparseChunk, HASH_WIDTH>, } #[allow(unsafe_code)] @@ -52,7 +49,7 @@ impl PoolDefault for Node { SparseChunk::default_uninit( target .as_mut_ptr() - .cast::, HashWidth>>>() + .cast::, HASH_WIDTH>>>() .as_mut() .unwrap(), ) @@ -69,7 +66,7 @@ where self.data.clone_uninit( target .as_mut_ptr() - .cast::, HashWidth>>>() + .cast::, HASH_WIDTH>>>() .as_mut() .unwrap(), ) @@ -471,8 +468,8 @@ impl CollisionNode { pub(crate) struct Iter<'a, A> { count: usize, - stack: Vec, HashWidth>>, - current: ChunkIter<'a, Entry, HashWidth>, + stack: Vec, HASH_WIDTH>>, + current: ChunkIter<'a, Entry, HASH_WIDTH>, collision: Option<(HashBits, SliceIter<'a, A>)>, } @@ -551,8 +548,8 @@ impl<'a, A> FusedIterator for Iter<'a, A> where A: 'a {} pub(crate) struct IterMut<'a, A> { count: usize, pool: Pool>, - stack: Vec, HashWidth>>, - current: ChunkIterMut<'a, Entry, HashWidth>, + stack: Vec, HASH_WIDTH>>, + current: ChunkIterMut<'a, Entry, HASH_WIDTH>, collision: Option<(HashBits, SliceIterMut<'a, A>)>, } diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index 24e7455..428bfc7 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -7,10 +7,6 @@ pub(crate) mod hamt; pub(crate) mod rrb; pub(crate) mod chunk { - use crate::config::VectorChunkSize; - use sized_chunks as sc; - use typenum::Unsigned; - - pub(crate) type Chunk = sc::sized_chunk::Chunk; - pub(crate) const CHUNK_SIZE: usize = VectorChunkSize::USIZE; + pub(crate) use crate::config::VECTOR_CHUNK_SIZE as CHUNK_SIZE; + pub(crate) type Chunk = sized_chunks::sized_chunk::Chunk; } diff --git a/src/ord/map.rs b/src/ord/map.rs index 1c32a38..3e6895a 100644 --- a/src/ord/map.rs +++ b/src/ord/map.rs @@ -2190,6 +2190,9 @@ mod test { use ::proptest::num::{i16, usize}; use ::proptest::{bool, collection, proptest}; + assert_covariant!(OrdMap in T); + assert_covariant!(OrdMap in T); + #[test] fn iterates_in_order() { let map = ordmap! { diff --git a/src/ord/set.rs b/src/ord/set.rs index 2fd674a..91a5bd9 100644 --- a/src/ord/set.rs +++ b/src/ord/set.rs @@ -1185,6 +1185,8 @@ mod test { use crate::proptest::*; use ::proptest::proptest; + assert_covariant!(OrdSet in T); + #[test] fn match_strings_with_string_slices() { let mut set: OrdSet = From::from(&ordset!["foo", "bar"]); diff --git a/src/test.rs b/src/test.rs index 9887d01..5762bca 100644 --- a/src/test.rs +++ b/src/test.rs @@ -4,8 +4,6 @@ use metrohash::MetroHash64; use std::hash::{BuildHasher, Hasher}; -use std::marker::PhantomData; -use typenum::{Unsigned, U64}; pub(crate) fn is_sorted(l: I) -> bool where @@ -22,13 +20,12 @@ where } } -pub(crate) struct LolHasher { +pub(crate) struct LolHasher { state: u64, shift: usize, - size: PhantomData, } -impl LolHasher { +impl LolHasher { fn feed_me(&mut self, byte: u8) { self.state ^= u64::from(byte) << self.shift; self.shift += 8; @@ -38,7 +35,7 @@ impl LolHasher { } } -impl Hasher for LolHasher { +impl Hasher for LolHasher { fn write(&mut self, bytes: &[u8]) { for byte in bytes { self.feed_me(*byte) @@ -46,21 +43,17 @@ impl Hasher for LolHasher { } fn finish(&self) -> u64 { - if N::USIZE == 64 { + if N == 64 { self.state } else { - self.state & ((1 << N::USIZE) - 1) + self.state & ((1 << N) - 1) } } } -impl Default for LolHasher { +impl Default for LolHasher { fn default() -> Self { - LolHasher { - state: 0, - shift: 0, - size: PhantomData, - } + LolHasher { state: 0, shift: 0 } } } diff --git a/src/util.rs b/src/util.rs index 5451f15..c5d6f59 100644 --- a/src/util.rs +++ b/src/util.rs @@ -140,3 +140,16 @@ macro_rules! def_pool { } }; } + +#[cfg(test)] +macro_rules! assert_covariant { + ($name:ident<$($gen:tt),*> in $param:ident) => { + #[allow(unused_assignments, unused_variables)] + const _: () = { + type Tmp<$param> = $name<$($gen),*>; + fn assign<'a, 'b: 'a>(src: Tmp<&'b i32>, mut dst: Tmp<&'a i32>) { + dst = src; + } + }; + } +} diff --git a/src/vector/mod.rs b/src/vector/mod.rs index 96170d3..a69d289 100644 --- a/src/vector/mod.rs +++ b/src/vector/mod.rs @@ -2270,6 +2270,8 @@ mod test { use ::proptest::num::{i32, usize}; use ::proptest::proptest; + assert_covariant!(Vector in T); + #[test] fn macro_allows_trailing_comma() { let vec1 = vector![1, 2, 3];