From ae475950a3cc18bd914aec74ea411cca170e2b38 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 10:37:59 -0700 Subject: [PATCH 01/12] ci: upgrade to actions/checkout@v7 --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fddf5f14..fb3a5c89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: bench: test build benchmarks steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -72,7 +72,7 @@ jobs: target: thumbv6m-none-eabi steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -85,7 +85,7 @@ jobs: clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@beta with: components: clippy @@ -94,7 +94,7 @@ jobs: miri: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@nightly with: components: miri, rust-src @@ -110,7 +110,7 @@ jobs: name: Check MSRV and minimal-versions runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@1.85.0 # MSRV - uses: taiki-e/install-action@v2 From 417437d1aae0a963015a6a4fad13a512df562a05 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 10:41:26 -0700 Subject: [PATCH 02/12] use underscored lint names --- Cargo.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ae02d906..0f1b6b5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,15 +62,15 @@ rustdoc-args = ["--cfg", "docsrs"] members = ["test-nostd", "test-serde", "test-sval"] [lints.rust] -private-bounds = "deny" -private-interfaces = "deny" -unnameable-types = "deny" -unreachable-pub = "deny" +private_bounds = "deny" +private_interfaces = "deny" +unnameable_types = "deny" +unreachable_pub = "deny" # We *mostly* avoid unsafe code, but there are a few fine-grained cases allowed -unsafe-code = "deny" +unsafe_code = "deny" -rust-2018-idioms = "warn" +rust_2018_idioms = "warn" [lints.clippy] style = "allow" From 79f7bd948506f3e52224b8eb096611576a6cd19b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 10:41:43 -0700 Subject: [PATCH 03/12] fix clippy::lint_groups_priority --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0f1b6b5a..f1725e56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ unreachable_pub = "deny" # We *mostly* avoid unsafe code, but there are a few fine-grained cases allowed unsafe_code = "deny" -rust_2018_idioms = "warn" +rust_2018_idioms = { level = "warn", priority = -1 } [lints.clippy] style = "allow" From bb35663f7593b37976ed6271c09f747559d646fe Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 10:44:05 -0700 Subject: [PATCH 04/12] expect clippy::reversed_empty_ranges in tests --- src/map/tests.rs | 2 ++ src/set/tests.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/map/tests.rs b/src/map/tests.rs index 79041905..f01dc6db 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -768,6 +768,7 @@ fn get_range() { let result = index_map.get_range(2..2); assert!(result.unwrap().is_empty()); + #[expect(clippy::reversed_empty_ranges)] let result = index_map.get_range(4..2); assert!(result.is_none()); @@ -789,6 +790,7 @@ fn get_range_mut() { let result = index_map.get_range_mut(2..2); assert!(result.unwrap().is_empty()); + #[expect(clippy::reversed_empty_ranges)] let result = index_map.get_range_mut(4..2); assert!(result.is_none()); diff --git a/src/set/tests.rs b/src/set/tests.rs index bf761e08..07916d5a 100644 --- a/src/set/tests.rs +++ b/src/set/tests.rs @@ -852,6 +852,7 @@ fn get_range() { let result = set.get_range(0..0); assert_eq!(result.unwrap().len(), 0); + #[expect(clippy::reversed_empty_ranges)] let result = set.get_range(2..1); assert!(result.is_none()); } From be7ffd0d154c06f76152b6884ee44f88eebe68aa Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 10:46:25 -0700 Subject: [PATCH 05/12] expect clippy::unnecessary_get_then_check in benches --- benches/bench.rs | 2 ++ benches/faststring.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/benches/bench.rs b/benches/bench.rs index 3c5e1993..f9394e20 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -289,6 +289,7 @@ fn lookup_hashmap_10_000_exist(b: &mut Bencher) { } b.iter(|| { let mut found = 0; + #[expect(clippy::unnecessary_get_then_check)] for key in 5000..c { found += map.get(&key).is_some() as i32; } @@ -306,6 +307,7 @@ fn lookup_hashmap_10_000_noexist(b: &mut Bencher) { } b.iter(|| { let mut found = 0; + #[expect(clippy::unnecessary_get_then_check)] for key in c..15000 { found += map.get(&key).is_some() as i32; } diff --git a/benches/faststring.rs b/benches/faststring.rs index bfe39590..7acae402 100644 --- a/benches/faststring.rs +++ b/benches/faststring.rs @@ -117,6 +117,7 @@ fn lookup_hashmap_10_000_exist_string(b: &mut Bencher) { let lookups = (5000..c).map(|x| x.to_string()).collect::>(); b.iter(|| { let mut found = 0; + #[expect(clippy::unnecessary_get_then_check)] for key in &lookups { found += map.get(key).is_some() as i32; } @@ -137,6 +138,7 @@ fn lookup_hashmap_10_000_exist_string_oneshot(b: &mut Bencher) { .collect::>(); b.iter(|| { let mut found = 0; + #[expect(clippy::unnecessary_get_then_check)] for key in &lookups { found += map.get(key).is_some() as i32; } From 41760c52bbfdd6c7cb43331246c813506d825a49 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 10:48:23 -0700 Subject: [PATCH 06/12] fix clippy::map_entry --- tests/quick.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index 3f64b096..b6dd4ce3 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -796,9 +796,7 @@ quickcheck_limit! { // value seen for that key! let mut last_val_per_key = HashMap::new(); for &(k, v) in input.iter().rev() { - if !last_val_per_key.contains_key(&k) { - last_val_per_key.insert(k, v); - } + last_val_per_key.entry(k).or_insert(v); } // iterate over the keys in (A) in order, and match each one with From 64f4a06684dd4ed06dc6629dd8bbefc59394b2c9 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 10:50:02 -0700 Subject: [PATCH 07/12] fix clippy::int_plus_one --- src/map/tests.rs | 2 +- src/set/tests.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/map/tests.rs b/src/map/tests.rs index f01dc6db..a2bf024d 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -230,7 +230,7 @@ fn shrink_to_fit() { assert_eq!(map.len(), i); map.insert(i, i * i); assert_eq!(map.len(), i + 1); - assert!(map.capacity() >= i + 1); + assert!(map.capacity() > i); assert_eq!(map.get(&i), Some(&(i * i))); map.shrink_to_fit(); assert_eq!(map.len(), i + 1); diff --git a/src/set/tests.rs b/src/set/tests.rs index 07916d5a..e313196e 100644 --- a/src/set/tests.rs +++ b/src/set/tests.rs @@ -338,7 +338,7 @@ fn shrink_to_fit() { assert_eq!(set.len(), i); set.insert(i); assert_eq!(set.len(), i + 1); - assert!(set.capacity() >= i + 1); + assert!(set.capacity() > i); assert_eq!(set.get(&i), Some(&i)); set.shrink_to_fit(); assert_eq!(set.len(), i + 1); From 1c2be7b00c5bb61f654210355d1e9f0d9be03956 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 10:52:08 -0700 Subject: [PATCH 08/12] use inherent usize::MAX --- src/map/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/map/tests.rs b/src/map/tests.rs index a2bf024d..979a42f6 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -207,10 +207,10 @@ fn reserve() { assert_eq!(map.capacity(), capacity); assert_eq!(map.get(&i), Some(&(i * i))); } - map.insert(capacity, std::usize::MAX); + map.insert(capacity, usize::MAX); assert_eq!(map.len(), capacity + 1); assert!(map.capacity() > capacity); - assert_eq!(map.get(&capacity), Some(&std::usize::MAX)); + assert_eq!(map.get(&capacity), Some(&usize::MAX)); } #[test] From 21963654e41f86f396a2ba6d9582ccd8eb7df52c Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 11:01:26 -0700 Subject: [PATCH 09/12] fix clippy::useless_vec (and more) in tests --- src/map/tests.rs | 45 +++++++++++++++++---------------------------- src/set/tests.rs | 20 +++++++++----------- 2 files changed, 26 insertions(+), 39 deletions(-) diff --git a/src/map/tests.rs b/src/map/tests.rs index 979a42f6..86a6a032 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -43,8 +43,8 @@ fn insert() { #[test] fn insert_full() { - let insert = vec![9, 2, 7, 1, 4, 6, 13]; - let present = vec![1, 6, 2]; + let insert = [9, 2, 7, 1, 4, 6, 13]; + let present = [1, 6, 2]; let mut map = IndexMap::with_capacity(insert.len()); for (i, &elt) in insert.iter().enumerate() { @@ -327,11 +327,11 @@ fn partial_eq_and_eq() { #[test] fn extend() { let mut map = IndexMap::new(); - map.extend(vec![(&1, &2), (&3, &4)]); - map.extend(vec![(5, 6)]); + map.extend([(&1, &2), (&3, &4)]); + map.extend([(5, 6)]); assert_eq!( map.into_iter().collect::>(), - vec![(1, 2), (3, 4), (5, 6)] + [(1, 2), (3, 4), (5, 6)] ); } @@ -485,8 +485,7 @@ fn from_entries() { #[test] fn keys() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: IndexMap<_, _> = vec.into_iter().collect(); + let map = IndexMap::<_, _>::from_iter([(1, 'a'), (2, 'b'), (3, 'c')]); let keys: Vec<_> = map.keys().copied().collect(); assert_eq!(keys.len(), 3); assert!(keys.contains(&1)); @@ -496,8 +495,7 @@ fn keys() { #[test] fn into_keys() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: IndexMap<_, _> = vec.into_iter().collect(); + let map = IndexMap::<_, _>::from_iter([(1, 'a'), (2, 'b'), (3, 'c')]); let keys: Vec = map.into_keys().collect(); assert_eq!(keys.len(), 3); assert!(keys.contains(&1)); @@ -507,8 +505,7 @@ fn into_keys() { #[test] fn values() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: IndexMap<_, _> = vec.into_iter().collect(); + let map = IndexMap::<_, _>::from_iter([(1, 'a'), (2, 'b'), (3, 'c')]); let values: Vec<_> = map.values().copied().collect(); assert_eq!(values.len(), 3); assert!(values.contains(&'a')); @@ -518,8 +515,7 @@ fn values() { #[test] fn values_mut() { - let vec = vec![(1, 1), (2, 2), (3, 3)]; - let mut map: IndexMap<_, _> = vec.into_iter().collect(); + let mut map = IndexMap::<_, _>::from_iter([(1, 1), (2, 2), (3, 3)]); for value in map.values_mut() { *value *= 2 } @@ -532,8 +528,7 @@ fn values_mut() { #[test] fn into_values() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: IndexMap<_, _> = vec.into_iter().collect(); + let map = IndexMap::<_, _>::from_iter([(1, 'a'), (2, 'b'), (3, 'c')]); let values: Vec = map.into_values().collect(); assert_eq!(values.len(), 3); assert!(values.contains(&'a')); @@ -698,8 +693,7 @@ fn shift_remove_full() { #[test] fn sorted_unstable_by() { - let mut map: IndexMap = IndexMap::new(); - map.extend(vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50)]); + let map = IndexMap::::from_iter([(1, 10), (2, 20), (3, 30), (4, 40), (5, 50)]); let sorted = map.sorted_unstable_by(|_a, b, _c, d| d.cmp(&b)); assert_eq!( @@ -750,29 +744,24 @@ fn insert_before_oob() { #[test] fn clear() { - let mut map: IndexMap = IndexMap::new(); - map.extend(vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50)]); + let mut map = IndexMap::::from_iter([(1, 10), (2, 20), (3, 30), (4, 40), (5, 50)]); + assert_ne!(map.len(), 0); map.clear(); assert_eq!(map.len(), 0); } #[test] fn get_range() { - let mut index_map: IndexMap = IndexMap::new(); - index_map.insert(1, 10); - index_map.insert(2, 20); - index_map.insert(3, 30); - index_map.insert(4, 40); - index_map.insert(5, 50); + let map = IndexMap::::from_iter([(1, 10), (2, 20), (3, 30), (4, 40), (5, 50)]); - let result = index_map.get_range(2..2); + let result = map.get_range(2..2); assert!(result.unwrap().is_empty()); #[expect(clippy::reversed_empty_ranges)] - let result = index_map.get_range(4..2); + let result = map.get_range(4..2); assert!(result.is_none()); - let result = index_map.get_range(2..4); + let result = map.get_range(2..4); let slice: &Slice = result.unwrap(); assert_eq!(slice.len(), 2); assert_eq!(slice, &[(3, 30), (4, 40)]); diff --git a/src/set/tests.rs b/src/set/tests.rs index e313196e..9bef6492 100644 --- a/src/set/tests.rs +++ b/src/set/tests.rs @@ -42,8 +42,8 @@ fn insert() { #[test] fn insert_full() { - let insert = vec![9, 2, 7, 1, 4, 6, 13]; - let present = vec![1, 6, 2]; + let insert = [9, 2, 7, 1, 4, 6, 13]; + let present = [1, 6, 2]; let mut set = IndexSet::with_capacity(insert.len()); for (i, &elt) in insert.iter().enumerate() { @@ -90,8 +90,7 @@ fn insert_2() { #[test] fn insert_dup() { - let mut elements = vec![0, 2, 4, 6, 8]; - let mut set: IndexSet = elements.drain(..).collect(); + let mut set = IndexSet::::from_iter([0, 2, 4, 6, 8]); { let (i, v) = set.get_full(&0).unwrap(); assert_eq!(set.len(), 5); @@ -175,8 +174,8 @@ fn replace() { #[test] fn replace_full() { - let replace = vec![9, 2, 7, 1, 4, 6, 13]; - let present = vec![1, 6, 2]; + let replace = [9, 2, 7, 1, 4, 6, 13]; + let present = [1, 6, 2]; let mut set = IndexSet::with_capacity(replace.len()); for (i, &elt) in replace.iter().enumerate() { @@ -223,8 +222,7 @@ fn replace_2() { #[test] fn replace_dup() { - let mut elements = vec![0, 2, 4, 6, 8]; - let mut set: IndexSet = elements.drain(..).collect(); + let mut set = IndexSet::::from_iter([0, 2, 4, 6, 8]); { let (i, v) = set.get_full(&0).unwrap(); assert_eq!(set.len(), 5); @@ -426,9 +424,9 @@ fn partial_eq_and_eq() { #[test] fn extend() { let mut set = IndexSet::new(); - set.extend(vec![&1, &2, &3, &4]); - set.extend(vec![5, 6]); - assert_eq!(set.into_iter().collect::>(), vec![1, 2, 3, 4, 5, 6]); + set.extend([&1, &2, &3, &4]); + set.extend([5, 6]); + assert_eq!(set.into_iter().collect::>(), [1, 2, 3, 4, 5, 6]); } #[test] From c95da18c97d2742fe8731f36e81c1d04f0620c6a Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 11:02:43 -0700 Subject: [PATCH 10/12] fix clippy::derivable_impls --- src/map/tests.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/map/tests.rs b/src/map/tests.rs index 86a6a032..5b7a174c 100644 --- a/src/map/tests.rs +++ b/src/map/tests.rs @@ -374,18 +374,13 @@ fn entry_and_modify() { fn entry_or_default() { let mut map = IndexMap::new(); - #[derive(Debug, PartialEq)] + #[derive(Debug, Default, PartialEq)] enum TestEnum { + #[default] DefaultValue, NonDefaultValue, } - impl Default for TestEnum { - fn default() -> Self { - TestEnum::DefaultValue - } - } - map.insert(1, TestEnum::NonDefaultValue); assert_eq!(&mut TestEnum::NonDefaultValue, map.entry(1).or_default()); From af93b43ecd62d28deb59a6ac23f98bd115475eaf Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 11:04:14 -0700 Subject: [PATCH 11/12] expect clippy::redundant_slicing in tests --- src/map/slice.rs | 1 + src/set/slice.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/map/slice.rs b/src/map/slice.rs index 0cb0c911..e0835f61 100644 --- a/src/map/slice.rs +++ b/src/map/slice.rs @@ -578,6 +578,7 @@ mod tests { let slice = map.as_slice(); // RangeFull + #[expect(clippy::redundant_slicing)] check(&vec[..], &map[..], &slice[..]); for i in 0usize..10 { diff --git a/src/set/slice.rs b/src/set/slice.rs index 857e06d9..b0fda998 100644 --- a/src/set/slice.rs +++ b/src/set/slice.rs @@ -394,6 +394,7 @@ mod tests { let slice = set.as_slice(); // RangeFull + #[expect(clippy::redundant_slicing)] check(&vec[..], &set[..], &slice[..]); for i in 0usize..10 { From ada540e17cc7d8f38359f7d83c225e38f367c86b Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 28 Aug 2026 11:10:01 -0700 Subject: [PATCH 12/12] Release 2.14.1 --- Cargo.toml | 2 +- RELEASES.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f1725e56..f6be75f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "indexmap" edition = "2024" -version = "2.14.0" +version = "2.14.1" documentation = "https://docs.rs/indexmap/" repository = "https://github.com/indexmap-rs/indexmap" license = "Apache-2.0 OR MIT" diff --git a/RELEASES.md b/RELEASES.md index 6d8b42b2..da24c16d 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,11 @@ # Releases +## 2.14.1 (2026-08-28) + +- Simplify comparisons where `Equivalent` isn't needed (`Q = K`). +- Unify index assertions for bounds checks. +- Fix (or `expect`) clippy lints. + ## 2.14.0 (2026-04-09) - **MSRV**: Rust 1.85.0 or later is now required.