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, 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 span: Span,
51 code: String,
53 expanded_span: Span,
55}
56
57pub(crate) struct ExpandedCodeVisitor<'ast> {
62 expanded_codes: Vec<ExpandedCodeInfo>,
63 source_map: &'ast SourceMap,
64}
65
66impl<'ast> ExpandedCodeVisitor<'ast> {
67 fn handle_new_span<F: Fn() -> String>(&mut self, new_span: Span, f: F) {
68 if new_span.is_dummy() || !new_span.from_expansion() {
69 return;
70 }
71 let callsite_span = new_span.source_callsite();
72 if let Some(index) =
73 self.expanded_codes.iter().position(|info| info.span.overlaps(callsite_span))
74 {
75 let info = &mut self.expanded_codes[index];
76 if new_span.contains(info.expanded_span) {
77 info.span = callsite_span;
79 info.expanded_span = new_span;
80 info.code = f();
81 } else {
82 let expanded_code = &mut self.expanded_codes[index];
84 expanded_code.code.push('\n');
85 expanded_code.code.push_str(&f());
86 let lo = BytePos(expanded_code.expanded_span.lo().0.min(new_span.lo().0));
87 let hi = BytePos(expanded_code.expanded_span.hi().0.min(new_span.hi().0));
88 expanded_code.expanded_span = expanded_code.expanded_span.with_lo(lo).with_hi(hi);
89 }
90 } else {
91 self.expanded_codes.push(ExpandedCodeInfo {
93 span: callsite_span,
94 code: f(),
95 expanded_span: new_span,
96 });
97 }
98 }
99
100 fn compute_expanded(mut self) -> FxHashMap<BytePos, Vec<ExpandedCode>> {
101 self.expanded_codes.sort_unstable_by(|item1, item2| item1.span.cmp(&item2.span));
102 let mut expanded: FxHashMap<BytePos, Vec<ExpandedCode>> = FxHashMap::default();
103 for ExpandedCodeInfo { span, code, .. } in self.expanded_codes {
104 if let Ok(lines) = self.source_map.span_to_lines(span)
105 && !lines.lines.is_empty()
106 {
107 let mut out = String::new();
108 super::highlight::write_code(&mut out, &code, None, None, None);
109 let first = lines.lines.first().unwrap();
110 let end = lines.lines.last().unwrap();
111 expanded.entry(lines.file.start_pos).or_default().push(ExpandedCode {
112 start_line: first.line_index as u32 + 1,
113 end_line: end.line_index as u32 + 1,
114 code: out,
115 span,
116 });
117 }
118 }
119 expanded
120 }
121}
122
123impl<'ast> Visitor<'ast> for ExpandedCodeVisitor<'ast> {
128 fn visit_expr(&mut self, expr: &'ast Expr) {
129 if expr.span.from_expansion() {
130 self.handle_new_span(expr.span, || rustc_ast_pretty::pprust::expr_to_string(expr));
131 } else {
132 walk_expr(self, expr);
133 }
134 }
135
136 fn visit_item(&mut self, item: &'ast Item) {
137 if item.span.from_expansion() {
138 self.handle_new_span(item.span, || rustc_ast_pretty::pprust::item_to_string(item));
139 } else {
140 walk_item(self, item);
141 }
142 }
143
144 fn visit_stmt(&mut self, stmt: &'ast Stmt) {
145 if stmt.span.from_expansion() {
146 self.handle_new_span(stmt.span, || rustc_ast_pretty::pprust::stmt_to_string(stmt));
147 } else {
148 walk_stmt(self, stmt);
149 }
150 }
151
152 fn visit_pat(&mut self, pat: &'ast Pat) {
153 if pat.span.from_expansion() {
154 self.handle_new_span(pat.span, || rustc_ast_pretty::pprust::pat_to_string(pat));
155 } else {
156 walk_pat(self, pat);
157 }
158 }
159
160 fn visit_ty(&mut self, ty: &'ast Ty) {
161 if ty.span.from_expansion() {
162 self.handle_new_span(ty.span, || rustc_ast_pretty::pprust::ty_to_string(ty));
163 } else {
164 walk_ty(self, ty);
165 }
166 }
167
168 fn visit_assoc_item(&mut self, item: &'ast AssocItem, ctxt: AssocCtxt) -> Self::Result {
169 if item.span.from_expansion() {
170 self.handle_new_span(item.span, || {
171 rustc_ast_pretty::pprust::assoc_item_to_string(item)
172 });
173 } else {
174 walk_assoc_item(self, item, ctxt);
175 }
176 }
177}