|
| 1 | +use rustc_ast::visit::{Visitor, walk_crate, walk_expr, walk_item}; |
| 2 | +use rustc_ast::{Crate, Expr, Item}; |
| 3 | +use rustc_data_structures::fx::FxHashMap; |
| 4 | +use rustc_span::source_map::SourceMap; |
| 5 | +use rustc_span::{BytePos, Span}; |
| 6 | + |
| 7 | +use crate::config::{OutputFormat, RenderOptions}; |
| 8 | + |
| 9 | +/// It returns the expanded macros correspondence map. |
| 10 | +pub(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)] |
| 30 | +pub(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. |
| 45 | +struct ExpandedCodeInfo { |
| 46 | + /// Callsite of the macro. |
| 47 | + span: Span, |
| 48 | + /// Expanded macro source code. |
| 49 | + code: String, |
| 50 | + /// Expanded span |
| 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 more information needed when running the source code highlighter. |
| 58 | +pub(crate) struct ExpandedCodeVisitor<'ast> { |
| 59 | + expanded_codes: Vec<ExpandedCodeInfo>, |
| 60 | + source_map: &'ast SourceMap, |
| 61 | +} |
| 62 | + |
| 63 | +impl<'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 | + // We replace the item. |
| 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. |
| 124 | +impl<'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 | +} |
0 commit comments