• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use rustc_hir::def_id::DefId;
2 use rustc_middle::hir;
3 use rustc_middle::mir::*;
4 use rustc_middle::ty::TyCtxt;
5 use rustc_session::config::MirSpanview;
6 use rustc_span::{BytePos, Pos, Span};
7 
8 use std::cmp;
9 use std::io::{self, Write};
10 
11 pub const TOOLTIP_INDENT: &str = "    ";
12 
13 const CARET: char = '\u{2038}'; // Unicode `CARET`
14 const ANNOTATION_LEFT_BRACKET: char = '\u{298a}'; // Unicode `Z NOTATION RIGHT BINDING BRACKET`
15 const ANNOTATION_RIGHT_BRACKET: char = '\u{2989}'; // Unicode `Z NOTATION LEFT BINDING BRACKET`
16 const NEW_LINE_SPAN: &str = "</span>\n<span class=\"line\">";
17 const HEADER: &str = r#"<!DOCTYPE html>
18 <html lang="en">
19 <head>
20 <meta charset="utf-8">"#;
21 const START_BODY: &str = r#"</head>
22 <body>"#;
23 const FOOTER: &str = r#"</body>
24 </html>"#;
25 
26 const STYLE_SECTION: &str = r#"<style>
27     .line {
28         counter-increment: line;
29     }
30     .line:before {
31         content: counter(line) ": ";
32         font-family: Menlo, Monaco, monospace;
33         font-style: italic;
34         width: 3.8em;
35         display: inline-block;
36         text-align: right;
37         filter: opacity(50%);
38         -webkit-user-select: none;
39     }
40     .code {
41         color: #dddddd;
42         background-color: #222222;
43         font-family: Menlo, Monaco, monospace;
44         line-height: 1.4em;
45         border-bottom: 2px solid #222222;
46         white-space: pre;
47         display: inline-block;
48     }
49     .odd {
50         background-color: #55bbff;
51         color: #223311;
52     }
53     .even {
54         background-color: #ee7756;
55         color: #551133;
56     }
57     .code {
58         --index: calc(var(--layer) - 1);
59         padding-top: calc(var(--index) * 0.15em);
60         filter:
61             hue-rotate(calc(var(--index) * 25deg))
62             saturate(calc(100% - (var(--index) * 2%)))
63             brightness(calc(100% - (var(--index) * 1.5%)));
64     }
65     .annotation {
66         color: #4444ff;
67         font-family: monospace;
68         font-style: italic;
69         display: none;
70         -webkit-user-select: none;
71     }
72     body:active .annotation {
73         /* requires holding mouse down anywhere on the page */
74         display: inline-block;
75     }
76     span:hover .annotation {
77         /* requires hover over a span ONLY on its first line */
78         display: inline-block;
79     }
80 </style>"#;
81 
82 /// Metadata to highlight the span of a MIR BasicBlock, Statement, or Terminator.
83 #[derive(Clone, Debug)]
84 pub struct SpanViewable {
85     pub bb: BasicBlock,
86     pub span: Span,
87     pub id: String,
88     pub tooltip: String,
89 }
90 
91 /// Write a spanview HTML+CSS file to analyze MIR element spans.
write_mir_fn_spanview<'tcx, W>( tcx: TyCtxt<'tcx>, body: &Body<'tcx>, spanview: MirSpanview, title: &str, w: &mut W, ) -> io::Result<()> where W: Write,92 pub fn write_mir_fn_spanview<'tcx, W>(
93     tcx: TyCtxt<'tcx>,
94     body: &Body<'tcx>,
95     spanview: MirSpanview,
96     title: &str,
97     w: &mut W,
98 ) -> io::Result<()>
99 where
100     W: Write,
101 {
102     let def_id = body.source.def_id();
103     let hir_body = hir_body(tcx, def_id);
104     if hir_body.is_none() {
105         return Ok(());
106     }
107     let body_span = hir_body.unwrap().value.span;
108     let mut span_viewables = Vec::new();
109     for (bb, data) in body.basic_blocks.iter_enumerated() {
110         match spanview {
111             MirSpanview::Statement => {
112                 for (i, statement) in data.statements.iter().enumerate() {
113                     if let Some(span_viewable) =
114                         statement_span_viewable(tcx, body_span, bb, i, statement)
115                     {
116                         span_viewables.push(span_viewable);
117                     }
118                 }
119                 if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) {
120                     span_viewables.push(span_viewable);
121                 }
122             }
123             MirSpanview::Terminator => {
124                 if let Some(span_viewable) = terminator_span_viewable(tcx, body_span, bb, data) {
125                     span_viewables.push(span_viewable);
126                 }
127             }
128             MirSpanview::Block => {
129                 if let Some(span_viewable) = block_span_viewable(tcx, body_span, bb, data) {
130                     span_viewables.push(span_viewable);
131                 }
132             }
133         }
134     }
135     write_document(tcx, fn_span(tcx, def_id), span_viewables, title, w)?;
136     Ok(())
137 }
138 
139 /// Generate a spanview HTML+CSS document for the given local function `def_id`, and a pre-generated
140 /// list `SpanViewable`s.
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,141 pub fn write_document<'tcx, W>(
142     tcx: TyCtxt<'tcx>,
143     spanview_span: Span,
144     mut span_viewables: Vec<SpanViewable>,
145     title: &str,
146     w: &mut W,
147 ) -> io::Result<()>
148 where
149     W: Write,
150 {
151     let mut from_pos = spanview_span.lo();
152     let end_pos = spanview_span.hi();
153     let source_map = tcx.sess.source_map();
154     let start = source_map.lookup_char_pos(from_pos);
155     let indent_to_initial_start_col = " ".repeat(start.col.to_usize());
156     debug!(
157         "spanview_span={:?}; source is:\n{}{}",
158         spanview_span,
159         indent_to_initial_start_col,
160         source_map.span_to_snippet(spanview_span).expect("function should have printable source")
161     );
162     writeln!(w, "{}", HEADER)?;
163     writeln!(w, "<title>{}</title>", title)?;
164     writeln!(w, "{}", STYLE_SECTION)?;
165     writeln!(w, "{}", START_BODY)?;
166     write!(
167         w,
168         r#"<div class="code" style="counter-reset: line {}"><span class="line">{}"#,
169         start.line - 1,
170         indent_to_initial_start_col,
171     )?;
172     span_viewables.sort_unstable_by(|a, b| {
173         let a = a.span;
174         let b = b.span;
175         if a.lo() == b.lo() {
176             // Sort hi() in reverse order so shorter spans are attempted after longer spans.
177             // This should give shorter spans a higher "layer", so they are not covered by
178             // the longer spans.
179             b.hi().partial_cmp(&a.hi())
180         } else {
181             a.lo().partial_cmp(&b.lo())
182         }
183         .unwrap()
184     });
185     let mut ordered_viewables = &span_viewables[..];
186     const LOWEST_VIEWABLE_LAYER: usize = 1;
187     let mut alt = false;
188     while ordered_viewables.len() > 0 {
189         debug!(
190             "calling write_next_viewable with from_pos={}, end_pos={}, and viewables len={}",
191             from_pos.to_usize(),
192             end_pos.to_usize(),
193             ordered_viewables.len()
194         );
195         let curr_id = &ordered_viewables[0].id;
196         let (next_from_pos, next_ordered_viewables) = write_next_viewable_with_overlaps(
197             tcx,
198             from_pos,
199             end_pos,
200             ordered_viewables,
201             alt,
202             LOWEST_VIEWABLE_LAYER,
203             w,
204         )?;
205         debug!(
206             "DONE calling write_next_viewable, with new from_pos={}, \
207              and remaining viewables len={}",
208             next_from_pos.to_usize(),
209             next_ordered_viewables.len()
210         );
211         assert!(
212             from_pos != next_from_pos || ordered_viewables.len() != next_ordered_viewables.len(),
213             "write_next_viewable_with_overlaps() must make a state change"
214         );
215         from_pos = next_from_pos;
216         if next_ordered_viewables.len() != ordered_viewables.len() {
217             ordered_viewables = next_ordered_viewables;
218             if let Some(next_ordered_viewable) = ordered_viewables.first() {
219                 if &next_ordered_viewable.id != curr_id {
220                     alt = !alt;
221                 }
222             }
223         }
224     }
225     if from_pos < end_pos {
226         write_coverage_gap(tcx, from_pos, end_pos, w)?;
227     }
228     writeln!(w, r#"</span></div>"#)?;
229     writeln!(w, "{}", FOOTER)?;
230     Ok(())
231 }
232 
233 /// Format a string showing the start line and column, and end line and column within a file.
source_range_no_file(tcx: TyCtxt<'_>, span: Span) -> String234 pub fn source_range_no_file(tcx: TyCtxt<'_>, span: Span) -> String {
235     let source_map = tcx.sess.source_map();
236     let start = source_map.lookup_char_pos(span.lo());
237     let end = source_map.lookup_char_pos(span.hi());
238     format!("{}:{}-{}:{}", start.line, start.col.to_usize() + 1, end.line, end.col.to_usize() + 1)
239 }
240 
statement_kind_name(statement: &Statement<'_>) -> &'static str241 pub fn statement_kind_name(statement: &Statement<'_>) -> &'static str {
242     use StatementKind::*;
243     match statement.kind {
244         Assign(..) => "Assign",
245         FakeRead(..) => "FakeRead",
246         SetDiscriminant { .. } => "SetDiscriminant",
247         Deinit(..) => "Deinit",
248         StorageLive(..) => "StorageLive",
249         StorageDead(..) => "StorageDead",
250         Retag(..) => "Retag",
251         PlaceMention(..) => "PlaceMention",
252         AscribeUserType(..) => "AscribeUserType",
253         Coverage(..) => "Coverage",
254         Intrinsic(..) => "Intrinsic",
255         ConstEvalCounter => "ConstEvalCounter",
256         Nop => "Nop",
257     }
258 }
259 
terminator_kind_name(term: &Terminator<'_>) -> &'static str260 pub fn terminator_kind_name(term: &Terminator<'_>) -> &'static str {
261     use TerminatorKind::*;
262     match term.kind {
263         Goto { .. } => "Goto",
264         SwitchInt { .. } => "SwitchInt",
265         Resume => "Resume",
266         Terminate => "Terminate",
267         Return => "Return",
268         Unreachable => "Unreachable",
269         Drop { .. } => "Drop",
270         Call { .. } => "Call",
271         Assert { .. } => "Assert",
272         Yield { .. } => "Yield",
273         GeneratorDrop => "GeneratorDrop",
274         FalseEdge { .. } => "FalseEdge",
275         FalseUnwind { .. } => "FalseUnwind",
276         InlineAsm { .. } => "InlineAsm",
277     }
278 }
279 
statement_span_viewable<'tcx>( tcx: TyCtxt<'tcx>, body_span: Span, bb: BasicBlock, i: usize, statement: &Statement<'tcx>, ) -> Option<SpanViewable>280 fn statement_span_viewable<'tcx>(
281     tcx: TyCtxt<'tcx>,
282     body_span: Span,
283     bb: BasicBlock,
284     i: usize,
285     statement: &Statement<'tcx>,
286 ) -> Option<SpanViewable> {
287     let span = statement.source_info.span;
288     if !body_span.contains(span) {
289         return None;
290     }
291     let id = format!("{}[{}]", bb.index(), i);
292     let tooltip = tooltip(tcx, &id, span, vec![statement.clone()], &None);
293     Some(SpanViewable { bb, span, id, tooltip })
294 }
295 
terminator_span_viewable<'tcx>( tcx: TyCtxt<'tcx>, body_span: Span, bb: BasicBlock, data: &BasicBlockData<'tcx>, ) -> Option<SpanViewable>296 fn terminator_span_viewable<'tcx>(
297     tcx: TyCtxt<'tcx>,
298     body_span: Span,
299     bb: BasicBlock,
300     data: &BasicBlockData<'tcx>,
301 ) -> Option<SpanViewable> {
302     let term = data.terminator();
303     let span = term.source_info.span;
304     if !body_span.contains(span) {
305         return None;
306     }
307     let id = format!("{}:{}", bb.index(), terminator_kind_name(term));
308     let tooltip = tooltip(tcx, &id, span, vec![], &data.terminator);
309     Some(SpanViewable { bb, span, id, tooltip })
310 }
311 
block_span_viewable<'tcx>( tcx: TyCtxt<'tcx>, body_span: Span, bb: BasicBlock, data: &BasicBlockData<'tcx>, ) -> Option<SpanViewable>312 fn block_span_viewable<'tcx>(
313     tcx: TyCtxt<'tcx>,
314     body_span: Span,
315     bb: BasicBlock,
316     data: &BasicBlockData<'tcx>,
317 ) -> Option<SpanViewable> {
318     let span = compute_block_span(data, body_span);
319     if !body_span.contains(span) {
320         return None;
321     }
322     let id = format!("{}", bb.index());
323     let tooltip = tooltip(tcx, &id, span, data.statements.clone(), &data.terminator);
324     Some(SpanViewable { bb, span, id, tooltip })
325 }
326 
compute_block_span(data: &BasicBlockData<'_>, body_span: Span) -> Span327 fn compute_block_span(data: &BasicBlockData<'_>, body_span: Span) -> Span {
328     let mut span = data.terminator().source_info.span;
329     for statement_span in data.statements.iter().map(|statement| statement.source_info.span) {
330         // Only combine Spans from the root context, and within the function's body_span.
331         if statement_span.ctxt().is_root() && body_span.contains(statement_span) {
332             span = span.to(statement_span);
333         }
334     }
335     span
336 }
337 
338 /// Recursively process each ordered span. Spans that overlap will have progressively varying
339 /// styles, such as increased padding for each overlap. Non-overlapping adjacent spans will
340 /// have alternating style choices, to help distinguish between them if, visually adjacent.
341 /// The `layer` is incremented for each overlap, and the `alt` bool alternates between true
342 /// and false, for each adjacent non-overlapping span. Source code between the spans (code
343 /// that is not in any coverage region) has neutral styling.
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,344 fn write_next_viewable_with_overlaps<'tcx, 'b, W>(
345     tcx: TyCtxt<'tcx>,
346     mut from_pos: BytePos,
347     mut to_pos: BytePos,
348     ordered_viewables: &'b [SpanViewable],
349     alt: bool,
350     layer: usize,
351     w: &mut W,
352 ) -> io::Result<(BytePos, &'b [SpanViewable])>
353 where
354     W: Write,
355 {
356     let debug_indent = "  ".repeat(layer);
357     let (viewable, mut remaining_viewables) =
358         ordered_viewables.split_first().expect("ordered_viewables should have some");
359 
360     if from_pos < viewable.span.lo() {
361         debug!(
362             "{}advance from_pos to next SpanViewable (from from_pos={} to viewable.span.lo()={} \
363              of {:?}), with to_pos={}",
364             debug_indent,
365             from_pos.to_usize(),
366             viewable.span.lo().to_usize(),
367             viewable.span,
368             to_pos.to_usize()
369         );
370         let hi = cmp::min(viewable.span.lo(), to_pos);
371         write_coverage_gap(tcx, from_pos, hi, w)?;
372         from_pos = hi;
373         if from_pos < viewable.span.lo() {
374             debug!(
375                 "{}EARLY RETURN: stopped before getting to next SpanViewable, at {}",
376                 debug_indent,
377                 from_pos.to_usize()
378             );
379             return Ok((from_pos, ordered_viewables));
380         }
381     }
382 
383     if from_pos < viewable.span.hi() {
384         // Set to_pos to the end of this `viewable` to ensure the recursive calls stop writing
385         // with room to print the tail.
386         to_pos = cmp::min(viewable.span.hi(), to_pos);
387         debug!(
388             "{}update to_pos (if not closer) to viewable.span.hi()={}; to_pos is now {}",
389             debug_indent,
390             viewable.span.hi().to_usize(),
391             to_pos.to_usize()
392         );
393     }
394 
395     let mut subalt = false;
396     while remaining_viewables.len() > 0 && remaining_viewables[0].span.overlaps(viewable.span) {
397         let overlapping_viewable = &remaining_viewables[0];
398         debug!("{}overlapping_viewable.span={:?}", debug_indent, overlapping_viewable.span);
399 
400         let span =
401             trim_span(viewable.span, from_pos, cmp::min(overlapping_viewable.span.lo(), to_pos));
402         let mut some_html_snippet = if from_pos <= viewable.span.hi() || viewable.span.is_empty() {
403             // `viewable` is not yet fully rendered, so start writing the span, up to either the
404             // `to_pos` or the next `overlapping_viewable`, whichever comes first.
405             debug!(
406                 "{}make html_snippet (may not write it if early exit) for partial span {:?} \
407                  of viewable.span {:?}",
408                 debug_indent, span, viewable.span
409             );
410             from_pos = span.hi();
411             make_html_snippet(tcx, span, Some(&viewable))
412         } else {
413             None
414         };
415 
416         // Defer writing the HTML snippet (until after early return checks) ONLY for empty spans.
417         // An empty Span with Some(html_snippet) is probably a tail marker. If there is an early
418         // exit, there should be another opportunity to write the tail marker.
419         if !span.is_empty() {
420             if let Some(ref html_snippet) = some_html_snippet {
421                 debug!(
422                     "{}write html_snippet for that partial span of viewable.span {:?}",
423                     debug_indent, viewable.span
424                 );
425                 write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
426             }
427             some_html_snippet = None;
428         }
429 
430         if from_pos < overlapping_viewable.span.lo() {
431             debug!(
432                 "{}EARLY RETURN: from_pos={} has not yet reached the \
433                  overlapping_viewable.span {:?}",
434                 debug_indent,
435                 from_pos.to_usize(),
436                 overlapping_viewable.span
437             );
438             // must have reached `to_pos` before reaching the start of the
439             // `overlapping_viewable.span`
440             return Ok((from_pos, ordered_viewables));
441         }
442 
443         if from_pos == to_pos
444             && !(from_pos == overlapping_viewable.span.lo() && overlapping_viewable.span.is_empty())
445         {
446             debug!(
447                 "{}EARLY RETURN: from_pos=to_pos={} and overlapping_viewable.span {:?} is not \
448                  empty, or not from_pos",
449                 debug_indent,
450                 to_pos.to_usize(),
451                 overlapping_viewable.span
452             );
453             // `to_pos` must have occurred before the overlapping viewable. Return
454             // `ordered_viewables` so we can continue rendering the `viewable`, from after the
455             // `to_pos`.
456             return Ok((from_pos, ordered_viewables));
457         }
458 
459         if let Some(ref html_snippet) = some_html_snippet {
460             debug!(
461                 "{}write html_snippet for that partial span of viewable.span {:?}",
462                 debug_indent, viewable.span
463             );
464             write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
465         }
466 
467         debug!(
468             "{}recursively calling write_next_viewable with from_pos={}, to_pos={}, \
469              and viewables len={}",
470             debug_indent,
471             from_pos.to_usize(),
472             to_pos.to_usize(),
473             remaining_viewables.len()
474         );
475         // Write the overlaps (and the overlaps' overlaps, if any) up to `to_pos`.
476         let curr_id = &remaining_viewables[0].id;
477         let (next_from_pos, next_remaining_viewables) = write_next_viewable_with_overlaps(
478             tcx,
479             from_pos,
480             to_pos,
481             &remaining_viewables,
482             subalt,
483             layer + 1,
484             w,
485         )?;
486         debug!(
487             "{}DONE recursively calling write_next_viewable, with new from_pos={}, and remaining \
488              viewables len={}",
489             debug_indent,
490             next_from_pos.to_usize(),
491             next_remaining_viewables.len()
492         );
493         assert!(
494             from_pos != next_from_pos
495                 || remaining_viewables.len() != next_remaining_viewables.len(),
496             "write_next_viewable_with_overlaps() must make a state change"
497         );
498         from_pos = next_from_pos;
499         if next_remaining_viewables.len() != remaining_viewables.len() {
500             remaining_viewables = next_remaining_viewables;
501             if let Some(next_ordered_viewable) = remaining_viewables.first() {
502                 if &next_ordered_viewable.id != curr_id {
503                     subalt = !subalt;
504                 }
505             }
506         }
507     }
508     if from_pos <= viewable.span.hi() {
509         let span = trim_span(viewable.span, from_pos, to_pos);
510         debug!(
511             "{}After overlaps, writing (end span?) {:?} of viewable.span {:?}",
512             debug_indent, span, viewable.span
513         );
514         if let Some(ref html_snippet) = make_html_snippet(tcx, span, Some(&viewable)) {
515             from_pos = span.hi();
516             write_span(html_snippet, &viewable.tooltip, alt, layer, w)?;
517         }
518     }
519     debug!("{}RETURN: No more overlap", debug_indent);
520     Ok((
521         from_pos,
522         if from_pos < viewable.span.hi() { ordered_viewables } else { remaining_viewables },
523     ))
524 }
525 
526 #[inline(always)]
write_coverage_gap<W>(tcx: TyCtxt<'_>, lo: BytePos, hi: BytePos, w: &mut W) -> io::Result<()> where W: Write,527 fn write_coverage_gap<W>(tcx: TyCtxt<'_>, lo: BytePos, hi: BytePos, w: &mut W) -> io::Result<()>
528 where
529     W: Write,
530 {
531     let span = Span::with_root_ctxt(lo, hi);
532     if let Some(ref html_snippet) = make_html_snippet(tcx, span, None) {
533         write_span(html_snippet, "", false, 0, w)
534     } else {
535         Ok(())
536     }
537 }
538 
write_span<W>( html_snippet: &str, tooltip: &str, alt: bool, layer: usize, w: &mut W, ) -> io::Result<()> where W: Write,539 fn write_span<W>(
540     html_snippet: &str,
541     tooltip: &str,
542     alt: bool,
543     layer: usize,
544     w: &mut W,
545 ) -> io::Result<()>
546 where
547     W: Write,
548 {
549     let maybe_alt_class = if layer > 0 {
550         if alt { " odd" } else { " even" }
551     } else {
552         ""
553     };
554     let maybe_title_attr = if !tooltip.is_empty() {
555         format!(" title=\"{}\"", escape_attr(tooltip))
556     } else {
557         "".to_owned()
558     };
559     if layer == 1 {
560         write!(w, "<span>")?;
561     }
562     for (i, line) in html_snippet.lines().enumerate() {
563         if i > 0 {
564             write!(w, "{}", NEW_LINE_SPAN)?;
565         }
566         write!(
567             w,
568             r#"<span class="code{}" style="--layer: {}"{}>{}</span>"#,
569             maybe_alt_class, layer, maybe_title_attr, line
570         )?;
571     }
572     // Check for and translate trailing newlines, because `str::lines()` ignores them
573     if html_snippet.ends_with('\n') {
574         write!(w, "{}", NEW_LINE_SPAN)?;
575     }
576     if layer == 1 {
577         write!(w, "</span>")?;
578     }
579     Ok(())
580 }
581 
make_html_snippet( tcx: TyCtxt<'_>, span: Span, some_viewable: Option<&SpanViewable>, ) -> Option<String>582 fn make_html_snippet(
583     tcx: TyCtxt<'_>,
584     span: Span,
585     some_viewable: Option<&SpanViewable>,
586 ) -> Option<String> {
587     let source_map = tcx.sess.source_map();
588     let snippet = source_map
589         .span_to_snippet(span)
590         .unwrap_or_else(|err| bug!("span_to_snippet error for span {:?}: {:?}", span, err));
591     let html_snippet = if let Some(viewable) = some_viewable {
592         let is_head = span.lo() == viewable.span.lo();
593         let is_tail = span.hi() == viewable.span.hi();
594         let mut labeled_snippet = if is_head {
595             format!(r#"<span class="annotation">{}{}</span>"#, viewable.id, ANNOTATION_LEFT_BRACKET)
596         } else {
597             "".to_owned()
598         };
599         if span.is_empty() {
600             if is_head && is_tail {
601                 labeled_snippet.push(CARET);
602             }
603         } else {
604             labeled_snippet.push_str(&escape_html(&snippet));
605         };
606         if is_tail {
607             labeled_snippet.push_str(&format!(
608                 r#"<span class="annotation">{}{}</span>"#,
609                 ANNOTATION_RIGHT_BRACKET, viewable.id
610             ));
611         }
612         labeled_snippet
613     } else {
614         escape_html(&snippet)
615     };
616     if html_snippet.is_empty() { None } else { Some(html_snippet) }
617 }
618 
tooltip<'tcx>( tcx: TyCtxt<'tcx>, spanview_id: &str, span: Span, statements: Vec<Statement<'tcx>>, terminator: &Option<Terminator<'tcx>>, ) -> String619 fn tooltip<'tcx>(
620     tcx: TyCtxt<'tcx>,
621     spanview_id: &str,
622     span: Span,
623     statements: Vec<Statement<'tcx>>,
624     terminator: &Option<Terminator<'tcx>>,
625 ) -> String {
626     let source_map = tcx.sess.source_map();
627     let mut text = Vec::new();
628     text.push(format!("{}: {}:", spanview_id, &source_map.span_to_embeddable_string(span)));
629     for statement in statements {
630         let source_range = source_range_no_file(tcx, statement.source_info.span);
631         text.push(format!(
632             "\n{}{}: {}: {:?}",
633             TOOLTIP_INDENT,
634             source_range,
635             statement_kind_name(&statement),
636             statement
637         ));
638     }
639     if let Some(term) = terminator {
640         let source_range = source_range_no_file(tcx, term.source_info.span);
641         text.push(format!(
642             "\n{}{}: {}: {:?}",
643             TOOLTIP_INDENT,
644             source_range,
645             terminator_kind_name(term),
646             term.kind
647         ));
648     }
649     text.join("")
650 }
651 
trim_span(span: Span, from_pos: BytePos, to_pos: BytePos) -> Span652 fn trim_span(span: Span, from_pos: BytePos, to_pos: BytePos) -> Span {
653     trim_span_hi(trim_span_lo(span, from_pos), to_pos)
654 }
655 
trim_span_lo(span: Span, from_pos: BytePos) -> Span656 fn trim_span_lo(span: Span, from_pos: BytePos) -> Span {
657     if from_pos <= span.lo() { span } else { span.with_lo(cmp::min(span.hi(), from_pos)) }
658 }
659 
trim_span_hi(span: Span, to_pos: BytePos) -> Span660 fn trim_span_hi(span: Span, to_pos: BytePos) -> Span {
661     if to_pos >= span.hi() { span } else { span.with_hi(cmp::max(span.lo(), to_pos)) }
662 }
663 
fn_span(tcx: TyCtxt<'_>, def_id: DefId) -> Span664 fn fn_span(tcx: TyCtxt<'_>, def_id: DefId) -> Span {
665     let fn_decl_span = tcx.def_span(def_id);
666     if let Some(body_span) = hir_body(tcx, def_id).map(|hir_body| hir_body.value.span) {
667         if fn_decl_span.eq_ctxt(body_span) { fn_decl_span.to(body_span) } else { body_span }
668     } else {
669         fn_decl_span
670     }
671 }
672 
hir_body(tcx: TyCtxt<'_>, def_id: DefId) -> Option<&rustc_hir::Body<'_>>673 fn hir_body(tcx: TyCtxt<'_>, def_id: DefId) -> Option<&rustc_hir::Body<'_>> {
674     let hir_node = tcx.hir().get_if_local(def_id).expect("expected DefId is local");
675     hir::map::associated_body(hir_node).map(|(_, fn_body_id)| tcx.hir().body(fn_body_id))
676 }
677 
escape_html(s: &str) -> String678 fn escape_html(s: &str) -> String {
679     s.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;")
680 }
681 
escape_attr(s: &str) -> String682 fn escape_attr(s: &str) -> String {
683     s.replace('&', "&amp;")
684         .replace('\"', "&quot;")
685         .replace('\'', "&#39;")
686         .replace('<', "&lt;")
687         .replace('>', "&gt;")
688 }
689