From fe98c708e57054d404ddf49627721d85961c49ea 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 109e20d..ffd88a8 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -23,11 +23,14 @@ macro_rules! ordermap_with_default { ($H:ty; $($key:expr => $value:expr,)+) => { $crate::ordermap_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::OrderMap::with_capacity_and_hasher(CAP, builder); + let mut map = $crate::OrderMap::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! ordermap_with_default { /// ``` macro_rules! ordermap { ($($key:expr => $value:expr,)+) => { $crate::ordermap!($($key => $value),+) }; - ($($key:expr => $value:expr),*) => { - { + ($($key:expr => $value:expr),*) => {{ + let mut map = $crate::OrderMap::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::OrderMap::with_capacity(CAP); - $( - map.insert($key, $value); - )* - map - } - }; + const { <[()]>::len(&[$({ stringify!($key); }),*]) }, + ); + $( + map.insert($key, $value); + )* + map + }}; } /// Create an [`OrderSet`][crate::OrderSet] from a list of values @@ -97,11 +99,14 @@ macro_rules! ordermap { macro_rules! orderset_with_default { ($H:ty; $($value:expr,)+) => { $crate::orderset_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::OrderSet::with_capacity_and_hasher(CAP, builder); + let mut set = $crate::OrderSet::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,16 +137,15 @@ macro_rules! orderset_with_default { /// ``` macro_rules! orderset { ($($value:expr,)+) => { $crate::orderset!($($value),+) }; - ($($value:expr),*) => { - { + ($($value:expr),*) => {{ + let mut set = $crate::OrderSet::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::OrderSet::with_capacity(CAP); - $( - set.insert($value); - )* - set - } - }; + const { <[()]>::len(&[$({ stringify!($value); }),*]) }, + ); + $( + set.insert($value); + )* + set + }}; } diff --git a/tests/macros_full_path.rs b/tests/macros_full_path.rs index 3edf279..2fadb76 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 = ordermap::ordermap! { + 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 = ordermap::ordermap_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 = ordermap::orderset!(CAP); + assert_eq!(s[0], CAP); +} + +#[test] +fn test_set_shadow_default() { + const CAP: usize = 42; + let s = ordermap::orderset_with_default!(fnv::FnvHasher; CAP); + assert_eq!(s[0], CAP); +} From 00c8061a2b46f6dc5f4b2a3df1d78720246fdc7d 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 ffd88a8..c1cda90 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 ordermap::{OrderMap, ordermap_with_default}; +/// use fnv::FnvBuildHasher; // = BuildHasherDefault +/// use std::sync::Mutex; +/// +/// static GLOBAL: Mutex> = +/// Mutex::new(ordermap_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! ordermap_with_default { - ($H:ty; $($key:expr => $value:expr,)+) => { $crate::ordermap_with_default!($H; $($key => $value),+) }; - ($H:ty; $($key:expr => $value:expr),*) => {{ - #[allow(unused_mut)] + () => { const { + $crate::OrderMap::with_hasher( + // Let type inference figure out the hasher: + ::core::hash::BuildHasherDefault::new(), + ) + }}; + ($H:ty $(;)?) => { const { + $crate::OrderMap::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::OrderMap::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! ordermap_with_default { ); $( map.insert($key, $value); - )* + )+ map }}; } @@ -60,8 +89,8 @@ macro_rules! ordermap_with_default { /// assert_eq!(map.keys().next(), Some(&"a")); /// ``` macro_rules! ordermap { - ($($key:expr => $value:expr,)+) => { $crate::ordermap!($($key => $value),+) }; - ($($key:expr => $value:expr),*) => {{ + () => { $crate::OrderMap::new() }; + ($($key:expr => $value:expr),+ $(,)?) => {{ let mut map = $crate::OrderMap::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! ordermap { ); $( map.insert($key, $value); - )* + )+ map }}; } @@ -95,21 +124,52 @@ macro_rules! ordermap { /// // "a" is the first value /// assert_eq!(set.iter().next(), Some(&"a")); /// ``` +/// +/// This can also be initialized in `const` contexts: +/// +/// ``` +/// use ordermap::{OrderSet, orderset_with_default}; +/// use fnv::FnvBuildHasher; // = BuildHasherDefault +/// use std::sync::Mutex; +/// +/// static INTERN: Mutex> = +/// Mutex::new(orderset_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! orderset_with_default { - ($H:ty; $($value:expr,)+) => { $crate::orderset_with_default!($H; $($value),+) }; - ($H:ty; $($value:expr),*) => {{ - #[allow(unused_mut)] + () => { const { + $crate::OrderSet::with_hasher( + // Let type inference figure out the hasher: + ::core::hash::BuildHasherDefault::new(), + ) + }}; + ($H:ty $(;)?) => { const { + $crate::OrderSet::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::OrderSet::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! orderset_with_default { /// assert_eq!(set.iter().next(), Some(&"a")); /// ``` macro_rules! orderset { - ($($value:expr,)+) => { $crate::orderset!($($value),+) }; - ($($value:expr),*) => {{ + () => { $crate::OrderSet::new() }; + ($($value:expr),+ $(,)?) => {{ let mut set = $crate::OrderSet::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! orderset { ); $( set.insert($value); - )* + )+ set }}; } From ca4e02c16ccb09d4fd8375ef00da74f88a7f8210 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 4 Sep 2026 19:00:50 -0700 Subject: [PATCH 3/3] Release 1.2.2 --- Cargo.toml | 4 ++-- RELEASES.md | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 087c5d6..5ccd563 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ordermap" edition = "2024" -version = "1.2.1" +version = "1.2.2" documentation = "https://docs.rs/ordermap/" repository = "https://github.com/indexmap-rs/ordermap" license = "Apache-2.0 OR MIT" @@ -14,7 +14,7 @@ rust-version = "1.85" bench = false [dependencies] -indexmap = { version = "2.14.1", default-features = false } +indexmap = { version = "2.14.2", default-features = false } arbitrary = { version = "1.0", optional = true, default-features = false } quickcheck = { version = "1.0", optional = true, default-features = false } diff --git a/RELEASES.md b/RELEASES.md index 6a11048..426d974 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,12 @@ # Releases +## 1.2.2 (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 `ordermap_with_default!` and + `orderset_with_default!`. The hasher may also be omitted if it's inferrable. + ## 1.2.1 (2026-08-28) - Sync changes from `indexmap v2.14.1`