Skip to content

Commit 0728388

Browse files
Do macro expansion at AST level rather than HIR
1 parent cdb6696 commit 0728388

File tree

12 files changed

+264
-205
lines changed

12 files changed

+264
-205
lines changed

src/librustdoc/core.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use crate::clean::inline::build_trait;
3131
use crate::clean::{self, ItemId};
3232
use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
3333
use crate::formats::cache::Cache;
34+
use crate::html::macro_expansion::{ExpandedCode, source_macro_expansion};
3435
use crate::passes;
3536
use crate::passes::Condition::*;
3637
use crate::passes::collect_intra_doc_links::LinkCollector;
@@ -335,11 +336,19 @@ pub(crate) fn run_global_ctxt(
335336
show_coverage: bool,
336337
render_options: RenderOptions,
337338
output_format: OutputFormat,
338-
) -> (clean::Crate, RenderOptions, Cache) {
339+
) -> (clean::Crate, RenderOptions, Cache, FxHashMap<rustc_span::BytePos, Vec<ExpandedCode>>) {
339340
// Certain queries assume that some checks were run elsewhere
340341
// (see https://github.com/rust-lang/rust/pull/73566#issuecomment-656954425),
341342
// so type-check everything other than function bodies in this crate before running lints.
342343

344+
let expanded_macros = {
345+
// We need for these variables to be removed to ensure that the `Crate` won't be "stolen"
346+
// anymore.
347+
let (_resolver, krate) = &*tcx.resolver_for_lowering().borrow();
348+
349+
source_macro_expansion(&krate, &render_options, output_format, tcx.sess.source_map())
350+
};
351+
343352
// NOTE: this does not call `tcx.analysis()` so that we won't
344353
// typeck function bodies or run the default rustc lints.
345354
// (see `override_queries` in the `config`)
@@ -453,7 +462,7 @@ pub(crate) fn run_global_ctxt(
453462

454463
tcx.dcx().abort_if_errors();
455464

456-
(krate, ctxt.render_options, ctxt.cache)
465+
(krate, ctxt.render_options, ctxt.cache, expanded_macros)
457466
}
458467

459468
/// Due to <https://github.com/rust-lang/rust/pull/73566>,

src/librustdoc/formats/renderer.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,6 @@ pub(crate) trait FormatRenderer<'tcx>: Sized {
3131
/// reset the information between each call to `item` by using `restore_module_data`.
3232
type ModuleData;
3333

34-
/// Sets up any state required for the renderer. When this is called the cache has already been
35-
/// populated.
36-
fn init(
37-
krate: clean::Crate,
38-
options: RenderOptions,
39-
cache: Cache,
40-
tcx: TyCtxt<'tcx>,
41-
) -> Result<(Self, clean::Crate), Error>;
42-
4334
/// This method is called right before call [`Self::item`]. This method returns a type
4435
/// containing information that needs to be reset after the [`Self::item`] method has been
4536
/// called with the [`Self::restore_module_data`] method.
@@ -107,18 +98,23 @@ fn run_format_inner<'tcx, T: FormatRenderer<'tcx>>(
10798
}
10899

109100
/// Main method for rendering a crate.
110-
pub(crate) fn run_format<'tcx, T: FormatRenderer<'tcx>>(
101+
pub(crate) fn run_format<
102+
'tcx,
103+
T: FormatRenderer<'tcx>,
104+
F: FnOnce(clean::Crate, RenderOptions, Cache, TyCtxt<'tcx>) -> Result<(T, clean::Crate), Error>,
105+
>(
111106
krate: clean::Crate,
112107
options: RenderOptions,
113108
cache: Cache,
114109
tcx: TyCtxt<'tcx>,
110+
init: F,
115111
) -> Result<(), Error> {
116112
let prof = &tcx.sess.prof;
117113

118114
let emit_crate = options.should_emit_crate();
119115
let (mut format_renderer, krate) = prof
120116
.verbose_generic_activity_with_arg("create_renderer", T::descr())
121-
.run(|| T::init(krate, options, cache, tcx))?;
117+
.run(|| init(krate, options, cache, tcx))?;
122118

123119
if !emit_crate {
124120
return Ok(());

src/librustdoc/html/highlight.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ use rustc_span::{BytePos, DUMMY_SP, Span};
1818
use super::format::{self, write_str};
1919
use crate::clean::PrimitiveType;
2020
use crate::html::escape::EscapeBodyText;
21-
use crate::html::render::{Context, ExpandedCode, LinkFromSrc};
21+
use crate::html::macro_expansion::ExpandedCode;
22+
use crate::html::render::{Context, LinkFromSrc};
2223

2324
/// This type is needed in case we want to render links on items to allow to go to their definition.
2425
pub(crate) struct HrefContext<'a, 'tcx> {
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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+
}

src/librustdoc/html/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub(crate) mod highlight;
44
pub(crate) mod layout;
55
mod length_limit;
66
// used by the error-index generator, so it needs to be public
7+
pub(crate) mod macro_expansion;
78
pub mod markdown;
89
pub(crate) mod render;
910
pub(crate) mod sources;

src/librustdoc/html/render/context.rs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ use crate::formats::item_type::ItemType;
2929
use crate::html::escape::Escape;
3030
use crate::html::format::join_with_double_colon;
3131
use crate::html::layout::{self, BufDisplay};
32+
use crate::html::macro_expansion::ExpandedCode;
3233
use crate::html::markdown::{self, ErrorCodes, IdMap, plain_text_summary};
33-
use crate::html::render::ExpandedCode;
3434
use crate::html::render::write_shared::write_shared;
3535
use crate::html::url_parts_builder::UrlPartsBuilder;
3636
use crate::html::{sources, static_files};
@@ -456,20 +456,13 @@ impl<'tcx> Context<'tcx> {
456456
}
457457
}
458458

459-
/// Generates the documentation for `crate` into the directory `dst`
460-
impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
461-
fn descr() -> &'static str {
462-
"html"
463-
}
464-
465-
const RUN_ON_MODULE: bool = true;
466-
type ModuleData = ContextInfo;
467-
468-
fn init(
459+
impl<'tcx> Context<'tcx> {
460+
pub(crate) fn init(
469461
krate: clean::Crate,
470462
options: RenderOptions,
471463
cache: Cache,
472464
tcx: TyCtxt<'tcx>,
465+
expanded_codes: FxHashMap<BytePos, Vec<ExpandedCode>>,
473466
) -> Result<(Self, clean::Crate), Error> {
474467
// need to save a copy of the options for rendering the index page
475468
let md_opts = options.clone();
@@ -488,7 +481,6 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
488481
generate_redirect_map,
489482
show_type_layout,
490483
generate_link_to_definition,
491-
generate_macro_expansion,
492484
call_locations,
493485
no_emit_shared,
494486
html_no_source,
@@ -547,13 +539,12 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
547539
}
548540
}
549541

550-
let (local_sources, matches, expanded_codes) = collect_spans_and_sources(
542+
let (local_sources, matches) = collect_spans_and_sources(
551543
tcx,
552544
&krate,
553545
&src_root,
554546
include_sources,
555547
generate_link_to_definition,
556-
generate_macro_expansion,
557548
);
558549

559550
let (sender, receiver) = channel();
@@ -605,6 +596,16 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
605596

606597
Ok((cx, krate))
607598
}
599+
}
600+
601+
/// Generates the documentation for `crate` into the directory `dst`
602+
impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
603+
fn descr() -> &'static str {
604+
"html"
605+
}
606+
607+
const RUN_ON_MODULE: bool = true;
608+
type ModuleData = ContextInfo;
608609

609610
fn save_module_data(&mut self) -> Self::ModuleData {
610611
self.deref_id_map.borrow_mut().clear();

src/librustdoc/html/render/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ use serde::{Serialize, Serializer};
6464
use tracing::{debug, info};
6565

6666
pub(crate) use self::context::*;
67-
pub(crate) use self::span_map::{ExpandedCode, LinkFromSrc, collect_spans_and_sources};
67+
pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources};
6868
pub(crate) use self::write_shared::*;
6969
use crate::clean::{self, ItemId, RenderedLink};
7070
use crate::display::{Joined as _, MaybeDisplay as _};

0 commit comments

Comments
 (0)