Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 147 additions & 5 deletions src/linked_hash_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,36 @@ where
}
}

#[inline]
pub fn back_entry(&mut self) -> Option<RawOccupiedEntryMut<'_, K, V, S>> {
if self.is_empty() {
return None;
}
unsafe {
let last_key = (&*(*self.values.as_ptr()).links.value.prev.as_ptr()).key_ref();
let RawEntryMut::Occupied(occu) = self.raw_entry_mut().from_key(last_key) else {
unreachable!("the back entry's key was not found in the hashtable")
};

Some(occu)
}
}

#[inline]
pub fn front_entry(&mut self) -> Option<RawOccupiedEntryMut<'_, K, V, S>> {
if self.is_empty() {
return None;
}
unsafe {
let first_key = (&*((*self.values.as_ptr()).links.value.next.as_ptr())).key_ref();
let RawEntryMut::Occupied(occu) = self.raw_entry_mut().from_key(first_key) else {
unreachable!("the front entry's key was not found in the hashtable")
};

Some(occu)
}
}

#[inline]
pub fn get<Q>(&self, k: &Q) -> Option<&V>
where
Expand Down Expand Up @@ -875,6 +905,16 @@ impl<'a, K, V, S> OccupiedEntry<'a, K, V, S> {
self.raw_entry.cursor_mut()
}

/// Returns a `RawOccupiedEntryMut` over the current entry.
#[inline]
pub fn raw_entry_mut(self) -> RawOccupiedEntryMut<'a, K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
self.raw_entry
}

/// Replaces the entry's key with the key provided to `LinkedHashMap::entry`, and replaces the
/// entry's value with the given `value` parameter.
///
Expand Down Expand Up @@ -927,6 +967,30 @@ impl<'a, K, V, S> VacantEntry<'a, K, V, S> {
{
self.raw_entry.insert(self.key, value).1
}

/// Insert's the key for this vacant entry paired with the given value as a new entry at the
/// *back* of the internal linked list.
/// This function then also then returns the OccupiedEntry pointing to the inserted element.
pub fn insert_entry(self, value: V) -> RawOccupiedEntryMut<'a, K, V, S>
where
K: Hash,
S: BuildHasher,
{
//We cannot return OccupiedEntry only RawOccupiedEntryMut because
//OccupiedEntry has api methods like replace_key which assume that we hold a copy of the key.
//We certainly do not hold a copy of the key anymore after inserting it.
self.raw_entry.insert_entry(self.key, value)
}

/// Returns a `RawVacantEntryMut` over the current entry.
#[inline]
pub fn raw_entry_mut(self) -> RawVacantEntryMut<'a, K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
{
self.raw_entry
}
}

pub struct RawEntryBuilder<'a, K, V, S> {
Expand Down Expand Up @@ -1254,6 +1318,21 @@ impl<'a, K, V, S> RawVacantEntryMut<'a, K, V, S> {
value: V,
hasher: impl Fn(&K) -> u64,
) -> (&'a mut K, &'a mut V)
where
S: BuildHasher,
{
self.insert_entry_with_hasher(hash, key, value, hasher)
.into_key_value()
}

#[inline]
pub fn insert_entry_with_hasher(
self,
hash: u64,
key: K,
value: V,
hasher: impl Fn(&K) -> u64,
) -> RawOccupiedEntryMut<'a, K, V, S>
where
S: BuildHasher,
{
Expand All @@ -1266,13 +1345,41 @@ impl<'a, K, V, S> RawVacantEntryMut<'a, K, V, S> {
let node = self
.entry
.into_table()
.insert_unique(hash, new_node, move |k| hasher((*k).as_ref().key_ref()))
.into_mut();
.insert_unique(hash, new_node, move |k| hasher((*k).as_ref().key_ref()));

let (key, value) = (*node.as_ptr()).entry_mut();
(key, value)
RawOccupiedEntryMut {
hash_builder: self.hash_builder,
free: self.free,
values: self.values,
entry: node,
}
}
}

#[inline]
pub fn insert_entry_hashed_nocheck(
self,
hash: u64,
key: K,
value: V,
) -> RawOccupiedEntryMut<'a, K, V, S>
where
K: Hash,
S: BuildHasher,
{
let hash_builder = self.hash_builder;
self.insert_entry_with_hasher(hash, key, value, |k| hash_key(hash_builder, k))
}

#[inline]
pub fn insert_entry(self, key: K, value: V) -> RawOccupiedEntryMut<'a, K, V, S>
where
K: Hash,
S: BuildHasher,
{
let hash = hash_key(self.hash_builder, &key);
self.insert_entry_hashed_nocheck(hash, key, value)
}
}

impl<K, V, S> fmt::Debug for RawEntryBuilderMut<'_, K, V, S> {
Expand Down Expand Up @@ -1732,7 +1839,7 @@ pub struct CursorMut<'a, K, V, S> {
table: &'a mut hashbrown::HashTable<NonNull<Node<K, V>>>,
}

impl<K, V, S> CursorMut<'_, K, V, S> {
impl<'a, K, V, S> CursorMut<'a, K, V, S> {
/// Returns an `Option` of the current element in the list, provided it is not the
/// _guard_ node, and `None` overwise.
#[inline]
Expand All @@ -1743,6 +1850,41 @@ impl<K, V, S> CursorMut<'_, K, V, S> {
}
}

