Skip to content

Commit

Permalink
Implement Arbitrary from the arbitrary crate.
Browse files Browse the repository at this point in the history
  • Loading branch information
bodil committed Jan 17, 2020
1 parent 6ad00ea commit be6852c
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
refer to the same content in memory, by testing for pointer equality. (#117)
- `HashMap` had lost its `Arbitrary` implementation for the `quickcheck` feature flag. It's now
been restored. (#118)
- Implementations for `Arbitrary` from the [`arbitrary`](https://crates.io/crates/arbitrary/)
crate have been added behind the `arbitrary` feature flag.

### Fixed

Expand Down
9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ readme = "./README.md"
categories = ["data-structures"]
keywords = ["immutable", "persistent", "hamt", "b-tree", "rrb-tree"]
build = "./build.rs"
exclude = ["dist/**", "rc/**", "release.toml", "Makefile.toml", "proptest-regressions/**"]
exclude = [
"dist/**",
"rc/**",
"release.toml",
"Makefile.toml",
"proptest-regressions/**"
]

[lib]
path = "./src/lib.rs"
Expand Down Expand Up @@ -40,6 +46,7 @@ proptest = { version = "0.9", optional = true }
serde = { version = "1.0", optional = true }
rayon = { version = "1.0", optional = true }
refpool = { version = "0.2.2", optional = true }
arbitrary = { version = "0.3", optional = true }

[dev-dependencies]
proptest = "0.9"
Expand Down
149 changes: 149 additions & 0 deletions src/arbitrary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// 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 std::hash::{BuildHasher, Hash};
use std::iter;

use ::arbitrary::{size_hint, Arbitrary, Result, Unstructured};

use crate::{HashMap, HashSet, OrdMap, OrdSet, Vector};

fn empty<T: 'static>() -> Box<dyn Iterator<Item = T>> {
Box::new(iter::empty())
}

fn shrink_collection<T: Clone, A: Clone + Arbitrary>(
entries: impl Iterator<Item = T>,
f: impl Fn(&T) -> Box<dyn Iterator<Item = A>>,
) -> Box<dyn Iterator<Item = Vec<A>>> {
let entries: Vec<_> = entries.collect();
if entries.is_empty() {
return empty();
}

let mut shrinkers: Vec<Vec<_>> = vec![];
let mut i = entries.len();
loop {
shrinkers.push(entries.iter().take(i).map(&f).collect());
i /= 2;
if i == 0 {
break;
}
}
Box::new(iter::once(Vec::new()).chain(iter::from_fn(move || loop {
let mut shrinker = shrinkers.pop()?;
let x: Option<Vec<A>> = shrinker.iter_mut().map(|s| s.next()).collect();
if x.is_none() {
continue;
}
shrinkers.push(shrinker);
return x;
})))
}

impl<A: Arbitrary + Clone> Arbitrary for Vector<A> {
fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
u.arbitrary_iter()?.collect()
}

fn arbitrary_take_rest(u: Unstructured<'_>) -> Result<Self> {
u.arbitrary_take_rest_iter()?.collect()
}

fn size_hint() -> (usize, Option<usize>) {
size_hint::and(<usize as Arbitrary>::size_hint(), (0, None))
}

fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let collections = shrink_collection(self.iter(), |x| x.shrink());
Box::new(collections.map(|entries| entries.into_iter().collect()))
}
}

impl<K: Arbitrary + Ord + Clone, V: Arbitrary + Clone> Arbitrary for OrdMap<K, V> {
fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
u.arbitrary_iter()?.collect()
}

fn arbitrary_take_rest(u: Unstructured<'_>) -> Result<Self> {
u.arbitrary_take_rest_iter()?.collect()
}

fn size_hint() -> (usize, Option<usize>) {
size_hint::and(<usize as Arbitrary>::size_hint(), (0, None))
}

fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let collections =
shrink_collection(self.iter(), |(k, v)| Box::new(k.shrink().zip(v.shrink())));
Box::new(collections.map(|entries| entries.into_iter().collect()))
}
}

