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, 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
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    original_span: Span,
50    /// Callsite of the macro.
51    span: Span,
52    /// Expanded macro source code (HTML escaped).
53    code: String,
54    /// Span of macro-generated code.
55    expanded_span: Span,
56}
57
58/// HIR visitor which retrieves expanded macro.
59///
60/// Once done, the `expanded_codes` will be transformed into a vec of [`ExpandedCode`]
61/// which contains the information needed when running the source code highlighter.
62pub(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            // If the new span we got has the exact same span information as a span already in the
77            // list, it means it's generated from the same macro but is a different item, so we need
78            // to add it as well.
79            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                // New macro expansion recursively contains the old one, so replace it.
83                info.span = callsite_span;
84                info.expanded_span = new_span;
85                info.code = f();
86            } else {
87                // We push the new item after the existing one.
88                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            // We add a new item.
96            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                    // NOTE: This is only "an approximation" or "best effort" since the edition of
119                    // individual tokens contained in the expansion can differ from the the edition
120                    // of the entire expansion. And we can't fix that since code is just a `String`
121                    // that was produced by `rustc_ast_pretty` meaning more precise edition
122                    // information has been lost.
123                    //
124                    // Here is an example:
125                    //
126                    // ```edition2015
127                    // #[macro_export]
128                    // macro_rules! generate {
129                    //     ($kw:ident) => {
130                    //         pub fn host() {
131                    //             let _ = $kw {};
132                    //         }
133                    //     };
134                    // }
135                    // ```
136                    //
137                    // ```edition2024
138                    // dependency::generate!(async);
139                    // ```
140                    //
141                    // Here, the `async` keyword wouldn't be highlighted in the rendered expansion
142                    // `let _ = async {}` since it uses the edition of the entire expansion (which
143                    // is Rust 2015) but the `async` in the Rust 2015 expansion does actually refer
144                    // to Rust 2024 `async` keyword and thus contains an `async` block, not a struct
145                    // expression! That's because the keyword `async` originates from a Rust 2024
146                    // crate (root expansion).
147                    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
164// We need to use the AST pretty printing because:
165//
166// 1. HIR pretty printing doesn't display accurately the code (like `impl Trait`).
167// 2. `SourceMap::snippet_opt` might fail if the source is not available.
168impl<'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}