feat(allocator): construct ArenaHashSet with any Default hasher (#26372)

This PR is inspired by #26333, addresses the repeated `with_hasher_in(IdentBuildHasher, allocator)` call, and removes the extra `'ident` lifetime.

`ArenaHashMap::new_in` works with any `Default` hasher, so `ArenaIdentHashMap::new_in(allocator)` picks `IdentBuildHasher` automatically. `ArenaHashSet` only has `new_in`, `with_capacity_in` and `from_iter_in` for `FxBuildHasher`, so an `Ident`-keyed arena set has to call `with_hasher_in(IdentBuildHasher, allocator)` every time (see #26333).

Mirror the map's `S: Default` impl on the set and add an `ArenaIdentHashSet<'alloc>` alias next to `ArenaIdentHashMap`. Nothing calls the `FxBuildHasher`-only constructors today, so no other code changes.

The alias takes a single lifetime, same as `ArenaIdentHashMap`. A separate lifetime for the `Ident` keys is not needed, and dropping it is sound: both `ArenaHashSet` and `Ident` are covariant, so when the idents are borrowed from somewhere other than the arena (for example `Scoping` in the mangler), the borrow checker shrinks `'alloc` to the shorter of the two borrows. The set can never outlive the strings its idents point to.
This commit is contained in:
Dunqing committed 2026-09-07 05:09:38 +00:00
1 parent f7acdc0bd9
commit 32bdc5b1d1
3 files changed
+29 -6

No files matched your search

+8 -5
View File
@@ -119,15 +119,17 @@ impl<'alloc, T, S> HashSet<'alloc, T, S> {
}
}
/// Methods that use the default [`FxBuildHasher`].
impl<'alloc, T> HashSet<'alloc, T> {
/// Methods for any hasher that implements [`Default`].
///
/// This includes [`FxBuildHasher`] and any custom hasher (e.g. `IdentBuildHasher`).
impl<'alloc, T, S: Default> HashSet<'alloc, T, S> {
/// Creates an empty [`HashSet`]. It will be allocated with the given allocator.
///
/// The hash set is initially created with a capacity of 0, so it will not allocate
/// until it is first inserted into.
#[inline(always)]
pub fn new_in(allocator: &'alloc Allocator) -> Self {
Self::with_hasher_in(FxBuildHasher, allocator)
Self::with_hasher_in(S::default(), allocator)
}
/// Creates an empty [`HashSet`] with the specified capacity. It will be allocated with the given allocator.
@@ -136,7 +138,7 @@ impl<'alloc, T> HashSet<'alloc, T> {
/// If capacity is 0, the hash set will not allocate.
#[inline(always)]
pub fn with_capacity_in(capacity: usize, allocator: &'alloc Allocator) -> Self {
Self::with_capacity_and_hasher_in(capacity, FxBuildHasher, allocator)
Self::with_capacity_and_hasher_in(capacity, S::default(), allocator)
}
/// Create a new [`HashSet`] whose elements are taken from an iterator and allocated in the given `allocator`.
@@ -146,6 +148,7 @@ impl<'alloc, T> HashSet<'alloc, T> {
pub fn from_iter_in<I: IntoIterator<Item = T>>(iter: I, allocator: &'alloc Allocator) -> Self
where
T: Eq + Hash,
S: BuildHasher,
{
const { Self::ASSERT_T_IS_NOT_DROP };
@@ -160,7 +163,7 @@ impl<'alloc, T> HashSet<'alloc, T> {
// e.g. filter iterators.
let capacity = iter.size_hint().0;
let set =
InnerHashSet::with_capacity_and_hasher_in(capacity, FxBuildHasher, allocator.arena());
InnerHashSet::with_capacity_and_hasher_in(capacity, S::default(), allocator.arena());
// Wrap in `ManuallyDrop` *before* calling `for_each`, so compiler doesn't insert unnecessary code
// to drop the `FxHashSet` in case of a panic in iterator's `next` method
let mut set = ManuallyDrop::new(set);
+20
View File
@@ -542,6 +542,10 @@ pub type ArenaIdentHashMap<'alloc, V> =
/// Hash set of [`Ident`], using precomputed ident hash.
pub type IdentHashSet<'a> = hashbrown::HashSet<Ident<'a>, IdentBuildHasher>;
/// Arena-allocated hash set of [`Ident`], using precomputed ident hash.
pub type ArenaIdentHashSet<'alloc> =
oxc_allocator::ArenaHashSet<'alloc, Ident<'alloc>, IdentBuildHasher>;
/// Creates an [`Ident<'static>`] for a string literal, evaluated at compile time.
///
/// ```
@@ -852,4 +856,20 @@ mod test {
assert_eq!(map.get("hello"), Some(&42));
assert_eq!(map.get(&Ident::from("hello")), Some(&42));
}
#[test]
fn arena_ident_hashset() {
let allocator = Allocator::new();
let mut set = ArenaIdentHashSet::new_in(&allocator);
set.insert(Ident::from_in("hello", &allocator));
assert!(set.contains("hello"));
assert!(set.contains(&Ident::from("hello")));
assert!(!set.contains("world"));
let set =
ArenaIdentHashSet::from_iter_in([Ident::from("foo"), Ident::from("bar")], &allocator);
assert_eq!(set.len(), 2);
assert!(set.contains("foo"));
assert!(set.contains("bar"));
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ mod ident_hasher;
mod str;
pub use compact_str::{CompactStr, MAX_INLINE_LEN};
pub use ident::{ArenaIdentHashMap, Ident, IdentHashMap, IdentHashSet};
pub use ident::{ArenaIdentHashMap, ArenaIdentHashSet, Ident, IdentHashMap, IdentHashSet};
pub use ident_hasher::{IdentBuildHasher, IdentHasher};
pub use str::{Str, Str as ArenaStr};