Skip to content

Commit cd1f821

Browse files
committed
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.
1 parent dea4c9c commit cd1f821

File tree

2 files changed

+21
-69
lines changed

2 files changed

+21
-69
lines changed

src/range.rs

Lines changed: 20 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ use {
22
crate::TextSize,
33
std::{
44
cmp,
5-
convert::{TryFrom, TryInto},
5+
convert::TryInto,
66
fmt,
7-
ops::{Bound, Index, IndexMut, Range, RangeBounds},
7+
ops::{Bound, Index, IndexMut, RangeBounds},
88
},
99
};
1010

@@ -30,15 +30,11 @@ use {
3030
/// † See the note on [`TextRange::len`] for differing behavior for incorrect reverse ranges.
3131
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
3232
pub struct TextRange {
33+
// Invariant: start <= end
3334
start: TextSize,
3435
end: TextSize,
3536
}
3637

37-
#[allow(non_snake_case)]
38-
pub(crate) const fn TextRange(start: TextSize, end: TextSize) -> TextRange {
39-
TextRange { start, end }
40-
}
41-
4238
impl fmt::Debug for TextRange {
4339
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4440
write!(f, "[{}..{})", self.start(), self.end())
@@ -47,6 +43,16 @@ impl fmt::Debug for TextRange {
4743

4844
/// Identity methods.
4945
impl TextRange {
46+
/// Creates a new `TextRange` with given `start` and `end.
47+
///
48+
/// # Panics
49+
///
50+
/// Panics if `end < start`.
51+
pub fn new(start: TextSize, end: TextSize) -> TextRange {
52+
assert!(start <= end);
53+
TextRange { start, end }
54+
}
55+
5056
/// The start point of this range.
5157
pub const fn start(self) -> TextSize {
5258
self.start
@@ -58,11 +64,6 @@ impl TextRange {
5864
}
5965

6066
/// The size of this range.
61-
///
62-
/// # Panics
63-
///
64-
/// When `end() < start()`, triggers a subtraction overflow.
65-
/// This will panic with debug assertions, and overflow without.
6667
pub const fn len(self) -> TextSize {
6768
// HACK for const fn: math on primitives only
6869
TextSize(self.end().raw - self.start().raw)
@@ -71,11 +72,10 @@ impl TextRange {
7172
/// Check if this range empty or reversed.
7273
///
7374
/// When `end() < start()`, this returns false.
74-
/// Code should prefer `is_empty()` to `len() == 0`,
75-
/// as this safeguards against incorrect reverse ranges.
75+
/// Code should prefer `is_empty()` to `len() == 0`.
7676
pub const fn is_empty(self) -> bool {
7777
// HACK for const fn: math on primitives only
78-
self.start().raw >= self.end().raw
78+
self.start().raw == self.end().raw
7979
}
8080
}
8181

@@ -91,14 +91,17 @@ impl TextRange {
9191
pub fn intersection(lhs: TextRange, rhs: TextRange) -> Option<TextRange> {
9292
let start = cmp::max(lhs.start(), rhs.start());
9393
let end = cmp::min(lhs.end(), rhs.end());
94-
Some(TextRange(start, end)).filter(|_| start <= end)
94+
if end < start {
95+
return None;
96+
}
97+
Some(TextRange::new(start, end))
9598
}
9699

97100
/// The smallest range that completely contains both ranges.
98101
pub fn covering(lhs: TextRange, rhs: TextRange) -> TextRange {
99102
let start = cmp::min(lhs.start(), rhs.start());
100103
let end = cmp::max(lhs.end(), rhs.end());
101-
TextRange(start, end)
104+
TextRange::new(start, end)
102105
}
103106

104107
/// Check if this range contains a point.
@@ -145,54 +148,3 @@ impl RangeBounds<TextSize> for TextRange {
145148
Bound::Excluded(&self.end)
146149
}
147150
}
148-
149-
macro_rules! conversions {
150-
(From<$lte:ident> for TextRange) => {
151-
impl From<Range<$lte>> for TextRange {
152-
fn from(value: Range<$lte>) -> TextRange {
153-
TextRange(value.start.into(), value.end.into())
154-
}
155-
}
156-
// Just support `start..end` for now, not `..end`, `start..=end`, `..=end`.
157-
};
158-
(TryFrom<$gt:ident> for TextRange) => {
159-
impl TryFrom<Range<$gt>> for TextRange {
160-
type Error = <$gt as TryInto<u32>>::Error;
161-
fn try_from(value: Range<$gt>) -> Result<TextRange, Self::Error> {
162-
Ok(TextRange(value.start.try_into()?, value.end.try_into()?))
163-
}
164-
}
165-
// Just support `start..end` for now, not `..end`, `start..=end`, `..=end`.
166-
};
167-
{
168-
lt TextSize [$($lt:ident)*]
169-
eq TextSize [$($eq:ident)*]
170-
gt TextSize [$($gt:ident)*]
171-
varries [$($var:ident)*]
172-
} => {
173-
$(
174-
conversions!(From<$lt> for TextRange);
175-
// unlike TextSize, we do not provide conversions in the "out" direction.
176-
)*
177-
178-
$(
179-
conversions!(From<$eq> for TextRange);
180-
)*
181-
182-
$(
183-
conversions!(TryFrom<$gt> for TextRange);
184-
)*
185-
186-
$(
187-
conversions!(TryFrom<$var> for TextRange);
188-
)*
189-
};
190-
}
191-
192-
// FIXME: when `default impl` is usable, change to blanket impls for [Try]Into<TextSize> instead
193-
conversions! {
194-
lt TextSize [u8 u16]
195-
eq TextSize [u32 TextSize]
196-
gt TextSize [u64]
197-
varries [usize]
198-
}

tests/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ fn size(x: u32) -> TextSize {
55
}
66

77
fn range(x: ops::Range<u32>) -> TextRange {
8-
TextRange::from(x)
8+
TextRange::new(x.start.into(), x.end.into())
99
}
1010

1111
#[test]

0 commit comments

Comments
 (0)