diff --git a/library/core/src/internal_macros.rs b/library/core/src/internal_macros.rs index 0d0ff23fe2946..684c72fad8125 100644 --- a/library/core/src/internal_macros.rs +++ b/library/core/src/internal_macros.rs @@ -108,3 +108,30 @@ macro_rules! impl_fn_for_zst { )+ } } + +// Used by both iter and range +macro_rules! impl_fold_via_try_fold { + (fold -> try_fold) => { + impl_fold_via_try_fold! { @internal fold -> try_fold } + }; + (rfold -> try_rfold) => { + impl_fold_via_try_fold! { @internal rfold -> try_rfold } + }; + (spec_fold -> spec_try_fold) => { + impl_fold_via_try_fold! { @internal spec_fold -> spec_try_fold } + }; + (spec_rfold -> spec_try_rfold) => { + impl_fold_via_try_fold! { @internal spec_rfold -> spec_try_rfold } + }; + (@internal $fold:ident -> $try_fold:ident) => { + #[inline] + fn $fold(mut self, init: AAA, fold: FFF) -> AAA + where + FFF: FnMut(AAA, Self::Item) -> AAA, + { + use crate::ops::NeverShortCircuit; + + self.$try_fold(init, NeverShortCircuit::wrap_mut_2(fold)).0 + } + }; +} diff --git a/library/core/src/iter/mod.rs b/library/core/src/iter/mod.rs index 9ddafd47807f2..6ce6b503cc594 100644 --- a/library/core/src/iter/mod.rs +++ b/library/core/src/iter/mod.rs @@ -355,33 +355,6 @@ #![stable(feature = "rust1", since = "1.0.0")] -// This needs to be up here in order to be usable in the child modules -macro_rules! impl_fold_via_try_fold { - (fold -> try_fold) => { - impl_fold_via_try_fold! { @internal fold -> try_fold } - }; - (rfold -> try_rfold) => { - impl_fold_via_try_fold! { @internal rfold -> try_rfold } - }; - (spec_fold -> spec_try_fold) => { - impl_fold_via_try_fold! { @internal spec_fold -> spec_try_fold } - }; - (spec_rfold -> spec_try_rfold) => { - impl_fold_via_try_fold! { @internal spec_rfold -> spec_try_rfold } - }; - (@internal $fold:ident -> $try_fold:ident) => { - #[inline] - fn $fold(mut self, init: AAA, fold: FFF) -> AAA - where - FFF: FnMut(AAA, Self::Item) -> AAA, - { - use crate::ops::NeverShortCircuit; - - self.$try_fold(init, NeverShortCircuit::wrap_mut_2(fold)).0 - } - }; -} - #[unstable(feature = "iter_array_chunks", issue = "100450")] pub use self::adapters::ArrayChunks; #[unstable(feature = "std_internals", issue = "none")] diff --git a/library/core/src/range/iter.rs b/library/core/src/range/iter.rs index 01b69554a0b1b..828be8059e8f7 100644 --- a/library/core/src/range/iter.rs +++ b/library/core/src/range/iter.rs @@ -1,9 +1,12 @@ +use crate::hint::cold_path; use crate::iter::{ FusedIterator, Step, TrustedLen, TrustedRandomAccess, TrustedRandomAccessNoCoerce, TrustedStep, }; +use crate::marker::Destruct; use crate::num::NonZero; +use crate::ops::Try; use crate::range::{Range, RangeFrom, RangeInclusive, legacy}; -use crate::{intrinsics, mem}; +use crate::{fmt, intrinsics, mem}; /// By-value [`Range`] iterator. #[stable(feature = "new_range_api", since = "1.96.0")] @@ -168,10 +171,60 @@ impl IntoIterator for Range { /// By-value [`RangeInclusive`] iterator. #[stable(feature = "new_range_inclusive_api", since = "1.95.0")] -#[derive(Debug, Clone)] -pub struct RangeInclusiveIter(legacy::RangeInclusive); +#[derive(Clone)] +pub struct RangeInclusiveIter { + // When created from `start..=last`, this range is + // - Preferably `start..(last+1)`, so we only need to delegate to the exclusive range + // - If necessary (because `last` is a maximal element) `start..last`, + // with the `is_inclusive` field set to `true` + range: legacy::Range, + // Preferably this is `false`, denoting that we successfully converted the inclusive + // range into an exclusive range, and thus have no need for extra handling. + // If this is true, however, that means that we must return one final item + // after iterating it as an exclusive range. + // This must only be true if the iterator is non-empty, implying + // `range.start <= range.end`. (If the original inclusive range is empty because + // `!(start <= last)`, it's stored as the empty exclusive range `start..last` ) + is_inclusive: bool, +} + +#[stable(feature = "new_range_inclusive_api", since = "1.95.0")] +impl fmt::Debug for RangeInclusiveIter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { range: legacy::Range { start, end }, is_inclusive } = self; + let inclusive; + let exclusive; + let field: &dyn fmt::Debug = if *is_inclusive { + inclusive = &start..=&end; + &inclusive + } else { + exclusive = &start..&end; + &exclusive + }; + fmt::Formatter::debug_tuple_field1_finish(f, "RangeInclusiveIter", field) + } +} impl RangeInclusiveIter { + #[rustc_const_unstable(feature = "const_range", issue = "none")] + #[inline] + const fn is_empty(&self) -> bool + where + A: [const] PartialOrd, + { + let Self { range, is_inclusive } = self; + // We expect to need the range comparison (since inclusive is rare), + // so run it outside the `if` to tell the backend that it's ok to look + // at those fields unconditionally. + let range_is_empty = range.is_empty(); + if *is_inclusive { + debug_assert!(range.start <= range.end); + false + } else { + range_is_empty + } + } + /// Returns the remainder of the range being iterated over. /// /// If the iterator is exhausted or empty, returns `None`. @@ -191,11 +244,18 @@ impl RangeInclusiveIter { /// ``` #[unstable(feature = "new_range_remainder", issue = "154458")] pub fn remainder(self) -> Option> { - if self.0.is_empty() { + if self.is_empty() { return None; } - Some(RangeInclusive { start: self.0.start, last: self.0.end }) + let Self { range: legacy::Range { start, end }, is_inclusive } = self; + let last = if is_inclusive { + end + } else { + // Can't overflow because the range isn't empty + Step::backward(end, 1) + }; + Some(RangeInclusive { start, last }) } } @@ -205,27 +265,75 @@ impl Iterator for RangeInclusiveIter { #[inline] fn next(&mut self) -> Option { - self.0.next() + // Conveniently, regardless of whether we were able to convert to exclusive, + // the normal case is to return a value when `range.start < range.end`. + // Only rarely do we need to handle something else. + + let Self { range, is_inclusive } = self; + if let next @ Some(_) = range.next() { + next + } else { + cold_path(); + if *is_inclusive { + // Tighter invariant check than normal because we only get here after + // having already returned all the previous items. + // As an exclusive range it's empty, but we give out one more. + debug_assert!(range.start == range.end); + *is_inclusive = false; + // Because we're going forward, prefer giving out `start` since + // that's what the `range.next()` above returned. + let last = range.start.clone(); + Some(last) + } else { + None + } + } } #[inline] fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() + let Self { range, is_inclusive } = self; + let (low, high) = range.size_hint(); + let extra = *is_inclusive as usize; + (low.saturating_add(extra), try { high?.checked_add(extra)? }) } #[inline] + #[rustc_inherit_overflow_checks] fn count(self) -> usize { - self.0.count() + let Self { range, is_inclusive } = self; + let extra = is_inclusive as usize; + range.count() + extra } - #[inline] - fn nth(&mut self, n: usize) -> Option { - self.0.nth(n) + impl_fold_via_try_fold! { fold -> try_fold } + + fn try_fold(&mut self, init: B, mut f: F) -> R + where + Self: Sized, + F: FnMut(B, Self::Item) -> R + Destruct, + R: Try, + { + let Self { range, is_inclusive } = self; + let mut accum = init; + + accum = range.try_fold(accum, &mut f)?; + + if *is_inclusive { + cold_path(); + debug_assert!(range.start == range.end); + *is_inclusive = false; + let last = range.start.clone(); + // Update the state before this call so it happens even if `?` short-circuits + accum = f(accum, last)?; + } + + try { accum } } #[inline] fn last(self) -> Option { - self.0.last() + { self }.next_back() } #[inline] @@ -233,7 +341,7 @@ impl Iterator for RangeInclusiveIter { where A: Ord, { - self.0.min() + { self }.next() } #[inline] @@ -241,7 +349,7 @@ impl Iterator for RangeInclusiveIter { where A: Ord, { - self.0.max() + { self }.next_back() } #[inline] @@ -251,7 +359,25 @@ impl Iterator for RangeInclusiveIter { #[inline] fn advance_by(&mut self, n: usize) -> Result<(), NonZero> { - self.0.advance_by(n) + let Self { range, is_inclusive } = self; + match range.advance_by(n) { + Ok(()) => Ok(()), + Err(remainder) => { + if *is_inclusive { + cold_path(); + debug_assert!(range.start == range.end); + // `remainder` is `NonZero`, so we always pass the final element + *is_inclusive = false; + if let Some(remainder) = NonZero::new(remainder.get() - 1) { + Err(remainder) + } else { + Ok(()) + } + } else { + Err(remainder) + } + } + } } } @@ -259,17 +385,61 @@ impl Iterator for RangeInclusiveIter { impl DoubleEndedIterator for RangeInclusiveIter { #[inline] fn next_back(&mut self) -> Option { - self.0.next_back() + // Sadly when iterating backwards we have to always check whether we're + // inclusive, even though it's rare. + + let Self { range, is_inclusive } = self; + if *is_inclusive { + cold_path(); + debug_assert!(range.start <= range.end); + *is_inclusive = false; + let last = range.end.clone(); + Some(last) + } else { + range.next_back() + } } - #[inline] - fn nth_back(&mut self, n: usize) -> Option { - self.0.nth_back(n) + impl_fold_via_try_fold! { rfold -> try_rfold } + + fn try_rfold(&mut self, init: B, mut f: F) -> R + where + Self: Sized, + F: FnMut(B, Self::Item) -> R + Destruct, + R: Try, + { + let Self { range, is_inclusive } = self; + let mut accum = init; + + if *is_inclusive { + cold_path(); + debug_assert!(range.start <= range.end); + *is_inclusive = false; + let last = range.end.clone(); + // Update the state before this call so it happens even if `?` short-circuits + accum = f(accum, last)?; + } + + accum = range.try_rfold(accum, f)?; + + try { accum } } #[inline] - fn advance_back_by(&mut self, n: usize) -> Result<(), NonZero> { - self.0.advance_back_by(n) + fn advance_back_by(&mut self, mut n: usize) -> Result<(), NonZero> { + let Self { range, is_inclusive } = self; + if *is_inclusive { + cold_path(); + debug_assert!(range.start <= range.end); + *is_inclusive = false; + if let Some(new_n) = n.checked_sub(1) { + n = new_n; + } else { + return Ok(()); + } + } + + range.advance_back_by(n) } } @@ -284,8 +454,29 @@ impl IntoIterator for RangeInclusive { type Item = A; type IntoIter = RangeInclusiveIter; + #[inline] fn into_iter(self) -> Self::IntoIter { - RangeInclusiveIter(self.into()) + // This is the core opportunity for us to do something different from the + // legacy `RangeInclusive` type. For the old one `into_iter` is forced to + // be identity, but here we can try to adjust it *outside* the loop. + + let Self { start, last } = self; + let is_inclusive; + let end = if let Some(end) = Step::forward_checked(last.clone(), 1) { + is_inclusive = false; + end + } else { + is_inclusive = start <= last; + if !is_inclusive { + // This is unreachable for `Ord` types, but `Step` accepts partial orders. + // So it's possible for the range to be empty even if `last` is + // a maximal element in the DAG. + debug_assert_eq!(PartialOrd::partial_cmp(&start, &last), None); + } + last + }; + let range = legacy::Range { start, end }; + RangeInclusiveIter { range, is_inclusive } } } @@ -307,7 +498,11 @@ macro_rules! range_exact_iter_impl { macro_rules! range_incl_exact_iter_impl { ($($t:ty)*) => ($( #[stable(feature = "new_range_inclusive_api", since = "1.95.0")] - impl ExactSizeIterator for RangeInclusiveIter<$t> { } + impl ExactSizeIterator for RangeInclusiveIter<$t> { + fn is_empty(&self) -> bool { + self.is_empty() + } + } )*) } diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 92fccea38bac9..5d6a42fce3338 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -44,6 +44,7 @@ #![feature(cstr_display)] #![feature(debug_closure_helpers)] #![feature(dec2flt)] +#![feature(derive_const)] #![feature(drop_guard)] #![feature(duration_constants)] #![feature(duration_constructors)] @@ -88,6 +89,7 @@ #![feature(maybe_uninit_uninit_array_transpose)] #![feature(min_specialization)] #![feature(never_type)] +#![feature(new_range_remainder)] #![feature(next_index)] #![feature(non_exhaustive_omitted_patterns_lint)] #![feature(num_internals)] @@ -166,6 +168,7 @@ macro_rules! test_runtime_and_compiletime { } } +// tidy-alphabetical-start mod alloc; mod any; mod array; @@ -205,6 +208,7 @@ mod pattern; mod pin; mod pin_macro; mod ptr; +mod range; mod result; mod simd; mod slice; @@ -216,6 +220,7 @@ mod tuple; mod unicode; mod waker; mod wtf8; +// tidy-alphabetical-end /// Copied from `std::test_helpers::test_rng`, see that function for rationale. #[track_caller] diff --git a/library/coretests/tests/range.rs b/library/coretests/tests/range.rs new file mode 100644 index 0000000000000..f478c7232be78 --- /dev/null +++ b/library/coretests/tests/range.rs @@ -0,0 +1,259 @@ +//! Various tests for the new-style range types + +use core::cmp::Ordering; +use core::iter::Step; +use core::num::NonZero; +use core::ops::ControlFlow; +use core::panicking::panic; +use core::range::RangeInclusive; + +#[test] +fn test_range_inclusive_to_exclusive_transform() { + // The Debug format is *not* a stable guarantee, but is convenient for internal tests. + let iter = RangeInclusive { start: '0', last: '9' }.into_iter(); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter('0'..':')"); + + let iter = RangeInclusive { start: 10, last: 100 }.into_iter(); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(10..101)"); + let iter = RangeInclusive { start: 100, last: 100 }.into_iter(); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(100..101)"); + let iter = RangeInclusive { start: 100, last: 10 }.into_iter(); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(100..11)"); + let iter = RangeInclusive { start: 0, last: 255_u8 }.into_iter(); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(0..=255)"); + let iter = RangeInclusive { start: 255, last: 255_u8 }.into_iter(); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(255..=255)"); + let iter = RangeInclusive { start: 255_u8, last: 254 }.into_iter(); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(255..255)"); + + // Also check with a !Ord type... + let iter = RangeInclusive { start: NotOrd::A(200), last: NotOrd::A(255) }.into_iter(); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(A(200)..=A(255))"); + assert_eq!(iter.clone().next(), Some(NotOrd::A(200))); + assert_eq!(iter.clone().next_back(), Some(NotOrd::A(255))); + let iter = RangeInclusive { start: NotOrd::B(200), last: NotOrd::B(255) }.into_iter(); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(B(200)..B(256))"); + assert_eq!(iter.clone().next(), Some(NotOrd::B(200))); + assert_eq!(iter.clone().next_back(), Some(NotOrd::B(255))); + // ...particularly for these cases where neither start ≤ last nor start ≥ last. + let iter = RangeInclusive { start: NotOrd::A(200), last: NotOrd::B(255) }.into_iter(); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(A(200)..B(256))"); + assert_eq!(iter.clone().next(), None); + assert_eq!(iter.clone().next_back(), None); + let iter = RangeInclusive { start: NotOrd::B(200), last: NotOrd::A(255) }.into_iter(); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(B(200)..A(255))"); + assert_eq!(iter.clone().next(), None); + assert_eq!(iter.clone().next_back(), None); +} + +#[test] +fn test_range_inclusive_iter_exclusive_inner() { + let mut iter = RangeInclusive:: { start: 10, last: 12 }.into_iter(); + assert_eq!(iter.next(), Some(10)); + assert_eq!(iter.next(), Some(11)); + assert_eq!(iter.next(), Some(12)); + assert_eq!(iter.next(), None); + assert_eq!(iter.next(), None); + + let mut iter = RangeInclusive:: { start: 10, last: 12 }.into_iter(); + assert_eq!(iter.next_back(), Some(12)); + assert_eq!(iter.next_back(), Some(11)); + assert_eq!(iter.next_back(), Some(10)); + assert_eq!(iter.next_back(), None); + assert_eq!(iter.next_back(), None); +} + +#[test] +fn test_range_inclusive_iter_inclusive_inner() { + let mut iter = RangeInclusive:: { start: 252, last: 255 }.into_iter(); + assert_eq!(iter.next(), Some(252)); + assert_eq!(iter.next(), Some(253)); + assert_eq!(iter.next(), Some(254)); + assert_eq!(iter.next(), Some(255)); + assert_eq!(iter.next(), None); + assert_eq!(iter.next(), None); + + let mut iter = RangeInclusive:: { start: 252, last: 255 }.into_iter(); + assert_eq!(iter.next_back(), Some(255)); + assert_eq!(iter.next_back(), Some(254)); + assert_eq!(iter.next_back(), Some(253)); + assert_eq!(iter.next_back(), Some(252)); + assert_eq!(iter.next_back(), None); + assert_eq!(iter.next_back(), None); + + let mut iter = RangeInclusive:: { start: 253, last: 255 }.into_iter(); + assert_eq!(iter.next(), Some(253)); + assert_eq!(iter.next_back(), Some(255)); + assert_eq!(iter.next(), Some(254)); + assert_eq!(iter.next_back(), None); + assert_eq!(iter.next(), None); +} + +#[test] +fn test_range_inclusive_iter_folds() { + let iter = RangeInclusive:: { start: 53, last: 55 }.into_iter(); + let mut vec = Vec::new(); + let count = iter.fold(0, |i, x| { + vec.push((i, x)); + i + 1 + }); + assert_eq!(count, 3); + assert_eq!(vec, [(0, 53), (1, 54), (2, 55)]); + + let iter = RangeInclusive:: { start: 253, last: 255 }.into_iter(); + let mut vec = Vec::new(); + let count = iter.fold(0, |i, x| { + vec.push((i, x)); + i + 1 + }); + assert_eq!(count, 3); + assert_eq!(vec, [(0, 253), (1, 254), (2, 255)]); + + let iter = RangeInclusive:: { start: 53, last: 55 }.into_iter(); + let mut vec = Vec::new(); + let count = iter.rfold(0, |i, x| { + vec.push((i, x)); + i + 1 + }); + assert_eq!(count, 3); + assert_eq!(vec, [(0, 55), (1, 54), (2, 53)]); + + let iter = RangeInclusive:: { start: 253, last: 255 }.into_iter(); + let mut vec = Vec::new(); + let count = iter.rfold(0, |i, x| { + vec.push((i, x)); + i + 1 + }); + assert_eq!(count, 3); + assert_eq!(vec, [(0, 255), (1, 254), (2, 253)]); +} + +#[test] +fn test_range_inclusive_iter_try_resumption() { + let mut iter = RangeInclusive:: { start: 53, last: 55 }.into_iter(); + let mut n = || iter.try_for_each(ControlFlow::Break).break_value(); + assert_eq!(n(), Some(53)); + assert_eq!(n(), Some(54)); + assert_eq!(n(), Some(55)); + assert_eq!(n(), None); + assert_eq!(n(), None); + + let mut iter = RangeInclusive:: { start: 253, last: 255 }.into_iter(); + let mut n = || iter.try_for_each(ControlFlow::Break).break_value(); + assert_eq!(n(), Some(253)); + assert_eq!(n(), Some(254)); + assert_eq!(n(), Some(255)); + assert_eq!(n(), None); + assert_eq!(n(), None); + + let mut iter = RangeInclusive:: { start: 53, last: 55 }.into_iter().rev(); + let mut n = || iter.try_for_each(ControlFlow::Break).break_value(); + assert_eq!(n(), Some(55)); + assert_eq!(n(), Some(54)); + assert_eq!(n(), Some(53)); + assert_eq!(n(), None); + assert_eq!(n(), None); + + let mut iter = RangeInclusive:: { start: 253, last: 255 }.into_iter().rev(); + let mut n = || iter.try_for_each(ControlFlow::Break).break_value(); + assert_eq!(n(), Some(255)); + assert_eq!(n(), Some(254)); + assert_eq!(n(), Some(253)); + assert_eq!(n(), None); + assert_eq!(n(), None); +} + +#[test] +fn test_range_inclusive_iter_advance() { + // The Debug format is *not* a stable guarantee, but is convenient for internal tests. + + let in_middle = || RangeInclusive:: { start: 10, last: 12 }.into_iter(); + assert_eq!(format!("{:?}", in_middle()), "RangeInclusiveIter(10..13)"); + let at_end = || RangeInclusive:: { start: 125, last: 127 }.into_iter(); + assert_eq!(format!("{:?}", at_end()), "RangeInclusiveIter(125..=127)"); + + let mut iter = in_middle(); + assert_eq!(iter.advance_by(2), Ok(())); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(12..13)"); + assert_eq!(iter.advance_by(2), Err(NonZero::new(1).unwrap())); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(13..13)"); + + let mut iter = at_end(); + assert_eq!(iter.advance_by(2), Ok(())); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(127..=127)"); + assert_eq!(iter.advance_by(2), Err(NonZero::new(1).unwrap())); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(127..127)"); + + let mut iter = in_middle(); + assert_eq!(iter.advance_back_by(2), Ok(())); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(10..11)"); + assert_eq!(iter.advance_back_by(2), Err(NonZero::new(1).unwrap())); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(10..10)"); + + let mut iter = at_end(); + assert_eq!(iter.advance_back_by(2), Ok(())); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(125..126)"); + assert_eq!(iter.advance_back_by(2), Err(NonZero::new(1).unwrap())); + assert_eq!(format!("{iter:?}"), "RangeInclusiveIter(125..125)"); +} + +#[test] +fn test_range_inclusive_iter_empty_and_remainder() { + let values = [u8::MIN, u8::MIN + 1, 6, 7, u8::MAX - 1, u8::MAX]; + for start in values { + for last in values { + let iter = RangeInclusive { start, last }.into_iter(); + let non_empty = start <= last; + let expected_remainder = non_empty.then_some(RangeInclusive { start, last }); + assert_eq!(iter.is_empty(), !non_empty); + assert_eq!(iter.remainder(), expected_remainder); + } + } +} + +/// A type that's a valid `Step` but isn't `Ord` +#[derive_const(Clone, PartialEq)] +#[derive(Debug)] +enum NotOrd { + A(u8), + B(usize), +} +const impl core::cmp::PartialOrd for NotOrd { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (NotOrd::A(left), NotOrd::A(right)) => Some(Ord::cmp(left, right)), + (NotOrd::B(left), NotOrd::B(right)) => Some(Ord::cmp(left, right)), + _ => None, + } + } +} +const impl Step for NotOrd { + fn steps_between(_start: &Self, _end: &Self) -> (usize, Option) { + // I guess? + (0, None) + } + fn forward_checked(start: Self, count: usize) -> Option { + match start { + NotOrd::A(v) => { + let Ok(count) = count.try_into() else { return None }; + v.checked_add(count).map(NotOrd::A) + } + NotOrd::B(v) => v.checked_add(count).map(NotOrd::B), + } + } + fn forward_overflowing(_start: Self, _count: usize) -> (Self, bool) { + panic("todo") + } + fn backward_checked(start: Self, count: usize) -> Option { + match start { + NotOrd::A(v) => { + let Ok(count) = count.try_into() else { return None }; + v.checked_sub(count).map(NotOrd::A) + } + NotOrd::B(v) => v.checked_sub(count).map(NotOrd::B), + } + } + fn backward_overflowing(_start: Self, _count: usize) -> (Self, bool) { + panic("todo") + } +} diff --git a/tests/codegen-llvm/lib-optimizations/new-range-iters.rs b/tests/codegen-llvm/lib-optimizations/new-range-iters.rs new file mode 100644 index 0000000000000..598f96d3d5924 --- /dev/null +++ b/tests/codegen-llvm/lib-optimizations/new-range-iters.rs @@ -0,0 +1,65 @@ +//@ compile-flags: -O +//@ ignore-std-debug-assertions +//@ only-64bit +#![crate_type = "lib"] +#![feature(exact_size_is_empty)] + +use std::range::{RangeInclusive, RangeInclusiveIter}; + +// Check that a for loop over the new `..=` optimizes to the obvious loop +#[no_mangle] +pub fn every_fencepost(slice: &[u8]) { + // CHECK-LABEL: @every_fencepost + + // CHECK: start: + // CHECK-NEXT: br label %[[LOOP:.+$]] + + // CHECK: [[LOOP]]: + // CHECK-NEXT: [[I:%.+]] = phi i64 [ 0, %start ], [ [[NEXT_I:%.+]], %[[LOOP]] ] + // CHECK-NEXT: [[NEXT_I]] = add nuw i64 [[I]], 1 + // CHECK-NEXT: call void @do_something(i64{{.*}} [[I]]) + // CHECK-NEXT: [[DONE:%.+]] = icmp eq i64 [[I]], %slice.1 + // CHECK-NEXT: br i1 [[DONE]], label %[[EXIT:.+]], label %[[LOOP]] + + // CHECK: [[EXIT]]: + // CHECK-NEXT: ret void + + for i in (RangeInclusive { start: 0, last: slice.len() }) { + do_something(i) + } +} + +unsafe extern "Rust" { + safe fn do_something(_: usize); +} + +// Ensure that, despite the pre-processing done in `into_iter`, simple things +// still optimize down to simple operations. +#[no_mangle] +pub fn make_ord_iter_check_empty(first: u8, last: u8) -> bool { + // CHECK-LABEL: @make_ord_iter_check_empty + // CHECK: [[RET:%.+]] = icmp ugt i8 %first, %last + // CHECK: ret i1 [[RET]] + RangeInclusive { start: first, last }.into_iter().is_empty() +} + +// Ensure that for an `Ord` type (here `u64`) there's only one check needed for this. +// AKA that the second check (needed for `PartialOrd`-only things) is optimized out. +#[no_mangle] +pub fn make_ord_iter(first: u64, last: u64) -> RangeInclusiveIter { + // CHECK-LABEL: @make_ord_iter + // CHECK: start: + // CHECK-NEXT: [[NEEDS_EXCLUSIVE:%.+]] = icmp eq i64 %last, -1 + // CHECK-NEXT: [[LAST_P1:%.+]] = add nuw i64 %last, 1 + // CHECK-NEXT: [[END:%.+]] = select i1 [[NEEDS_EXCLUSIVE]], i64 -1, i64 [[LAST_P1]] + // CHECK-NEXT: [[IS_EXCLUSIVE:%.+]] = zext i1 [[NEEDS_EXCLUSIVE]] to i8 + // CHECK-NOT: store + // CHECK: store i64 %first, + // CHECK-NOT: store + // CHECK: store i64 [[END]], + // CHECK-NOT: store + // CHECK: store i8 [[IS_EXCLUSIVE]], + // CHECK-NOT: store + // CHECK: ret void + RangeInclusive { start: first, last }.into_iter() +}