diff --git a/Cargo.lock b/Cargo.lock index 5fa2d37b..25a29e26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,9 +222,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af91f40b7355f82b0a891f50e70399475945bb0b0da4f1700ce60761c9d3e359" +checksum = "0b015497079b9a9d69c02ad25de6c0a6edef051ea6360a327d0bd05802ef64ad" dependencies = [ "csv-core", "itoa", @@ -349,9 +349,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" @@ -422,9 +422,9 @@ checksum = "04b6f3f92034b6b6ecdaa8362c8de550eb2a620c29dea5aec38dbf21ee0e1817" [[package]] name = "nb" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" [[package]] name = "no-std-net" @@ -567,9 +567,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pkg-config" @@ -656,9 +656,9 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rayon" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.2" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -704,9 +704,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "same-file" @@ -731,9 +731,9 @@ checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e" dependencies = [ "serde_derive", ] @@ -761,9 +761,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217" dependencies = [ "proc-macro2", "quote", @@ -772,9 +772,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "itoa", "ryu", @@ -973,6 +973,14 @@ dependencies = [ "syn", ] +[[package]] +name = "toad-map" +version = "0.0.0" +dependencies = [ + "tinyvec", + "toad-len 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "toad-msg" version = "0.15.0" @@ -1008,9 +1016,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-width" diff --git a/release-please-config.json b/release-please-config.json index d28d6836..6bf0bb6e 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -50,6 +50,16 @@ "extra-files": ["src/lib.rs"], "prerelease": true }, + "toad-map": { + "package-name": "toad-map", + "changelog-path": "CHANGELOG.md", + "release-type": "rust", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "extra-files": ["src/lib.rs"], + "prerelease": true + }, "toad-array": { "package-name": "toad-array", "changelog-path": "CHANGELOG.md", diff --git a/toad-hash/src/lib.rs b/toad-hash/src/lib.rs index dc99d46f..3127474d 100644 --- a/toad-hash/src/lib.rs +++ b/toad-hash/src/lib.rs @@ -37,7 +37,7 @@ use blake2::{Blake2b, Digest}; /// ``` /// use core::hash::{Hash, Hasher}; /// -/// use toad_common::hash::Blake2Hasher; +/// use toad_hash::Blake2Hasher; /// /// let mut hasher_a = Blake2Hasher::new(); /// let mut hasher_b = Blake2Hasher::new(); diff --git a/toad-map/CHANGELOG.md b/toad-map/CHANGELOG.md new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/toad-map/CHANGELOG.md @@ -0,0 +1 @@ + diff --git a/toad-map/Cargo.toml b/toad-map/Cargo.toml new file mode 100644 index 00000000..7ebe2b8f --- /dev/null +++ b/toad-map/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "toad-map" +version = "0.0.0" +edition = "2021" +description = "Map / Dictionary trait that is no_std and heap-allocator-optional" +authors = ["Orion Kindel "] +license = "MIT OR Apache-2.0" +homepage = "https://github.com/clov-coffee/toad/toad" +repository = "https://github.com/clov-coffee/toad/toad" +readme = "README.md" +keywords = ["coap", "iot", "networking", "no_std", "wasm"] +categories = ["network-programming"] + +[badges] +maintenance = { status = "actively-developed" } + +[features] +default = ["std"] +std = ["alloc", "toad-len/std"] +alloc = ["toad-len/alloc"] +test = [] +docs = [] + +[dependencies] +tinyvec = {version = "1.5", default_features = false, features = ["rustc_1_55"]} +toad-len = {version = "0.1.1", default_features = false} diff --git a/toad-map/Makefile.toml b/toad-map/Makefile.toml new file mode 100644 index 00000000..32e5903a --- /dev/null +++ b/toad-map/Makefile.toml @@ -0,0 +1,32 @@ +extend = "../Makefile.toml" + +[tasks.bench] +install_crate = "cargo-criterion" +command = "cargo" +args = ["criterion"] + +[tasks.flame] +install_crate = "cargo-flamegraph" +command = "cargo" +args = ["flamegraph", "--bench", "profile", "--", "--bench"] + +[tasks.check-no-std] +command = "cargo" +args = ["check", "--no-default-features"] + +[tasks.check-alloc] +command = "cargo" +args = ["check", "--no-default-features", "--features", "alloc"] + +[tasks.ci] +dependencies = ["test", "fmt-check", "clippy-check", "check-no-std", "check-alloc"] + +[tasks.tdd] +install_crate = "cargo-watch" +command = "cargo" +args = [ "watch" + , "--clear" + , "--watch", "toad-TODO/src" + , "--delay", "0" + , "-x", "make --cwd toad-TODO -t test-quiet --loglevel error" + ] diff --git a/toad-map/README.md b/toad-map/README.md new file mode 100644 index 00000000..44cd1241 --- /dev/null +++ b/toad-map/README.md @@ -0,0 +1,24 @@ +[![crates.io](https://img.shields.io/crates/v/toad-TODO.svg)](https://crates.io/crates/toad-TODO) +[![docs.rs](https://docs.rs/toad-TODO/badge.svg)](https://docs.rs/toad-TODO/latest) +![Maintenance](https://img.shields.io/badge/maintenance-activly--developed-brightgreen.svg) + +# toad-map + +This microcrate contains a `Map` trait that generalizes `HashMap` semantics +to `std`, `alloc` and `no_std` platforms. + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/toad-map/README.tpl b/toad-map/README.tpl new file mode 100644 index 00000000..f4c6e68d --- /dev/null +++ b/toad-map/README.tpl @@ -0,0 +1,23 @@ +[![crates.io](https://img.shields.io/crates/v/toad-TODO.svg)](https://crates.io/crates/toad-TODO) +[![docs.rs](https://docs.rs/toad-TODO/badge.svg)](https://docs.rs/toad-TODO/latest) +{{badges}} + +# {{crate}} + +{{readme}} + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/toad-map/src/lib.rs b/toad-map/src/lib.rs new file mode 100644 index 00000000..094f2caa --- /dev/null +++ b/toad-map/src/lib.rs @@ -0,0 +1,600 @@ +//! This microcrate contains a `Map` trait that generalizes `HashMap` semantics +//! to `std`, `alloc` and `no_std` platforms. + +// docs +#![doc(html_root_url = "https://docs.rs/toad-map/0.0.0")] +#![cfg_attr(any(docsrs, feature = "docs"), feature(doc_cfg))] +// - +// style +#![allow(clippy::unused_unit)] +// - +// deny +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(missing_copy_implementations)] +#![cfg_attr(not(test), deny(unsafe_code))] +// - +// warnings +#![cfg_attr(not(test), warn(unreachable_pub))] +// - +// features +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "alloc")] +extern crate alloc as std_alloc; + +use core::borrow::Borrow; +use core::hash::Hash; +use core::ops::{Deref, DerefMut}; +use core::{iter, slice}; +#[cfg(feature = "std")] +use std::collections::{hash_map, HashMap}; + +#[cfg(feature = "alloc")] +use std_alloc::collections::{btree_map, BTreeMap}; + +use toad_len::Len; + +/// Things that can go unhappily when trying to insert into a map +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq)] +pub enum InsertError { + /// The new value was inserted successfully but there was already a value in the map for that key. + Exists(V), + /// The map is at capacity and cannot fit any more pairs. + CapacityExhausted, +} + +/// An collection of key-value pairs +/// +/// # Provided implementations +/// - [`HashMap`]`` +/// - [`tinyvec::ArrayVec`]`<(K, V)>` +/// - [`Vec`]`<(K, V)>` +/// +/// # Requirements +/// - [`Default`] for creating the map +/// - [`Extend`] for adding new entries to the map +/// - [`Len`] for bound checks, empty checks, and accessing the length +/// - [`FromIterator`] for [`collect`](core::iter::Iterator#method.collect)ing into the map +/// - [`IntoIterator`] for iterating and destroying the map +pub trait Map: + Default + Len + Extend<(K, V)> + FromIterator<(K, V)> + IntoIterator +{ + /// See [`HashMap.insert`] + fn insert(&mut self, key: K, val: V) -> Result<(), InsertError>; + + /// See [`HashMap.remove`] + fn remove(&mut self, key: &Q) -> Option + where K: Borrow, Q: Hash + Eq + Ord; + + /// See [`HashMap.get`] + fn get<'a, Q: Hash + Eq + Ord>(&'a self, key: &Q) -> Option<&'a V> + where K: Borrow + 'a; + + /// See [`HashMap.get_mut`] + fn get_mut<'a, Q: Hash + Eq + Ord>(&'a mut self, key: &Q) -> Option<&'a mut V> + where K: Borrow + 'a; + + /// See [`HashMap.contains_key`] + fn has(&self, key: &Q) -> bool + where K: Borrow + { + self.get(key).is_some() + } + + /// See [`HashMap.iter`] + fn iter(&self) -> Iter<'_, K, V>; + + /// See [`HashMap.iter_mut`] + fn iter_mut(&mut self) -> IterMut<'_, K, V>; +} + +#[cfg(feature = "alloc")] +impl Map for BTreeMap { + fn insert(&mut self, key: K, val: V) -> Result<(), InsertError> { + match self.insert(key, val) + .map(InsertError::Exists) + .ok_or(()) { + Ok(e) => Err(e), + Err(()) => Ok(()) + } + } + + fn remove(&mut self, key: &Q) -> Option + where K: Borrow + { + self.remove(key) + } + + fn get<'a, Q: Hash + Eq + Ord>(&'a self, key: &Q) -> Option<&'a V> + where K: Borrow + 'a + { + self.get(key) + } + + fn get_mut<'a, Q: Hash + Eq + Ord>(&'a mut self, key: &Q) -> Option<&'a mut V> + where K: Borrow + 'a + { + self.get_mut(key) + } + + fn iter(&self) -> Iter<'_, K, V> { + Iter { array_iter: None, + #[cfg(feature = "std")] + hashmap_iter: None, + btreemap_iter: Some(self.iter()) } + } + + fn iter_mut(&mut self) -> IterMut<'_, K, V> { + IterMut { array_iter: None, + #[cfg(feature = "std")] + hashmap_iter: None, + btreemap_iter: Some(self.iter_mut()) } + } +} + +#[cfg(feature = "std")] +impl Map for HashMap { + fn iter(&self) -> Iter<'_, K, V> { + Iter { array_iter: None, + btreemap_iter: None, + hashmap_iter: Some(self.iter()) } + } + + fn iter_mut(&mut self) -> IterMut<'_, K, V> { + IterMut { array_iter: None, + btreemap_iter: None, + hashmap_iter: Some(self.iter_mut()) } + } + + fn get<'a, Q: Hash + Eq + Ord>(&'a self, key: &Q) -> Option<&'a V> + where K: Borrow + 'a + { + self.get(key) + } + + fn get_mut<'a, Q: Hash + Eq + Ord>(&'a mut self, key: &Q) -> Option<&'a mut V> + where K: Borrow + 'a + { + self.get_mut(key) + } + + fn insert(&mut self, key: K, val: V) -> Result<(), InsertError> { + match self.insert(key, val) + .map(InsertError::Exists) + .ok_or(()) { + Ok(e) => Err(e), + Err(()) => Ok(()), + } + } + + fn remove(&mut self, key: &Q) -> Option + where K: Borrow + { + self.remove(key) + } +} + +impl, K: Eq + Hash + Ord, V> Map for tinyvec::ArrayVec { + fn insert(&mut self, key: K, mut val: V) -> Result<(), InsertError> { + match self.iter_mut().find(|(k, _)| k == &&key) { + | Some((_, exist)) => { + core::mem::swap(exist, &mut val); + Err(InsertError::Exists(val)) + }, + | None => match self.is_full() { + | true => Err(InsertError::CapacityExhausted), + | false => { + self.push((key, val)); + Ok(()) + }, + }, + } + } + + fn remove(&mut self, key: &Q) -> Option + where K: Borrow + { + match self.iter() + .enumerate() + .find(|(_, (k, _))| Borrow::::borrow(*k) == key) + { + | Some((ix, _)) => Some(self.remove(ix).1), + | None => None, + } + } + + fn get<'a, Q: Hash + Eq + Ord>(&'a self, key: &Q) -> Option<&'a V> + where K: Borrow + 'a + { + match self.iter().find(|(k, _)| Borrow::::borrow(*k) == key) { + | Some((_, v)) => Some(v), + | None => None, + } + } + + fn get_mut<'a, Q: Hash + Eq + Ord>(&'a mut self, key: &Q) -> Option<&'a mut V> + where K: Borrow + 'a + { + match self.iter_mut() + .find(|(k, _)| Borrow::::borrow(*k) == key) + { + | Some((_, v)) => Some(v), + | None => None, + } + } + + fn iter(&self) -> Iter<'_, K, V> { + Iter { array_iter: Some(self.deref().iter().map(Iter::coerce_array_iter)), + #[cfg(feature = "alloc")] + btreemap_iter: None, + #[cfg(feature = "std")] + hashmap_iter: None } + } + + fn iter_mut(&mut self) -> IterMut<'_, K, V> { + IterMut { array_iter: Some(self.deref_mut().iter_mut().map(IterMut::coerce_array_iter)), + #[cfg(feature = "alloc")] + btreemap_iter: None, + #[cfg(feature = "std")] + hashmap_iter: None } + } +} + +#[cfg(feature = "alloc")] +impl Map for Vec<(K, V)> where K: Ord + Hash { + fn insert(&mut self, key: K, mut val: V) -> Result<(), InsertError> { + match self.iter_mut().find(|(k, _)| k == &&key) { + | Some((_, exist)) => { + core::mem::swap(exist, &mut val); + Err(InsertError::Exists(val)) + }, + | None => match self.is_full() { + | true => Err(InsertError::CapacityExhausted), + | false => { + self.push((key, val)); + Ok(()) + }, + }, + } + } + + fn remove(&mut self, key: &Q) -> Option + where K: Borrow, Q: Hash + Eq + Ord + { + match self.iter() + .enumerate() + .find(|(_, (k, _))| Borrow::::borrow(*k) == key) + { + | Some((ix, _)) => Some(self.remove(ix).1), + | None => None, + } + } + + fn get<'a, Q: Hash + Eq + Ord>(&'a self, key: &Q) -> Option<&'a V> + where K: Borrow + 'a + { + match self.iter().find(|(k, _)| Borrow::::borrow(*k) == key) { + | Some((_, v)) => Some(v), + | None => None, + } + } + + fn get_mut<'a, Q: Hash + Eq + Ord>(&'a mut self, key: &Q) -> Option<&'a mut V> + where K: Borrow + 'a + { + match self.iter_mut() + .find(|(k, _)| Borrow::::borrow(*k) == key) + { + | Some((_, v)) => Some(v), + | None => None, + } + } + + fn iter(&self) -> Iter<'_, K, V> { + Iter { array_iter: Some(self.deref().iter().map(Iter::coerce_array_iter)), + #[cfg(feature = "alloc")] + btreemap_iter: None, + #[cfg(feature = "std")] + hashmap_iter: None } + } + + fn iter_mut(&mut self) -> IterMut<'_, K, V> { + IterMut { array_iter: Some(self.deref_mut().iter_mut().map(IterMut::coerce_array_iter)), + #[cfg(feature = "alloc")] + btreemap_iter: None, + #[cfg(feature = "std")] + hashmap_iter: None } + } +} + +type ArrayIterCoercer<'a, K, V> = fn(&'a (K, V)) -> (&'a K, &'a V); +type ArrayIterMapped<'a, K, V> = iter::Map, ArrayIterCoercer<'a, K, V>>; + +type ArrayIterMutCoercer<'a, K, V> = fn(&'a mut (K, V)) -> (&'a K, &'a mut V); +type ArrayIterMutMapped<'a, K, V> = + iter::Map, ArrayIterMutCoercer<'a, K, V>>; + +/// An iterator over the entries of a `Map`. +/// +/// This `struct` is created by the [`iter`] method on [`Map`]. +/// See its documentation for more. +/// +/// [`iter`]: Map::iter +/// +/// # Example +/// +/// ``` +/// use std::collections::HashMap; +/// +/// use toad_common::Map; +/// +/// let mut map = HashMap::from([("a", 1)]); +/// +/// fn do_stuff(map: &impl Map<&'static str, usize>) { +/// let iter = map.iter(); +/// } +/// ``` +#[derive(Debug)] +pub struct Iter<'a, K: Eq + Hash, V> { + #[cfg(feature = "std")] + hashmap_iter: Option>, + #[cfg(feature = "alloc")] + btreemap_iter: Option>, + array_iter: Option>, +} + +impl<'a, K: Eq + Hash, V> Iter<'a, K, V> { + #[inline(always)] + fn coerce_array_iter((k, v): &'a (K, V)) -> (&'a K, &'a V) { + (k, v) + } + + #[allow(unreachable_code)] + fn get_iter(&mut self) -> &mut dyn Iterator { + #[cfg(feature = "std")] + { + let (a, b, c) = (self.hashmap_iter.as_mut().map(|a| a as &mut _), + self.array_iter.as_mut().map(|a| a as &mut _), + self.btreemap_iter.as_mut().map(|a| a as &mut _)); + return a.or(b).or(c).unwrap(); + }; + + #[cfg(feature = "alloc")] + { + let (a, b) = (self.array_iter.as_mut().map(|a| a as &mut _), + self.btreemap_iter.as_mut().map(|a| a as &mut _)); + return a.or(b).unwrap(); + } + + // no_std and no alloc; must be array + self.array_iter.as_mut().map(|a| a as &mut _).unwrap() + } +} + +impl<'a, K: Eq + Hash, V> Iterator for Iter<'a, K, V> { + type Item = (&'a K, &'a V); + + fn next(&mut self) -> Option { + self.get_iter().next() + } +} + +/// A mutable iterator over the entries of a `Map`. +/// +/// This `struct` is created by the [`iter_mut`] method on [`Map`]. See its +/// documentation for more. +/// +/// [`iter_mut`]: Map::iter_mut +/// +/// # Example +/// +/// ``` +/// use std::collections::HashMap; +/// +/// use toad_common::Map; +/// +/// let mut map = HashMap::from([("a", 1)]); +/// +/// fn do_stuff(map: &mut impl Map<&'static str, usize>) { +/// let iter = map.iter_mut(); +/// } +/// ``` +#[derive(Debug)] +pub struct IterMut<'a, K: Eq + Hash, V> { + #[cfg(feature = "std")] + hashmap_iter: Option>, + #[cfg(feature = "alloc")] + btreemap_iter: Option>, + array_iter: Option>, +} + +impl<'a, K: Eq + Hash, V> IterMut<'a, K, V> { + #[inline(always)] + fn coerce_array_iter((k, v): &'a mut (K, V)) -> (&'a K, &'a mut V) { + (k, v) + } + + #[allow(unreachable_code)] + fn get_iter(&mut self) -> &mut dyn Iterator { + #[cfg(feature = "std")] + { + let (a, b, c) = (self.hashmap_iter.as_mut().map(|a| a as &mut _), + self.array_iter.as_mut().map(|a| a as &mut _), + self.btreemap_iter.as_mut().map(|a| a as &mut _)); + return a.or(b).or(c).unwrap(); + }; + + #[cfg(feature = "alloc")] + { + let (a, b) = (self.array_iter.as_mut().map(|a| a as &mut _), + self.btreemap_iter.as_mut().map(|a| a as &mut _)); + return a.or(b).unwrap(); + } + + // no_std and no alloc; must be array + self.array_iter.as_mut().map(|a| a as &mut _).unwrap() + } +} + +impl<'a, K: Eq + Hash, V> Iterator for IterMut<'a, K, V> { + type Item = (&'a K, &'a mut V); + + fn next(&mut self) -> Option { + self.get_iter().next() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn impls( + ) + -> (impl Map, + impl Map, + impl Map, + impl Map) + { + (HashMap::::from([("foo".into(), "bar".into())]), + BTreeMap::::from([("foo".into(), "bar".into())]), + tinyvec::array_vec!([(String, String); 16] => ("foo".into(), "bar".into())), + vec![("foo".to_string(), "bar".to_string())]) + } + + macro_rules! each_impl { + ($work:expr) => {{ + let (hm, bt, av, vc) = impls(); + println!("hashmap"); + $work(hm); + println!("btreemap"); + $work(bt); + println!("arrayvec"); + $work(av); + println!("vec"); + $work(vc); + }}; + } + + #[test] + fn get() { + fn test_get>(map: M) { + assert_eq!(map.get(&"foo".to_string()), Some(&"bar".into())); + assert_eq!(map.get(&"foot".to_string()), None); + } + + each_impl!(test_get); + } + + #[test] + fn get_mut() { + fn test_get_mut>(mut map: M) { + let old = map.get_mut(&"foo".to_string()).unwrap(); + *old = format!("{}f", old); + + assert_eq!(map.get(&"foo".to_string()), Some(&"barf".into())); + } + + each_impl!(test_get_mut); + } + + #[test] + fn insert() { + fn test_insert>(mut map: M) { + let old = map.insert("foot".to_string(), "butt".to_string()); + + assert_eq!(old, Ok(())); + assert_eq!(map.get(&"foo".to_string()).unwrap().as_str(), "bar"); + assert_eq!(map.get(&"foot".to_string()).unwrap().as_str(), "butt"); + + let old = map.insert("foot".to_string(), "squat".to_string()); + assert_eq!(old, Err(InsertError::Exists("butt".to_string()))); + assert_eq!(map.get(&"foot".to_string()).unwrap().as_str(), "squat"); + } + + each_impl!(test_insert); + } + + #[test] + fn remove() { + fn test_remove>(mut map: M) { + let old = map.remove(&"foo".to_string()); + assert_eq!(old, Some("bar".to_string())); + + let old = map.remove(&"foo".to_string()); + assert_eq!(old, None); + } + + each_impl!(test_remove); + } + + #[test] + fn has() { + fn test_has>(map: M) { + assert!(map.has(&"foo".to_string())); + assert!(!map.has(&"foot".to_string())); + } + + each_impl!(test_has); + } + + #[test] + fn into_iter() { + fn test_into_iter>(mut map: M) { + map.insert("a".into(), "a".into()).unwrap(); + map.insert("b".into(), "b".into()).unwrap(); + map.insert("c".into(), "c".into()).unwrap(); + + let mut kvs = map.into_iter().collect::>(); + kvs.sort(); + + assert_eq!(kvs, + vec![("a".into(), "a".into()), + ("b".into(), "b".into()), + ("c".into(), "c".into()), + ("foo".into(), "bar".into()),]); + } + + each_impl!(test_into_iter); + } + + #[test] + fn iter() { + fn test_iter>(mut map: M) { + map.insert("a".into(), "a".into()).unwrap(); + map.insert("b".into(), "b".into()).unwrap(); + map.insert("c".into(), "c".into()).unwrap(); + + let mut kvs = map.iter().collect::>(); + kvs.sort(); + + assert_eq!(kvs, + vec![(&"a".into(), &"a".into()), + (&"b".into(), &"b".into()), + (&"c".into(), &"c".into()), + (&"foo".into(), &"bar".into()),]); + } + + each_impl!(test_iter); + } + + #[test] + fn iter_mut() { + fn test_iter_mut>(mut map: M) { + map.insert("a".into(), "a".into()).unwrap(); + map.insert("b".into(), "b".into()).unwrap(); + map.insert("c".into(), "c".into()).unwrap(); + + let mut kvs = map.iter_mut().collect::>(); + kvs.sort(); + + assert_eq!(kvs, + vec![(&"a".into(), &mut "a".into()), + (&"b".into(), &mut "b".into()), + (&"c".into(), &mut "c".into()), + (&"foo".into(), &mut "bar".into()),]); + } + + each_impl!(test_iter_mut); + } +} diff --git a/tpl/.versionrc.json b/tpl/.versionrc.json deleted file mode 100644 index d0d19e21..00000000 --- a/tpl/.versionrc.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "packageFiles": [ - { - "filename": "Cargo.toml", - "updater": "../.utils/cargo-updater.js" - } - ], - "bumpFiles": [ - { - "filename": "Cargo.toml", - "updater": "../.utils/cargo-updater.js" - }, - { - "filename": "src/lib.rs", - "updater": "../.utils/html-root-updater.js" - } - ] -}