diff --git a/Cargo.toml b/Cargo.toml index 3d46fd3..b796c5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ringmap" edition = "2024" -version = "0.2.6" +version = "0.2.7" documentation = "https://docs.rs/ringmap/" repository = "https://github.com/indexmap-rs/ringmap" license = "Apache-2.0 OR MIT" diff --git a/RELEASES.md b/RELEASES.md index dd93c0b..f93909f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,12 @@ # Releases +## 0.2.7 (2026-09-04) + +- Fix item hygiene in map and set macros. Previously, an internal `const CAP` + could shadow the same name in the caller's namespace. +- Allow `const` initialization of empty `ringmap_with_default!` and + `ringset_with_default!`. The hasher may also be omitted if it's inferrable. + ## 0.2.6 (2026-08-28) - Simplify comparisons where `Equivalent` isn't needed (`Q = K`). diff --git a/src/macros.rs b/src/macros.rs index 15c7732..8ab4cc3 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -19,18 +19,50 @@ /// // "a" is the first key /// assert_eq!(map.keys().next(), Some(&"a")); /// ``` +/// +/// This can also be initialized in `const` contexts: +/// +/// ``` +/// use ringmap::{RingMap, ringmap_with_default}; +/// use fnv::FnvBuildHasher; // = BuildHasherDefault +/// use std::sync::Mutex; +/// +/// static GLOBAL: Mutex> = +/// Mutex::new(ringmap_with_default!()); +/// +/// if let Ok(mut map) = GLOBAL.lock() { +/// map.insert("a".into(), 1); +/// map.insert("b".into(), 2); +/// } +/// +/// assert_eq!(GLOBAL.lock().unwrap()["a"], 1); +/// assert_eq!(GLOBAL.lock().unwrap()["b"], 2); +/// ``` #[macro_export] macro_rules! ringmap_with_default { - ($H:ty; $($key:expr => $value:expr,)+) => { $crate::ringmap_with_default!($H; $($key => $value),+) }; - ($H:ty; $($key:expr => $value:expr),*) => {{ - let builder = ::core::hash::BuildHasherDefault::<$H>::new(); - const CAP: usize = <[()]>::len(&[$({ stringify!($key); }),*]); - #[allow(unused_mut)] - // Specify your custom `H` (must implement Default + Hasher) as the hasher: - let mut map = $crate::RingMap::with_capacity_and_hasher(CAP, builder); + () => { const { + $crate::RingMap::with_hasher( + // Let type inference figure out the hasher: + ::core::hash::BuildHasherDefault::new(), + ) + }}; + ($H:ty $(;)?) => { const { + $crate::RingMap::with_hasher( + // Specify your custom `H` (must implement Default + Hasher) as the hasher: + ::core::hash::BuildHasherDefault::<$H>::new(), + ) + }}; + ($H:ty; $($key:expr => $value:expr),+ $(,)?) => {{ + let mut map = $crate::RingMap::with_capacity_and_hasher( + // Note: `stringify!($key)` is just here to consume the repetition, + // but we throw away that string literal during constant evaluation. + const { <[()]>::len(&[$({ stringify!($key); }),*]) }, + // Specify your custom `H` (must implement Default + Hasher) as the hasher: + ::core::hash::BuildHasherDefault::<$H>::new(), + ); $( map.insert($key, $value); - )* + )+ map }}; } @@ -57,19 +89,18 @@ macro_rules! ringmap_with_default { /// assert_eq!(map.keys().next(), Some(&"a")); /// ``` macro_rules! ringmap { - ($($key:expr => $value:expr,)+) => { $crate::ringmap!($($key => $value),+) }; - ($($key:expr => $value:expr),*) => { - { + () => { $crate::RingMap::new() }; + ($($key:expr => $value:expr),+ $(,)?) => {{ + let mut map = $crate::RingMap::with_capacity( // Note: `stringify!($key)` is just here to consume the repetition, // but we throw away that string literal during constant evaluation. - const CAP: usize = <[()]>::len(&[$({ stringify!($key); }),*]); - let mut map = $crate::RingMap::with_capacity(CAP); - $( - map.insert($key, $value); - )* - map - } - }; + const { <[()]>::len(&[$({ stringify!($key); }),*]) }, + ); + $( + map.insert($key, $value); + )+ + map + }}; } /// Create a [`RingSet`][crate::RingSet] from a list of values @@ -93,18 +124,52 @@ macro_rules! ringmap { /// // "a" is the first value /// assert_eq!(set.iter().next(), Some(&"a")); /// ``` +/// +/// This can also be initialized in `const` contexts: +/// +/// ``` +/// use ringmap::{RingSet, ringset_with_default}; +/// use fnv::FnvBuildHasher; // = BuildHasherDefault +/// use std::sync::Mutex; +/// +/// static INTERN: Mutex> = +/// Mutex::new(ringset_with_default!()); +/// +/// if let Ok(mut set) = INTERN.lock() { +/// set.insert("a".into()); +/// set.insert("b".into()); +/// set.insert("c".into()); +/// } +/// +/// assert!(INTERN.lock().unwrap().contains("a")); +/// assert!(INTERN.lock().unwrap().contains("b")); +/// assert!(INTERN.lock().unwrap().contains("c")); +/// ``` #[macro_export] macro_rules! ringset_with_default { - ($H:ty; $($value:expr,)+) => { $crate::ringset_with_default!($H; $($value),+) }; - ($H:ty; $($value:expr),*) => {{ - let builder = ::core::hash::BuildHasherDefault::<$H>::new(); - const CAP: usize = <[()]>::len(&[$({ stringify!($value); }),*]); - #[allow(unused_mut)] - // Specify your custom `H` (must implement Default + Hash) as the hasher: - let mut set = $crate::RingSet::with_capacity_and_hasher(CAP, builder); + () => { const { + $crate::RingSet::with_hasher( + // Let type inference figure out the hasher: + ::core::hash::BuildHasherDefault::new(), + ) + }}; + ($H:ty $(;)?) => { const { + $crate::RingSet::with_hasher( + // Specify your custom `H` (must implement Default + Hasher) as the hasher: + ::core::hash::BuildHasherDefault::<$H>::new(), + ) + }}; + ($H:ty; $($value:expr),+ $(,)?) => {{ + let mut set = $crate::RingSet::with_capacity_and_hasher( + // Note: `stringify!($value)` is just here to consume the repetition, + // but we throw away that string literal during constant evaluation. + const { <[()]>::len(&[$({ stringify!($value); }),*]) }, + // Specify your custom `H` (must implement Default + Hasher) as the hasher: + ::core::hash::BuildHasherDefault::<$H>::new(), + ); $( set.insert($value); - )* + )+ set }}; } @@ -131,19 +196,18 @@ macro_rules! ringset_with_default { /// assert_eq!(set.iter().next(), Some(&"a")); /// ``` macro_rules! ringset { - ($($value:expr,)+) => { $crate::ringset!($($value),+) }; - ($($value:expr),*) => { - { + () => { $crate::RingSet::new() }; + ($($value:expr),+ $(,)?) => {{ + let mut set = $crate::RingSet::with_capacity( // Note: `stringify!($value)` is just here to consume the repetition, // but we throw away that string literal during constant evaluation. - const CAP: usize = <[()]>::len(&[$({ stringify!($value); }),*]); - let mut set = $crate::RingSet::with_capacity(CAP); - $( - set.insert($value); - )* - set - } - }; + const { <[()]>::len(&[$({ stringify!($value); }),*]) }, + ); + $( + set.insert($value); + )+ + set + }}; } // generate all the Iterator methods by just forwarding to the underlying diff --git a/tests/macros_full_path.rs b/tests/macros_full_path.rs index 39983e3..43fe173 100644 --- a/tests/macros_full_path.rs +++ b/tests/macros_full_path.rs @@ -17,3 +17,43 @@ fn test_create_set() { 3, }; } + +#[test] +fn test_map_shadow() { + // The macro used to have its own `const CAP` which would shadow this, because items are not + // protected by macro hygiene. Now we avoid any items in the macro, and its local `map` *is* + // hygienic vs. the local `map` here. + const CAP: usize = 42; + let map = -1; + let m = ringmap::ringmap! { + map => CAP, + }; + assert_eq!(m[&map], CAP); + assert_eq!(m[0], CAP); +} + +#[test] +fn test_map_shadow_default() { + const CAP: usize = 42; + let map = -1; + let m = ringmap::ringmap_with_default! { + fnv::FnvHasher; + map => CAP, + }; + assert_eq!(m[&map], CAP); + assert_eq!(m[0], CAP); +} + +#[test] +fn test_set_shadow() { + const CAP: usize = 42; + let s = ringmap::ringset!(CAP); + assert_eq!(s[0], CAP); +} + +#[test] +fn test_set_shadow_default() { + const CAP: usize = 42; + let s = ringmap::ringset_with_default!(fnv::FnvHasher; CAP); + assert_eq!(s[0], CAP); +}