Visitar URL original
Merge pull request #450 from cuviper/macros · indexmap-rs/indexmap@41a8708 · GitHub
Skip to content

Commit 41a8708

Browse files
authored
Merge pull request #450 from cuviper/macros
Improve the map/set macros
2 parents fdf7e17 + 0fb7b5c commit 41a8708

4 files changed

Lines changed: 150 additions & 39 deletions

File tree

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "indexmap"
33
edition = "2024"
4-
version = "2.14.1"
4+
version = "2.14.2"
55
documentation = "https://docs.rs/indexmap/"
66
repository = "https://github.com/indexmap-rs/indexmap"
77
license = "Apache-2.0 OR MIT"

‎RELEASES.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Releases
22

3+
## 2.14.2 (2026-09-04)
4+
5+
- Fix item hygiene in map and set macros. Previously, an internal `const CAP`
6+
could shadow the same name in the caller's namespace.
7+
- Allow `const` initialization of empty `indexmap_with_default!` and
8+
`indexset_with_default!`. The hasher may also be omitted if it's inferrable.
9+
310
## 2.14.1 (2026-08-28)
411

512
- Simplify comparisons where `Equivalent` isn't needed (`Q = K`).

‎src/macros.rs‎

Lines changed: 102 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,50 @@
1919
/// // "a" is the first key
2020
/// assert_eq!(map.keys().next(), Some(&"a"));
2121
/// ```
22+
///
23+
/// This can also be initialized in `const` contexts:
24+
///
25+
/// ```
26+
/// use indexmap::{IndexMap, indexmap_with_default};
27+
/// use fnv::FnvBuildHasher; // = BuildHasherDefault<FnvHasher>
28+
/// use std::sync::Mutex;
29+
///
30+
/// static GLOBAL: Mutex<IndexMap<String, i32, FnvBuildHasher>> =
31+
/// Mutex::new(indexmap_with_default!());
32+
///
33+
/// if let Ok(mut map) = GLOBAL.lock() {
34+
/// map.insert("a".into(), 1);
35+
/// map.insert("b".into(), 2);
36+
/// }
37+
///
38+
/// assert_eq!(GLOBAL.lock().unwrap()["a"], 1);
39+
/// assert_eq!(GLOBAL.lock().unwrap()["b"], 2);
40+
/// ```
2241
#[macro_export]
2342
macro_rules! indexmap_with_default {
24-
($H:ty; $($key:expr => $value:expr,)+) => { $crate::indexmap_with_default!($H; $($key => $value),+) };
25-
($H:ty; $($key:expr => $value:expr),*) => {{
26-
let builder = ::core::hash::BuildHasherDefault::<$H>::new();
27-
const CAP: usize = <[()]>::len(&[$({ stringify!($key); }),*]);
28-
#[allow(unused_mut)]
29-
// Specify your custom `H` (must implement Default + Hasher) as the hasher:
30-
let mut map = $crate::IndexMap::with_capacity_and_hasher(CAP, builder);
43+
() => { const {
44+
$crate::IndexMap::with_hasher(
45+
// Let type inference figure out the hasher:
46+
::core::hash::BuildHasherDefault::new(),
47+
)
48+
}};
49+
($H:ty $(;)?) => { const {
50+
$crate::IndexMap::with_hasher(
51+
// Specify your custom `H` (must implement Default + Hasher) as the hasher:
52+
::core::hash::BuildHasherDefault::<$H>::new(),
53+
)
54+
}};
55+
($H:ty; $($key:expr => $value:expr),+ $(,)?) => {{
56+
let mut map = $crate::IndexMap::with_capacity_and_hasher(
57+
// Note: `stringify!($key)` is just here to consume the repetition,
58+
// but we throw away that string literal during constant evaluation.
59+
const { <[()]>::len(&[$({ stringify!($key); }),*]) },
60+
// Specify your custom `H` (must implement Default + Hasher) as the hasher:
61+
::core::hash::BuildHasherDefault::<$H>::new(),
62+
);
3163
$(
3264
map.insert($key, $value);
33-
)*
65+
)+
3466
map
3567
}};
3668
}
@@ -57,19 +89,18 @@ macro_rules! indexmap_with_default {
5789
/// assert_eq!(map.keys().next(), Some(&"a"));
5890
/// ```
5991
macro_rules! indexmap {
60-
($($key:expr => $value:expr,)+) => { $crate::indexmap!($($key => $value),+) };
61-
($($key:expr => $value:expr),*) => {
62-
{
92+
() => { $crate::IndexMap::new() };
93+
($($key:expr => $value:expr),+ $(,)?) => {{
94+
let mut map = $crate::IndexMap::with_capacity(
6395
// Note: `stringify!($key)` is just here to consume the repetition,
6496
// but we throw away that string literal during constant evaluation.
65-
const CAP: usize = <[()]>::len(&[$({ stringify!($key); }),*]);
66-
let mut map = $crate::IndexMap::with_capacity(CAP);
67-
$(
68-
map.insert($key, $value);
69-
)*
70-
map
71-
}
72-
};
97+
const { <[()]>::len(&[$({ stringify!($key); }),*]) },
98+
);
99+
$(
100+
map.insert($key, $value);
101+
)+
102+
map
103+
}};
73104
}
74105

75106
/// Create an [`IndexSet`][crate::IndexSet] from a list of values
@@ -93,18 +124,52 @@ macro_rules! indexmap {
93124
/// // "a" is the first value
94125
/// assert_eq!(set.iter().next(), Some(&"a"));
95126
/// ```
127+
///
128+
/// This can also be initialized in `const` contexts:
129+
///
130+
/// ```
131+
/// use indexmap::{IndexSet, indexset_with_default};
132+
/// use fnv::FnvBuildHasher; // = BuildHasherDefault<FnvHasher>
133+
/// use std::sync::Mutex;
134+
///
135+
/// static INTERN: Mutex<IndexSet<String, FnvBuildHasher>> =
136+
/// Mutex::new(indexset_with_default!());
137+
///
138+
/// if let Ok(mut set) = INTERN.lock() {
139+
/// set.insert("a".into());
140+
/// set.insert("b".into());
141+
/// set.insert("c".into());
142+
/// }
143+
///
144+
/// assert!(INTERN.lock().unwrap().contains("a"));
145+
/// assert!(INTERN.lock().unwrap().contains("b"));
146+
/// assert!(INTERN.lock().unwrap().contains("c"));
147+
/// ```
96148
#[macro_export]
97149
macro_rules! indexset_with_default {
98-
($H:ty; $($value:expr,)+) => { $crate::indexset_with_default!($H; $($value),+) };
99-
($H:ty; $($value:expr),*) => {{
100-
let builder = ::core::hash::BuildHasherDefault::<$H>::new();
101-
const CAP: usize = <[()]>::len(&[$({ stringify!($value); }),*]);
102-
#[allow(unused_mut)]
103-
// Specify your custom `H` (must implement Default + Hash) as the hasher:
104-
let mut set = $crate::IndexSet::with_capacity_and_hasher(CAP, builder);
150+
() => { const {
151+
$crate::IndexSet::with_hasher(
152+
// Let type inference figure out the hasher:
153+
::core::hash::BuildHasherDefault::new(),
154+
)
155+
}};
156+
($H:ty $(;)?) => { const {
157+
$crate::IndexSet::with_hasher(
158+
// Specify your custom `H` (must implement Default + Hasher) as the hasher:
159+
::core::hash::BuildHasherDefault::<$H>::new(),
160+
)
161+
}};
162+
($H:ty; $($value:expr),+ $(,)?) => {{
163+
let mut set = $crate::IndexSet::with_capacity_and_hasher(
164+
// Note: `stringify!($value)` is just here to consume the repetition,
165+
// but we throw away that string literal during constant evaluation.
166+
const { <[()]>::len(&[$({ stringify!($value); }),*]) },
167+
// Specify your custom `H` (must implement Default + Hasher) as the hasher:
168+
::core::hash::BuildHasherDefault::<$H>::new(),
169+
);
105170
$(
106171
set.insert($value);
107-
)*
172+
)+
108173
set
109174
}};
110175
}
@@ -131,19 +196,18 @@ macro_rules! indexset_with_default {
131196
/// assert_eq!(set.iter().next(), Some(&"a"));
132197
/// ```
133198
macro_rules! indexset {
134-
($($value:expr,)+) => { $crate::indexset!($($value),+) };
135-
($($value:expr),*) => {
136-
{
199+
() => { $crate::IndexSet::new() };
200+
($($value:expr),+ $(,)?) => {{
201+
let mut set = $crate::IndexSet::with_capacity(
137202
// Note: `stringify!($value)` is just here to consume the repetition,
138203
// but we throw away that string literal during constant evaluation.
139-
const CAP: usize = <[()]>::len(&[$({ stringify!($value); }),*]);
140-
let mut set = $crate::IndexSet::with_capacity(CAP);
141-
$(
142-
set.insert($value);
143-
)*
144-
set
145-
}
146-
};
204+
const { <[()]>::len(&[$({ stringify!($value); }),*]) },
205+
);
206+
$(
207+
set.insert($value);
208+
)+
209+
set
210+
}};
147211
}
148212

149213
// generate all the Iterator methods by just forwarding to the underlying

‎tests/macros_full_path.rs‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,43 @@ fn test_create_set() {
1717
3,
1818
};
1919
}
20+
21+
#[test]
22+
fn test_map_shadow() {
23+
// The macro used to have its own `const CAP` which would shadow this, because items are not
24+
// protected by macro hygiene. Now we avoid any items in the macro, and its local `map` *is*
25+
// hygienic vs. the local `map` here.
26+
const CAP: usize = 42;
27+
let map = -1;
28+
let m = indexmap::indexmap! {
29+
map => CAP,
30+
};
31+
assert_eq!(m[&map], CAP);
32+
assert_eq!(m[0], CAP);
33+
}
34+
35+
#[test]
36+
fn test_map_shadow_default() {
37+
const CAP: usize = 42;
38+
let map = -1;
39+
let m = indexmap::indexmap_with_default! {
40+
fnv::FnvHasher;
41+
map => CAP,
42+
};
43+
assert_eq!(m[&map], CAP);
44+
assert_eq!(m[0], CAP);
45+
}
46+
47+
#[test]
48+
fn test_set_shadow() {
49+
const CAP: usize = 42;
50+
let s = indexmap::indexset!(CAP);
51+
assert_eq!(s[0], CAP);
52+
}
53+
54+
#[test]
55+
fn test_set_shadow_default() {
56+
const CAP: usize = 42;
57+
let s = indexmap::indexset_with_default!(fnv::FnvHasher; CAP);
58+
assert_eq!(s[0], CAP);
59+
}

0 commit comments

Comments
 (0)