Skip to main content

rustdoc/html/
macro_expansion.rs

1use 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
12/// It returns the expanded macros correspondence map.
13pub(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/// Contains information about macro expansion in the source code pages.
32#[derive(Debug)]
33pub(crate) struct ExpandedCode {
34    /// The line where the macro expansion starts.
35    pub(crate) start_line: u32,
36    /// The line where the macro expansion ends.
37    pub(crate) end_line: u32,
38    /// The source code of the expanded macro.
39    pub(crate) code: String,
40    /// The span of macro callsite.
41    pub(crate) span: Span,
42}
43
44/// Contains temporary information of macro expanded code.
45///
46/// As we go through the HIR visitor, if any span overlaps with another, they will
47/// both be merged.
48struct ExpandedCodeInfo {
49    /// Callsite of the macro.
50    span: Span,
51    /// Expanded macro source code (HTML escaped).
52    code: String,
53    /// Span of macro-generated code.
54    expanded_span: Span,
55}
56
57/// HIR visitor which retrieves expanded macro.
58///
59/// Once done, the `expanded_codes` will be transformed into a vec of [`ExpandedCode`]
60/// which contains the information needed when running the source code highlighter.
61pub(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                // New macro expansion recursively contains the old one, so replace it.
78                info.span = callsite_span;
79                info.expanded_span = new_span;
80                info.code = f();
81            } else {
82                // We push the new item after the existing one.
83                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            // We add a new item.
92            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
123// We need to use the AST pretty printing because:
124//
125// 1. HIR pretty printing doesn't display accurately the code (like `impl Trait`).
126// 2. `SourceMap::snippet_opt` might fail if the source is not available.
127impl<'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}