rustdoc/html/
macro_expansion.rs

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