rustdoc/html/
macro_expansion.rs1use rustc_ast::visit::{Visitor, walk_crate, walk_expr, walk_item, walk_pat, walk_stmt};
2use rustc_ast::{Crate, Expr, Item, Pat, Stmt};
3use rustc_data_structures::fx::FxHashMap;
4use rustc_span::source_map::SourceMap;
5use rustc_span::{BytePos, Span};
6
7use crate::config::{OutputFormat, RenderOptions};
8
9pub(crate) fn source_macro_expansion(
11 krate: &Crate,
12 render_options: &RenderOptions,
13 output_format: OutputFormat,
14 source_map: &SourceMap,
15) -> FxHashMap<BytePos, Vec<ExpandedCode>> {
16 if output_format == OutputFormat::Html
17 && !render_options.html_no_source
18 && render_options.generate_macro_expansion
19 {
20 let mut expanded_visitor = ExpandedCodeVisitor { expanded_codes: Vec::new(), source_map };
21 walk_crate(&mut expanded_visitor, krate);
22 expanded_visitor.compute_expanded()
23 } else {
24 Default::default()
25 }
26}
27
28#[derive(Debug)]
30pub(crate) struct ExpandedCode {
31 pub(crate) start_line: u32,
33 pub(crate) end_line: u32,
35 pub(crate) code: String,
37 pub(crate) span: Span,
39}
40
41struct ExpandedCodeInfo {
46 span: Span,
48 code: String,
50 expanded_span: Span,
52}
53
54pub(crate) struct ExpandedCodeVisitor<'ast> {
59 expanded_codes: Vec<ExpandedCodeInfo>,
60 source_map: &'ast SourceMap,
61}
62
63impl<'ast> ExpandedCodeVisitor<'ast> {
64 fn handle_new_span<F: Fn() -> String>(&mut self, new_span: Span, f: F) {
65 if new_span.is_dummy() || !new_span.from_expansion() {
66 return;
67 }
68 let callsite_span = new_span.source_callsite();
69 if let Some(index) =
70 self.expanded_codes.iter().position(|info| info.span.overlaps(callsite_span))
71 {
72 let info = &mut self.expanded_codes[index];
73 if new_span.contains(info.expanded_span) {
74 info.span = callsite_span;
76 info.expanded_span = new_span;
77 info.code = f();
78 } else {
79 let expanded_code = &mut self.expanded_codes[index];
81 expanded_code.code.push('\n');
82 expanded_code.code.push_str(&f());
83 let lo = BytePos(expanded_code.expanded_span.lo().0.min(new_span.lo().0));
84 let hi = BytePos(expanded_code.expanded_span.hi().0.min(new_span.hi().0));
85 expanded_code.expanded_span = expanded_code.expanded_span.with_lo(lo).with_hi(hi);
86 }
87 } else {
88 self.expanded_codes.push(ExpandedCodeInfo {
90 span: callsite_span,
91 code: f(),
92 expanded_span: new_span,
93 });
94 }
95 }
96
97 fn compute_expanded(mut self) -> FxHashMap<BytePos, Vec<ExpandedCode>> {
98 self.expanded_codes.sort_unstable_by(|item1, item2| item1.span.cmp(&item2.span));
99 let mut expanded: FxHashMap<BytePos, Vec<ExpandedCode>> = FxHashMap::default();
100 for ExpandedCodeInfo { span, code, .. } in self.expanded_codes {
101 if let Ok(lines) = self.source_map.span_to_lines(span)
102 && !lines.lines.is_empty()
103 {
104 let mut out = String::new();
105 super::highlight::write_code(&mut out, &code, None, None, None);
106 let first = lines.lines.first().unwrap();
107 let end = lines.lines.last().unwrap();
108 expanded.entry(lines.file.start_pos).or_default().push(ExpandedCode {
109 start_line: first.line_index as u32 + 1,
110 end_line: end.line_index as u32 + 1,
111 code: out,
112 span,
113 });
114 }
115 }
116 expanded
117 }
118}
119
120impl<'ast> Visitor<'ast> for ExpandedCodeVisitor<'ast> {
125 fn visit_expr(&mut self, expr: &'ast Expr) {
126 if expr.span.from_expansion() {
127 self.handle_new_span(expr.span, || rustc_ast_pretty::pprust::expr_to_string(expr));
128 } else {
129 walk_expr(self, expr);
130 }
131 }
132
133 fn visit_item(&mut self, item: &'ast Item) {
134 if item.span.from_expansion() {
135 self.handle_new_span(item.span, || rustc_ast_pretty::pprust::item_to_string(item));
136 } else {
137 walk_item(self, item);
138 }
139 }
140
141 fn visit_stmt(&mut self, stmt: &'ast Stmt) {
142 if stmt.span.from_expansion() {
143 self.handle_new_span(stmt.span, || rustc_ast_pretty::pprust::stmt_to_string(stmt));
144 } else {
145 walk_stmt(self, stmt);
146 }
147 }
148
149 fn visit_pat(&mut self, pat: &'ast Pat) {
150 if pat.span.from_expansion() {
151 self.handle_new_span(pat.span, || rustc_ast_pretty::pprust::pat_to_string(pat));
152 } else {
153 walk_pat(self, pat);
154 }
155 }
156}