rustc_utils/source_map/
span.rsuse std::cmp;
use log::trace;
use rustc_middle::ty::TyCtxt;
use rustc_span::{source_map::SourceMap, BytePos, Pos, Span, SpanData, SyntaxContext};
pub trait SpanExt {
fn subtract(&self, child_spans: Vec<Span>) -> Vec<Span>;
fn as_local(&self, outer_span: Span) -> Option<Span>;
fn overlaps_inclusive(&self, other: Span) -> bool;
fn trim_end(&self, other: Span) -> Option<Span>;
fn merge_overlaps(spans: Vec<Span>) -> Vec<Span>;
fn trim_leading_whitespace(&self, source_map: &SourceMap) -> Option<Vec<Span>>;
fn to_string(&self, tcx: TyCtxt<'_>) -> String;
fn size(&self) -> u32;
}
impl SpanExt for Span {
fn trim_end(&self, other: Span) -> Option<Span> {
let span = self.data();
let other = other.data();
if span.lo < other.lo {
Some(span.with_hi(cmp::min(span.hi, other.lo)))
} else {
None
}
}
fn subtract(&self, mut child_spans: Vec<Span>) -> Vec<Span> {
child_spans.retain(|s| s.overlaps_inclusive(*self));
let mut outer_spans = vec![];
if !child_spans.is_empty() {
child_spans = Span::merge_overlaps(child_spans);
if let Some(start) = self.trim_end(*child_spans.first().unwrap()) {
outer_spans.push(start);
}
for children in child_spans.windows(2) {
outer_spans.push(children[0].between(children[1]));
}
if let Some(end) = self.trim_start(*child_spans.last().unwrap()) {
outer_spans.push(end);
}
} else {
outer_spans.push(*self);
};
trace!("outer span for {self:?} with inner spans {child_spans:?} is {outer_spans:?}");
outer_spans
}
fn as_local(&self, outer_span: Span) -> Option<Span> {
if outer_span.contains(*self) {
return Some(*self);
}
let sp = self.source_callsite();
if outer_span.contains(sp) {
return Some(sp);
}
None
}
fn overlaps_inclusive(&self, other: Span) -> bool {
let s1 = self.data();
let s2 = other.data();
s1.lo <= s2.hi && s2.lo <= s1.hi
}
fn merge_overlaps(mut spans: Vec<Span>) -> Vec<Span> {
spans.sort_by_key(|s| (s.lo(), s.hi()));
for span in &mut spans {
*span = span.with_ctxt(SyntaxContext::root());
}
let mut output = Vec::new();
for span in spans {
match output
.iter_mut()
.find(|other| span.overlaps_inclusive(**other))
{
Some(other) => {
*other = span.to(*other);
}
None => {
output.push(span);
}
}
}
output
}
fn to_string(&self, tcx: TyCtxt<'_>) -> String {
let source_map = tcx.sess.source_map();
let lo = source_map.lookup_char_pos(self.lo());
let hi = source_map.lookup_char_pos(self.hi());
let snippet = source_map.span_to_snippet(*self).unwrap();
format!(
"{snippet} ({}:{}-{}:{})",
lo.line,
lo.col.to_usize() + 1,
hi.line,
hi.col.to_usize() + 1
)
}
fn size(&self) -> u32 {
self.hi().0 - self.lo().0
}
fn trim_leading_whitespace(&self, source_map: &SourceMap) -> Option<Vec<Span>> {
let snippet = source_map.span_to_snippet(*self).ok()?;
let mut spans = Vec::new();
let mut start = self.lo();
for line in snippet.split('\n') {
let offset = line
.chars()
.take_while(|c| c.is_whitespace())
.map(char::len_utf8)
.sum::<usize>();
let end = (start + BytePos(u32::try_from(line.len()).unwrap())).min(self.hi());
spans.push(
self
.with_lo(start + BytePos(u32::try_from(offset).unwrap()))
.with_hi(end),
);
start = end + BytePos(1);
}
Some(spans)
}
}
pub trait SpanDataExt {
fn size(&self) -> u32;
}
impl SpanDataExt for SpanData {
fn size(&self) -> u32 {
self.hi.0 - self.lo.0
}
}
#[cfg(test)]
mod test {
use rustc_span::BytePos;
use super::*;
#[test]
fn test_span_subtract() {
rustc_span::create_default_session_globals_then(|| {
let mk = |lo, hi| Span::with_root_ctxt(BytePos(lo), BytePos(hi));
let outer = mk(1, 10);
let inner: Vec<Span> = vec![mk(0, 2), mk(3, 4), mk(3, 5), mk(7, 8), mk(9, 13)];
let desired: Vec<Span> = vec![mk(2, 3), mk(5, 7), mk(8, 9)];
assert_eq!(outer.subtract(inner), desired);
});
}
}