rustc_utils/source_map/
span.rs

1use std::cmp;
2
3use log::trace;
4use rustc_middle::ty::TyCtxt;
5use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext, source_map::SourceMap};
6
7/// Extension trait for [`Span`].
8pub trait SpanExt {
9  /// Returns spans for regions in `self` not in `child_spans`.
10  ///
11  /// For example:
12  /// ```text
13  /// self:          ---------------
14  /// child_spans:    ---      --  ---
15  /// output:        -   ------  --
16  /// ```
17  fn subtract(&self, child_spans: Vec<Span>) -> Vec<Span>;
18
19  /// Returns the version of this span that is local to the current
20  /// crate, and must be contained in `outer_span`.
21  fn as_local(&self, outer_span: Span) -> Option<Span>;
22
23  /// Returns true if `self` overlaps with `other` including boundaries.
24  fn overlaps_inclusive(&self, other: Span) -> bool;
25
26  /// Returns a new span whose end is no later than the start of `other`,
27  /// returning `None` if this would return an empty span.
28  fn trim_end(&self, other: Span) -> Option<Span>;
29
30  /// Merges all overlapping spans in the input vector into single spans.
31  fn merge_overlaps(spans: Vec<Span>) -> Vec<Span>;
32
33  /// Returns a collection of spans inside `self` that have leading whitespace removed.
34  ///
35  /// Returns `None` if [`SourceMap::span_to_snippet`] fails.
36  fn trim_leading_whitespace(&self, source_map: &SourceMap) -> Option<Vec<Span>>;
37
38  /// Returns a pretty debug representation of a span.
39  fn to_string(&self, tcx: TyCtxt<'_>) -> String;
40
41  /// Returns the size (in bytes) of the spanned text.
42  fn size(&self) -> u32;
43}
44
45impl SpanExt for Span {
46  fn trim_end(&self, other: Span) -> Option<Span> {
47    let span = self.data();
48    let other = other.data();
49    if span.lo < other.lo {
50      Some(span.with_hi(cmp::min(span.hi, other.lo)))
51    } else {
52      None
53    }
54  }
55
56  fn subtract(&self, mut child_spans: Vec<Span>) -> Vec<Span> {
57    child_spans.retain(|s| s.overlaps_inclusive(*self));
58
59    let mut outer_spans = vec![];
60    if !child_spans.is_empty() {
61      // Output will be sorted
62      child_spans = Span::merge_overlaps(child_spans);
63
64      if let Some(start) = self.trim_end(*child_spans.first().unwrap()) {
65        outer_spans.push(start);
66      }
67
68      for children in child_spans.windows(2) {
69        outer_spans.push(children[0].between(children[1]));
70      }
71
72      if let Some(end) = self.trim_start(*child_spans.last().unwrap()) {
73        outer_spans.push(end);
74      }
75    } else {
76      outer_spans.push(*self);
77    }
78
79    trace!("outer span for {self:?} with inner spans {child_spans:?} is {outer_spans:?}");
80
81    outer_spans
82  }
83
84  fn as_local(&self, outer_span: Span) -> Option<Span> {
85    // Before we call source_callsite, we check and see if the span is already local.
86    // This is important b/c in print!("{}", y) if the user selects `y`, the source_callsite
87    // of that span is the entire macro.
88    if outer_span.contains(*self) {
89      return Some(*self);
90    }
91
92    let sp = self.source_callsite();
93    if outer_span.contains(sp) {
94      return Some(sp);
95    }
96
97    None
98  }
99
100  fn overlaps_inclusive(&self, other: Span) -> bool {
101    let s1 = self.data();
102    let s2 = other.data();
103    s1.lo <= s2.hi && s2.lo <= s1.hi
104  }
105
106  fn merge_overlaps(mut spans: Vec<Span>) -> Vec<Span> {
107    spans.sort_by_key(|s| (s.lo(), s.hi()));
108
109    // See note in Span::subtract
110    for span in &mut spans {
111      *span = span.with_ctxt(SyntaxContext::root());
112    }
113
114    let mut output = Vec::new();
115    for span in spans {
116      match output
117        .iter_mut()
118        .find(|other| span.overlaps_inclusive(**other))
119      {
120        Some(other) => {
121          *other = span.to(*other);
122        }
123        None => {
124          output.push(span);
125        }
126      }
127    }
128    output
129  }
130
131  fn to_string(&self, tcx: TyCtxt<'_>) -> String {
132    let source_map = tcx.sess.source_map();
133    let lo = source_map.lookup_char_pos(self.lo());
134    let hi = source_map.lookup_char_pos(self.hi());
135    let snippet = source_map.span_to_snippet(*self).unwrap();
136    format!(
137      "{snippet} ({}:{}-{}:{})",
138      lo.line,
139      lo.col.to_usize() + 1,
140      hi.line,
141      hi.col.to_usize() + 1
142    )
143  }
144
145  fn size(&self) -> u32 {
146    self.hi().0 - self.lo().0
147  }
148
149  fn trim_leading_whitespace(&self, source_map: &SourceMap) -> Option<Vec<Span>> {
150    let snippet = source_map.span_to_snippet(*self).ok()?;
151    let mut spans = Vec::new();
152    let mut start = self.lo();
153    for line in snippet.split('\n') {
154      let offset = line
155        .chars()
156        .take_while(|c| c.is_whitespace())
157        .map(char::len_utf8)
158        .sum::<usize>();
159      let end = (start + BytePos(u32::try_from(line.len()).unwrap())).min(self.hi());
160      spans.push(
161        self
162          .with_lo(start + BytePos(u32::try_from(offset).unwrap()))
163          .with_hi(end),
164      );
165      start = end + BytePos(1);
166    }
167    Some(spans)
168  }
169}
170
171/// Extension trait for [`SpanData`].
172pub trait SpanDataExt {
173  /// Returns the size (in bytes) of the spanned text.
174  fn size(&self) -> u32;
175}
176
177impl SpanDataExt for SpanData {
178  fn size(&self) -> u32 {
179    self.hi.0 - self.lo.0
180  }
181}
182
183#[cfg(test)]
184mod test {
185  use rustc_span::BytePos;
186
187  use super::*;
188
189  #[test]
190  fn test_span_subtract() {
191    rustc_span::create_default_session_globals_then(|| {
192      let mk = |lo, hi| Span::with_root_ctxt(BytePos(lo), BytePos(hi));
193      let outer = mk(1, 10);
194      let inner: Vec<Span> = vec![mk(0, 2), mk(3, 4), mk(3, 5), mk(7, 8), mk(9, 13)];
195      let desired: Vec<Span> = vec![mk(2, 3), mk(5, 7), mk(8, 9)];
196      assert_eq!(outer.subtract(inner), desired);
197    });
198  }
199}