impl<A: Arbitrary + Ord + Clone> Arbitrary for OrdSet<A> {
fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
u.arbitrary_iter()?.collect()
}

fn arbitrary_take_rest(u: Unstructured<'_>) -> Result<Self> {
u.arbitrary_take_rest_iter()?.collect()
}

fn size_hint() -> (usize, Option<usize>) {
size_hint::and(<usize as Arbitrary>::size_hint(), (0, None))
}

fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let collections = shrink_collection(self.iter(), |v| v.shrink());
Box::new(collections.map(|entries| entries.into_iter().collect()))
}
}

impl<K, V, S> Arbitrary for HashMap<K, V, S>
where
K: Arbitrary + Hash + Eq + Clone,
V: Arbitrary + Clone,
S: BuildHasher + Default + 'static,
{
fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
u.arbitrary_iter()?.collect()
}

fn arbitrary_take_rest(u: Unstructured<'_>) -> Result<Self> {
u.arbitrary_take_rest_iter()?.collect()
}

fn size_hint() -> (usize, Option<usize>) {
size_hint::and(<usize as Arbitrary>::size_hint(), (0, None))
}

fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let collections =
shrink_collection(self.iter(), |(k, v)| Box::new(k.shrink().zip(v.shrink())));
Box::new(collections.map(|entries| entries.into_iter().collect()))
}
}

impl<A, S> Arbitrary for HashSet<A, S>
where
A: Arbitrary + Hash + Eq + Clone,
S: BuildHasher + Default + 'static,
{
fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
u.arbitrary_iter()?.collect()
}

fn arbitrary_take_rest(u: Unstructured<'_>) -> Result<Self> {
u.arbitrary_take_rest_iter()?.collect()
}

fn size_hint() -> (usize, Option<usize>) {
size_hint::and(<usize as Arbitrary>::size_hint(), (0, None))
}

fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let collections = shrink_collection(self.iter(), |v| v.shrink());
Box::new(collections.map(|entries| entries.into_iter().collect()))
}
}
7 changes: 6 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,10 @@
//! | ------- | ----------- |
//! | [`pool`](https://crates.io/crates/refpool) | Constructors and pool types for [`refpool`](https://crates.io/crates/refpool) memory pools (recommended only for `im-rc`) |
//! | [`proptest`](https://crates.io/crates/proptest) | Strategies for all `im` datatypes under a `proptest` namespace, eg. `im::vector::proptest::vector()` |
//! | [`quickcheck`](https://crates.io/crates/quickcheck) | [`Arbitrary`](https://docs.rs/quickcheck/latest/quickcheck/trait.Arbitrary.html) implementations for all `im` datatypes (not available in `im-rc`) |
//! | [`quickcheck`](https://crates.io/crates/quickcheck) | [`quickcheck::Arbitrary`](https://docs.rs/quickcheck/latest/quickcheck/trait.Arbitrary.html) implementations for all `im` datatypes (not available in `im-rc`) |
//! | [`rayon`](https://crates.io/crates/rayon) | parallel iterator implementations for [`Vector`][vector::Vector] (not available in `im-rc`) |
//! | [`serde`](https://crates.io/crates/serde) | [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html) and [`Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html) implementations for all `im` datatypes |
//! | [`arbitrary`](https://crates.io/crates/arbitrary/) | [`arbitrary::Arbitrary`](https://docs.rs/arbitrary/latest/arbitrary/trait.Arbitrary.html) implementations for all `im` datatypes |
//!
//! [std::collections]: https://doc.rust-lang.org/std/collections/index.html
//! [std::collections::VecDeque]: https://doc.rust-lang.org/std/collections/struct.VecDeque.html
Expand Down Expand Up @@ -370,6 +371,10 @@ pub mod iter;
#[doc(hidden)]
pub mod ser;

#[cfg(feature = "arbitrary")]
#[doc(hidden)]
pub mod arbitrary;

#[cfg(not(feature = "pool"))]
mod fakepool;

Expand Down

0 comments on commit be6852c

Please sign in to comment.