From a704c01e0385a818ead0b5ffe715f19a2e7548f5 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 4 Sep 2026 17:32:29 -0700 Subject: [PATCH 1/3] Fix item hygiene in user macros (cherry picked from commit c067355e1ca5ec39348f42dcee582d4f222c3295) --- src/macros.rs | 60 +++++++++++++++++++++------------------ tests/macros_full_path.rs | 40 ++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 28 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 15c7732..ff14353 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -23,11 +23,14 @@ 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); + 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); )* @@ -58,18 +61,17 @@ macro_rules! ringmap_with_default { /// ``` macro_rules! ringmap { ($($key:expr => $value:expr,)+) => { $crate::ringmap!($($key => $value),+) }; - ($($key:expr => $value:expr),*) => { - { + ($($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 @@ -97,11 +99,14 @@ macro_rules! ringmap { 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); + 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 + Hash) as the hasher: + ::core::hash::BuildHasherDefault::<$H>::new(), + ); $( set.insert($value); )* @@ -132,18 +137,17 @@ macro_rules! ringset_with_default { /// ``` macro_rules! ringset { ($($value:expr,)+) => { $crate::ringset!($($value),+) }; - ($($value:expr),*) => { - { + ($($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); +} From 945e785eb2e98e23dca198fb2b6f070755932768 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 4 Sep 2026 18:19:00 -0700 Subject: [PATCH 2/3] Improve the empty macro cases In particular, `_with_default` can be `const`! (cherry picked from commit 55e6b28278f242b4d4190a14b6e86c09580dd75a) --- src/macros.rs | 90 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 15 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index ff14353..8ab4cc3 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -19,11 +19,40 @@ /// // "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),*) => {{ - #[allow(unused_mut)] + () => { 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. @@ -33,7 +62,7 @@ macro_rules! ringmap_with_default { ); $( map.insert($key, $value); - )* + )+ map }}; } @@ -60,8 +89,8 @@ 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. @@ -69,7 +98,7 @@ macro_rules! ringmap { ); $( map.insert($key, $value); - )* + )+ map }}; } @@ -95,21 +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),*) => {{ - #[allow(unused_mut)] + () => { 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 + Hash) as the hasher: + // Specify your custom `H` (must implement Default + Hasher) as the hasher: ::core::hash::BuildHasherDefault::<$H>::new(), ); $( set.insert($value); - )* + )+ set }}; } @@ -136,8 +196,8 @@ 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. @@ -145,7 +205,7 @@ macro_rules! ringset { ); $( set.insert($value); - )* + )+ set }}; } From db4b3b448863eb748e6f7299951de66c1b29c1ae Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 4 Sep 2026 19:11:40 -0700 Subject: [PATCH 3/3] Release 0.2.7 --- Cargo.toml | 2 +- RELEASES.md | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) 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`).