Unnamed repository; edit this file 'description' to name the repository.
Enforce invariant that ranges are well-formed
This is in contrast to `std::ops::Range`, which are not guaranteed to be proper. For this reason, we have to lose `From` impls as well.
Aleksey Kladov 2020-03-09
parent 72aef8c · commit 4166069
-rw-r--r--lib/text-size/src/range.rs88
-rw-r--r--lib/text-size/tests/main.rs2
2 files changed, 21 insertions, 69 deletions
diff --git a/lib/text-size/src/range.rs b/lib/text-size/src/range.rs
index fe227cdaff..8febcad082 100644
--- a/lib/text-size/src/range.rs
+++ b/lib/text-size/src/range.rs
@@ -2,9 +2,9 @@ use {
crate::TextSize,
std::{
cmp,
- convert::{TryFrom, TryInto},
+ convert::TryInto,
fmt,
- ops::{Bound, Index, IndexMut, Range, RangeBounds},
+ ops::{Bound, Index, IndexMut, RangeBounds},
},
};
@@ -30,15 +30,11 @@ use {
/// † See the note on [`TextRange::len`] for differing behavior for incorrect reverse ranges.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct TextRange {
+ // Invariant: start <= end
start: TextSize,
end: TextSize,
}
-#[allow(non_snake_case)]
-pub(crate) const fn TextRange(start: TextSize, end: TextSize) -> TextRange {
- TextRange { start, end }
-}
-
impl fmt::Debug for TextRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}..{})", self.start(), self.end())
@@ -47,6 +43,16 @@ impl fmt::Debug for TextRange {
/// Identity methods.
impl TextRange {
+ /// Creates a new `TextRange` with given `start` and `end.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `end < start`.
+ pub fn new(start: TextSize, end: TextSize) -> TextRange {
+ assert!(start <= end);
+ TextRange { start, end }
+ }
+
/// The start point of this range.
pub const fn start(self) -> TextSize {
self.start
@@ -58,11 +64,6 @@ impl TextRange {
}
/// The size of this range.
- ///
- /// # Panics
- ///
- /// When `end() < start()`, triggers a subtraction overflow.
- /// This will panic with debug assertions, and overflow without.
pub const fn len(self) -> TextSize {
// HACK for const fn: math on primitives only
TextSize(self.end().raw - self.start().raw)
@@ -71,11 +72,10 @@ impl TextRange {
/// Check if this range empty or reversed.
///
/// When `end() < start()`, this returns false.
- /// Code should prefer `is_empty()` to `len() == 0`,
- /// as this safeguards against incorrect reverse ranges.
+ /// Code should prefer `is_empty()` to `len() == 0`.
pub const fn is_empty(self) -> bool {
// HACK for const fn: math on primitives only
- self.start().raw >= self.end().raw
+ self.start().raw == self.end().raw
}
}
@@ -91,14 +91,17 @@ impl TextRange {
pub fn intersection(lhs: TextRange, rhs: TextRange) -> Option<TextRange> {
let start = cmp::max(lhs.start(), rhs.start());
let end = cmp::min(lhs.end(), rhs.end());
- Some(TextRange(start, end)).filter(|_| start <= end)
+ if end < start {
+ return None;
+ }
+ Some(TextRange::new(start, end))
}
/// The smallest range that completely contains both ranges.
pub fn covering(lhs: TextRange, rhs: TextRange) -> TextRange {
let start = cmp::min(lhs.start(), rhs.start());
let end = cmp::max(lhs.end(), rhs.end());
- TextRange(start, end)
+ TextRange::new(start, end)
}
/// Check if this range contains a point.
@@ -145,54 +148,3 @@ impl RangeBounds<TextSize> for TextRange {
Bound::Excluded(&self.end)
}
}
-
-macro_rules! conversions {
- (From<$lte:ident> for TextRange) => {
- impl From<Range<$lte>> for TextRange {
- fn from(value: Range<$lte>) -> TextRange {
- TextRange(value.start.into(), value.end.into())
- }
- }
- // Just support `start..end` for now, not `..end`, `start..=end`, `..=end`.
- };
- (TryFrom<$gt:ident> for TextRange) => {
- impl TryFrom<Range<$gt>> for TextRange {
- type Error = <$gt as TryInto<u32>>::Error;
- fn try_from(value: Range<$gt>) -> Result<TextRange, Self::Error> {
- Ok(TextRange(value.start.try_into()?, value.end.try_into()?))
- }
- }
- // Just support `start..end` for now, not `..end`, `start..=end`, `..=end`.
- };
- {
- lt TextSize [$($lt:ident)*]
- eq TextSize [$($eq:ident)*]
- gt TextSize [$($gt:ident)*]
- varries [$($var:ident)*]
- } => {
- $(
- conversions!(From<$lt> for TextRange);
- // unlike TextSize, we do not provide conversions in the "out" direction.
- )*
-
- $(
- conversions!(From<$eq> for TextRange);
- )*
-
- $(
- conversions!(TryFrom<$gt> for TextRange);
- )*
-
- $(
- conversions!(TryFrom<$var> for TextRange);
- )*
- };
-}
-
-// FIXME: when `default impl` is usable, change to blanket impls for [Try]Into<TextSize> instead
-conversions! {
- lt TextSize [u8 u16]
- eq TextSize [u32 TextSize]
- gt TextSize [u64]
- varries [usize]
-}
diff --git a/lib/text-size/tests/main.rs b/lib/text-size/tests/main.rs
index 3288c27320..e25f59bacf 100644
--- a/lib/text-size/tests/main.rs
+++ b/lib/text-size/tests/main.rs
@@ -5,7 +5,7 @@ fn size(x: u32) -> TextSize {
}
fn range(x: ops::Range<u32>) -> TextRange {
- TextRange::from(x)
+ TextRange::new(x.start.into(), x.end.into())
}
#[test]