diff --git a/crates/oxc_allocator/src/clone_in.rs b/crates/oxc_allocator/src/clone_in.rs index d8777bcb2c4c5..99914c777a10f 100644 --- a/crates/oxc_allocator/src/clone_in.rs +++ b/crates/oxc_allocator/src/clone_in.rs @@ -1,6 +1,6 @@ -use std::{alloc::Layout, cell::Cell, ptr::NonNull, slice}; +use std::{alloc::Layout, cell::Cell, hash::Hash, ptr::NonNull, slice}; -use crate::{Allocator, Box, Vec}; +use crate::{Allocator, Box, HashMap, Vec}; /// A trait to explicitly clone an object into an arena allocator. /// @@ -207,6 +207,41 @@ where } } +impl<'new_alloc, K, V, CK, CV> CloneIn<'new_alloc> for HashMap<'_, K, V> +where + K: CloneIn<'new_alloc, Cloned = CK>, + V: CloneIn<'new_alloc, Cloned = CV>, + CK: Hash + Eq, +{ + type Cloned = HashMap<'new_alloc, CK, CV>; + + fn clone_in(&self, allocator: &'new_alloc Allocator) -> Self::Cloned { + // Keys in original hash map are guaranteed to be unique. + // Unfortunately, we have no static guarantee that `CloneIn` maintains that uniqueness + // - original keys (`K`) are guaranteed unique, but cloned keys (`CK`) might not be. + // If we did have that guarantee, we could use the faster `insert_unique_unchecked` here. + // `hashbrown::HashMap` also has a faster cloning method in its `Clone` implementation, + // but those APIs are not exposed, and `Clone` doesn't support custom allocators. + // So sadly this is a lot slower than it could be, especially for `Copy` types. + let mut cloned = HashMap::with_capacity_in(self.len(), allocator); + for (key, value) in self { + cloned.insert(key.clone_in(allocator), value.clone_in(allocator)); + } + cloned + } + + fn clone_in_with_semantic_ids(&self, allocator: &'new_alloc Allocator) -> Self::Cloned { + let mut cloned = HashMap::with_capacity_in(self.len(), allocator); + for (key, value) in self { + cloned.insert( + key.clone_in_with_semantic_ids(allocator), + value.clone_in_with_semantic_ids(allocator), + ); + } + cloned + } +} + impl<'alloc, T: Copy> CloneIn<'alloc> for Cell { type Cloned = Cell; @@ -246,7 +281,7 @@ impl_clone_in! { #[cfg(test)] mod test { - use super::{Allocator, CloneIn, Vec}; + use super::{Allocator, CloneIn, HashMap, Vec}; #[test] fn clone_in_boxed_slice() { @@ -280,4 +315,30 @@ mod test { assert_eq!(cloned2.as_slice(), &[1, 2, 3]); assert_eq!(cloned2.capacity(), 3); } + + #[test] + fn clone_in_hash_map() { + let allocator = Allocator::default(); + + let mut original = HashMap::with_capacity_in(8, &allocator); + original.extend(&[("x", "xx"), ("y", "yy"), ("z", "zz")]); + + let cloned = original.clone_in(&allocator); + let cloned2 = original.clone_in_with_semantic_ids(&allocator); + *original.get_mut("y").unwrap() = "changed"; + + let mut original_as_vec = original.iter().collect::>(); + original_as_vec.sort_unstable(); + assert_eq!(original_as_vec, &[(&"x", &"xx"), (&"y", &"changed"), (&"z", &"zz")]); + + assert_eq!(cloned.capacity(), 3); + let mut cloned_as_vec = cloned.iter().collect::>(); + cloned_as_vec.sort_unstable(); + assert_eq!(cloned_as_vec, &[(&"x", &"xx"), (&"y", &"yy"), (&"z", &"zz")]); + + assert_eq!(cloned2.capacity(), 3); + let mut cloned2_as_vec = cloned2.iter().collect::>(); + cloned2_as_vec.sort_unstable(); + assert_eq!(cloned2_as_vec, &[(&"x", &"xx"), (&"y", &"yy"), (&"z", &"zz")]); + } }