blob: 9098e92a5cd416919ecdabd66c776dbfd8c59f3f [file] [log] [blame]
use rustc_ast::visit::{Visitor, walk_crate, walk_expr, walk_item, walk_pat, walk_stmt};
use rustc_ast::{Crate, Expr, Item, Pat, Stmt};
use rustc_data_structures::fx::FxHashMap;
use rustc_span::source_map::SourceMap;
use rustc_span::{BytePos, Span};
use crate::config::{OutputFormat, RenderOptions};
/// It returns the expanded macros correspondence map.
pub(crate) fn source_macro_expansion(
krate: &Crate,
render_options: &RenderOptions,
output_format: OutputFormat,
source_map: &SourceMap,
) -> FxHashMap<BytePos, Vec<ExpandedCode>> {
if output_format == OutputFormat::Html
&& !render_options.html_no_source
&& render_options.generate_macro_expansion
{
let mut expanded_visitor = ExpandedCodeVisitor { expanded_codes: Vec::new(), source_map };
walk_crate(&mut expanded_visitor, krate);
expanded_visitor.compute_expanded()
} else {
Default::default()
}
}
/// Contains information about macro expansion in the source code pages.
#[derive(Debug)]
pub(crate) struct ExpandedCode {
/// The line where the macro expansion starts.
pub(crate) start_line: u32,
/// The line where the macro expansion ends.
pub(crate) end_line: u32,
/// The source code of the expanded macro.
pub(crate) code: String,
/// The span of macro callsite.
pub(crate) span: Span,
}
/// Contains temporary information of macro expanded code.
///
/// As we go through the HIR visitor, if any span overlaps with another, they will
/// both be merged.
struct ExpandedCodeInfo {
/// Callsite of the macro.
span: Span,
/// Expanded macro source code (HTML escaped).
code: String,
/// Span of macro-generated code.
expanded_span: Span,
}
/// HIR visitor which retrieves expanded macro.
///
/// Once done, the `expanded_codes` will be transformed into a vec of [`ExpandedCode`]
/// which contains the information needed when running the source code highlighter.
pub(crate) struct ExpandedCodeVisitor<'ast> {
expanded_codes: Vec<ExpandedCodeInfo>,
source_map: &'ast SourceMap,
}
impl<'ast> ExpandedCodeVisitor<'ast> {
fn handle_new_span<F: Fn() -> String>(&mut self, new_span: Span, f: F) {
if new_span.is_dummy() || !new_span.from_expansion() {
return;
}
let callsite_span = new_span.source_callsite();
if let Some(index) =
self.expanded_codes.iter().position(|info| info.span.overlaps(callsite_span))
{
let info = &mut self.expanded_codes[index];
if new_span.contains(info.expanded_span) {
// New macro expansion recursively contains the old one, so replace it.
info.span = callsite_span;
info.expanded_span = new_span;
info.code = f();
} else {
// We push the new item after the existing one.
let expanded_code = &mut self.expanded_codes[index];
expanded_code.code.push('\n');
expanded_code.code.push_str(&f());
let lo = BytePos(expanded_code.expanded_span.lo().0.min(new_span.lo().0));
let hi = BytePos(expanded_code.expanded_span.hi().0.min(new_span.hi().0));
expanded_code.expanded_span = expanded_code.expanded_span.with_lo(lo).with_hi(hi);
}
} else {
// We add a new item.
self.expanded_codes.push(ExpandedCodeInfo {
span: callsite_span,
code: f(),
expanded_span: new_span,
});
}
}
fn compute_expanded(mut self) -> FxHashMap<BytePos, Vec<ExpandedCode>> {
self.expanded_codes.sort_unstable_by(|item1, item2| item1.span.cmp(&item2.span));
let mut expanded: FxHashMap<BytePos, Vec<ExpandedCode>> = FxHashMap::default();
for ExpandedCodeInfo { span, code, .. } in self.expanded_codes {
if let Ok(lines) = self.source_map.span_to_lines(span)
&& !lines.lines.is_empty()
{
let mut out = String::new();
super::highlight::write_code(&mut out, &code, None, None, None);
let first = lines.lines.first().unwrap();
let end = lines.lines.last().unwrap();
expanded.entry(lines.file.start_pos).or_default().push(ExpandedCode {
start_line: first.line_index as u32 + 1,
end_line: end.line_index as u32 + 1,
code: out,
span,
});
}
}
expanded
}
}
// We need to use the AST pretty printing because:
//
// 1. HIR pretty printing doesn't display accurately the code (like `impl Trait`).
// 2. `SourceMap::snippet_opt` might fail if the source is not available.
impl<'ast> Visitor<'ast> for ExpandedCodeVisitor<'ast> {
fn visit_expr(&mut self, expr: &'ast Expr) {
if expr.span.from_expansion() {
self.handle_new_span(expr.span, || rustc_ast_pretty::pprust::expr_to_string(expr));
} else {
walk_expr(self, expr);
}
}
fn visit_item(&mut self, item: &'ast Item) {
if item.span.from_expansion() {
self.handle_new_span(item.span, || rustc_ast_pretty::pprust::item_to_string(item));
} else {
walk_item(self, item);
}
}
fn visit_stmt(&mut self, stmt: &'ast Stmt) {
if stmt.span.from_expansion() {
self.handle_new_span(stmt.span, || rustc_ast_pretty::pprust::stmt_to_string(stmt));
} else {
walk_stmt(self, stmt);
}
}
fn visit_pat(&mut self, pat: &'ast Pat) {
if pat.span.from_expansion() {
self.handle_new_span(pat.span, || rustc_ast_pretty::pprust::pat_to_string(pat));
} else {
walk_pat(self, pat);
}
}
}