#[inline]
pub fn current_entry(self) -> Result<RawOccupiedEntryMut<'a, K, V, S>, Self>
where
K: Eq + Hash,
S: BuildHasher,
{
unsafe {
let Some(values) = self.values else {
return Err(self);
};

if values.as_ptr() == self.cur {
return Err(self);
}

let key = (*self.cur).key_ref();

let hash = hash_key(self.hash_builder, &key);

let Ok(entry) = self
.table
.find_entry(hash, |o| (*o).as_ref().key_ref().eq(key))
else {
unreachable!("current entry not found in hash table");
};

Ok(RawOccupiedEntryMut {
hash_builder: self.hash_builder,
free: self.free,
values: self.values,
entry,
})
}
}

/// Retrieves the next element in the list (moving towards the end).
#[inline]
pub fn peek_next(&mut self) -> Option<(&K, &mut V)> {
Expand Down
137 changes: 137 additions & 0 deletions tests/linked_hash_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{
rc::Rc,
};

use hashlink::linked_hash_map::Entry;
use hashlink::{LinkedHashMap, linked_hash_map};

#[allow(dead_code)]
Expand Down Expand Up @@ -838,6 +839,142 @@ fn test_cursor_back_mut() {
assert_eq!(cursor.current().unwrap().1, &mut 3);
}

#[test]
fn test_vacant_entry_insert_entry() {
let mut map: LinkedHashMap<i32, i32> = LinkedHashMap::new();

map.insert(1, 1);
map.insert(2, 2);
map.insert(3, 3);

let Entry::Vacant(vacant) = map.entry(4) else {
panic!("no entry with 4 in map");
};

let occu = vacant.insert_entry(55);
let (k, v) = occu.remove_entry();
assert_eq!(k, 4);
assert_eq!(v, 55);

assert!(map.get(&4).is_none());

let Entry::Vacant(vacant) = map.entry(4) else {
panic!("no entry with 4 in map");
};

let occu = vacant.insert_entry(56);
assert_eq!(occu.get(), &56);
assert_eq!(occu.key(), &4);

assert_eq!(map.get(&4), Some(&56));
assert_eq!(map.back(), Some((&4, &56)));
}

#[test]
fn test_front_back_entry() {
let mut map: LinkedHashMap<i32, i32> = LinkedHashMap::new();
assert!(map.front_entry().is_none());
assert!(map.back_entry().is_none());

map.insert(1, 5);
map.insert(2, 6);
map.insert(3, 7);

let Some(fe) = map.front_entry() else {
panic!("no front entry");
};

assert_eq!(fe.key(), &1);
assert_eq!(fe.get(), &5);

let Some(fe) = map.back_entry() else {
panic!("no back entry");
};

assert_eq!(fe.key(), &3);
assert_eq!(fe.get(), &7);

let Some(fe) = map.front_entry() else {
panic!("no front entry");
};

assert_eq!(fe.remove_entry(), (1, 5));
assert_eq!(map.front(), Some((&2, &6)));
assert_eq!(map.back(), Some((&3, &7)));

let Some(fe) = map.back_entry() else {
panic!("no back entry");
};

assert_eq!(fe.remove_entry(), (3, 7));
assert_eq!(map.front(), Some((&2, &6)));
assert_eq!(map.back(), Some((&2, &6)));
assert_eq!(map.len(), 1);

let Some(fe) = map.back_entry() else {
panic!("no back entry");
};
assert_eq!(fe.remove_entry(), (2, 6));
assert!(map.is_empty());
}

#[test]
fn test_cursor_to_entry() {
let mut map: LinkedHashMap<i32, i32> = LinkedHashMap::new();
assert!(map.cursor_back_mut().current_entry().is_err());
map.insert(1, 5);
map.insert(2, 6);
map.insert(3, 7);

let e = map
.cursor_back_mut()
.current_entry()
.map_err(|_| ())
.expect("map empty");
assert_eq!(e.key(), &3);
assert_eq!(e.get(), &7);

let mut cursor = map.cursor_back_mut();
cursor.move_prev();
let e = cursor.current_entry().map_err(|_| ()).expect("map empty");
assert_eq!(e.key(), &2);
assert_eq!(e.get(), &6);

let e = map
.cursor_front_mut()
.current_entry()
.map_err(|_| ())
.expect("map empty");
assert_eq!(e.key(), &1);
assert_eq!(e.get(), &5);

let mut cursor = map.cursor_back_mut();
cursor.move_prev();
let e = cursor.current_entry().map_err(|_| ()).expect("map empty");
assert_eq!(e.remove_entry(), (2, 6));

assert_eq!(map.len(), 2);

let mut cursor = map.cursor_back_mut();
cursor.move_prev();
let e = cursor.current_entry().map_err(|_| ()).expect("map empty");
assert_eq!(e.key(), &1);
assert_eq!(e.get(), &5);

let mut cursor = map.cursor_back_mut();
cursor.move_prev();
cursor.move_prev();
assert!(cursor.current_entry().is_err());

let mut cursor = map.cursor_front_mut();
cursor.move_next();
cursor.move_next();
assert!(cursor.current_entry().is_err());

let collected = map.into_iter().collect::<Vec<_>>();
assert_eq!(collected, vec![(1, 5), (3, 7)]);
}

// Regression test for https://github.com/djc/hashlink/issues/43
//
// A panic while dropping an entry during `clear` must not leave a moved-out node reachable
Expand Down
Loading