Auto merge of #137229 - GuillaumeGomez:expand-macro, r=lolbinarycat
Add support for macro expansion in rustdoc source code pages This is what it looks like:   You can test it [here](https://rustdoc.crud.net/imperio/macro-expansion/src/lib/lib.rs.html). In this case, I also enabled the `--generate-link-to-definition` to show that both options work well together. Note: <del>There is a bug currently in firefox where the line numbers are not displayed correctly if they're inside the "macro expansion" span: https://bugzilla.mozilla.org/show_bug.cgi?id=1949948<del> Found a workaround around this bug. r? `@notriddle`
This commit is contained in:
commit
809200ec95
21 changed files with 795 additions and 93 deletions
|
|
@ -796,3 +796,7 @@ will be split as follows:
|
|||
"you today?",
|
||||
]
|
||||
```
|
||||
|
||||
## `--generate-macro-expansion`: Generate macros expansion toggles in source code
|
||||
|
||||
This flag enables the generation of toggles to expand macros in the HTML source code pages.
|
||||
|
|
|
|||
|
|
@ -305,6 +305,8 @@ pub(crate) struct RenderOptions {
|
|||
pub(crate) parts_out_dir: Option<PathToParts>,
|
||||
/// disable minification of CSS/JS
|
||||
pub(crate) disable_minification: bool,
|
||||
/// If `true`, HTML source pages will generate the possibility to expand macros.
|
||||
pub(crate) generate_macro_expansion: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
|
|
@ -786,6 +788,7 @@ impl Options {
|
|||
let show_type_layout = matches.opt_present("show-type-layout");
|
||||
let nocapture = matches.opt_present("nocapture");
|
||||
let generate_link_to_definition = matches.opt_present("generate-link-to-definition");
|
||||
let generate_macro_expansion = matches.opt_present("generate-macro-expansion");
|
||||
let extern_html_root_takes_precedence =
|
||||
matches.opt_present("extern-html-root-takes-precedence");
|
||||
let html_no_source = matches.opt_present("html-no-source");
|
||||
|
|
@ -801,6 +804,13 @@ impl Options {
|
|||
.with_note("`--generate-link-to-definition` option will be ignored")
|
||||
.emit();
|
||||
}
|
||||
if generate_macro_expansion && (show_coverage || output_format != OutputFormat::Html) {
|
||||
dcx.struct_warn(
|
||||
"`--generate-macro-expansion` option can only be used with HTML output format",
|
||||
)
|
||||
.with_note("`--generate-macro-expansion` option will be ignored")
|
||||
.emit();
|
||||
}
|
||||
|
||||
let scrape_examples_options = ScrapeExamplesOptions::new(matches, dcx);
|
||||
let with_examples = matches.opt_strs("with-examples");
|
||||
|
|
@ -881,6 +891,7 @@ impl Options {
|
|||
unstable_features,
|
||||
emit,
|
||||
generate_link_to_definition,
|
||||
generate_macro_expansion,
|
||||
call_locations,
|
||||
no_emit_shared: false,
|
||||
html_no_source,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ use crate::clean::inline::build_trait;
|
|||
use crate::clean::{self, ItemId};
|
||||
use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
|
||||
use crate::formats::cache::Cache;
|
||||
use crate::html::macro_expansion::{ExpandedCode, source_macro_expansion};
|
||||
use crate::passes;
|
||||
use crate::passes::Condition::*;
|
||||
use crate::passes::collect_intra_doc_links::LinkCollector;
|
||||
|
|
@ -334,11 +335,19 @@ pub(crate) fn run_global_ctxt(
|
|||
show_coverage: bool,
|
||||
render_options: RenderOptions,
|
||||
output_format: OutputFormat,
|
||||
) -> (clean::Crate, RenderOptions, Cache) {
|
||||
) -> (clean::Crate, RenderOptions, Cache, FxHashMap<rustc_span::BytePos, Vec<ExpandedCode>>) {
|
||||
// Certain queries assume that some checks were run elsewhere
|
||||
// (see https://github.com/rust-lang/rust/pull/73566#issuecomment-656954425),
|
||||
// so type-check everything other than function bodies in this crate before running lints.
|
||||
|
||||
let expanded_macros = {
|
||||
// We need for these variables to be removed to ensure that the `Crate` won't be "stolen"
|
||||
// anymore.
|
||||
let (_resolver, krate) = &*tcx.resolver_for_lowering().borrow();
|
||||
|
||||
source_macro_expansion(&krate, &render_options, output_format, tcx.sess.source_map())
|
||||
};
|
||||
|
||||
// NOTE: this does not call `tcx.analysis()` so that we won't
|
||||
// typeck function bodies or run the default rustc lints.
|
||||
// (see `override_queries` in the `config`)
|
||||
|
|
@ -448,7 +457,7 @@ pub(crate) fn run_global_ctxt(
|
|||
|
||||
tcx.dcx().abort_if_errors();
|
||||
|
||||
(krate, ctxt.render_options, ctxt.cache)
|
||||
(krate, ctxt.render_options, ctxt.cache, expanded_macros)
|
||||
}
|
||||
|
||||
/// Due to <https://github.com/rust-lang/rust/pull/73566>,
|
||||
|
|
|
|||
|
|
@ -31,15 +31,6 @@ pub(crate) trait FormatRenderer<'tcx>: Sized {
|
|||
/// reset the information between each call to `item` by using `restore_module_data`.
|
||||
type ModuleData;
|
||||
|
||||
/// Sets up any state required for the renderer. When this is called the cache has already been
|
||||
/// populated.
|
||||
fn init(
|
||||
krate: clean::Crate,
|
||||
options: RenderOptions,
|
||||
cache: Cache,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
) -> Result<(Self, clean::Crate), Error>;
|
||||
|
||||
/// This method is called right before call [`Self::item`]. This method returns a type
|
||||
/// containing information that needs to be reset after the [`Self::item`] method has been
|
||||
/// called with the [`Self::restore_module_data`] method.
|
||||
|
|
@ -105,18 +96,23 @@ fn run_format_inner<'tcx, T: FormatRenderer<'tcx>>(
|
|||
}
|
||||
|
||||
/// Main method for rendering a crate.
|
||||
pub(crate) fn run_format<'tcx, T: FormatRenderer<'tcx>>(
|
||||
pub(crate) fn run_format<
|
||||
'tcx,
|
||||
T: FormatRenderer<'tcx>,
|
||||
F: FnOnce(clean::Crate, RenderOptions, Cache, TyCtxt<'tcx>) -> Result<(T, clean::Crate), Error>,
|
||||
>(
|
||||
krate: clean::Crate,
|
||||
options: RenderOptions,
|
||||
cache: Cache,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
init: F,
|
||||
) -> Result<(), Error> {
|
||||
let prof = &tcx.sess.prof;
|
||||
|
||||
let emit_crate = options.should_emit_crate();
|
||||
let (mut format_renderer, krate) = prof
|
||||
.verbose_generic_activity_with_arg("create_renderer", T::descr())
|
||||
.run(|| T::init(krate, options, cache, tcx))?;
|
||||
.run(|| init(krate, options, cache, tcx))?;
|
||||
|
||||
if !emit_crate {
|
||||
return Ok(());
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
//!
|
||||
//! Use the `render_with_highlighting` to highlight some rust code.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::{self, Display, Write};
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ use rustc_span::{BytePos, DUMMY_SP, Span};
|
|||
use super::format::{self, write_str};
|
||||
use crate::clean::PrimitiveType;
|
||||
use crate::html::escape::EscapeBodyText;
|
||||
use crate::html::macro_expansion::ExpandedCode;
|
||||
use crate::html::render::{Context, LinkFromSrc};
|
||||
|
||||
/// This type is needed in case we want to render links on items to allow to go to their definition.
|
||||
|
|
@ -163,11 +165,22 @@ struct TokenHandler<'a, 'tcx, F: Write> {
|
|||
current_class: Option<Class>,
|
||||
/// We need to keep the `Class` for each element because it could contain a `Span` which is
|
||||
/// used to generate links.
|
||||
pending_elems: Vec<(&'a str, Option<Class>)>,
|
||||
pending_elems: Vec<(Cow<'a, str>, Option<Class>)>,
|
||||
href_context: Option<HrefContext<'a, 'tcx>>,
|
||||
write_line_number: fn(&mut F, u32, &'static str),
|
||||
}
|
||||
|
||||
impl<F: Write> std::fmt::Debug for TokenHandler<'_, '_, F> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TokenHandler")
|
||||
.field("closing_tags", &self.closing_tags)
|
||||
.field("pending_exit_span", &self.pending_exit_span)
|
||||
.field("current_class", &self.current_class)
|
||||
.field("pending_elems", &self.pending_elems)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Write> TokenHandler<'_, '_, F> {
|
||||
fn handle_exit_span(&mut self) {
|
||||
// We can't get the last `closing_tags` element using `pop()` because `closing_tags` is
|
||||
|
|
@ -220,6 +233,10 @@ impl<F: Write> TokenHandler<'_, '_, F> {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
// To prevent opening a macro expansion span being closed right away because
|
||||
// the currently open item is replaced by a new class.
|
||||
let last_pending =
|
||||
self.pending_elems.pop_if(|(_, class)| *class == Some(Class::Expansion));
|
||||
for (text, class) in self.pending_elems.iter() {
|
||||
string(
|
||||
self.out,
|
||||
|
|
@ -233,6 +250,16 @@ impl<F: Write> TokenHandler<'_, '_, F> {
|
|||
if let Some(close_tag) = close_tag {
|
||||
exit_span(self.out, close_tag);
|
||||
}
|
||||
if let Some((text, class)) = last_pending {
|
||||
string(
|
||||
self.out,
|
||||
EscapeBodyText(&text),
|
||||
class,
|
||||
&self.href_context,
|
||||
close_tag.is_none(),
|
||||
self.write_line_number,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.pending_elems.clear();
|
||||
true
|
||||
|
|
@ -271,6 +298,100 @@ fn empty_line_number(out: &mut impl Write, _: u32, extra: &'static str) {
|
|||
out.write_str(extra).unwrap();
|
||||
}
|
||||
|
||||
fn get_next_expansion(
|
||||
expanded_codes: &[ExpandedCode],
|
||||
line: u32,
|
||||
span: Span,
|
||||
) -> Option<&ExpandedCode> {
|
||||
expanded_codes.iter().find(|code| code.start_line == line && code.span.lo() > span.lo())
|
||||
}
|
||||
|
||||
fn get_expansion<'a, W: Write>(
|
||||
token_handler: &mut TokenHandler<'_, '_, W>,
|
||||
expanded_codes: &'a [ExpandedCode],
|
||||
line: u32,
|
||||
span: Span,
|
||||
) -> Option<&'a ExpandedCode> {
|
||||
if let Some(expanded_code) = get_next_expansion(expanded_codes, line, span) {
|
||||
let (closing, reopening) = if let Some(current_class) = token_handler.current_class
|
||||
&& let class = current_class.as_html()
|
||||
&& !class.is_empty()
|
||||
{
|
||||
("</span>", format!("<span class=\"{class}\">"))
|
||||
} else {
|
||||
("", String::new())
|
||||
};
|
||||
let id = format!("expand-{line}");
|
||||
token_handler.pending_elems.push((
|
||||
Cow::Owned(format!(
|
||||
"{closing}\
|
||||
<span class=expansion>\
|
||||
<input id={id} \
|
||||
tabindex=0 \
|
||||
type=checkbox \
|
||||
aria-label=\"Collapse/expand macro\" \
|
||||
title=\"\"Collapse/expand macro\">{reopening}",
|
||||
)),
|
||||
Some(Class::Expansion),
|
||||
));
|
||||
Some(expanded_code)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn start_expansion(out: &mut Vec<(Cow<'_, str>, Option<Class>)>, expanded_code: &ExpandedCode) {
|
||||
out.push((
|
||||
Cow::Owned(format!(
|
||||
"<span class=expanded>{}</span><span class=original>",
|
||||
expanded_code.code,
|
||||
)),
|
||||
Some(Class::Expansion),
|
||||
));
|
||||
}
|
||||
|
||||
fn end_expansion<'a, W: Write>(
|
||||
token_handler: &mut TokenHandler<'_, '_, W>,
|
||||
expanded_codes: &'a [ExpandedCode],
|
||||
expansion_start_tags: &[(&'static str, Class)],
|
||||
line: u32,
|
||||
span: Span,
|
||||
) -> Option<&'a ExpandedCode> {
|
||||
if let Some(expanded_code) = get_next_expansion(expanded_codes, line, span) {
|
||||
// We close the current "original" content.
|
||||
token_handler.pending_elems.push((Cow::Borrowed("</span>"), Some(Class::Expansion)));
|
||||
return Some(expanded_code);
|
||||
}
|
||||
if expansion_start_tags.is_empty() && token_handler.closing_tags.is_empty() {
|
||||
// No need tag opened so we can just close expansion.
|
||||
token_handler.pending_elems.push((Cow::Borrowed("</span></span>"), Some(Class::Expansion)));
|
||||
return None;
|
||||
}
|
||||
|
||||
// If tags were opened inside the expansion, we need to close them and re-open them outside
|
||||
// of the expansion span.
|
||||
let mut out = String::new();
|
||||
let mut end = String::new();
|
||||
|
||||
let mut closing_tags = token_handler.closing_tags.iter().peekable();
|
||||
let mut start_closing_tags = expansion_start_tags.iter().peekable();
|
||||
|
||||
while let (Some(tag), Some(start_tag)) = (closing_tags.peek(), start_closing_tags.peek())
|
||||
&& tag == start_tag
|
||||
{
|
||||
closing_tags.next();
|
||||
start_closing_tags.next();
|
||||
}
|
||||
for (tag, class) in start_closing_tags.chain(closing_tags) {
|
||||
out.push_str(tag);
|
||||
end.push_str(&format!("<span class=\"{}\">", class.as_html()));
|
||||
}
|
||||
token_handler
|
||||
.pending_elems
|
||||
.push((Cow::Owned(format!("</span></span>{out}{end}")), Some(Class::Expansion)));
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct LineInfo {
|
||||
pub(super) start_line: u32,
|
||||
|
|
@ -317,7 +438,7 @@ pub(super) fn write_code(
|
|||
closing_tags: Vec::new(),
|
||||
pending_exit_span: None,
|
||||
current_class: None,
|
||||
pending_elems: Vec::new(),
|
||||
pending_elems: Vec::with_capacity(20),
|
||||
href_context,
|
||||
write_line_number: match line_info {
|
||||
Some(line_info) => {
|
||||
|
|
@ -338,12 +459,23 @@ pub(super) fn write_code(
|
|||
(0, u32::MAX)
|
||||
};
|
||||
|
||||
let (expanded_codes, file_span) = match token_handler.href_context.as_ref().and_then(|c| {
|
||||
let expanded_codes = c.context.shared.expanded_codes.get(&c.file_span.lo())?;
|
||||
Some((expanded_codes, c.file_span))
|
||||
}) {
|
||||
Some((expanded_codes, file_span)) => (expanded_codes.as_slice(), file_span),
|
||||
None => (&[] as &[ExpandedCode], DUMMY_SP),
|
||||
};
|
||||
let mut current_expansion = get_expansion(&mut token_handler, expanded_codes, line, file_span);
|
||||
token_handler.write_pending_elems(None);
|
||||
let mut expansion_start_tags = Vec::new();
|
||||
|
||||
Classifier::new(
|
||||
&src,
|
||||
token_handler.href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
|
||||
decoration_info,
|
||||
)
|
||||
.highlight(&mut |highlight| {
|
||||
.highlight(&mut |span, highlight| {
|
||||
match highlight {
|
||||
Highlight::Token { text, class } => {
|
||||
// If we received a `ExitSpan` event and then have a non-compatible `Class`, we
|
||||
|
|
@ -369,10 +501,42 @@ pub(super) fn write_code(
|
|||
if text == "\n" {
|
||||
line += 1;
|
||||
if line < max_lines {
|
||||
token_handler.pending_elems.push((text, Some(Class::Backline(line))));
|
||||
token_handler
|
||||
.pending_elems
|
||||
.push((Cow::Borrowed(text), Some(Class::Backline(line))));
|
||||
}
|
||||
if current_expansion.is_none() {
|
||||
current_expansion =
|
||||
get_expansion(&mut token_handler, expanded_codes, line, span);
|
||||
expansion_start_tags = token_handler.closing_tags.clone();
|
||||
}
|
||||
if let Some(ref current_expansion) = current_expansion
|
||||
&& current_expansion.span.lo() == span.hi()
|
||||
{
|
||||
start_expansion(&mut token_handler.pending_elems, current_expansion);
|
||||
}
|
||||
} else {
|
||||
token_handler.pending_elems.push((text, class));
|
||||
token_handler.pending_elems.push((Cow::Borrowed(text), class));
|
||||
|
||||
let mut need_end = false;
|
||||
if let Some(ref current_expansion) = current_expansion {
|
||||
if current_expansion.span.lo() == span.hi() {
|
||||
start_expansion(&mut token_handler.pending_elems, current_expansion);
|
||||
} else if current_expansion.end_line == line
|
||||
&& span.hi() >= current_expansion.span.hi()
|
||||
{
|
||||
need_end = true;
|
||||
}
|
||||
}
|
||||
if need_end {
|
||||
current_expansion = end_expansion(
|
||||
&mut token_handler,
|
||||
expanded_codes,
|
||||
&expansion_start_tags,
|
||||
line,
|
||||
span,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Highlight::EnterSpan { class } => {
|
||||
|
|
@ -440,6 +604,8 @@ enum Class {
|
|||
QuestionMark,
|
||||
Decoration(&'static str),
|
||||
Backline(u32),
|
||||
/// Macro expansion.
|
||||
Expansion,
|
||||
}
|
||||
|
||||
impl Class {
|
||||
|
|
@ -489,6 +655,7 @@ impl Class {
|
|||
Class::QuestionMark => "question-mark",
|
||||
Class::Decoration(kind) => kind,
|
||||
Class::Backline(_) => "",
|
||||
Class::Expansion => "",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -513,7 +680,8 @@ impl Class {
|
|||
| Self::Lifetime
|
||||
| Self::QuestionMark
|
||||
| Self::Decoration(_)
|
||||
| Self::Backline(_) => None,
|
||||
| Self::Backline(_)
|
||||
| Self::Expansion => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -628,6 +796,13 @@ impl Decorations {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convenient wrapper to create a [`Span`] from a position in the file.
|
||||
fn new_span(lo: u32, text: &str, file_span: Span) -> Span {
|
||||
let hi = lo + text.len() as u32;
|
||||
let file_lo = file_span.lo();
|
||||
file_span.with_lo(file_lo + BytePos(lo)).with_hi(file_lo + BytePos(hi))
|
||||
}
|
||||
|
||||
/// Processes program tokens, classifying strings of text by highlighting
|
||||
/// category (`Class`).
|
||||
struct Classifier<'src> {
|
||||
|
|
@ -660,13 +835,6 @@ impl<'src> Classifier<'src> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convenient wrapper to create a [`Span`] from a position in the file.
|
||||
fn new_span(&self, lo: u32, text: &str) -> Span {
|
||||
let hi = lo + text.len() as u32;
|
||||
let file_lo = self.file_span.lo();
|
||||
self.file_span.with_lo(file_lo + BytePos(lo)).with_hi(file_lo + BytePos(hi))
|
||||
}
|
||||
|
||||
/// Concatenate colons and idents as one when possible.
|
||||
fn get_full_ident_path(&mut self) -> Vec<(TokenKind, usize, usize)> {
|
||||
let start = self.byte_pos as usize;
|
||||
|
|
@ -735,18 +903,18 @@ impl<'src> Classifier<'src> {
|
|||
/// The general structure for this method is to iterate over each token,
|
||||
/// possibly giving it an HTML span with a class specifying what flavor of
|
||||
/// token is used.
|
||||
fn highlight(mut self, sink: &mut dyn FnMut(Highlight<'src>)) {
|
||||
fn highlight(mut self, sink: &mut dyn FnMut(Span, Highlight<'src>)) {
|
||||
loop {
|
||||
if let Some(decs) = self.decorations.as_mut() {
|
||||
let byte_pos = self.byte_pos;
|
||||
let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count();
|
||||
for (_, kind) in decs.starts.drain(0..n_starts) {
|
||||
sink(Highlight::EnterSpan { class: Class::Decoration(kind) });
|
||||
sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Decoration(kind) });
|
||||
}
|
||||
|
||||
let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count();
|
||||
for _ in decs.ends.drain(0..n_ends) {
|
||||
sink(Highlight::ExitSpan);
|
||||
sink(DUMMY_SP, Highlight::ExitSpan);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -784,14 +952,22 @@ impl<'src> Classifier<'src> {
|
|||
&mut self,
|
||||
token: TokenKind,
|
||||
text: &'src str,
|
||||
sink: &mut dyn FnMut(Highlight<'src>),
|
||||
sink: &mut dyn FnMut(Span, Highlight<'src>),
|
||||
before: u32,
|
||||
) {
|
||||
let lookahead = self.peek();
|
||||
let no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None });
|
||||
let whitespace = |sink: &mut dyn FnMut(_)| {
|
||||
let file_span = self.file_span;
|
||||
let no_highlight = |sink: &mut dyn FnMut(_, _)| {
|
||||
sink(new_span(before, text, file_span), Highlight::Token { text, class: None })
|
||||
};
|
||||
let whitespace = |sink: &mut dyn FnMut(_, _)| {
|
||||
let mut start = 0u32;
|
||||
for part in text.split('\n').intersperse("\n").filter(|s| !s.is_empty()) {
|
||||
sink(Highlight::Token { text: part, class: None });
|
||||
sink(
|
||||
new_span(before + start, part, file_span),
|
||||
Highlight::Token { text: part, class: None },
|
||||
);
|
||||
start += part.len() as u32;
|
||||
}
|
||||
};
|
||||
let class = match token {
|
||||
|
|
@ -807,8 +983,8 @@ impl<'src> Classifier<'src> {
|
|||
// leading identifier.
|
||||
TokenKind::Bang if self.in_macro => {
|
||||
self.in_macro = false;
|
||||
sink(Highlight::Token { text, class: None });
|
||||
sink(Highlight::ExitSpan);
|
||||
sink(new_span(before, text, file_span), Highlight::Token { text, class: None });
|
||||
sink(DUMMY_SP, Highlight::ExitSpan);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -819,12 +995,18 @@ impl<'src> Classifier<'src> {
|
|||
Some((TokenKind::Whitespace, _)) => return whitespace(sink),
|
||||
Some((TokenKind::Ident, "mut")) => {
|
||||
self.next();
|
||||
sink(Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) });
|
||||
sink(
|
||||
DUMMY_SP,
|
||||
Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) },
|
||||
);
|
||||
return;
|
||||
}
|
||||
Some((TokenKind::Ident, "const")) => {
|
||||
self.next();
|
||||
sink(Highlight::Token { text: "*const", class: Some(Class::RefKeyWord) });
|
||||
sink(
|
||||
DUMMY_SP,
|
||||
Highlight::Token { text: "*const", class: Some(Class::RefKeyWord) },
|
||||
);
|
||||
return;
|
||||
}
|
||||
_ => Class::RefKeyWord,
|
||||
|
|
@ -832,18 +1014,21 @@ impl<'src> Classifier<'src> {
|
|||
TokenKind::And => match self.tokens.peek() {
|
||||
Some((TokenKind::And, _)) => {
|
||||
self.next();
|
||||
sink(Highlight::Token { text: "&&", class: None });
|
||||
sink(DUMMY_SP, Highlight::Token { text: "&&", class: None });
|
||||
return;
|
||||
}
|
||||
Some((TokenKind::Eq, _)) => {
|
||||
self.next();
|
||||
sink(Highlight::Token { text: "&=", class: None });
|
||||
sink(DUMMY_SP, Highlight::Token { text: "&=", class: None });
|
||||
return;
|
||||
}
|
||||
Some((TokenKind::Whitespace, _)) => return whitespace(sink),
|
||||
Some((TokenKind::Ident, "mut")) => {
|
||||
self.next();
|
||||
sink(Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) });
|
||||
sink(
|
||||
DUMMY_SP,
|
||||
Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) },
|
||||
);
|
||||
return;
|
||||
}
|
||||
_ => Class::RefKeyWord,
|
||||
|
|
@ -853,19 +1038,19 @@ impl<'src> Classifier<'src> {
|
|||
TokenKind::Eq => match lookahead {
|
||||
Some(TokenKind::Eq) => {
|
||||
self.next();
|
||||
sink(Highlight::Token { text: "==", class: None });
|
||||
sink(DUMMY_SP, Highlight::Token { text: "==", class: None });
|
||||
return;
|
||||
}
|
||||
Some(TokenKind::Gt) => {
|
||||
self.next();
|
||||
sink(Highlight::Token { text: "=>", class: None });
|
||||
sink(DUMMY_SP, Highlight::Token { text: "=>", class: None });
|
||||
return;
|
||||
}
|
||||
_ => return no_highlight(sink),
|
||||
},
|
||||
TokenKind::Minus if lookahead == Some(TokenKind::Gt) => {
|
||||
self.next();
|
||||
sink(Highlight::Token { text: "->", class: None });
|
||||
sink(DUMMY_SP, Highlight::Token { text: "->", class: None });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -916,16 +1101,22 @@ impl<'src> Classifier<'src> {
|
|||
self.next();
|
||||
if let Some(TokenKind::OpenBracket) = self.peek() {
|
||||
self.in_attribute = true;
|
||||
sink(Highlight::EnterSpan { class: Class::Attribute });
|
||||
sink(
|
||||
new_span(before, text, file_span),
|
||||
Highlight::EnterSpan { class: Class::Attribute },
|
||||
);
|
||||
}
|
||||
sink(Highlight::Token { text: "#", class: None });
|
||||
sink(Highlight::Token { text: "!", class: None });
|
||||
sink(DUMMY_SP, Highlight::Token { text: "#", class: None });
|
||||
sink(DUMMY_SP, Highlight::Token { text: "!", class: None });
|
||||
return;
|
||||
}
|
||||
// Case 2: #[outer_attribute]
|
||||
Some(TokenKind::OpenBracket) => {
|
||||
self.in_attribute = true;
|
||||
sink(Highlight::EnterSpan { class: Class::Attribute });
|
||||
sink(
|
||||
new_span(before, text, file_span),
|
||||
Highlight::EnterSpan { class: Class::Attribute },
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
|
@ -934,8 +1125,11 @@ impl<'src> Classifier<'src> {
|
|||
TokenKind::CloseBracket => {
|
||||
if self.in_attribute {
|
||||
self.in_attribute = false;
|
||||
sink(Highlight::Token { text: "]", class: None });
|
||||
sink(Highlight::ExitSpan);
|
||||
sink(
|
||||
new_span(before, text, file_span),
|
||||
Highlight::Token { text: "]", class: None },
|
||||
);
|
||||
sink(DUMMY_SP, Highlight::ExitSpan);
|
||||
return;
|
||||
}
|
||||
return no_highlight(sink);
|
||||
|
|
@ -956,15 +1150,16 @@ impl<'src> Classifier<'src> {
|
|||
TokenKind::GuardedStrPrefix => return no_highlight(sink),
|
||||
TokenKind::Ident | TokenKind::RawIdent if lookahead == Some(TokenKind::Bang) => {
|
||||
self.in_macro = true;
|
||||
sink(Highlight::EnterSpan { class: Class::Macro(self.new_span(before, text)) });
|
||||
sink(Highlight::Token { text, class: None });
|
||||
let span = new_span(before, text, file_span);
|
||||
sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Macro(span) });
|
||||
sink(span, Highlight::Token { text, class: None });
|
||||
return;
|
||||
}
|
||||
TokenKind::Ident => match get_real_ident_class(text, false) {
|
||||
None => match text {
|
||||
"Option" | "Result" => Class::PreludeTy(self.new_span(before, text)),
|
||||
"Option" | "Result" => Class::PreludeTy(new_span(before, text, file_span)),
|
||||
"Some" | "None" | "Ok" | "Err" => {
|
||||
Class::PreludeVal(self.new_span(before, text))
|
||||
Class::PreludeVal(new_span(before, text, file_span))
|
||||
}
|
||||
// "union" is a weak keyword and is only considered as a keyword when declaring
|
||||
// a union type.
|
||||
|
|
@ -973,13 +1168,13 @@ impl<'src> Classifier<'src> {
|
|||
self.in_macro_nonterminal = false;
|
||||
Class::MacroNonTerminal
|
||||
}
|
||||
"self" | "Self" => Class::Self_(self.new_span(before, text)),
|
||||
_ => Class::Ident(self.new_span(before, text)),
|
||||
"self" | "Self" => Class::Self_(new_span(before, text, file_span)),
|
||||
_ => Class::Ident(new_span(before, text, file_span)),
|
||||
},
|
||||
Some(c) => c,
|
||||
},
|
||||
TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => {
|
||||
Class::Ident(self.new_span(before, text))
|
||||
Class::Ident(new_span(before, text, file_span))
|
||||
}
|
||||
TokenKind::Lifetime { .. }
|
||||
| TokenKind::RawLifetime
|
||||
|
|
@ -988,8 +1183,13 @@ impl<'src> Classifier<'src> {
|
|||
};
|
||||
// Anything that didn't return above is the simple case where we the
|
||||
// class just spans a single token, so we can use the `string` method.
|
||||
let mut start = 0u32;
|
||||
for part in text.split('\n').intersperse("\n").filter(|s| !s.is_empty()) {
|
||||
sink(Highlight::Token { text: part, class: Some(class) });
|
||||
sink(
|
||||
new_span(before + start, part, file_span),
|
||||
Highlight::Token { text: part, class: Some(class) },
|
||||
);
|
||||
start += part.len() as u32;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1042,9 +1242,9 @@ fn exit_span(out: &mut impl Write, closing_tag: &str) {
|
|||
/// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function
|
||||
/// will then try to find this `span` in the `span_correspondence_map`. If found, it'll then
|
||||
/// generate a link for this element (which corresponds to where its definition is located).
|
||||
fn string<T: Display, W: Write>(
|
||||
fn string<W: Write>(
|
||||
out: &mut W,
|
||||
text: T,
|
||||
text: EscapeBodyText<'_>,
|
||||
klass: Option<Class>,
|
||||
href_context: &Option<HrefContext<'_, '_>>,
|
||||
open_tag: bool,
|
||||
|
|
@ -1052,6 +1252,9 @@ fn string<T: Display, W: Write>(
|
|||
) {
|
||||
if let Some(Class::Backline(line)) = klass {
|
||||
write_line_number_callback(out, line, "\n");
|
||||
} else if let Some(Class::Expansion) = klass {
|
||||
// This has already been escaped so we get the text to write it directly.
|
||||
out.write_str(text.0).unwrap();
|
||||
} else if let Some(closing_tag) =
|
||||
string_without_closing_tag(out, text, klass, href_context, open_tag)
|
||||
{
|
||||
|
|
|
|||
156
src/librustdoc/html/macro_expansion.rs
Normal file
156
src/librustdoc/html/macro_expansion.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ pub(crate) mod format;
|
|||
pub(crate) mod highlight;
|
||||
pub(crate) mod layout;
|
||||
mod length_limit;
|
||||
pub(crate) mod macro_expansion;
|
||||
// used by the error-index generator, so it needs to be public
|
||||
pub mod markdown;
|
||||
pub(crate) mod render;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use rustc_hir::def_id::{DefIdMap, LOCAL_CRATE};
|
|||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::Session;
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::{FileName, Symbol, sym};
|
||||
use rustc_span::{BytePos, FileName, Symbol, sym};
|
||||
use tracing::info;
|
||||
|
||||
use super::print_item::{full_path, print_item, print_item_path};
|
||||
|
|
@ -28,6 +28,7 @@ use crate::formats::FormatRenderer;
|
|||
use crate::formats::cache::Cache;
|
||||
use crate::formats::item_type::ItemType;
|
||||
use crate::html::escape::Escape;
|
||||
use crate::html::macro_expansion::ExpandedCode;
|
||||
use crate::html::markdown::{self, ErrorCodes, IdMap, plain_text_summary};
|
||||
use crate::html::render::write_shared::write_shared;
|
||||
use crate::html::url_parts_builder::UrlPartsBuilder;
|
||||
|
|
@ -139,6 +140,7 @@ pub(crate) struct SharedContext<'tcx> {
|
|||
/// Correspondence map used to link types used in the source code pages to allow to click on
|
||||
/// links to jump to the type's definition.
|
||||
pub(crate) span_correspondence_map: FxHashMap<rustc_span::Span, LinkFromSrc>,
|
||||
pub(crate) expanded_codes: FxHashMap<BytePos, Vec<ExpandedCode>>,
|
||||
/// The [`Cache`] used during rendering.
|
||||
pub(crate) cache: Cache,
|
||||
pub(crate) call_locations: AllCallLocations,
|
||||
|
|
@ -458,20 +460,13 @@ impl<'tcx> Context<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Generates the documentation for `crate` into the directory `dst`
|
||||
impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
|
||||
fn descr() -> &'static str {
|
||||
"html"
|
||||
}
|
||||
|
||||
const RUN_ON_MODULE: bool = true;
|
||||
type ModuleData = ContextInfo;
|
||||
|
||||
fn init(
|
||||
impl<'tcx> Context<'tcx> {
|
||||
pub(crate) fn init(
|
||||
krate: clean::Crate,
|
||||
options: RenderOptions,
|
||||
cache: Cache,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
expanded_codes: FxHashMap<BytePos, Vec<ExpandedCode>>,
|
||||
) -> Result<(Self, clean::Crate), Error> {
|
||||
// need to save a copy of the options for rendering the index page
|
||||
let md_opts = options.clone();
|
||||
|
|
@ -579,6 +574,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
|
|||
cache,
|
||||
call_locations,
|
||||
should_merge: options.should_merge,
|
||||
expanded_codes,
|
||||
};
|
||||
|
||||
let dst = output;
|
||||
|
|
@ -604,6 +600,16 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
|
|||
|
||||
Ok((cx, krate))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the documentation for `crate` into the directory `dst`
|
||||
impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
|
||||
fn descr() -> &'static str {
|
||||
"html"
|
||||
}
|
||||
|
||||
const RUN_ON_MODULE: bool = true;
|
||||
type ModuleData = ContextInfo;
|
||||
|
||||
fn save_module_data(&mut self) -> Self::ModuleData {
|
||||
self.deref_id_map.borrow_mut().clear();
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ pub(crate) enum LinkFromSrc {
|
|||
/// 1. Generate a `span` correspondence map which links an item `span` to its definition `span`.
|
||||
/// 2. Collect the source code files.
|
||||
///
|
||||
/// It returns the `krate`, the source code files and the `span` correspondence map.
|
||||
/// It returns the source code files and the `span` correspondence map.
|
||||
///
|
||||
/// Note about the `span` correspondence map: the keys are actually `(lo, hi)` of `span`s. We don't
|
||||
/// need the `span` context later on, only their position, so instead of keeping a whole `Span`, we
|
||||
|
|
|
|||
|
|
@ -956,6 +956,40 @@ rustdoc-topbar {
|
|||
.example-wrap.digits-8 { --example-wrap-digits-count: 8ch; }
|
||||
.example-wrap.digits-9 { --example-wrap-digits-count: 9ch; }
|
||||
|
||||
.example-wrap .expansion {
|
||||
position: relative;
|
||||
display: inline;
|
||||
}
|
||||
.example-wrap .expansion > input {
|
||||
display: block;
|
||||
position: absolute;
|
||||
appearance: none;
|
||||
content: '↕';
|
||||
left: -20px;
|
||||
top: 0;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--main-color);
|
||||
padding: 0 2px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.example-wrap .expansion > input::after {
|
||||
content: "↕";
|
||||
}
|
||||
.example-wrap .expansion .expanded {
|
||||
display: none;
|
||||
color: var(--main-color);
|
||||
}
|
||||
.example-wrap .expansion > input:checked ~ .expanded,
|
||||
.example-wrap .expansion > input:checked ~ * .expanded {
|
||||
display: inherit;
|
||||
}
|
||||
.example-wrap .expansion > input:checked ~ .original,
|
||||
.example-wrap .expansion > input:checked ~ * .original {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.example-wrap [data-nosnippet] {
|
||||
width: calc(var(--example-wrap-digits-count) + var(--line-number-padding) * 2);
|
||||
}
|
||||
|
|
@ -964,6 +998,17 @@ rustdoc-topbar {
|
|||
var(--example-wrap-digits-count) + var(--line-number-padding) * 2
|
||||
+ var(--line-number-right-margin));
|
||||
}
|
||||
.src .example-wrap .expansion [data-nosnippet] {
|
||||
/* FIXME: Once <https://bugzilla.mozilla.org/show_bug.cgi?id=1949948> is solved, uncomment
|
||||
next line and remove the two other rules. */
|
||||
/*left: calc((
|
||||
var(--example-wrap-digits-count) + var(--line-number-padding) * 2
|
||||
+ var(--line-number-right-margin)) * -1);*/
|
||||
position: initial;
|
||||
margin-left: calc((
|
||||
var(--example-wrap-digits-count) + var(--line-number-padding) * 2
|
||||
+ var(--line-number-right-margin)) * -1);
|
||||
}
|
||||
|
||||
.example-wrap [data-nosnippet] {
|
||||
color: var(--src-line-numbers-span-color);
|
||||
|
|
@ -978,9 +1023,6 @@ rustdoc-topbar {
|
|||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
.example-wrap .line-highlighted[data-nosnippet] {
|
||||
background-color: var(--src-line-number-highlighted-background-color);
|
||||
}
|
||||
.example-wrap pre > code {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
|
@ -995,6 +1037,9 @@ rustdoc-topbar {
|
|||
.example-wrap [data-nosnippet]:target {
|
||||
border-right: none;
|
||||
}
|
||||
.example-wrap .line-highlighted[data-nosnippet] {
|
||||
background-color: var(--src-line-number-highlighted-background-color);
|
||||
}
|
||||
.example-wrap.hide-lines [data-nosnippet] {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,15 +175,8 @@ fn target(sess: &rustc_session::Session) -> types::Target {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
|
||||
fn descr() -> &'static str {
|
||||
"json"
|
||||
}
|
||||
|
||||
const RUN_ON_MODULE: bool = false;
|
||||
type ModuleData = ();
|
||||
|
||||
fn init(
|
||||
impl<'tcx> JsonRenderer<'tcx> {
|
||||
pub(crate) fn init(
|
||||
krate: clean::Crate,
|
||||
options: RenderOptions,
|
||||
cache: Cache,
|
||||
|
|
@ -205,6 +198,15 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
|
|||
krate,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
|
||||
fn descr() -> &'static str {
|
||||
"json"
|
||||
}
|
||||
|
||||
const RUN_ON_MODULE: bool = false;
|
||||
type ModuleData = ();
|
||||
|
||||
fn save_module_data(&mut self) -> Self::ModuleData {
|
||||
unreachable!("RUN_ON_MODULE = false, should never call save_module_data")
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ use rustc_session::{EarlyDiagCtxt, getopts};
|
|||
use tracing::info;
|
||||
|
||||
use crate::clean::utils::DOC_RUST_LANG_ORG_VERSION;
|
||||
use crate::error::Error;
|
||||
use crate::formats::cache::Cache;
|
||||
|
||||
/// A macro to create a FxHashMap.
|
||||
///
|
||||
|
|
@ -663,6 +665,14 @@ fn opts() -> Vec<RustcOptGroup> {
|
|||
"disable the minification of CSS/JS files (perma-unstable, do not use with cached files)",
|
||||
"",
|
||||
),
|
||||
opt(
|
||||
Unstable,
|
||||
Flag,
|
||||
"",
|
||||
"generate-macro-expansion",
|
||||
"Add possibility to expand macros in the HTML source code pages",
|
||||
"",
|
||||
),
|
||||
// deprecated / removed options
|
||||
opt(
|
||||
Stable,
|
||||
|
|
@ -726,13 +736,23 @@ pub(crate) fn wrap_return(dcx: DiagCtxtHandle<'_>, res: Result<(), String>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>(
|
||||
fn run_renderer<
|
||||
'tcx,
|
||||
T: formats::FormatRenderer<'tcx>,
|
||||
F: FnOnce(
|
||||
clean::Crate,
|
||||
config::RenderOptions,
|
||||
Cache,
|
||||
TyCtxt<'tcx>,
|
||||
) -> Result<(T, clean::Crate), Error>,
|
||||
>(
|
||||
krate: clean::Crate,
|
||||
renderopts: config::RenderOptions,
|
||||
cache: formats::cache::Cache,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
init: F,
|
||||
) {
|
||||
match formats::run_format::<T>(krate, renderopts, cache, tcx) {
|
||||
match formats::run_format::<T, F>(krate, renderopts, cache, tcx, init) {
|
||||
Ok(_) => tcx.dcx().abort_if_errors(),
|
||||
Err(e) => {
|
||||
let mut msg =
|
||||
|
|
@ -862,6 +882,7 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
|
|||
let scrape_examples_options = options.scrape_examples_options.clone();
|
||||
let bin_crate = options.bin_crate;
|
||||
|
||||
let output_format = options.output_format;
|
||||
let config = core::create_config(input, options, &render_options);
|
||||
|
||||
let registered_lints = config.register_lints.is_some();
|
||||
|
|
@ -886,9 +907,10 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
|
|||
sess.dcx().fatal("Compilation failed, aborting rustdoc");
|
||||
}
|
||||
|
||||
let (krate, render_opts, mut cache) = sess.time("run_global_ctxt", || {
|
||||
core::run_global_ctxt(tcx, show_coverage, render_options, output_format)
|
||||
});
|
||||
let (krate, render_opts, mut cache, expanded_macros) = sess
|
||||
.time("run_global_ctxt", || {
|
||||
core::run_global_ctxt(tcx, show_coverage, render_options, output_format)
|
||||
});
|
||||
info!("finished with rustc");
|
||||
|
||||
if let Some(options) = scrape_examples_options {
|
||||
|
|
@ -919,10 +941,24 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
|
|||
info!("going to format");
|
||||
match output_format {
|
||||
config::OutputFormat::Html => sess.time("render_html", || {
|
||||
run_renderer::<html::render::Context<'_>>(krate, render_opts, cache, tcx)
|
||||
run_renderer(
|
||||
krate,
|
||||
render_opts,
|
||||
cache,
|
||||
tcx,
|
||||
|krate, render_opts, cache, tcx| {
|
||||
html::render::Context::init(
|
||||
krate,
|
||||
render_opts,
|
||||
cache,
|
||||
tcx,
|
||||
expanded_macros,
|
||||
)
|
||||
},
|
||||
)
|
||||
}),
|
||||
config::OutputFormat::Json => sess.time("render_json", || {
|
||||
run_renderer::<json::JsonRenderer<'_>>(krate, render_opts, cache, tcx)
|
||||
run_renderer(krate, render_opts, cache, tcx, json::JsonRenderer::init)
|
||||
}),
|
||||
// Already handled above with doctest runners.
|
||||
config::OutputFormat::Doctest => unreachable!(),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ use rustc_span::edition::Edition;
|
|||
use rustc_span::{BytePos, FileName, SourceFile};
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::formats::renderer::FormatRenderer;
|
||||
use crate::html::render::Context;
|
||||
use crate::{clean, config, formats};
|
||||
|
||||
|
|
@ -276,7 +275,8 @@ pub(crate) fn run(
|
|||
let inner = move || -> Result<(), String> {
|
||||
// Generates source files for examples
|
||||
renderopts.no_emit_shared = true;
|
||||
let (cx, _) = Context::init(krate, renderopts, cache, tcx).map_err(|e| e.to_string())?;
|
||||
let (cx, _) = Context::init(krate, renderopts, cache, tcx, Default::default())
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Collect CrateIds corresponding to provided target crates
|
||||
// If two different versions of the crate in the dependency tree, then examples will be
|
||||
|
|
|
|||
|
|
@ -194,6 +194,9 @@ Options:
|
|||
--disable-minification
|
||||
disable the minification of CSS/JS files
|
||||
(perma-unstable, do not use with cached files)
|
||||
--generate-macro-expansion
|
||||
Add possibility to expand macros in the HTML source
|
||||
code pages
|
||||
--plugin-path DIR
|
||||
removed, see issue #44136
|
||||
<https://github.com/rust-lang/rust/issues/44136> for
|
||||
|
|
|
|||
126
tests/rustdoc-gui/macro-expansion.goml
Normal file
126
tests/rustdoc-gui/macro-expansion.goml
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// This test ensures that the macro expansion is generated and working as expected.
|
||||
go-to: "file://" + |DOC_PATH| + "/src/macro_expansion/lib.rs.html"
|
||||
|
||||
define-function: (
|
||||
"check-expansion",
|
||||
[line, original_content],
|
||||
block {
|
||||
assert-text: ("a[id='" + |line| + "'] + .expansion .original", |original_content|)
|
||||
// The "original" content should be expanded.
|
||||
assert-css: ("a[id='" + |line| + "'] + .expansion .original", {"display": "inline"})
|
||||
// The expanded macro should be hidden.
|
||||
assert-css: ("a[id='" + |line| + "'] + .expansion .expanded", {"display": "none"})
|
||||
|
||||
// We "expand" the macro.
|
||||
click: "a[id='" + |line| + "'] + .expansion input[type=checkbox]"
|
||||
// The "original" content is hidden.
|
||||
assert-css: ("a[id='" + |line| + "'] + .expansion .original", {"display": "none"})
|
||||
// The expanded macro is visible.
|
||||
assert-css: ("a[id='" + |line| + "'] + .expansion .expanded", {"display": "inline"})
|
||||
|
||||
// We collapse the macro.
|
||||
click: "a[id='" + |line| + "'] + .expansion input[type=checkbox]"
|
||||
// The "original" content is expanded.
|
||||
assert-css: ("a[id='" + |line| + "'] + .expansion .original", {"display": "inline"})
|
||||
// The expanded macro is hidden.
|
||||
assert-css: ("a[id='" + |line| + "'] + .expansion .expanded", {"display": "none"})
|
||||
}
|
||||
)
|
||||
|
||||
// First we check the derive macro expansion at line 33.
|
||||
call-function: ("check-expansion", {"line": 35, "original_content": "Debug"})
|
||||
// Then we check the `bar` macro expansion at line 41.
|
||||
call-function: ("check-expansion", {"line": 43, "original_content": "bar!(y)"})
|
||||
// Then we check the `println` macro expansion at line 42-44.
|
||||
call-function: ("check-expansion", {"line": 44, "original_content": 'println!("
|
||||
45 {y}
|
||||
46 ")'})
|
||||
|
||||
// Then finally we check when there are two macro calls on a same line.
|
||||
assert-count: ("#expand-52 ~ .original", 2)
|
||||
assert-count: ("#expand-52 ~ .expanded", 2)
|
||||
|
||||
store-value: (repeat_o, '/following-sibling::*[@class="original"]')
|
||||
store-value: (repeat_e, '/following-sibling::*[@class="expanded"]')
|
||||
store-value: (expand_id, "expand-52")
|
||||
assert-text: ('//*[@id="' + |expand_id| + '"]' + |repeat_o|, "stringify!(foo)")
|
||||
assert-text: ('//*[@id="' + |expand_id| + '"]' + |repeat_o| + |repeat_o|, "stringify!(bar)")
|
||||
assert-text: ('//*[@id="' + |expand_id| + '"]' + |repeat_e|, '"foo"')
|
||||
assert-text: ('//*[@id="' + |expand_id| + '"]' + |repeat_e| + |repeat_e|, '"bar"')
|
||||
|
||||
// The "original" content should be expanded.
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_o|, {"display": "inline"})
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_o| + |repeat_o|, {"display": "inline"})
|
||||
// The expanded macro should be hidden.
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_e|, {"display": "none"})
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_e| + |repeat_e|, {"display": "none"})
|
||||
|
||||
// We "expand" the macro (because the line starts with a string, the label is not at the "top
|
||||
// level" of the `<code>`, so we need to use a different selector).
|
||||
click: "#" + |expand_id|
|
||||
// The "original" content is hidden.
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_o|, {"display": "none"})
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_o| + |repeat_o|, {"display": "none"})
|
||||
// The expanded macro is visible.
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_e|, {"display": "inline"})
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_e| + |repeat_e|, {"display": "inline"})
|
||||
|
||||
// We collapse the macro.
|
||||
click: "#" + |expand_id|
|
||||
// The "original" content is expanded.
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_o|, {"display": "inline"})
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_o| + |repeat_o|, {"display": "inline"})
|
||||
// The expanded macro is hidden.
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_e|, {"display": "none"})
|
||||
assert-css: ('//*[@id="' + |expand_id| + '"]' + |repeat_e| + |repeat_e|, {"display": "none"})
|
||||
|
||||
// Checking the line 48 `println` which needs to be handled differently because the line number is
|
||||
// inside a "comment" span.
|
||||
store-value: (expand_id, "expand-48")
|
||||
assert-text: ("#" + |expand_id| + " ~ .original", 'println!("
|
||||
49 {y}
|
||||
50 ")')
|
||||
// The "original" content should be expanded.
|
||||
assert-css: ("#" + |expand_id| + " ~ .original", {"display": "inline"})
|
||||
// The expanded macro should be hidden.
|
||||
assert-css: ("#" + |expand_id| + " ~ .expanded", {"display": "none"})
|
||||
|
||||
// We "expand" the macro.
|
||||
click: "#" + |expand_id|
|
||||
// The "original" content is hidden.
|
||||
assert-css: ("#" + |expand_id| + " ~ .original", {"display": "none"})
|
||||
// The expanded macro is visible.
|
||||
assert-css: ("#" + |expand_id| + " ~ .expanded", {"display": "inline"})
|
||||
|
||||
// We collapse the macro.
|
||||
click: "#" + |expand_id|
|
||||
// The "original" content is expanded.
|
||||
assert-css: ("#" + |expand_id| + " ~ .original", {"display": "inline"})
|
||||
// The expanded macro is hidden.
|
||||
assert-css: ("#" + |expand_id| + " ~ .expanded", {"display": "none"})
|
||||
|
||||
// Ensure that the toggles are focusable and can be interacted with keyboard.
|
||||
focus: "//a[@id='29']"
|
||||
press-key: "Tab"
|
||||
store-value: (expand_id, "expand-29")
|
||||
assert: "#" + |expand_id| + ":focus"
|
||||
assert-css: ("#" + |expand_id| +" ~ .expanded", {"display": "none"})
|
||||
assert-css: ("#" + |expand_id| +" ~ .original", {"display": "inline"})
|
||||
// We now expand the macro.
|
||||
press-key: "Space"
|
||||
assert-css: ("#" + |expand_id| + " ~ .expanded", {"display": "inline"})
|
||||
assert-css: ("#" + |expand_id| + " ~ .original", {"display": "none"})
|
||||
// We collapse the macro.
|
||||
press-key: "Space"
|
||||
assert-css: ("#" + |expand_id| + " ~ .expanded", {"display": "none"})
|
||||
assert-css: ("#" + |expand_id| + " ~ .original", {"display": "inline"})
|
||||
|
||||
// Now we check a macro coming from another file.
|
||||
store-value: (expand_id, "expand-55")
|
||||
// We "expand" the macro.
|
||||
click: "#" + |expand_id|
|
||||
// The "original" content is hidden.
|
||||
assert-css: ("#" + |expand_id| + " ~ .original", {"display": "none"})
|
||||
// The expanded macro is visible.
|
||||
assert-css: ("#" + |expand_id| + " ~ .expanded", {"display": "inline"})
|
||||
assert-text: ("#" + |expand_id| + " ~ .expanded", "{ y += 2; };")
|
||||
|
|
@ -71,7 +71,7 @@ assert: "//*[@class='dir-entry' and @open]/*[normalize-space()='sub_mod']"
|
|||
// Only "another_folder" should be "open" in "lib2".
|
||||
assert: "//*[@class='dir-entry' and not(@open)]/*[normalize-space()='another_mod']"
|
||||
// All other trees should be collapsed.
|
||||
assert-count: ("//*[@id='src-sidebar']/details[not(normalize-space()='lib2') and not(@open)]", 11)
|
||||
assert-count: ("//*[@id='src-sidebar']/details[not(normalize-space()='lib2') and not(@open)]", 12)
|
||||
|
||||
// We now switch to mobile mode.
|
||||
set-window-size: (600, 600)
|
||||
|
|
|
|||
7
tests/rustdoc-gui/src/macro_expansion/Cargo.lock
Normal file
7
tests/rustdoc-gui/src/macro_expansion/Cargo.lock
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "macro_expansion"
|
||||
version = "0.1.0"
|
||||
7
tests/rustdoc-gui/src/macro_expansion/Cargo.toml
Normal file
7
tests/rustdoc-gui/src/macro_expansion/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "macro_expansion"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
56
tests/rustdoc-gui/src/macro_expansion/lib.rs
Normal file
56
tests/rustdoc-gui/src/macro_expansion/lib.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// Test crate used to check the `--generate-macro-expansion` option.
|
||||
//@ compile-flags: -Zunstable-options --generate-macro-expansion --generate-link-to-definition
|
||||
|
||||
mod other;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! bar {
|
||||
($x:ident) => {{
|
||||
$x += 2;
|
||||
$x *= 2;
|
||||
}}
|
||||
}
|
||||
|
||||
macro_rules! bar2 {
|
||||
() => {
|
||||
fn foo2() -> impl std::fmt::Display {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! bar3 {
|
||||
() => {
|
||||
fn foo3() {}
|
||||
fn foo4() -> String { String::new() }
|
||||
}
|
||||
}
|
||||
|
||||
bar2!();
|
||||
bar3!();
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Bar;
|
||||
|
||||
#[derive(Debug
|
||||
)]
|
||||
pub struct Bar2;
|
||||
|
||||
fn y_f(_: &str, _: &str, _: &str) {}
|
||||
|
||||
fn foo() {
|
||||
let mut y = 0;
|
||||
bar!(y);
|
||||
println!("
|
||||
{y}
|
||||
");
|
||||
// comment
|
||||
println!("
|
||||
{y}
|
||||
");
|
||||
let s = y_f("\
|
||||
bla", stringify!(foo), stringify!(bar));
|
||||
|
||||
// Macro from another file.
|
||||
other_macro!(y);
|
||||
}
|
||||
6
tests/rustdoc-gui/src/macro_expansion/other.rs
Normal file
6
tests/rustdoc-gui/src/macro_expansion/other.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#[macro_export]
|
||||
macro_rules! other_macro {
|
||||
($x:ident) => {{
|
||||
$x += 2;
|
||||
}}
|
||||
}
|
||||
28
tests/rustdoc/macro/macro_expansion.rs
Normal file
28
tests/rustdoc/macro/macro_expansion.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// This test checks that patterns and statements are also getting expanded.
|
||||
|
||||
//@ compile-flags: -Zunstable-options --generate-macro-expansion
|
||||
|
||||
#![crate_name = "foo"]
|
||||
|
||||
//@ has 'src/foo/macro_expansion.rs.html'
|
||||
//@ count - '//span[@class="expansion"]' 2
|
||||
|
||||
macro_rules! pat {
|
||||
($x:literal) => {
|
||||
Some($x)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! stmt {
|
||||
($x:expr) => {{
|
||||
let _ = $x;
|
||||
}}
|
||||
}
|
||||
|
||||
fn bar() {
|
||||
match Some("hello") {
|
||||
pat!("blolb") => {}
|
||||
_ => {}
|
||||
}
|
||||
stmt!(1)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue