From 0d7e9933d3cac85bc1f11dc0fec67fcad77784ca Mon Sep 17 00:00:00 2001 From: kennytm Date: Tue, 19 Jun 2018 04:08:20 +0800 Subject: [PATCH 1/5] Change RangeInclusive to a three-field struct. Fix #45222. --- src/libcore/iter/range.rs | 100 ++++++++++---------------------- src/libcore/ops/range.rs | 38 +++++++++--- src/libcore/slice/mod.rs | 20 +++---- src/libcore/str/mod.rs | 20 +++---- src/test/codegen/issue-45222.rs | 74 +++++++++++++++++++++++ 5 files changed, 155 insertions(+), 97 deletions(-) create mode 100644 src/test/codegen/issue-45222.rs diff --git a/src/libcore/iter/range.rs b/src/libcore/iter/range.rs index 0b279f66b88d6..16849e84f2754 100644 --- a/src/libcore/iter/range.rs +++ b/src/libcore/iter/range.rs @@ -10,7 +10,7 @@ use convert::TryFrom; use mem; -use ops::{self, Add, Sub, Try}; +use ops::{self, Add, Sub}; use usize; use super::{FusedIterator, TrustedLen}; @@ -330,23 +330,23 @@ impl Iterator for ops::RangeInclusive { #[inline] fn next(&mut self) -> Option { - if self.start <= self.end { - if self.start < self.end { - let n = self.start.add_one(); - Some(mem::replace(&mut self.start, n)) - } else { - let last = self.start.replace_one(); - self.end.replace_zero(); - Some(last) - } + if self.is_empty() { + self.is_iterating = Some(false); + return None; + } + if self.start < self.end { + let n = self.start.add_one(); + self.is_iterating = Some(true); + Some(mem::replace(&mut self.start, n)) } else { - None + self.is_iterating = Some(false); + Some(self.start.clone()) } } #[inline] fn size_hint(&self) -> (usize, Option) { - if !(self.start <= self.end) { + if self.is_empty() { return (0, Some(0)); } @@ -358,25 +358,29 @@ impl Iterator for ops::RangeInclusive { #[inline] fn nth(&mut self, n: usize) -> Option { + if self.is_empty() { + self.is_iterating = Some(false); + return None; + } + if let Some(plus_n) = self.start.add_usize(n) { use cmp::Ordering::*; match plus_n.partial_cmp(&self.end) { Some(Less) => { + self.is_iterating = Some(true); self.start = plus_n.add_one(); return Some(plus_n) } Some(Equal) => { - self.start.replace_one(); - self.end.replace_zero(); + self.is_iterating = Some(false); return Some(plus_n) } _ => {} } } - self.start.replace_one(); - self.end.replace_zero(); + self.is_iterating = Some(false); None } @@ -394,68 +398,24 @@ impl Iterator for ops::RangeInclusive { fn max(mut self) -> Option { self.next_back() } - - #[inline] - fn try_fold(&mut self, init: B, mut f: F) -> R where - Self: Sized, F: FnMut(B, Self::Item) -> R, R: Try - { - let mut accum = init; - if self.start <= self.end { - loop { - let (x, done) = - if self.start < self.end { - let n = self.start.add_one(); - (mem::replace(&mut self.start, n), false) - } else { - self.end.replace_zero(); - (self.start.replace_one(), true) - }; - accum = f(accum, x)?; - if done { break } - } - } - Try::from_ok(accum) - } } #[stable(feature = "inclusive_range", since = "1.26.0")] impl DoubleEndedIterator for ops::RangeInclusive { #[inline] fn next_back(&mut self) -> Option { - if self.start <= self.end { - if self.start < self.end { - let n = self.end.sub_one(); - Some(mem::replace(&mut self.end, n)) - } else { - let last = self.end.replace_zero(); - self.start.replace_one(); - Some(last) - } - } else { - None + if self.is_empty() { + self.is_iterating = Some(false); + return None; } - } - - #[inline] - fn try_rfold(&mut self, init: B, mut f: F) -> R where - Self: Sized, F: FnMut(B, Self::Item) -> R, R: Try - { - let mut accum = init; - if self.start <= self.end { - loop { - let (x, done) = - if self.start < self.end { - let n = self.end.sub_one(); - (mem::replace(&mut self.end, n), false) - } else { - self.start.replace_one(); - (self.end.replace_zero(), true) - }; - accum = f(accum, x)?; - if done { break } - } + if self.start < self.end { + let n = self.end.sub_one(); + self.is_iterating = Some(true); + Some(mem::replace(&mut self.end, n)) + } else { + self.is_iterating = Some(false); + Some(self.end.clone()) } - Try::from_ok(accum) } } diff --git a/src/libcore/ops/range.rs b/src/libcore/ops/range.rs index 01e279589da98..3f9ac8a54bf2f 100644 --- a/src/libcore/ops/range.rs +++ b/src/libcore/ops/range.rs @@ -9,6 +9,7 @@ // except according to those terms. use fmt; +use hash::{Hash, Hasher}; /// An unbounded range (`..`). /// @@ -326,15 +327,37 @@ impl> RangeTo { /// assert_eq!(arr[1..=2], [ 1,2 ]); // RangeInclusive /// ``` #[doc(alias = "..=")] -#[derive(Clone, PartialEq, Eq, Hash)] // not Copy -- see #27186 +#[derive(Clone)] // not Copy -- see #27186 #[stable(feature = "inclusive_range", since = "1.26.0")] pub struct RangeInclusive { - // FIXME: The current representation follows RFC 1980, - // but it is known that LLVM is not able to optimize loops following that RFC. - // Consider adding an extra `bool` field to indicate emptiness of the range. - // See #45222 for performance test cases. pub(crate) start: Idx, pub(crate) end: Idx, + pub(crate) is_iterating: Option, + // This field is: + // - `None` when next() or next_back() was never called + // - `Some(true)` when `start <= end` assuming no overflow + // - `Some(false)` otherwise + // The field cannot be a simple `bool` because the `..=` constructor can + // accept non-PartialOrd types, also we want the constructor to be const. +} + +#[stable(feature = "inclusive_range", since = "1.26.0")] +impl PartialEq for RangeInclusive { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.start == other.start && self.end == other.end + } +} + +#[stable(feature = "inclusive_range", since = "1.26.0")] +impl Eq for RangeInclusive {} + +#[stable(feature = "inclusive_range", since = "1.26.0")] +impl Hash for RangeInclusive { + fn hash(&self, state: &mut H) { + self.start.hash(state); + self.end.hash(state); + } } impl RangeInclusive { @@ -350,7 +373,7 @@ impl RangeInclusive { #[stable(feature = "inclusive_range_methods", since = "1.27.0")] #[inline] pub const fn new(start: Idx, end: Idx) -> Self { - Self { start, end } + Self { start, end, is_iterating: None } } /// Returns the lower bound of the range (inclusive). @@ -492,8 +515,9 @@ impl> RangeInclusive { /// assert!(r.is_empty()); /// ``` #[unstable(feature = "range_is_empty", reason = "recently added", issue = "48111")] + #[inline] pub fn is_empty(&self) -> bool { - !(self.start <= self.end) + !self.is_iterating.unwrap_or_else(|| self.start <= self.end) } } diff --git a/src/libcore/slice/mod.rs b/src/libcore/slice/mod.rs index ed29d80cb903c..e6db4cb38ec23 100644 --- a/src/libcore/slice/mod.rs +++ b/src/libcore/slice/mod.rs @@ -2262,36 +2262,36 @@ impl SliceIndex<[T]> for ops::RangeInclusive { #[inline] fn get(self, slice: &[T]) -> Option<&[T]> { - if self.end == usize::max_value() { None } - else { (self.start..self.end + 1).get(slice) } + if *self.end() == usize::max_value() { None } + else { (*self.start()..self.end() + 1).get(slice) } } #[inline] fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> { - if self.end == usize::max_value() { None } - else { (self.start..self.end + 1).get_mut(slice) } + if *self.end() == usize::max_value() { None } + else { (*self.start()..self.end() + 1).get_mut(slice) } } #[inline] unsafe fn get_unchecked(self, slice: &[T]) -> &[T] { - (self.start..self.end + 1).get_unchecked(slice) + (*self.start()..self.end() + 1).get_unchecked(slice) } #[inline] unsafe fn get_unchecked_mut(self, slice: &mut [T]) -> &mut [T] { - (self.start..self.end + 1).get_unchecked_mut(slice) + (*self.start()..self.end() + 1).get_unchecked_mut(slice) } #[inline] fn index(self, slice: &[T]) -> &[T] { - if self.end == usize::max_value() { slice_index_overflow_fail(); } - (self.start..self.end + 1).index(slice) + if *self.end() == usize::max_value() { slice_index_overflow_fail(); } + (*self.start()..self.end() + 1).index(slice) } #[inline] fn index_mut(self, slice: &mut [T]) -> &mut [T] { - if self.end == usize::max_value() { slice_index_overflow_fail(); } - (self.start..self.end + 1).index_mut(slice) + if *self.end() == usize::max_value() { slice_index_overflow_fail(); } + (*self.start()..self.end() + 1).index_mut(slice) } } diff --git a/src/libcore/str/mod.rs b/src/libcore/str/mod.rs index 5ae2f6349e5b7..255e8a07d7549 100644 --- a/src/libcore/str/mod.rs +++ b/src/libcore/str/mod.rs @@ -2004,31 +2004,31 @@ mod traits { type Output = str; #[inline] fn get(self, slice: &str) -> Option<&Self::Output> { - if self.end == usize::max_value() { None } - else { (self.start..self.end+1).get(slice) } + if *self.end() == usize::max_value() { None } + else { (*self.start()..self.end()+1).get(slice) } } #[inline] fn get_mut(self, slice: &mut str) -> Option<&mut Self::Output> { - if self.end == usize::max_value() { None } - else { (self.start..self.end+1).get_mut(slice) } + if *self.end() == usize::max_value() { None } + else { (*self.start()..self.end()+1).get_mut(slice) } } #[inline] unsafe fn get_unchecked(self, slice: &str) -> &Self::Output { - (self.start..self.end+1).get_unchecked(slice) + (*self.start()..self.end()+1).get_unchecked(slice) } #[inline] unsafe fn get_unchecked_mut(self, slice: &mut str) -> &mut Self::Output { - (self.start..self.end+1).get_unchecked_mut(slice) + (*self.start()..self.end()+1).get_unchecked_mut(slice) } #[inline] fn index(self, slice: &str) -> &Self::Output { - if self.end == usize::max_value() { str_index_overflow_fail(); } - (self.start..self.end+1).index(slice) + if *self.end() == usize::max_value() { str_index_overflow_fail(); } + (*self.start()..self.end()+1).index(slice) } #[inline] fn index_mut(self, slice: &mut str) -> &mut Self::Output { - if self.end == usize::max_value() { str_index_overflow_fail(); } - (self.start..self.end+1).index_mut(slice) + if *self.end() == usize::max_value() { str_index_overflow_fail(); } + (*self.start()..self.end()+1).index_mut(slice) } } diff --git a/src/test/codegen/issue-45222.rs b/src/test/codegen/issue-45222.rs new file mode 100644 index 0000000000000..30a03243f0151 --- /dev/null +++ b/src/test/codegen/issue-45222.rs @@ -0,0 +1,74 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -O +// min-llvm-version 6.0 + +#![crate_type = "lib"] + +// verify that LLVM recognizes a loop involving 0..=n and will const-fold it. + +//------------------------------------------------------------------------------ +// Example from original issue #45222 + +fn foo2(n: u64) -> u64 { + let mut count = 0; + for _ in 0..n { + for j in (0..=n).rev() { + count += j; + } + } + count +} + +// CHECK-LABEL: @check_foo2 +#[no_mangle] +pub fn check_foo2() -> u64 { + // CHECK: ret i64 500005000000000 + foo2(100000) +} + +//------------------------------------------------------------------------------ +// Simplified example of #45222 + +fn triangle_inc(n: u64) -> u64 { + let mut count = 0; + for j in 0 ..= n { + count += j; + } + count +} + +// CHECK-LABEL: @check_triangle_inc +#[no_mangle] +pub fn check_triangle_inc() -> u64 { + // CHECK: ret i64 5000050000 + triangle_inc(100000) +} + +//------------------------------------------------------------------------------ +// Demo in #48012 + +fn foo3r(n: u64) -> u64 { + let mut count = 0; + (0..n).for_each(|_| { + (0 ..= n).rev().for_each(|j| { + count += j; + }) + }); + count +} + +// CHECK-LABEL: @check_foo3r +#[no_mangle] +pub fn check_foo3r() -> u64 { + // CHECK: ret i64 500005000000000 + foo3r(100000) +} From d299f03e285f0ea25c4cce8913368fd7500f74ff Mon Sep 17 00:00:00 2001 From: kennytm Date: Tue, 19 Jun 2018 16:56:18 +0800 Subject: [PATCH 2/5] Fix some RangeInclusive test cases. --- src/test/run-pass/range_inclusive.rs | 29 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/test/run-pass/range_inclusive.rs b/src/test/run-pass/range_inclusive.rs index b08d16e5088d7..2bedfc133b581 100644 --- a/src/test/run-pass/range_inclusive.rs +++ b/src/test/run-pass/range_inclusive.rs @@ -10,12 +10,18 @@ // Test inclusive range syntax. -use std::ops::{RangeInclusive, RangeToInclusive}; +#![feature(range_is_empty)] +#![allow(unused_comparisons)] + +use std::ops::RangeToInclusive; fn foo() -> isize { 42 } // Test that range syntax works in return statements -fn return_range_to() -> RangeToInclusive { return ..=1; } +pub fn return_range_to() -> RangeToInclusive { return ..=1; } + +#[derive(Debug)] +struct P(u8); pub fn main() { let mut count = 0; @@ -26,7 +32,7 @@ pub fn main() { assert_eq!(count, 55); let mut count = 0; - let mut range = 0_usize..=10; + let range = 0_usize..=10; for i in range { assert!(i >= 0 && i <= 10); count += i; @@ -80,7 +86,7 @@ pub fn main() { short.next(); assert_eq!(long.size_hint(), (255, Some(255))); assert_eq!(short.size_hint(), (0, Some(0))); - assert_eq!(short, 1..=0); + assert!(short.is_empty()); assert_eq!(long.len(), 255); assert_eq!(short.len(), 0); @@ -95,28 +101,31 @@ pub fn main() { for i in 3..=251 { assert_eq!(long.next(), Some(i)); } - assert_eq!(long, 1..=0); + assert!(long.is_empty()); // check underflow let mut narrow = 1..=0; assert_eq!(narrow.next_back(), None); - assert_eq!(narrow, 1..=0); + assert!(narrow.is_empty()); let mut zero = 0u8..=0; assert_eq!(zero.next_back(), Some(0)); assert_eq!(zero.next_back(), None); - assert_eq!(zero, 1..=0); + assert!(zero.is_empty()); let mut high = 255u8..=255; assert_eq!(high.next_back(), Some(255)); assert_eq!(high.next_back(), None); - assert_eq!(high, 1..=0); + assert!(high.is_empty()); // what happens if you have a nonsense range? let mut nonsense = 10..=5; assert_eq!(nonsense.next(), None); - assert_eq!(nonsense, 10..=5); + assert!(nonsense.is_empty()); // output assert_eq!(format!("{:?}", 0..=10), "0..=10"); assert_eq!(format!("{:?}", ..=10), "..=10"); - assert_eq!(format!("{:?}", long), "1..=0"); + assert_eq!(format!("{:?}", 9..=6), "9..=6"); + + // ensure that constructing a RangeInclusive does not need PartialOrd bound + assert_eq!(format!("{:?}", P(1)..=P(2)), "P(1)..=P(2)"); } From b6ea93e46410cccf8d115e57283d1df5968dd0f2 Mon Sep 17 00:00:00 2001 From: kennytm Date: Sat, 23 Jun 2018 01:42:29 +0800 Subject: [PATCH 3/5] Upgrade implementation of StepBy>. --- src/libcore/iter/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libcore/iter/mod.rs b/src/libcore/iter/mod.rs index 86b297557dddb..32134783516db 100644 --- a/src/libcore/iter/mod.rs +++ b/src/libcore/iter/mod.rs @@ -787,17 +787,19 @@ where #[inline] fn spec_next(&mut self) -> Option { self.first_take = false; - if !(self.iter.start <= self.iter.end) { + if self.iter.is_empty() { + self.iter.is_iterating = Some(false); return None; } // add 1 to self.step to get original step size back // it was decremented for the general case on construction if let Some(n) = self.iter.start.add_usize(self.step+1) { + self.iter.is_iterating = Some(n <= self.iter.end); let next = mem::replace(&mut self.iter.start, n); Some(next) } else { - let last = self.iter.start.replace_one(); - self.iter.end.replace_zero(); + let last = self.iter.start.clone(); + self.iter.is_iterating = Some(false); Some(last) } } From 6e0dd9ec0362af41996cf2d2a0afd520bf873d3a Mon Sep 17 00:00:00 2001 From: kennytm Date: Sat, 30 Jun 2018 17:13:21 +0800 Subject: [PATCH 4/5] Include is_empty() in PartialEq and Hash. When the index is not PartialOrd, always treat the range as empty. --- src/libcore/ops/range.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/libcore/ops/range.rs b/src/libcore/ops/range.rs index 3f9ac8a54bf2f..0f119789a75f8 100644 --- a/src/libcore/ops/range.rs +++ b/src/libcore/ops/range.rs @@ -341,11 +341,29 @@ pub struct RangeInclusive { // accept non-PartialOrd types, also we want the constructor to be const. } +trait RangeInclusiveEquality: Sized { + fn canonicalized_is_empty(range: &RangeInclusive) -> bool; +} +impl RangeInclusiveEquality for T { + #[inline] + default fn canonicalized_is_empty(range: &RangeInclusive) -> bool { + !range.is_iterating.unwrap_or(false) + } +} +impl RangeInclusiveEquality for T { + #[inline] + fn canonicalized_is_empty(range: &RangeInclusive) -> bool { + range.is_empty() + } +} + #[stable(feature = "inclusive_range", since = "1.26.0")] impl PartialEq for RangeInclusive { #[inline] fn eq(&self, other: &Self) -> bool { self.start == other.start && self.end == other.end + && RangeInclusiveEquality::canonicalized_is_empty(self) + == RangeInclusiveEquality::canonicalized_is_empty(other) } } @@ -357,6 +375,7 @@ impl Hash for RangeInclusive { fn hash(&self, state: &mut H) { self.start.hash(state); self.end.hash(state); + RangeInclusiveEquality::canonicalized_is_empty(self).hash(state); } } From 6093128ef3c5ae661ec66fbf3685833d6be217bb Mon Sep 17 00:00:00 2001 From: kennytm Date: Fri, 13 Jul 2018 13:08:28 +0800 Subject: [PATCH 5/5] Changed implementation of the third field to make LLVM optimize it better. --- src/libcore/iter/mod.rs | 8 ++++---- src/libcore/iter/range.rs | 42 +++++++++++++++++++-------------------- src/libcore/ops/range.rs | 20 +++++++++++++------ 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/libcore/iter/mod.rs b/src/libcore/iter/mod.rs index 32134783516db..35ae77411069c 100644 --- a/src/libcore/iter/mod.rs +++ b/src/libcore/iter/mod.rs @@ -787,19 +787,19 @@ where #[inline] fn spec_next(&mut self) -> Option { self.first_take = false; - if self.iter.is_empty() { - self.iter.is_iterating = Some(false); + self.iter.compute_is_empty(); + if self.iter.is_empty.unwrap_or_default() { return None; } // add 1 to self.step to get original step size back // it was decremented for the general case on construction if let Some(n) = self.iter.start.add_usize(self.step+1) { - self.iter.is_iterating = Some(n <= self.iter.end); + self.iter.is_empty = Some(!(n <= self.iter.end)); let next = mem::replace(&mut self.iter.start, n); Some(next) } else { let last = self.iter.start.clone(); - self.iter.is_iterating = Some(false); + self.iter.is_empty = Some(true); Some(last) } } diff --git a/src/libcore/iter/range.rs b/src/libcore/iter/range.rs index 16849e84f2754..651c7a35d413c 100644 --- a/src/libcore/iter/range.rs +++ b/src/libcore/iter/range.rs @@ -330,18 +330,18 @@ impl Iterator for ops::RangeInclusive { #[inline] fn next(&mut self) -> Option { - if self.is_empty() { - self.is_iterating = Some(false); + self.compute_is_empty(); + if self.is_empty.unwrap_or_default() { return None; } - if self.start < self.end { + let is_iterating = self.start < self.end; + self.is_empty = Some(!is_iterating); + Some(if is_iterating { let n = self.start.add_one(); - self.is_iterating = Some(true); - Some(mem::replace(&mut self.start, n)) + mem::replace(&mut self.start, n) } else { - self.is_iterating = Some(false); - Some(self.start.clone()) - } + self.start.clone() + }) } #[inline] @@ -358,8 +358,8 @@ impl Iterator for ops::RangeInclusive { #[inline] fn nth(&mut self, n: usize) -> Option { - if self.is_empty() { - self.is_iterating = Some(false); + self.compute_is_empty(); + if self.is_empty.unwrap_or_default() { return None; } @@ -368,19 +368,19 @@ impl Iterator for ops::RangeInclusive { match plus_n.partial_cmp(&self.end) { Some(Less) => { - self.is_iterating = Some(true); + self.is_empty = Some(false); self.start = plus_n.add_one(); return Some(plus_n) } Some(Equal) => { - self.is_iterating = Some(false); + self.is_empty = Some(true); return Some(plus_n) } _ => {} } } - self.is_iterating = Some(false); + self.is_empty = Some(true); None } @@ -404,18 +404,18 @@ impl Iterator for ops::RangeInclusive { impl DoubleEndedIterator for ops::RangeInclusive { #[inline] fn next_back(&mut self) -> Option { - if self.is_empty() { - self.is_iterating = Some(false); + self.compute_is_empty(); + if self.is_empty.unwrap_or_default() { return None; } - if self.start < self.end { + let is_iterating = self.start < self.end; + self.is_empty = Some(!is_iterating); + Some(if is_iterating { let n = self.end.sub_one(); - self.is_iterating = Some(true); - Some(mem::replace(&mut self.end, n)) + mem::replace(&mut self.end, n) } else { - self.is_iterating = Some(false); - Some(self.end.clone()) - } + self.end.clone() + }) } } diff --git a/src/libcore/ops/range.rs b/src/libcore/ops/range.rs index 0f119789a75f8..9c635678d7aa0 100644 --- a/src/libcore/ops/range.rs +++ b/src/libcore/ops/range.rs @@ -332,11 +332,11 @@ impl> RangeTo { pub struct RangeInclusive { pub(crate) start: Idx, pub(crate) end: Idx, - pub(crate) is_iterating: Option, + pub(crate) is_empty: Option, // This field is: // - `None` when next() or next_back() was never called - // - `Some(true)` when `start <= end` assuming no overflow - // - `Some(false)` otherwise + // - `Some(false)` when `start <= end` assuming no overflow + // - `Some(true)` otherwise // The field cannot be a simple `bool` because the `..=` constructor can // accept non-PartialOrd types, also we want the constructor to be const. } @@ -347,7 +347,7 @@ trait RangeInclusiveEquality: Sized { impl RangeInclusiveEquality for T { #[inline] default fn canonicalized_is_empty(range: &RangeInclusive) -> bool { - !range.is_iterating.unwrap_or(false) + range.is_empty.unwrap_or_default() } } impl RangeInclusiveEquality for T { @@ -392,7 +392,7 @@ impl RangeInclusive { #[stable(feature = "inclusive_range_methods", since = "1.27.0")] #[inline] pub const fn new(start: Idx, end: Idx) -> Self { - Self { start, end, is_iterating: None } + Self { start, end, is_empty: None } } /// Returns the lower bound of the range (inclusive). @@ -536,7 +536,15 @@ impl> RangeInclusive { #[unstable(feature = "range_is_empty", reason = "recently added", issue = "48111")] #[inline] pub fn is_empty(&self) -> bool { - !self.is_iterating.unwrap_or_else(|| self.start <= self.end) + self.is_empty.unwrap_or_else(|| !(self.start <= self.end)) + } + + // If this range's `is_empty` is field is unknown (`None`), update it to be a concrete value. + #[inline] + pub(crate) fn compute_is_empty(&mut self) { + if self.is_empty.is_none() { + self.is_empty = Some(!(self.start <= self.end)); + } } }