";
const HEADER: &str = r#"
@@ -80,7 +83,7 @@ const FOOTER: &str = r#"
/// Metadata to highlight the span of a MIR BasicBlock, Statement, or Terminator.
pub struct SpanViewable {
pub span: Span,
- pub title: String,
+ pub id: String,
pub tooltip: String,
}
@@ -139,16 +142,22 @@ where
W: Write,
{
let fn_span = fn_span(tcx, def_id);
- writeln!(w, "{}", HEADER)?;
- let mut next_pos = fn_span.lo();
+ let mut from_pos = fn_span.lo();
let end_pos = fn_span.hi();
let source_map = tcx.sess.source_map();
- let start = source_map.lookup_char_pos(next_pos);
+ let start = source_map.lookup_char_pos(from_pos);
+ let indent_to_initial_start_col = " ".repeat(start.col.to_usize());
+ debug!(
+ "fn_span source is:\n{}{}",
+ indent_to_initial_start_col,
+ source_map.span_to_snippet(fn_span).expect("function should have printable source")
+ );
+ writeln!(w, "{}", HEADER)?;
write!(
w,
r#"{}"#,
start.line - 1,
- " ".repeat(start.col.to_usize())
+ indent_to_initial_start_col,
)?;
span_viewables.sort_unstable_by(|a, b| {
let a = a.span;
@@ -163,14 +172,43 @@ where
}
.unwrap()
});
- let mut ordered_span_viewables = span_viewables.iter().peekable();
+ let mut ordered_viewables = &span_viewables[..];
+ const LOWEST_VIEWABLE_LAYER: usize = 1;
let mut alt = false;
- while ordered_span_viewables.peek().is_some() {
- next_pos = write_span_viewables(tcx, next_pos, &mut ordered_span_viewables, false, 1, w)?;
- alt = !alt;
+ while ordered_viewables.len() > 0 {
+ debug!(
+ "calling write_next_viewable with from_pos={}, end_pos={}, and viewables len={}",
+ from_pos.to_usize(),
+ end_pos.to_usize(),
+ ordered_viewables.len()
+ );
+ let (next_from_pos, next_ordered_viewables) = write_next_viewable_with_overlaps(
+ tcx,
+ from_pos,
+ end_pos,
+ ordered_viewables,
+ alt,
+ LOWEST_VIEWABLE_LAYER,
+ w,
+ )?;
+ debug!(
+ "DONE calling write_next_viewable, with new from_pos={}, \
+ and remaining viewables len={}",
+ next_from_pos.to_usize(),
+ next_ordered_viewables.len()
+ );
+ assert!(
+ from_pos != next_from_pos || ordered_viewables.len() != next_ordered_viewables.len(),
+ "write_next_viewable_with_overlaps() must make a state change"
+ );
+ from_pos = next_from_pos;
+ if next_ordered_viewables.len() != ordered_viewables.len() {
+ ordered_viewables = next_ordered_viewables;
+ alt = !alt;
+ }
}
- if next_pos < end_pos {
- write_coverage_gap(tcx, next_pos, end_pos, w)?;
+ if from_pos < end_pos {
+ write_coverage_gap(tcx, from_pos, end_pos, w)?;
}
write!(w, r#"
"#)?;
writeln!(w, "{}", FOOTER)?;
@@ -233,9 +271,9 @@ fn statement_span_viewable<'tcx>(
if !body_span.contains(span) {
return None;
}
- let title = format!("bb{}[{}]", bb.index(), i);
- let tooltip = tooltip(tcx, &title, span, vec![statement.clone()], &None);
- Some(SpanViewable { span, title, tooltip })
+ let id = format!("{}[{}]", bb.index(), i);
+ let tooltip = tooltip(tcx, &id, span, vec![statement.clone()], &None);
+ Some(SpanViewable { span, id, tooltip })
}
fn terminator_span_viewable<'tcx>(
@@ -249,9 +287,9 @@ fn terminator_span_viewable<'tcx>(
if !body_span.contains(span) {
return None;
}
- let title = format!("bb{}`{}`", bb.index(), terminator_kind_name(term));
- let tooltip = tooltip(tcx, &title, span, vec![], &data.terminator);
- Some(SpanViewable { span, title, tooltip })
+ let id = format!("{}:{}", bb.index(), terminator_kind_name(term));
+ let tooltip = tooltip(tcx, &id, span, vec![], &data.terminator);
+ Some(SpanViewable { span, id, tooltip })
}
fn block_span_viewable<'tcx>(
@@ -264,16 +302,16 @@ fn block_span_viewable<'tcx>(
if !body_span.contains(span) {
return None;
}
- let title = format!("bb{}", bb.index());
- let tooltip = tooltip(tcx, &title, span, data.statements.clone(), &data.terminator);
- Some(SpanViewable { span, title, tooltip })
+ let id = format!("{}", bb.index());
+ let tooltip = tooltip(tcx, &id, span, data.statements.clone(), &data.terminator);
+ Some(SpanViewable { span, id, tooltip })
}
fn compute_block_span<'tcx>(data: &BasicBlockData<'tcx>, body_span: Span) -> Span {
let mut span = data.terminator().source_info.span;
for statement_span in data.statements.iter().map(|statement| statement.source_info.span) {
- // Only combine Spans from the function's body_span.
- if body_span.contains(statement_span) {
+ // Only combine Spans from the root context, and within the function's body_span.
+ if statement_span.ctxt() == SyntaxContext::root() && body_span.contains(statement_span) {
span = span.to(statement_span);
}
}
@@ -286,100 +324,217 @@ fn compute_block_span<'tcx>(data: &BasicBlockData<'tcx>, body_span: Span) -> Spa
/// The `layer` is incremented for each overlap, and the `alt` bool alternates between true
/// and false, for each adjacent non-overlapping span. Source code between the spans (code
/// that is not in any coverage region) has neutral styling.
-fn write_span_viewables<'tcx, 'b, W>(
+fn write_next_viewable_with_overlaps<'tcx, 'b, W>(
tcx: TyCtxt<'tcx>,
- next_pos: BytePos,
- ordered_span_viewables: &mut Peekable>,
+ mut from_pos: BytePos,
+ mut to_pos: BytePos,
+ ordered_viewables: &'b [SpanViewable],
alt: bool,
layer: usize,
w: &mut W,
-) -> io::Result
+) -> io::Result<(BytePos, &'b [SpanViewable])>
where
W: Write,
{
- let span_viewable =
- ordered_span_viewables.next().expect("ordered_span_viewables should have some");
- if next_pos < span_viewable.span.lo() {
- write_coverage_gap(tcx, next_pos, span_viewable.span.lo(), w)?;
+ let debug_indent = " ".repeat(layer);
+ let (viewable, mut remaining_viewables) =
+ ordered_viewables.split_first().expect("ordered_viewables should have some");
+
+ if from_pos < viewable.span.lo() {
+ debug!(
+ "{}advance from_pos to next SpanViewable (from from_pos={} to viewable.span.lo()={} \
+ of {:?}), with to_pos={}",
+ debug_indent,
+ from_pos.to_usize(),
+ viewable.span.lo().to_usize(),
+ viewable.span,
+ to_pos.to_usize()
+ );
+ let hi = cmp::min(viewable.span.lo(), to_pos);
+ write_coverage_gap(tcx, from_pos, hi, w)?;
+ from_pos = hi;
+ if from_pos < viewable.span.lo() {
+ debug!(
+ "{}EARLY RETURN: stopped before getting to next SpanViewable, at {}",
+ debug_indent,
+ from_pos.to_usize()
+ );
+ return Ok((from_pos, ordered_viewables));
+ }
}
- let mut remaining_span = span_viewable.span;
+
+ if from_pos < viewable.span.hi() {
+ // Set to_pos to the end of this `viewable` to ensure the recursive calls stop writing
+ // with room to print the tail.
+ to_pos = cmp::min(viewable.span.hi(), to_pos);
+ debug!(
+ "{}update to_pos (if not closer) to viewable.span.hi()={}; to_pos is now {}",
+ debug_indent,
+ viewable.span.hi().to_usize(),
+ to_pos.to_usize()
+ );
+ }
+
let mut subalt = false;
- loop {
- let next_span_viewable = match ordered_span_viewables.peek() {
- None => break,
- Some(span_viewable) => *span_viewable,
+ while remaining_viewables.len() > 0 && remaining_viewables[0].span.overlaps(viewable.span) {
+ let overlapping_viewable = &remaining_viewables[0];
+ debug!("{}overlapping_viewable.span={:?}", debug_indent, overlapping_viewable.span);
+
+ let span =
+ trim_span(viewable.span, from_pos, cmp::min(overlapping_viewable.span.lo(), to_pos));
+ let mut some_html_snippet = if from_pos <= viewable.span.hi() || viewable.span.is_empty() {
+ // `viewable` is not yet fully rendered, so start writing the span, up to either the
+ // `to_pos` or the next `overlapping_viewable`, whichever comes first.
+ debug!(
+ "{}make html_snippet (may not write it if early exit) for partial span {:?} \
+ of viewable.span {:?}",
+ debug_indent, span, viewable.span
+ );
+ from_pos = span.hi();
+ make_html_snippet(tcx, span, Some(&viewable))
+ } else {
+ None
};
- if !next_span_viewable.span.overlaps(remaining_span) {
- break;
+
+ // Defer writing the HTML snippet (until after early return checks) ONLY for empty spans.
+ // An empty Span with Some(html_snippet) is probably a tail marker. If there is an early
+ // exit, there should be another opportunity to write the tail marker.
+ if !span.is_empty() {
+ if let Some(ref html_snippet) = some_html_snippet {
+ debug!(
+ "{}write html_snippet for that partial span of viewable.span {:?}",
+ debug_indent, viewable.span
+ );
+ write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
+ }
+ some_html_snippet = None;
}
- write_span(
- tcx,
- remaining_span.until(next_span_viewable.span),
- Some(span_viewable),
- alt,
- layer,
- w,
- )?;
- let next_pos = write_span_viewables(
+
+ if from_pos < overlapping_viewable.span.lo() {
+ debug!(
+ "{}EARLY RETURN: from_pos={} has not yet reached the \
+ overlapping_viewable.span {:?}",
+ debug_indent,
+ from_pos.to_usize(),
+ overlapping_viewable.span
+ );
+ // must have reached `to_pos` before reaching the start of the
+ // `overlapping_viewable.span`
+ return Ok((from_pos, ordered_viewables));
+ }
+
+ if from_pos == to_pos
+ && !(from_pos == overlapping_viewable.span.lo() && overlapping_viewable.span.is_empty())
+ {
+ debug!(
+ "{}EARLY RETURN: from_pos=to_pos={} and overlapping_viewable.span {:?} is not \
+ empty, or not from_pos",
+ debug_indent,
+ to_pos.to_usize(),
+ overlapping_viewable.span
+ );
+ // `to_pos` must have occurred before the overlapping viewable. Return
+ // `ordered_viewables` so we can continue rendering the `viewable`, from after the
+ // `to_pos`.
+ return Ok((from_pos, ordered_viewables));
+ }
+
+ if let Some(ref html_snippet) = some_html_snippet {
+ debug!(
+ "{}write html_snippet for that partial span of viewable.span {:?}",
+ debug_indent, viewable.span
+ );
+ write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
+ }
+
+ debug!(
+ "{}recursively calling write_next_viewable with from_pos={}, to_pos={}, \
+ and viewables len={}",
+ debug_indent,
+ from_pos.to_usize(),
+ to_pos.to_usize(),
+ remaining_viewables.len()
+ );
+ // Write the overlaps (and the overlaps' overlaps, if any) up to `to_pos`.
+ let (next_from_pos, next_remaining_viewables) = write_next_viewable_with_overlaps(
tcx,
- next_span_viewable.span.lo(),
- ordered_span_viewables,
+ from_pos,
+ to_pos,
+ &remaining_viewables,
subalt,
layer + 1,
w,
)?;
- subalt = !subalt;
- if next_pos < remaining_span.hi() {
- remaining_span = remaining_span.with_lo(next_pos);
- } else {
- return Ok(next_pos);
+ debug!(
+ "{}DONE recursively calling write_next_viewable, with new from_pos={}, and remaining \
+ viewables len={}",
+ debug_indent,
+ next_from_pos.to_usize(),
+ next_remaining_viewables.len()
+ );
+ assert!(
+ from_pos != next_from_pos
+ || remaining_viewables.len() != next_remaining_viewables.len(),
+ "write_next_viewable_with_overlaps() must make a state change"
+ );
+ from_pos = next_from_pos;
+ if next_remaining_viewables.len() != remaining_viewables.len() {
+ remaining_viewables = next_remaining_viewables;
+ subalt = !subalt;
+ }
+ }
+ if from_pos <= viewable.span.hi() {
+ let span = trim_span(viewable.span, from_pos, to_pos);
+ debug!(
+ "{}After overlaps, writing (end span?) {:?} of viewable.span {:?}",
+ debug_indent, span, viewable.span
+ );
+ if let Some(ref html_snippet) = make_html_snippet(tcx, span, Some(&viewable)) {
+ from_pos = span.hi();
+ write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
}
}
- write_span(tcx, remaining_span, Some(span_viewable), alt, layer, w)
+ debug!("{}RETURN: No more overlap", debug_indent);
+ Ok((
+ from_pos,
+ if from_pos < viewable.span.hi() { ordered_viewables } else { remaining_viewables },
+ ))
}
+#[inline(always)]
fn write_coverage_gap<'tcx, W>(
tcx: TyCtxt<'tcx>,
lo: BytePos,
hi: BytePos,
w: &mut W,
-) -> io::Result
+) -> io::Result<()>
where
W: Write,
{
- write_span(tcx, Span::with_root_ctxt(lo, hi), None, false, 0, w)
+ let span = Span::with_root_ctxt(lo, hi);
+ if let Some(ref html_snippet) = make_html_snippet(tcx, span, None) {
+ write_span(html_snippet, "", false, 0, w)
+ } else {
+ Ok(())
+ }
}
-fn write_span<'tcx, W>(
- tcx: TyCtxt<'tcx>,
- span: Span,
- span_viewable: Option<&SpanViewable>,
+fn write_span(
+ html_snippet: &str,
+ tooltip: &str,
alt: bool,
layer: usize,
w: &mut W,
-) -> io::Result
+) -> io::Result<()>
where
W: Write,
{
- let source_map = tcx.sess.source_map();
- let snippet = source_map
- .span_to_snippet(span)
- .unwrap_or_else(|err| bug!("span_to_snippet error for span {:?}: {:?}", span, err));
- let labeled_snippet = if let Some(SpanViewable { title, .. }) = span_viewable {
- if span.is_empty() {
- format!(r#"@{}"#, title)
- } else {
- format!(r#"@{}: {}"#, title, escape_html(&snippet))
- }
- } else {
- snippet
- };
- let maybe_alt = if layer > 0 {
+ let maybe_alt_class = if layer > 0 {
if alt { " odd" } else { " even" }
} else {
""
};
- let maybe_tooltip = if let Some(SpanViewable { tooltip, .. }) = span_viewable {
+ let maybe_title_attr = if !tooltip.is_empty() {
format!(" title=\"{}\"", escape_attr(tooltip))
} else {
"".to_owned()
@@ -387,32 +542,73 @@ where
if layer == 1 {
write!(w, "")?;
}
- for (i, line) in labeled_snippet.lines().enumerate() {
+ for (i, line) in html_snippet.lines().enumerate() {
if i > 0 {
write!(w, "{}", NEW_LINE_SPAN)?;
}
write!(
w,
r#"{}"#,
- maybe_alt, layer, maybe_tooltip, line
+ maybe_alt_class, layer, maybe_title_attr, line
)?;
}
+ // Check for and translate trailing newlines, because `str::lines()` ignores them
+ if html_snippet.ends_with('\n') {
+ write!(w, "{}", NEW_LINE_SPAN)?;
+ }
if layer == 1 {
write!(w, "")?;
}
- Ok(span.hi())
+ Ok(())
+}
+
+fn make_html_snippet<'tcx>(
+ tcx: TyCtxt<'tcx>,
+ span: Span,
+ some_viewable: Option<&SpanViewable>,
+) -> Option {
+ let source_map = tcx.sess.source_map();
+ let snippet = source_map
+ .span_to_snippet(span)
+ .unwrap_or_else(|err| bug!("span_to_snippet error for span {:?}: {:?}", span, err));
+ let html_snippet = if let Some(viewable) = some_viewable {
+ let is_head = span.lo() == viewable.span.lo();
+ let is_tail = span.hi() == viewable.span.hi();
+ let mut labeled_snippet = if is_head {
+ format!(r#"{}{}"#, viewable.id, ANNOTATION_LEFT_BRACKET)
+ } else {
+ "".to_owned()
+ };
+ if span.is_empty() {
+ if is_head && is_tail {
+ labeled_snippet.push(CARET);
+ }
+ } else {
+ labeled_snippet.push_str(&escape_html(&snippet));
+ };
+ if is_tail {
+ labeled_snippet.push_str(&format!(
+ r#"{}{}"#,
+ ANNOTATION_RIGHT_BRACKET, viewable.id
+ ));
+ }
+ labeled_snippet
+ } else {
+ escape_html(&snippet)
+ };
+ if html_snippet.is_empty() { None } else { Some(html_snippet) }
}
fn tooltip<'tcx>(
tcx: TyCtxt<'tcx>,
- title: &str,
+ spanview_id: &str,
span: Span,
statements: Vec>,
terminator: &Option>,
) -> String {
let source_map = tcx.sess.source_map();
let mut text = Vec::new();
- text.push(format!("{}: {}:", title, &source_map.span_to_string(span)));
+ text.push(format!("{}: {}:", spanview_id, &source_map.span_to_string(span)));
for statement in statements {
let source_range = source_range_no_file(tcx, &statement.source_info.span);
text.push(format!(
@@ -436,10 +632,25 @@ fn tooltip<'tcx>(
text.join("")
}
+fn trim_span(span: Span, from_pos: BytePos, to_pos: BytePos) -> Span {
+ trim_span_hi(trim_span_lo(span, from_pos), to_pos)
+}
+
+fn trim_span_lo(span: Span, from_pos: BytePos) -> Span {
+ if from_pos <= span.lo() { span } else { span.with_lo(cmp::min(span.hi(), from_pos)) }
+}
+
+fn trim_span_hi(span: Span, to_pos: BytePos) -> Span {
+ if to_pos >= span.hi() { span } else { span.with_hi(cmp::max(span.lo(), to_pos)) }
+}
+
fn fn_span<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Span {
let hir_id =
tcx.hir().local_def_id_to_hir_id(def_id.as_local().expect("expected DefId is local"));
- tcx.hir().span(hir_id)
+ let fn_decl_span = tcx.hir().span(hir_id);
+ let body_span = hir_body(tcx, def_id).value.span;
+ debug_assert_eq!(fn_decl_span.ctxt(), body_span.ctxt());
+ fn_decl_span.to(body_span)
}
fn hir_body<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> &'tcx rustc_hir::Body<'tcx> {
diff --git a/src/test/mir-opt/spanview_block.main.mir_map.0.html b/src/test/mir-opt/spanview_block.main.mir_map.0.html
index 7c1b7bc3b84b0..8f6b1307971b6 100644
--- a/src/test/mir-opt/spanview_block.main.mir_map.0.html
+++ b/src/test/mir-opt/spanview_block.main.mir_map.0.html
@@ -59,9 +59,9 @@
-fn main() fn main() @bb0: {}@bb2
+ 5:13-5:13: Goto: goto -> bb2">0⦊{}⦉0