Skip to main content

deed_diagnostics/
span.rs

1//! Byte ranges into a single source file.
2//!
3//! Spans are file relative. The owning [`FileId`](crate::FileId) lives on the
4//! diagnostic, not on every span, because spans are copied constantly and
5//! diagnostics are not.
6
7/// A half open byte range `[start, end)` into one source file.
8#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
9pub struct Span {
10    pub start: u32,
11    pub end: u32,
12}
13
14impl Span {
15    /// Creates a span covering `[start, end)`.
16    ///
17    /// # Panics
18    ///
19    /// Panics if `end < start`. A reversed span is always a bug in the caller
20    /// rather than something to recover from.
21    pub fn new(start: u32, end: u32) -> Self {
22        assert!(end >= start, "span end {end} precedes start {start}");
23        Self { start, end }
24    }
25
26    /// An empty span at `offset`, used to point between two characters.
27    pub fn at(offset: u32) -> Self {
28        Self {
29            start: offset,
30            end: offset,
31        }
32    }
33
34    pub fn is_empty(self) -> bool {
35        self.start == self.end
36    }
37
38    /// The smallest span covering both `self` and `other`.
39    pub fn to(self, other: Span) -> Span {
40        Span {
41            start: self.start.min(other.start),
42            end: self.end.max(other.end),
43        }
44    }
45
46    pub fn contains(self, offset: u32) -> bool {
47        offset >= self.start && offset < self.end
48    }
49
50    pub fn contains_span(self, other: Span) -> bool {
51        other.start >= self.start && other.end <= self.end
52    }
53
54    pub fn as_range(self) -> std::ops::Range<usize> {
55        self.start as usize..self.end as usize
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::Span;
62
63    #[test]
64    fn union_covers_both() {
65        let a = Span::new(2, 5);
66        let b = Span::new(10, 12);
67        assert_eq!(a.to(b), Span::new(2, 12));
68        assert_eq!(b.to(a), Span::new(2, 12));
69    }
70
71    #[test]
72    fn empty_span_contains_nothing() {
73        let s = Span::at(4);
74        assert!(s.is_empty());
75        assert!(!s.contains(4));
76    }
77
78    #[test]
79    fn a_span_contains_itself_and_spans_strictly_inside_it() {
80        let outer = Span::new(2, 8);
81        assert!(outer.contains_span(outer));
82        assert!(outer.contains_span(Span::new(3, 7)));
83    }
84
85    #[test]
86    fn a_span_does_not_contain_one_reaching_past_either_edge() {
87        let outer = Span::new(2, 8);
88        assert!(!outer.contains_span(Span::new(1, 7)));
89        assert!(!outer.contains_span(Span::new(3, 9)));
90    }
91
92    #[test]
93    fn end_is_exclusive() {
94        let s = Span::new(1, 3);
95        assert!(s.contains(1));
96        assert!(s.contains(2));
97        assert!(!s.contains(3));
98    }
99}