rustdoc/html/
macro_expansion.rs1use rustc_ast::visit::{
2 AssocCtxt, Visitor, walk_assoc_item, walk_crate, walk_expr, walk_item, walk_pat, walk_stmt,
3 walk_ty,
4};
5use rustc_ast::{AssocItem, Crate, Expr, ForeignItem, Item, Pat, Stmt, Ty};
6use rustc_data_structures::fx::FxHashMap;
7use rustc_span::source_map::SourceMap;
8use rustc_span::{BytePos, Span};
9
10use crate::config::{OutputFormat, RenderOptions};
11
12pub(crate) fn source_macro_expansion(
14 krate: &Crate,
15 render_options: &RenderOptions,
16 output_format: OutputFormat,
17 source_map: &SourceMap,
18) -> FxHashMap<BytePos, Vec<ExpandedCode>> {
19 if output_format == OutputFormat::Html
20 && !render_options.html_no_source
21 && render_options.generate_macro_expansion
22 {
23 let mut expanded_visitor = ExpandedCodeVisitor { expanded_codes: Vec::new(), source_map };
24 walk_crate(&mut expanded_visitor, krate);
25 expanded_visitor.compute_expanded()
26 } else {
27 Default::default()
28 }
29}
30
31#[derive(Debug)]
33pub(crate) struct ExpandedCode {
34 pub(crate) start_line: u32,
36 pub(crate) end_line: u32,
38 pub(crate) code: String,
40 pub(crate) span: Span,
42}
43
44struct ExpandedCodeInfo {
49 original_span: Span,
50 span: Span,
52 code: String,
54 expanded_span: Span,
56}
57
58pub(crate) struct ExpandedCodeVisitor<'ast> {
63 expanded_codes: Vec<ExpandedCodeInfo>,
64 source_map: &'ast SourceMap,
65}
66
67impl<'ast> ExpandedCodeVisitor<'ast> {
68 fn handle_new_span<F: Fn() -> String>(&mut self, new_span: Span, f: F) {
69 if new_span.is_dummy() || !new_span.from_expansion() {
70 return;
71 }
72 let callsite_span = new_span.source_callsite();
73 if let Some(info) =
74 self.expanded_codes.iter_mut().find(|info| info.span.overlaps(callsite_span))
75 {
76 let has_same_macro_origin =
80 new_span == info.original_span && callsite_span == info.span;
81 if !has_same_macro_origin && new_span.contains(info.expanded_span) {
82 info.span = callsite_span;
84 info.expanded_span = new_span;
85 info.code = f();
86 } else {
87 info.code.push('\n');
89 info.code.push_str(&f());
90 let lo = BytePos(info.expanded_span.lo().0.min(new_span.lo().0));
91 let hi = BytePos(info.expanded_span.hi().0.max(new_span.hi().0));
92 info.expanded_span = info.expanded_span.with_lo(lo).with_hi(hi);
93 }
94 } else {
95 self.expanded_codes.push(ExpandedCodeInfo {
97 original_span: new_span,
98 span: callsite_span,
99 code: f(),
100 expanded_span: new_span,
101 });
102 }
103 }
104
105 fn compute_expanded(mut self) -> FxHashMap<BytePos, Vec<ExpandedCode>> {
106 self.expanded_codes.sort_unstable_by(|item1, item2| item1.span.cmp(&item2.span));
107 let mut expanded: FxHashMap<BytePos, Vec<ExpandedCode>> = FxHashMap::default();
108 for ExpandedCodeInfo { span, code, original_span, .. } in self.expanded_codes {
109 if let Ok(lines) = self.source_map.span_to_lines(span)
110 && !lines.lines.is_empty()
111 {
112 let mut out = String::new();
113 super::highlight::write_code(
114 &mut out,
115 &code,
116 None,
117 None,
118 original_span.edition(),
148 None,
149 );
150 let first = lines.lines.first().unwrap();
151 let end = lines.lines.last().unwrap();
152 expanded.entry(lines.file.start_pos).or_default().push(ExpandedCode {
153 start_line: first.line_index as u32 + 1,
154 end_line: end.line_index as u32 + 1,
155 code: out,
156 span,
157 });
158 }
159 }
160 expanded
161 }
162}
163
164impl<'ast> Visitor<'ast> for ExpandedCodeVisitor<'ast> {
169 fn visit_expr(&mut self, expr: &'ast Expr) {
170 if expr.span.from_expansion() {
171 self.handle_new_span(expr.span, || rustc_ast_pretty::pprust::expr_to_string(expr));
172 } else {
173 walk_expr(self, expr);
174 }
175 }
176
177 fn visit_item(&mut self, item: &'ast Item) {
178 if item.span.from_expansion() {
179 self.handle_new_span(item.span, || rustc_ast_pretty::pprust::item_to_string(item));
180 } else {
181 walk_item(self, item);
182 }
183 }
184
185 fn visit_stmt(&mut self, stmt: &'ast Stmt) {
186 if stmt.span.from_expansion() {
187 self.handle_new_span(stmt.span, || rustc_ast_pretty::pprust::stmt_to_string(stmt));
188 } else {
189 walk_stmt(self, stmt);
190 }
191 }
192
193 fn visit_pat(&mut self, pat: &'ast Pat) {
194 if pat.span.from_expansion() {
195 self.handle_new_span(pat.span, || rustc_ast_pretty::pprust::pat_to_string(pat));
196 } else {
197 walk_pat(self, pat);
198 }
199 }
200
201 fn visit_ty(&mut self, ty: &'ast Ty) {
202 if ty.span.from_expansion() {
203 self.handle_new_span(ty.span, || rustc_ast_pretty::pprust::ty_to_string(ty));
204 } else {
205 walk_ty(self, ty);
206 }
207 }
208
209 fn visit_assoc_item(&mut self, item: &'ast AssocItem, ctxt: AssocCtxt) -> Self::Result {
210 if item.span.from_expansion() {
211 self.handle_new_span(item.span, || {
212 rustc_ast_pretty::pprust::assoc_item_to_string(item)
213 });
214 } else {
215 walk_assoc_item(self, item, ctxt);
216 }
217 }
218
219 fn visit_foreign_item(&mut self, item: &'ast ForeignItem) -> Self::Result {
220 if item.span.from_expansion() {
221 self.handle_new_span(item.span, || {
222 rustc_ast_pretty::pprust::foreign_item_to_string(item)
223 });
224 } else {
225 walk_item(self, item);
226 }
227 }
228}