mirror of
https://github.com/rust-lang/rust.git
synced 2024-10-30 22:12:15 +00:00
Rollup merge of #98039 - tnballo:master, r=thomcc
Fix `panic` message for `BTreeSet`'s `range` API and document `panic` cases Currently, the `panic` cases for [`BTreeSet`'s `range` API](https://doc.rust-lang.org/std/collections/struct.BTreeSet.html#method.range) are undocumented and produce a slightly wrong `panic` message (says `BTreeMap` instead of `BTreeSet`). Panic case 1 code: ```rust use std::collections::BTreeSet; use std::ops::Bound::Excluded; fn main() { let mut set = BTreeSet::new(); set.insert(3); set.insert(5); set.insert(8); for &elem in set.range((Excluded(&3), Excluded(&3))) { println!("{elem}"); } } ``` Panic case 1 message: ``` thread 'main' panicked at 'range start and end are equal and excluded in BTreeMap', /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/alloc/src/collections/btree/search.rs:105:17 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` Panic case 2 code: ```rust use std::collections::BTreeSet; use std::ops::Bound::Included; fn main() { let mut set = BTreeSet::new(); set.insert(3); set.insert(5); set.insert(8); for &elem in set.range((Included(&8), Included(&3))) { println!("{elem}"); } } ``` Panic case 2: ``` thread 'main' panicked at 'range start is greater than range end in BTreeMap', /rustc/fe5b13d681f25ee6474be29d748c65adcd91f69e/library/alloc/src/collections/btree/search.rs:110:17 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` This PR fixes the output messages to say `BTreeSet`, adds the relevant unit tests, and updates the documentation for the API.
This commit is contained in:
commit
6580d7e784
@ -16,6 +16,7 @@ use super::dedup_sorted_iter::DedupSortedIter;
|
|||||||
use super::navigate::{LazyLeafRange, LeafRange};
|
use super::navigate::{LazyLeafRange, LeafRange};
|
||||||
use super::node::{self, marker, ForceResult::*, Handle, NodeRef, Root};
|
use super::node::{self, marker, ForceResult::*, Handle, NodeRef, Root};
|
||||||
use super::search::SearchResult::*;
|
use super::search::SearchResult::*;
|
||||||
|
use super::set_val::SetValZST;
|
||||||
|
|
||||||
mod entry;
|
mod entry;
|
||||||
|
|
||||||
@ -271,7 +272,7 @@ impl<K: Clone, V: Clone, A: Allocator + Clone> Clone for BTreeMap<K, V, A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, Q: ?Sized, A: Allocator + Clone> super::Recover<Q> for BTreeMap<K, (), A>
|
impl<K, Q: ?Sized, A: Allocator + Clone> super::Recover<Q> for BTreeMap<K, SetValZST, A>
|
||||||
where
|
where
|
||||||
K: Borrow<Q> + Ord,
|
K: Borrow<Q> + Ord,
|
||||||
Q: Ord,
|
Q: Ord,
|
||||||
@ -318,7 +319,7 @@ where
|
|||||||
alloc: (*map.alloc).clone(),
|
alloc: (*map.alloc).clone(),
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
.insert(());
|
.insert(SetValZST::default());
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -897,6 +897,39 @@ fn test_range_mut() {
|
|||||||
map.check();
|
map.check();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[should_panic(expected = "range start is greater than range end in BTreeMap")]
|
||||||
|
#[test]
|
||||||
|
fn test_range_panic_1() {
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
map.insert(3, "a");
|
||||||
|
map.insert(5, "b");
|
||||||
|
map.insert(8, "c");
|
||||||
|
|
||||||
|
let _invalid_range = map.range((Included(&8), Included(&3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[should_panic(expected = "range start and end are equal and excluded in BTreeMap")]
|
||||||
|
#[test]
|
||||||
|
fn test_range_panic_2() {
|
||||||
|
let mut map = BTreeMap::new();
|
||||||
|
map.insert(3, "a");
|
||||||
|
map.insert(5, "b");
|
||||||
|
map.insert(8, "c");
|
||||||
|
|
||||||
|
let _invalid_range = map.range((Excluded(&5), Excluded(&5)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[should_panic(expected = "range start and end are equal and excluded in BTreeMap")]
|
||||||
|
#[test]
|
||||||
|
fn test_range_panic_3() {
|
||||||
|
let mut map: BTreeMap<i32, ()> = BTreeMap::new();
|
||||||
|
map.insert(3, ());
|
||||||
|
map.insert(5, ());
|
||||||
|
map.insert(8, ());
|
||||||
|
|
||||||
|
let _invalid_range = map.range((Excluded(&5), Excluded(&5)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_retain() {
|
fn test_retain() {
|
||||||
let mut map = BTreeMap::from_iter((0..100).map(|x| (x, x * 10)));
|
let mut map = BTreeMap::from_iter((0..100).map(|x| (x, x * 10)));
|
||||||
|
@ -10,6 +10,7 @@ mod node;
|
|||||||
mod remove;
|
mod remove;
|
||||||
mod search;
|
mod search;
|
||||||
pub mod set;
|
pub mod set;
|
||||||
|
mod set_val;
|
||||||
mod split;
|
mod split;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -97,17 +97,28 @@ impl<BorrowType: marker::BorrowType, K, V> NodeRef<BorrowType, K, V, marker::Lea
|
|||||||
K: Borrow<Q>,
|
K: Borrow<Q>,
|
||||||
R: RangeBounds<Q>,
|
R: RangeBounds<Q>,
|
||||||
{
|
{
|
||||||
|
// Determine if map or set is being searched
|
||||||
|
let is_set = <V as super::set_val::IsSetVal>::is_set_val();
|
||||||
|
|
||||||
// Inlining these variables should be avoided. We assume the bounds reported by `range`
|
// Inlining these variables should be avoided. We assume the bounds reported by `range`
|
||||||
// remain the same, but an adversarial implementation could change between calls (#81138).
|
// remain the same, but an adversarial implementation could change between calls (#81138).
|
||||||
let (start, end) = (range.start_bound(), range.end_bound());
|
let (start, end) = (range.start_bound(), range.end_bound());
|
||||||
match (start, end) {
|
match (start, end) {
|
||||||
(Bound::Excluded(s), Bound::Excluded(e)) if s == e => {
|
(Bound::Excluded(s), Bound::Excluded(e)) if s == e => {
|
||||||
panic!("range start and end are equal and excluded in BTreeMap")
|
if is_set {
|
||||||
|
panic!("range start and end are equal and excluded in BTreeSet")
|
||||||
|
} else {
|
||||||
|
panic!("range start and end are equal and excluded in BTreeMap")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(Bound::Included(s) | Bound::Excluded(s), Bound::Included(e) | Bound::Excluded(e))
|
(Bound::Included(s) | Bound::Excluded(s), Bound::Included(e) | Bound::Excluded(e))
|
||||||
if s > e =>
|
if s > e =>
|
||||||
{
|
{
|
||||||
panic!("range start is greater than range end in BTreeMap")
|
if is_set {
|
||||||
|
panic!("range start is greater than range end in BTreeSet")
|
||||||
|
} else {
|
||||||
|
panic!("range start is greater than range end in BTreeMap")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ use core::ops::{BitAnd, BitOr, BitXor, RangeBounds, Sub};
|
|||||||
|
|
||||||
use super::map::{BTreeMap, Keys};
|
use super::map::{BTreeMap, Keys};
|
||||||
use super::merge_iter::MergeIterInner;
|
use super::merge_iter::MergeIterInner;
|
||||||
|
use super::set_val::SetValZST;
|
||||||
use super::Recover;
|
use super::Recover;
|
||||||
|
|
||||||
use crate::alloc::{Allocator, Global};
|
use crate::alloc::{Allocator, Global};
|
||||||
@ -81,7 +82,7 @@ pub struct BTreeSet<
|
|||||||
T,
|
T,
|
||||||
#[unstable(feature = "allocator_api", issue = "32838")] A: Allocator + Clone = Global,
|
#[unstable(feature = "allocator_api", issue = "32838")] A: Allocator + Clone = Global,
|
||||||
> {
|
> {
|
||||||
map: BTreeMap<T, (), A>,
|
map: BTreeMap<T, SetValZST, A>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
@ -135,7 +136,7 @@ impl<T: Clone, A: Allocator + Clone> Clone for BTreeSet<T, A> {
|
|||||||
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub struct Iter<'a, T: 'a> {
|
pub struct Iter<'a, T: 'a> {
|
||||||
iter: Keys<'a, T, ()>,
|
iter: Keys<'a, T, SetValZST>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[stable(feature = "collection_debug", since = "1.17.0")]
|
#[stable(feature = "collection_debug", since = "1.17.0")]
|
||||||
@ -158,7 +159,7 @@ pub struct IntoIter<
|
|||||||
T,
|
T,
|
||||||
#[unstable(feature = "allocator_api", issue = "32838")] A: Allocator + Clone = Global,
|
#[unstable(feature = "allocator_api", issue = "32838")] A: Allocator + Clone = Global,
|
||||||
> {
|
> {
|
||||||
iter: super::map::IntoIter<T, (), A>,
|
iter: super::map::IntoIter<T, SetValZST, A>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator over a sub-range of items in a `BTreeSet`.
|
/// An iterator over a sub-range of items in a `BTreeSet`.
|
||||||
@ -171,7 +172,7 @@ pub struct IntoIter<
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[stable(feature = "btree_range", since = "1.17.0")]
|
#[stable(feature = "btree_range", since = "1.17.0")]
|
||||||
pub struct Range<'a, T: 'a> {
|
pub struct Range<'a, T: 'a> {
|
||||||
iter: super::map::Range<'a, T, ()>,
|
iter: super::map::Range<'a, T, SetValZST>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A lazy iterator producing elements in the difference of `BTreeSet`s.
|
/// A lazy iterator producing elements in the difference of `BTreeSet`s.
|
||||||
@ -375,6 +376,11 @@ impl<T, A: Allocator + Clone> BTreeSet<T, A> {
|
|||||||
/// `range((Excluded(4), Included(10)))` will yield a left-exclusive, right-inclusive
|
/// `range((Excluded(4), Included(10)))` will yield a left-exclusive, right-inclusive
|
||||||
/// range from 4 to 10.
|
/// range from 4 to 10.
|
||||||
///
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if range `start > end`.
|
||||||
|
/// Panics if range `start == end` and both bounds are `Excluded`.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@ -905,7 +911,7 @@ impl<T, A: Allocator + Clone> BTreeSet<T, A> {
|
|||||||
where
|
where
|
||||||
T: Ord,
|
T: Ord,
|
||||||
{
|
{
|
||||||
self.map.insert(value, ()).is_none()
|
self.map.insert(value, SetValZST::default()).is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a value to the set, replacing the existing element, if any, that is
|
/// Adds a value to the set, replacing the existing element, if any, that is
|
||||||
@ -1210,7 +1216,7 @@ impl<T: Ord> FromIterator<T> for BTreeSet<T> {
|
|||||||
|
|
||||||
impl<T: Ord, A: Allocator + Clone> BTreeSet<T, A> {
|
impl<T: Ord, A: Allocator + Clone> BTreeSet<T, A> {
|
||||||
fn from_sorted_iter<I: Iterator<Item = T>>(iter: I, alloc: A) -> BTreeSet<T, A> {
|
fn from_sorted_iter<I: Iterator<Item = T>>(iter: I, alloc: A) -> BTreeSet<T, A> {
|
||||||
let iter = iter.map(|k| (k, ()));
|
let iter = iter.map(|k| (k, SetValZST::default()));
|
||||||
let map = BTreeMap::bulk_build_from_sorted_iter(iter, alloc);
|
let map = BTreeMap::bulk_build_from_sorted_iter(iter, alloc);
|
||||||
BTreeSet { map }
|
BTreeSet { map }
|
||||||
}
|
}
|
||||||
@ -1234,7 +1240,7 @@ impl<T: Ord, const N: usize> From<[T; N]> for BTreeSet<T> {
|
|||||||
|
|
||||||
// use stable sort to preserve the insertion order.
|
// use stable sort to preserve the insertion order.
|
||||||
arr.sort();
|
arr.sort();
|
||||||
let iter = IntoIterator::into_iter(arr).map(|k| (k, ()));
|
let iter = IntoIterator::into_iter(arr).map(|k| (k, SetValZST::default()));
|
||||||
let map = BTreeMap::bulk_build_from_sorted_iter(iter, Global);
|
let map = BTreeMap::bulk_build_from_sorted_iter(iter, Global);
|
||||||
BTreeSet { map }
|
BTreeSet { map }
|
||||||
}
|
}
|
||||||
@ -1284,7 +1290,7 @@ pub struct DrainFilter<
|
|||||||
F: 'a + FnMut(&T) -> bool,
|
F: 'a + FnMut(&T) -> bool,
|
||||||
{
|
{
|
||||||
pred: F,
|
pred: F,
|
||||||
inner: super::map::DrainFilterInner<'a, T, ()>,
|
inner: super::map::DrainFilterInner<'a, T, SetValZST>,
|
||||||
/// The BTreeMap will outlive this IntoIter so we don't care about drop order for `alloc`.
|
/// The BTreeMap will outlive this IntoIter so we don't care about drop order for `alloc`.
|
||||||
alloc: A,
|
alloc: A,
|
||||||
}
|
}
|
||||||
@ -1319,7 +1325,7 @@ where
|
|||||||
|
|
||||||
fn next(&mut self) -> Option<T> {
|
fn next(&mut self) -> Option<T> {
|
||||||
let pred = &mut self.pred;
|
let pred = &mut self.pred;
|
||||||
let mut mapped_pred = |k: &T, _v: &mut ()| pred(k);
|
let mut mapped_pred = |k: &T, _v: &mut SetValZST| pred(k);
|
||||||
self.inner.next(&mut mapped_pred, self.alloc.clone()).map(|(k, _)| k)
|
self.inner.next(&mut mapped_pred, self.alloc.clone()).map(|(k, _)| k)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ use crate::vec::Vec;
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
use std::ops::Bound::{Excluded, Included};
|
||||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -831,3 +832,25 @@ fn from_array() {
|
|||||||
let unordered_duplicates = BTreeSet::from([4, 1, 4, 3, 2]);
|
let unordered_duplicates = BTreeSet::from([4, 1, 4, 3, 2]);
|
||||||
assert_eq!(set, unordered_duplicates);
|
assert_eq!(set, unordered_duplicates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[should_panic(expected = "range start is greater than range end in BTreeSet")]
|
||||||
|
#[test]
|
||||||
|
fn test_range_panic_1() {
|
||||||
|
let mut set = BTreeSet::new();
|
||||||
|
set.insert(3);
|
||||||
|
set.insert(5);
|
||||||
|
set.insert(8);
|
||||||
|
|
||||||
|
let _invalid_range = set.range((Included(&8), Included(&3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[should_panic(expected = "range start and end are equal and excluded in BTreeSet")]
|
||||||
|
#[test]
|
||||||
|
fn test_range_panic_2() {
|
||||||
|
let mut set = BTreeSet::new();
|
||||||
|
set.insert(3);
|
||||||
|
set.insert(5);
|
||||||
|
set.insert(8);
|
||||||
|
|
||||||
|
let _invalid_range = set.range((Excluded(&5), Excluded(&5)));
|
||||||
|
}
|
||||||
|
29
library/alloc/src/collections/btree/set_val.rs
Normal file
29
library/alloc/src/collections/btree/set_val.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/// Zero-Sized Type (ZST) for internal `BTreeSet` values.
|
||||||
|
/// Used instead of `()` to differentiate between:
|
||||||
|
/// * `BTreeMap<T, ()>` (possible user-defined map)
|
||||||
|
/// * `BTreeMap<T, SetValZST>` (internal set representation)
|
||||||
|
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Default)]
|
||||||
|
pub struct SetValZST;
|
||||||
|
|
||||||
|
/// A trait to differentiate between `BTreeMap` and `BTreeSet` values.
|
||||||
|
/// Returns `true` only for type `SetValZST`, `false` for all other types (blanket implementation).
|
||||||
|
/// `TypeId` requires a `'static` lifetime, use of this trait avoids that restriction.
|
||||||
|
///
|
||||||
|
/// [`TypeId`]: std::any::TypeId
|
||||||
|
pub trait IsSetVal {
|
||||||
|
fn is_set_val() -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blanket implementation
|
||||||
|
impl<V> IsSetVal for V {
|
||||||
|
default fn is_set_val() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specialization
|
||||||
|
impl IsSetVal for SetValZST {
|
||||||
|
fn is_set_val() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user