Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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 }
Expand Down
7 changes: 7 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -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`
Expand Down
140 changes: 102 additions & 38 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<FnvHasher>
/// use std::sync::Mutex;
///
/// static GLOBAL: Mutex<OrderMap<String, i32, FnvBuildHasher>> =
/// 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
}};
}
Expand All @@ -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
Expand All @@ -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<FnvHasher>
/// use std::sync::Mutex;
///
/// static INTERN: Mutex<OrderSet<String, FnvBuildHasher>> =
/// 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
}};
}
Expand All @@ -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
}};
}
40 changes: 40 additions & 0 deletions tests/macros_full_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}