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` diff --git a/src/macros.rs b/src/macros.rs index 109e20d..c1cda90 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 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),*) => {{ - 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); + () => { 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. + 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! 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. - 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 @@ -93,18 +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),*) => {{ - 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); + () => { 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 + Hasher) as the hasher: + ::core::hash::BuildHasherDefault::<$H>::new(), + ); $( set.insert($value); - )* + )+ set }}; } @@ -131,17 +196,16 @@ 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. - 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); +}