1747ad3c4Slazypassion //! `ScopedHashMap` 2747ad3c4Slazypassion //! 382c0a09bSChris Fallin //! This module defines a struct `ScopedHashMap<C, K, V>` which 482c0a09bSChris Fallin //! defines a `FxHashMap`-like container that has a concept of scopes 582c0a09bSChris Fallin //! that can be entered and exited, such that values inserted while 682c0a09bSChris Fallin //! inside a scope aren't visible outside the scope. 782c0a09bSChris Fallin //! 882c0a09bSChris Fallin //! The context type `C` is given to `CtxEq` and `CtxHash` methods on 982c0a09bSChris Fallin //! the key values so that keys do not need to be fully 1082c0a09bSChris Fallin //! self-contained. 11747ad3c4Slazypassion 1282c0a09bSChris Fallin use crate::ctxhash::{CtxEq, CtxHash, CtxHashMap}; 1390ac295eSAlex Crichton use smallvec::{SmallVec, smallvec}; 14747ad3c4Slazypassion 152be12a51SChris Fallin struct Val<V> { 16747ad3c4Slazypassion value: V, 172be12a51SChris Fallin level: u32, 182be12a51SChris Fallin generation: u32, 19747ad3c4Slazypassion } 20747ad3c4Slazypassion 21747ad3c4Slazypassion /// A view into an occupied entry in a `ScopedHashMap`. It is part of the `Entry` enum. 22747ad3c4Slazypassion pub struct OccupiedEntry<'a, K: 'a, V: 'a> { 2382c0a09bSChris Fallin entry: crate::ctxhash::OccupiedEntry<'a, K, Val<V>>, 24747ad3c4Slazypassion } 25747ad3c4Slazypassion 26747ad3c4Slazypassion impl<'a, K, V> OccupiedEntry<'a, K, V> { 27747ad3c4Slazypassion /// Gets a reference to the value in the entry. get(&self) -> &V28747ad3c4Slazypassion pub fn get(&self) -> &V { 29747ad3c4Slazypassion &self.entry.get().value 30747ad3c4Slazypassion } 31747ad3c4Slazypassion } 32747ad3c4Slazypassion 33747ad3c4Slazypassion /// A view into a vacant entry in a `ScopedHashMap`. It is part of the `Entry` enum. 34747ad3c4Slazypassion pub struct VacantEntry<'a, K: 'a, V: 'a> { 352be12a51SChris Fallin entry: InsertLoc<'a, K, V>, 362be12a51SChris Fallin depth: u32, 372be12a51SChris Fallin generation: u32, 38747ad3c4Slazypassion } 39747ad3c4Slazypassion 402be12a51SChris Fallin /// Where to insert from a `VacantEntry`. May be vacant or occupied in 412be12a51SChris Fallin /// the underlying map because of lazy (generation-based) deletion. 422be12a51SChris Fallin enum InsertLoc<'a, K: 'a, V: 'a> { 4382c0a09bSChris Fallin Vacant(crate::ctxhash::VacantEntry<'a, K, Val<V>>), 4482c0a09bSChris Fallin Occupied(crate::ctxhash::OccupiedEntry<'a, K, Val<V>>), 452be12a51SChris Fallin } 462be12a51SChris Fallin 472be12a51SChris Fallin impl<'a, K, V> VacantEntry<'a, K, V> { 48747ad3c4Slazypassion /// Sets the value of the entry with the `VacantEntry`'s key. insert(self, value: V)49747ad3c4Slazypassion pub fn insert(self, value: V) { 502be12a51SChris Fallin let val = Val { 51747ad3c4Slazypassion value, 522be12a51SChris Fallin level: self.depth, 532be12a51SChris Fallin generation: self.generation, 542be12a51SChris Fallin }; 552be12a51SChris Fallin match self.entry { 562be12a51SChris Fallin InsertLoc::Vacant(v) => { 572be12a51SChris Fallin v.insert(val); 582be12a51SChris Fallin } 592be12a51SChris Fallin InsertLoc::Occupied(mut o) => { 6082c0a09bSChris Fallin *o.get_mut() = val; 612be12a51SChris Fallin } 622be12a51SChris Fallin } 63747ad3c4Slazypassion } 64747ad3c4Slazypassion } 65747ad3c4Slazypassion 66747ad3c4Slazypassion /// A view into a single entry in a map, which may either be vacant or occupied. 67747ad3c4Slazypassion /// 68747ad3c4Slazypassion /// This enum is constructed from the `entry` method on `ScopedHashMap`. 69747ad3c4Slazypassion pub enum Entry<'a, K: 'a, V: 'a> { 70747ad3c4Slazypassion Occupied(OccupiedEntry<'a, K, V>), 71747ad3c4Slazypassion Vacant(VacantEntry<'a, K, V>), 72747ad3c4Slazypassion } 73747ad3c4Slazypassion 74747ad3c4Slazypassion /// A wrapper around a `FxHashMap` which adds the concept of scopes. Items inserted 75747ad3c4Slazypassion /// within a scope are removed when the scope is exited. 76747ad3c4Slazypassion /// 77747ad3c4Slazypassion /// Shadowing, where one scope has entries with the same keys as a containing scope, 78747ad3c4Slazypassion /// is not supported in this implementation. 79747ad3c4Slazypassion pub struct ScopedHashMap<K, V> { 8082c0a09bSChris Fallin map: CtxHashMap<K, Val<V>>, 812be12a51SChris Fallin generation_by_depth: SmallVec<[u32; 8]>, 822be12a51SChris Fallin generation: u32, 83747ad3c4Slazypassion } 84747ad3c4Slazypassion 85747ad3c4Slazypassion impl<K, V> ScopedHashMap<K, V> 86747ad3c4Slazypassion where 8782c0a09bSChris Fallin K: Clone, 88747ad3c4Slazypassion { 89747ad3c4Slazypassion /// Creates an empty `ScopedHashMap`. 90*099102d9SAlex Crichton #[cfg(test)] new() -> Self91747ad3c4Slazypassion pub fn new() -> Self { 9282c0a09bSChris Fallin Self::with_capacity(16) 932be12a51SChris Fallin } 942be12a51SChris Fallin 952be12a51SChris Fallin /// Creates an empty `ScopedHashMap` with some pre-allocated capacity. with_capacity(cap: usize) -> Self962be12a51SChris Fallin pub fn with_capacity(cap: usize) -> Self { 972be12a51SChris Fallin Self { 9882c0a09bSChris Fallin map: CtxHashMap::with_capacity(cap), 992be12a51SChris Fallin generation: 0, 1002be12a51SChris Fallin generation_by_depth: smallvec![0], 101747ad3c4Slazypassion } 102747ad3c4Slazypassion } 103747ad3c4Slazypassion 104747ad3c4Slazypassion /// Similar to `FxHashMap::entry`, gets the given key's corresponding entry in the map for 105747ad3c4Slazypassion /// in-place manipulation. entry<'a, C>(&'a mut self, ctx: &C, key: K) -> Entry<'a, K, V> where C: CtxEq<K, K> + CtxHash<K>,10682c0a09bSChris Fallin pub fn entry<'a, C>(&'a mut self, ctx: &C, key: K) -> Entry<'a, K, V> 10782c0a09bSChris Fallin where 10882c0a09bSChris Fallin C: CtxEq<K, K> + CtxHash<K>, 10982c0a09bSChris Fallin { 11082c0a09bSChris Fallin self.entry_with_depth(ctx, key, self.depth()) 1112be12a51SChris Fallin } 1122be12a51SChris Fallin 1132be12a51SChris Fallin /// Get the entry, setting the scope depth at which to insert. entry_with_depth<'a, C>(&'a mut self, ctx: &C, key: K, depth: usize) -> Entry<'a, K, V> where C: CtxEq<K, K> + CtxHash<K>,11482c0a09bSChris Fallin pub fn entry_with_depth<'a, C>(&'a mut self, ctx: &C, key: K, depth: usize) -> Entry<'a, K, V> 11582c0a09bSChris Fallin where 11682c0a09bSChris Fallin C: CtxEq<K, K> + CtxHash<K>, 11782c0a09bSChris Fallin { 1182be12a51SChris Fallin debug_assert!(depth <= self.generation_by_depth.len()); 1192be12a51SChris Fallin let generation = self.generation_by_depth[depth]; 1202be12a51SChris Fallin let depth = depth as u32; 12182c0a09bSChris Fallin match self.map.entry(key, ctx) { 12282c0a09bSChris Fallin crate::ctxhash::Entry::Occupied(entry) => { 1232be12a51SChris Fallin let entry_generation = entry.get().generation; 1242be12a51SChris Fallin let entry_depth = entry.get().level as usize; 1252be12a51SChris Fallin if self.generation_by_depth.get(entry_depth).cloned() == Some(entry_generation) { 1262be12a51SChris Fallin Entry::Occupied(OccupiedEntry { entry }) 1272be12a51SChris Fallin } else { 128747ad3c4Slazypassion Entry::Vacant(VacantEntry { 1292be12a51SChris Fallin entry: InsertLoc::Occupied(entry), 1302be12a51SChris Fallin depth, 1312be12a51SChris Fallin generation, 132747ad3c4Slazypassion }) 133747ad3c4Slazypassion } 134747ad3c4Slazypassion } 13582c0a09bSChris Fallin crate::ctxhash::Entry::Vacant(entry) => Entry::Vacant(VacantEntry { 1362be12a51SChris Fallin entry: InsertLoc::Vacant(entry), 1372be12a51SChris Fallin depth, 1382be12a51SChris Fallin generation, 1392be12a51SChris Fallin }), 1402be12a51SChris Fallin } 1412be12a51SChris Fallin } 1422be12a51SChris Fallin 1432be12a51SChris Fallin /// Get a value from a key, if present. get<'a, C>(&'a self, ctx: &C, key: &K) -> Option<&'a V> where C: CtxEq<K, K> + CtxHash<K>,14482c0a09bSChris Fallin pub fn get<'a, C>(&'a self, ctx: &C, key: &K) -> Option<&'a V> 14582c0a09bSChris Fallin where 14682c0a09bSChris Fallin C: CtxEq<K, K> + CtxHash<K>, 14782c0a09bSChris Fallin { 1482be12a51SChris Fallin self.map 14982c0a09bSChris Fallin .get(key, ctx) 1502be12a51SChris Fallin .filter(|entry| { 1512be12a51SChris Fallin let level = entry.level as usize; 1522be12a51SChris Fallin self.generation_by_depth.get(level).cloned() == Some(entry.generation) 1532be12a51SChris Fallin }) 1542be12a51SChris Fallin .map(|entry| &entry.value) 1552be12a51SChris Fallin } 1562be12a51SChris Fallin 1572be12a51SChris Fallin /// Insert a key-value pair if absent. No-op if already exists. insert_if_absent<C>(&mut self, ctx: &C, key: K, value: V) where C: CtxEq<K, K> + CtxHash<K>,15882c0a09bSChris Fallin pub fn insert_if_absent<C>(&mut self, ctx: &C, key: K, value: V) 15982c0a09bSChris Fallin where 16082c0a09bSChris Fallin C: CtxEq<K, K> + CtxHash<K>, 16182c0a09bSChris Fallin { 16282c0a09bSChris Fallin self.insert_if_absent_with_depth(ctx, key, value, self.depth()); 1632be12a51SChris Fallin } 1642be12a51SChris Fallin 1652be12a51SChris Fallin /// Insert a key-value pair if absent, using the given depth for 1662be12a51SChris Fallin /// the insertion. No-op if already exists. insert_if_absent_with_depth<C>(&mut self, ctx: &C, key: K, value: V, depth: usize) where C: CtxEq<K, K> + CtxHash<K>,16782c0a09bSChris Fallin pub fn insert_if_absent_with_depth<C>(&mut self, ctx: &C, key: K, value: V, depth: usize) 16882c0a09bSChris Fallin where 16982c0a09bSChris Fallin C: CtxEq<K, K> + CtxHash<K>, 17082c0a09bSChris Fallin { 17182c0a09bSChris Fallin match self.entry_with_depth(ctx, key, depth) { 1722be12a51SChris Fallin Entry::Vacant(v) => { 1732be12a51SChris Fallin v.insert(value); 1742be12a51SChris Fallin } 1752be12a51SChris Fallin Entry::Occupied(_) => { 1762be12a51SChris Fallin // Nothing. 1772be12a51SChris Fallin } 1782be12a51SChris Fallin } 179747ad3c4Slazypassion } 180747ad3c4Slazypassion 18182c0a09bSChris Fallin /// Insert a key-value pair, using the given depth for the 18282c0a09bSChris Fallin /// insertion. Removes existing entry and overwrites if already 18382c0a09bSChris Fallin /// existed. insert_with_depth<C>(&mut self, ctx: &C, key: K, value: V, depth: usize) where C: CtxEq<K, K> + CtxHash<K>,18482c0a09bSChris Fallin pub fn insert_with_depth<C>(&mut self, ctx: &C, key: K, value: V, depth: usize) 18582c0a09bSChris Fallin where 18682c0a09bSChris Fallin C: CtxEq<K, K> + CtxHash<K>, 18782c0a09bSChris Fallin { 18882c0a09bSChris Fallin let val = Val { 18982c0a09bSChris Fallin value, 19082c0a09bSChris Fallin level: depth as u32, 19182c0a09bSChris Fallin generation: self.generation_by_depth[depth], 19282c0a09bSChris Fallin }; 19382c0a09bSChris Fallin self.map.insert(key, val, ctx); 19482c0a09bSChris Fallin } 19582c0a09bSChris Fallin 196747ad3c4Slazypassion /// Enter a new scope. increment_depth(&mut self)197747ad3c4Slazypassion pub fn increment_depth(&mut self) { 1982be12a51SChris Fallin self.generation_by_depth.push(self.generation); 199747ad3c4Slazypassion } 200747ad3c4Slazypassion 201747ad3c4Slazypassion /// Exit the current scope. decrement_depth(&mut self)202747ad3c4Slazypassion pub fn decrement_depth(&mut self) { 2032be12a51SChris Fallin self.generation += 1; 2042be12a51SChris Fallin self.generation_by_depth.pop(); 205747ad3c4Slazypassion } 206747ad3c4Slazypassion 2072be12a51SChris Fallin /// Return the current scope depth. depth(&self) -> usize2082be12a51SChris Fallin pub fn depth(&self) -> usize { 2092be12a51SChris Fallin self.generation_by_depth 2102be12a51SChris Fallin .len() 2112be12a51SChris Fallin .checked_sub(1) 2122be12a51SChris Fallin .expect("generation_by_depth cannot be empty") 2132be12a51SChris Fallin } 214747ad3c4Slazypassion } 215747ad3c4Slazypassion 216747ad3c4Slazypassion #[cfg(test)] 217747ad3c4Slazypassion mod tests { 218747ad3c4Slazypassion use super::*; 21982c0a09bSChris Fallin use crate::ctxhash::NullCtx; 220747ad3c4Slazypassion 221747ad3c4Slazypassion #[test] basic()222747ad3c4Slazypassion fn basic() { 223747ad3c4Slazypassion let mut map: ScopedHashMap<i32, i32> = ScopedHashMap::new(); 224747ad3c4Slazypassion 22582c0a09bSChris Fallin match map.entry(&NullCtx, 0) { 226747ad3c4Slazypassion Entry::Occupied(_entry) => panic!(), 227747ad3c4Slazypassion Entry::Vacant(entry) => entry.insert(1), 228747ad3c4Slazypassion } 22982c0a09bSChris Fallin match map.entry(&NullCtx, 2) { 230747ad3c4Slazypassion Entry::Occupied(_entry) => panic!(), 231747ad3c4Slazypassion Entry::Vacant(entry) => entry.insert(8), 232747ad3c4Slazypassion } 23382c0a09bSChris Fallin match map.entry(&NullCtx, 2) { 234747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 8), 235747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 236747ad3c4Slazypassion } 237747ad3c4Slazypassion map.increment_depth(); 23882c0a09bSChris Fallin match map.entry(&NullCtx, 2) { 239747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 8), 240747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 241747ad3c4Slazypassion } 24282c0a09bSChris Fallin match map.entry(&NullCtx, 1) { 243747ad3c4Slazypassion Entry::Occupied(_entry) => panic!(), 244747ad3c4Slazypassion Entry::Vacant(entry) => entry.insert(3), 245747ad3c4Slazypassion } 24682c0a09bSChris Fallin match map.entry(&NullCtx, 1) { 247747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 3), 248747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 249747ad3c4Slazypassion } 25082c0a09bSChris Fallin match map.entry(&NullCtx, 0) { 251747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 1), 252747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 253747ad3c4Slazypassion } 25482c0a09bSChris Fallin match map.entry(&NullCtx, 2) { 255747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 8), 256747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 257747ad3c4Slazypassion } 258747ad3c4Slazypassion map.decrement_depth(); 25982c0a09bSChris Fallin match map.entry(&NullCtx, 0) { 260747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 1), 261747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 262747ad3c4Slazypassion } 26382c0a09bSChris Fallin match map.entry(&NullCtx, 2) { 264747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 8), 265747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 266747ad3c4Slazypassion } 267747ad3c4Slazypassion map.increment_depth(); 26882c0a09bSChris Fallin match map.entry(&NullCtx, 2) { 269747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 8), 270747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 271747ad3c4Slazypassion } 27282c0a09bSChris Fallin match map.entry(&NullCtx, 1) { 273747ad3c4Slazypassion Entry::Occupied(_entry) => panic!(), 274747ad3c4Slazypassion Entry::Vacant(entry) => entry.insert(4), 275747ad3c4Slazypassion } 27682c0a09bSChris Fallin match map.entry(&NullCtx, 1) { 277747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 4), 278747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 279747ad3c4Slazypassion } 28082c0a09bSChris Fallin match map.entry(&NullCtx, 2) { 281747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 8), 282747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 283747ad3c4Slazypassion } 284747ad3c4Slazypassion map.decrement_depth(); 285747ad3c4Slazypassion map.increment_depth(); 286747ad3c4Slazypassion map.increment_depth(); 287747ad3c4Slazypassion map.increment_depth(); 28882c0a09bSChris Fallin match map.entry(&NullCtx, 2) { 289747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 8), 290747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 291747ad3c4Slazypassion } 29282c0a09bSChris Fallin match map.entry(&NullCtx, 1) { 293747ad3c4Slazypassion Entry::Occupied(_entry) => panic!(), 294747ad3c4Slazypassion Entry::Vacant(entry) => entry.insert(5), 295747ad3c4Slazypassion } 29682c0a09bSChris Fallin match map.entry(&NullCtx, 1) { 297747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 5), 298747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 299747ad3c4Slazypassion } 30082c0a09bSChris Fallin match map.entry(&NullCtx, 2) { 301747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 8), 302747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 303747ad3c4Slazypassion } 304747ad3c4Slazypassion map.decrement_depth(); 305747ad3c4Slazypassion map.decrement_depth(); 306747ad3c4Slazypassion map.decrement_depth(); 30782c0a09bSChris Fallin match map.entry(&NullCtx, 2) { 308747ad3c4Slazypassion Entry::Occupied(entry) => assert!(*entry.get() == 8), 309747ad3c4Slazypassion Entry::Vacant(_entry) => panic!(), 310747ad3c4Slazypassion } 31182c0a09bSChris Fallin match map.entry(&NullCtx, 1) { 312747ad3c4Slazypassion Entry::Occupied(_entry) => panic!(), 313747ad3c4Slazypassion Entry::Vacant(entry) => entry.insert(3), 314747ad3c4Slazypassion } 315747ad3c4Slazypassion } 3162be12a51SChris Fallin 3172be12a51SChris Fallin #[test] insert_arbitrary_depth()3182be12a51SChris Fallin fn insert_arbitrary_depth() { 3192be12a51SChris Fallin let mut map: ScopedHashMap<i32, i32> = ScopedHashMap::new(); 32082c0a09bSChris Fallin map.insert_if_absent(&NullCtx, 1, 2); 32182c0a09bSChris Fallin assert_eq!(map.get(&NullCtx, &1), Some(&2)); 3222be12a51SChris Fallin map.increment_depth(); 32382c0a09bSChris Fallin assert_eq!(map.get(&NullCtx, &1), Some(&2)); 32482c0a09bSChris Fallin map.insert_if_absent(&NullCtx, 3, 4); 32582c0a09bSChris Fallin assert_eq!(map.get(&NullCtx, &3), Some(&4)); 3262be12a51SChris Fallin map.decrement_depth(); 32782c0a09bSChris Fallin assert_eq!(map.get(&NullCtx, &3), None); 3282be12a51SChris Fallin map.increment_depth(); 32982c0a09bSChris Fallin map.insert_if_absent_with_depth(&NullCtx, 3, 4, 0); 33082c0a09bSChris Fallin assert_eq!(map.get(&NullCtx, &3), Some(&4)); 3312be12a51SChris Fallin map.decrement_depth(); 33282c0a09bSChris Fallin assert_eq!(map.get(&NullCtx, &3), Some(&4)); 3332be12a51SChris Fallin } 334747ad3c4Slazypassion } 335