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