| use rustc_hir::def_id::DefId; |
| use rustc_middle::hir; |
| use rustc_middle::mir::*; |
| use rustc_middle::ty::TyCtxt; |
| use rustc_session::config::MirSpanview; |
| use rustc_span::{BytePos, Pos, Span, SyntaxContext}; |
| |
| use std::cmp; |
| use std::io::{self, Write}; |
| |
| pub const TOOLTIP_INDENT: &str = " "; |
| |
| const CARET: char = '\u{2038}'; // Unicode `CARET` |
| const ANNOTATION_LEFT_BRACKET: char = '\u{298a}'; // Unicode `Z NOTATION RIGHT BINDING BRACKET |
| const ANNOTATION_RIGHT_BRACKET: char = '\u{2989}'; // Unicode `Z NOTATION LEFT BINDING BRACKET` |
| const NEW_LINE_SPAN: &str = "</span>\n<span class=\"line\">"; |
| const HEADER: &str = r#"<!DOCTYPE html> |
| <html> |
| <head>"#; |
| const START_BODY: &str = r#"</head> |
| <body>"#; |
| const FOOTER: &str = r#"</body> |
| </html>"#; |
| |
| const STYLE_SECTION: &str = r#"<style> |
| .line { |
| counter-increment: line; |
| } |
| .line:before { |
| content: counter(line) ": "; |
| font-family: Menlo, Monaco, monospace; |
| font-style: italic; |
| width: 3.8em; |
| display: inline-block; |
| text-align: right; |
| filter: opacity(50%); |
| -webkit-user-select: none; |
| } |
| .code { |
| color: #dddddd; |
| background-color: #222222; |
| font-family: Menlo, Monaco, monospace; |
| line-height: 1.4em; |
| border-bottom: 2px solid #222222; |
| white-space: pre; |
| display: inline-block; |
| } |
| .odd { |
| background-color: #55bbff; |
| color: #223311; |
| } |
| .even { |
| background-color: #ee7756; |
| color: #551133; |
| } |
| .code { |
| --index: calc(var(--layer) - 1); |
| padding-top: calc(var(--index) * 0.15em); |
| filter: |
| hue-rotate(calc(var(--index) * 25deg)) |
| saturate(calc(100% - (var(--index) * 2%))) |
| brightness(calc(100% - (var(--index) * 1.5%))); |
| } |
| .annotation { |
| color: #4444ff; |
| font-family: monospace; |
| font-style: italic; |
| display: none; |
| -webkit-user-select: none; |
| } |
| body:active .annotation { |
| /* requires holding mouse down anywhere on the page */ |
| display: inline-block; |
| } |
| span:hover .annotation { |
| /* requires hover over a span ONLY on its first line */ |
| display: inline-block; |
| } |
| </style>"#; |
| |
| /// Metadata to highlight the span of a MIR BasicBlock, Statement, or Terminator. |
| #[derive(Clone, Debug)] |
| pub struct SpanViewable { |
| pub bb: BasicBlock, |
| pub span: Span, |
| pub id: String, |
| pub tooltip: String, |
| } |
| |
| /// Write a spanview HTML+CSS file to analyze MIR element spans. |
| pub fn write_mir_fn_spanview<'tcx, W>( |
| tcx: TyCtxt<'tcx>, |
| body: &Body<'tcx>, |
| spanview: MirSpanview, |
| title: &str, |
| w: &mut W, |
| ) -> io::Result<()> |
| where |
| W: Write, |
| { |
| let def_id = body.source.def_id(); |
| let hir_body = hir_body(tcx, def_id); |
| if hir_body.is_none() { |
| return Ok(()); |
| } |
| let body_span = hir_body.unwrap().value.span; |
| let mut span_viewables = Vec::new(); |
| for (bb, data) in body.basic_blocks().iter_enumerated() { |
| match spanview { |
| MirSpanview::Statement => { |
| for (i, statement) in data.statements.iter().enumerate() { |
| if let Some(span_viewable) = |
| statement_span_viewable(tcx, body_span, bb, i, statement) |
| { |
| span_viewables.push(span_viewable); |
| } |
| } |
| if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) { |
| span_viewables.push(span_viewable); |
| } |
| } |
| MirSpanview::Terminator => { |
| if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) { |
| span_viewables.push(span_viewable); |
| } |
| } |
| MirSpanview::Block => { |
| if let Some(span_viewable) = block_span_viewable(tcx, body_span, bb, data) { |
| span_viewables.push(span_viewable); |
| } |
| } |
| } |
| } |
| write_document(tcx, fn_span(tcx, def_id), span_viewables, title, w)?; |
| Ok(()) |
| } |
| |
| /// Generate a spanview HTML+CSS document for the given local function `def_id`, and a pre-generated |
| /// list `SpanViewable`s. |
| pub fn write_document<'tcx, W>( |
| tcx: TyCtxt<'tcx>, |
| spanview_span: Span, |
| mut span_viewables: Vec<SpanViewable>, |
| title: &str, |
| w: &mut W, |
| ) -> io::Result<()> |
| where |
| W: Write, |
| { |
| let mut from_pos = spanview_span.lo(); |
| let end_pos = spanview_span.hi(); |
| let source_map = tcx.sess.source_map(); |
| let start = source_map.lookup_char_pos(from_pos); |
| let indent_to_initial_start_col = " ".repeat(start.col.to_usize()); |
| debug!( |
| "spanview_span={:?}; source is:\n{}{}", |
| spanview_span, |
| indent_to_initial_start_col, |
| source_map.span_to_snippet(spanview_span).expect("function should have printable source") |
| ); |
| writeln!(w, "{}", HEADER)?; |
| writeln!(w, "<title>{}</title>", title)?; |
| writeln!(w, "{}", STYLE_SECTION)?; |
| writeln!(w, "{}", START_BODY)?; |
| write!( |
| w, |
| r#"<div class="code" style="counter-reset: line {}"><span class="line">{}"#, |
| start.line - 1, |
| indent_to_initial_start_col, |
| )?; |
| span_viewables.sort_unstable_by(|a, b| { |
| let a = a.span; |
| let b = b.span; |
| if a.lo() == b.lo() { |
| // Sort hi() in reverse order so shorter spans are attempted after longer spans. |
| // This should give shorter spans a higher "layer", so they are not covered by |
| // the longer spans. |
| b.hi().partial_cmp(&a.hi()) |
| } else { |
| a.lo().partial_cmp(&b.lo()) |
| } |
| .unwrap() |
| }); |
| let mut ordered_viewables = &span_viewables[..]; |
| const LOWEST_VIEWABLE_LAYER: usize = 1; |
| let mut alt = false; |
| 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 curr_id = &ordered_viewables[0].id; |
| 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; |
| if let Some(next_ordered_viewable) = ordered_viewables.first() { |
| if &next_ordered_viewable.id != curr_id { |
| alt = !alt; |
| } |
| } |
| } |
| } |
| if from_pos < end_pos { |
| write_coverage_gap(tcx, from_pos, end_pos, w)?; |
| } |
| writeln!(w, r#"</span></div>"#)?; |
| writeln!(w, "{}", FOOTER)?; |
| Ok(()) |
| } |
| |
| /// Format a string showing the start line and column, and end line and column within a file. |
| pub fn source_range_no_file<'tcx>(tcx: TyCtxt<'tcx>, span: Span) -> String { |
| let source_map = tcx.sess.source_map(); |
| let start = source_map.lookup_char_pos(span.lo()); |
| let end = source_map.lookup_char_pos(span.hi()); |
| format!("{}:{}-{}:{}", start.line, start.col.to_usize() + 1, end.line, end.col.to_usize() + 1) |
| } |
| |
| pub fn statement_kind_name(statement: &Statement<'_>) -> &'static str { |
| use StatementKind::*; |
| match statement.kind { |
| Assign(..) => "Assign", |
| FakeRead(..) => "FakeRead", |
| SetDiscriminant { .. } => "SetDiscriminant", |
| Deinit(..) => "Deinit", |
| StorageLive(..) => "StorageLive", |
| StorageDead(..) => "StorageDead", |
| Retag(..) => "Retag", |
| AscribeUserType(..) => "AscribeUserType", |
| Coverage(..) => "Coverage", |
| CopyNonOverlapping(..) => "CopyNonOverlapping", |
| Nop => "Nop", |
| } |
| } |
| |
| pub fn terminator_kind_name(term: &Terminator<'_>) -> &'static str { |
| use TerminatorKind::*; |
| match term.kind { |
| Goto { .. } => "Goto", |
| SwitchInt { .. } => "SwitchInt", |
| Resume => "Resume", |
| Abort => "Abort", |
| Return => "Return", |
| Unreachable => "Unreachable", |
| Drop { .. } => "Drop", |
| DropAndReplace { .. } => "DropAndReplace", |
| Call { .. } => "Call", |
| Assert { .. } => "Assert", |
| Yield { .. } => "Yield", |
| GeneratorDrop => "GeneratorDrop", |
| FalseEdge { .. } => "FalseEdge", |
| FalseUnwind { .. } => "FalseUnwind", |
| InlineAsm { .. } => "InlineAsm", |
| } |
| } |
| |
| fn statement_span_viewable<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| body_span: Span, |
| bb: BasicBlock, |
| i: usize, |
| statement: &Statement<'tcx>, |
| ) -> Option<SpanViewable> { |
| let span = statement.source_info.span; |
| if !body_span.contains(span) { |
| return None; |
| } |
| let id = format!("{}[{}]", bb.index(), i); |
| let tooltip = tooltip(tcx, &id, span, vec![statement.clone()], &None); |
| Some(SpanViewable { bb, span, id, tooltip }) |
| } |
| |
| fn terminator_span_viewable<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| body_span: Span, |
| bb: BasicBlock, |
| data: &BasicBlockData<'tcx>, |
| ) -> Option<SpanViewable> { |
| let term = data.terminator(); |
| let span = term.source_info.span; |
| if !body_span.contains(span) { |
| return None; |
| } |
| let id = format!("{}:{}", bb.index(), terminator_kind_name(term)); |
| let tooltip = tooltip(tcx, &id, span, vec![], &data.terminator); |
| Some(SpanViewable { bb, span, id, tooltip }) |
| } |
| |
| fn block_span_viewable<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| body_span: Span, |
| bb: BasicBlock, |
| data: &BasicBlockData<'tcx>, |
| ) -> Option<SpanViewable> { |
| let span = compute_block_span(data, body_span); |
| if !body_span.contains(span) { |
| return None; |
| } |
| let id = format!("{}", bb.index()); |
| let tooltip = tooltip(tcx, &id, span, data.statements.clone(), &data.terminator); |
| Some(SpanViewable { bb, 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 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); |
| } |
| } |
| span |
| } |
| |
| /// Recursively process each ordered span. Spans that overlap will have progressively varying |
| /// styles, such as increased padding for each overlap. Non-overlapping adjacent spans will |
| /// have alternating style choices, to help distinguish between them if, visually adjacent. |
| /// 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_next_viewable_with_overlaps<'tcx, 'b, W>( |
| tcx: TyCtxt<'tcx>, |
| mut from_pos: BytePos, |
| mut to_pos: BytePos, |
| ordered_viewables: &'b [SpanViewable], |
| alt: bool, |
| layer: usize, |
| w: &mut W, |
| ) -> io::Result<(BytePos, &'b [SpanViewable])> |
| where |
| W: Write, |
| { |
| 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)); |
| } |
| } |
| |
| 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; |
| 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 |
| }; |
| |
| // 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; |
| } |
| |
| 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 curr_id = &remaining_viewables[0].id; |
| let (next_from_pos, next_remaining_viewables) = write_next_viewable_with_overlaps( |
| tcx, |
| from_pos, |
| to_pos, |
| &remaining_viewables, |
| subalt, |
| layer + 1, |
| w, |
| )?; |
| 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; |
| if let Some(next_ordered_viewable) = remaining_viewables.first() { |
| if &next_ordered_viewable.id != curr_id { |
| 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)?; |
| } |
| } |
| 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<()> |
| where |
| W: Write, |
| { |
| 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<W>( |
| html_snippet: &str, |
| tooltip: &str, |
| alt: bool, |
| layer: usize, |
| w: &mut W, |
| ) -> io::Result<()> |
| where |
| W: Write, |
| { |
| let maybe_alt_class = if layer > 0 { |
| if alt { " odd" } else { " even" } |
| } else { |
| "" |
| }; |
| let maybe_title_attr = if !tooltip.is_empty() { |
| format!(" title=\"{}\"", escape_attr(tooltip)) |
| } else { |
| "".to_owned() |
| }; |
| if layer == 1 { |
| write!(w, "<span>")?; |
| } |
| for (i, line) in html_snippet.lines().enumerate() { |
| if i > 0 { |
| write!(w, "{}", NEW_LINE_SPAN)?; |
| } |
| write!( |
| w, |
| r#"<span class="code{}" style="--layer: {}"{}>{}</span>"#, |
| 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, "</span>")?; |
| } |
| Ok(()) |
| } |
| |
| fn make_html_snippet<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| span: Span, |
| some_viewable: Option<&SpanViewable>, |
| ) -> Option<String> { |
| 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#"<span class="annotation">{}{}</span>"#, 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#"<span class="annotation">{}{}</span>"#, |
| 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>, |
| spanview_id: &str, |
| span: Span, |
| statements: Vec<Statement<'tcx>>, |
| terminator: &Option<Terminator<'tcx>>, |
| ) -> String { |
| let source_map = tcx.sess.source_map(); |
| let mut text = Vec::new(); |
| text.push(format!("{}: {}:", spanview_id, &source_map.span_to_embeddable_string(span))); |
| for statement in statements { |
| let source_range = source_range_no_file(tcx, statement.source_info.span); |
| text.push(format!( |
| "\n{}{}: {}: {:?}", |
| TOOLTIP_INDENT, |
| source_range, |
| statement_kind_name(&statement), |
| statement |
| )); |
| } |
| if let Some(term) = terminator { |
| let source_range = source_range_no_file(tcx, term.source_info.span); |
| text.push(format!( |
| "\n{}{}: {}: {:?}", |
| TOOLTIP_INDENT, |
| source_range, |
| terminator_kind_name(term), |
| term.kind |
| )); |
| } |
| 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 fn_decl_span = tcx.def_span(def_id); |
| if let Some(body_span) = hir_body(tcx, def_id).map(|hir_body| hir_body.value.span) { |
| if fn_decl_span.ctxt() == body_span.ctxt() { fn_decl_span.to(body_span) } else { body_span } |
| } else { |
| fn_decl_span |
| } |
| } |
| |
| fn hir_body<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<&'tcx rustc_hir::Body<'tcx>> { |
| let hir_node = tcx.hir().get_if_local(def_id).expect("expected DefId is local"); |
| hir::map::associated_body(hir_node).map(|fn_body_id| tcx.hir().body(fn_body_id)) |
| } |
| |
| fn escape_html(s: &str) -> String { |
| s.replace('&', "&").replace('<', "<").replace('>', ">") |
| } |
| |
| fn escape_attr(s: &str) -> String { |
| s.replace('&', "&") |
| .replace('\"', """) |
| .replace('\'', "'") |
| .replace('<', "<") |
| .replace('>', ">") |
| } |