rustdoc: Cache preprocessed markdown links

This commit is contained in:
Vadim Petrochenkov 2022-04-16 23:59:21 +03:00
parent 72ed101428
commit de287df862
4 changed files with 68 additions and 45 deletions

View file

@ -29,13 +29,13 @@ use crate::clean::inline::build_external_trait;
use crate::clean::{self, ItemId, TraitWithExtraInfo};
use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
use crate::formats::cache::Cache;
use crate::html::markdown::MarkdownLink;
use crate::passes::collect_intra_doc_links::PreprocessedMarkdownLink;
use crate::passes::{self, Condition::*};
crate use rustc_session::config::{DebuggingOptions, Input, Options};
crate struct ResolverCaches {
crate markdown_links: Option<FxHashMap<String, Vec<MarkdownLink>>>,
crate markdown_links: Option<FxHashMap<String, Vec<PreprocessedMarkdownLink>>>,
crate doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option<Res<NodeId>>>,
/// Traits in scope for a given module.
/// See `collect_intra_doc_links::traits_implemented_by` for more details.

View file

@ -1255,7 +1255,7 @@ crate struct MarkdownLink {
pub range: Range<usize>,
}
crate fn markdown_links(md: &str) -> Vec<MarkdownLink> {
crate fn markdown_links<R>(md: &str, filter_map: impl Fn(MarkdownLink) -> Option<R>) -> Vec<R> {
if md.is_empty() {
return vec![];
}
@ -1295,11 +1295,12 @@ crate fn markdown_links(md: &str) -> Vec<MarkdownLink> {
let mut push = |link: BrokenLink<'_>| {
let span = span_for_link(&link.reference, link.span);
links.borrow_mut().push(MarkdownLink {
filter_map(MarkdownLink {
kind: LinkType::ShortcutUnknown,
link: link.reference.to_string(),
range: span,
});
})
.map(|link| links.borrow_mut().push(link));
None
};
let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(&mut push))
@ -1314,7 +1315,8 @@ crate fn markdown_links(md: &str) -> Vec<MarkdownLink> {
if let Event::Start(Tag::Link(kind, dest, _)) = ev.0 {
debug!("found link: {dest}");
let span = span_for_link(&dest, ev.1);
links.borrow_mut().push(MarkdownLink { kind, link: dest.into_string(), range: span });
filter_map(MarkdownLink { kind, link: dest.into_string(), range: span })
.map(|link| links.borrow_mut().push(link));
}
}

View file

@ -160,7 +160,7 @@ impl TryFrom<ResolveRes> for Res {
}
/// A link failed to resolve.
#[derive(Debug)]
#[derive(Clone, Debug)]
enum ResolutionFailure<'a> {
/// This resolved, but with the wrong namespace.
WrongNamespace {
@ -200,7 +200,7 @@ enum ResolutionFailure<'a> {
Dummy,
}
#[derive(Debug)]
#[derive(Clone, Debug)]
enum MalformedGenerics {
/// This link has unbalanced angle brackets.
///
@ -253,6 +253,7 @@ impl ResolutionFailure<'_> {
}
}
#[derive(Clone, Copy)]
enum AnchorFailure {
/// User error: `[std#x#y]` is not valid
MultipleAnchors,
@ -1064,7 +1065,7 @@ impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
.take()
.expect("`markdown_links` are already borrowed");
if !tmp_links.contains_key(&doc) {
tmp_links.insert(doc.clone(), markdown_links(&doc));
tmp_links.insert(doc.clone(), preprocessed_markdown_links(&doc));
}
for md_link in &tmp_links[&doc] {
let link = self.resolve_link(&item, &doc, parent_node, md_link);
@ -1088,18 +1089,19 @@ impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
}
}
enum PreprocessingError<'a> {
enum PreprocessingError {
Anchor(AnchorFailure),
Disambiguator(Range<usize>, String),
Resolution(ResolutionFailure<'a>, String, Option<Disambiguator>),
Resolution(ResolutionFailure<'static>, String, Option<Disambiguator>),
}
impl From<AnchorFailure> for PreprocessingError<'_> {
impl From<AnchorFailure> for PreprocessingError {
fn from(err: AnchorFailure) -> Self {
Self::Anchor(err)
}
}
#[derive(Clone)]
struct PreprocessingInfo {
path_str: String,
disambiguator: Option<Disambiguator>,
@ -1107,15 +1109,18 @@ struct PreprocessingInfo {
link_text: String,
}
// Not a typedef to avoid leaking several private structures from this module.
crate struct PreprocessedMarkdownLink(Result<PreprocessingInfo, PreprocessingError>, MarkdownLink);
/// Returns:
/// - `None` if the link should be ignored.
/// - `Some(Err)` if the link should emit an error
/// - `Some(Ok)` if the link is valid
///
/// `link_buffer` is needed for lifetime reasons; it will always be overwritten and the contents ignored.
fn preprocess_link<'a>(
ori_link: &'a MarkdownLink,
) -> Option<Result<PreprocessingInfo, PreprocessingError<'a>>> {
fn preprocess_link(
ori_link: &MarkdownLink,
) -> Option<Result<PreprocessingInfo, PreprocessingError>> {
// [] is mostly likely not supposed to be a link
if ori_link.link.is_empty() {
return None;
@ -1194,6 +1199,12 @@ fn preprocess_link<'a>(
}))
}
fn preprocessed_markdown_links(s: &str) -> Vec<PreprocessedMarkdownLink> {
markdown_links(s, |link| {
preprocess_link(&link).map(|pp_link| PreprocessedMarkdownLink(pp_link, link))
})
}
impl LinkCollector<'_, '_> {
/// This is the entry point for resolving an intra-doc link.
///
@ -1203,8 +1214,9 @@ impl LinkCollector<'_, '_> {
item: &Item,
dox: &str,
parent_node: Option<DefId>,
ori_link: &MarkdownLink,
link: &PreprocessedMarkdownLink,
) -> Option<ItemLink> {
let PreprocessedMarkdownLink(pp_link, ori_link) = link;
trace!("considering link '{}'", ori_link.link);
let diag_info = DiagnosticInfo {
@ -1214,28 +1226,29 @@ impl LinkCollector<'_, '_> {
link_range: ori_link.range.clone(),
};
let PreprocessingInfo { ref path_str, disambiguator, extra_fragment, link_text } =
match preprocess_link(&ori_link)? {
Ok(x) => x,
Err(err) => {
match err {
PreprocessingError::Anchor(err) => anchor_failure(self.cx, diag_info, err),
PreprocessingError::Disambiguator(range, msg) => {
disambiguator_error(self.cx, diag_info, range, &msg)
}
PreprocessingError::Resolution(err, path_str, disambiguator) => {
resolution_failure(
self,
diag_info,
&path_str,
disambiguator,
smallvec![err],
);
}
let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text } = match pp_link
{
Ok(x) => x,
Err(err) => {
match err {
PreprocessingError::Anchor(err) => anchor_failure(self.cx, diag_info, *err),
PreprocessingError::Disambiguator(range, msg) => {
disambiguator_error(self.cx, diag_info, range.clone(), msg)
}
PreprocessingError::Resolution(err, path_str, disambiguator) => {
resolution_failure(
self,
diag_info,
path_str,
*disambiguator,
smallvec![err.clone()],
);
}
return None;
}
};
return None;
}
};
let disambiguator = *disambiguator;
let inner_docs = item.inner_docs(self.cx.tcx);
@ -1272,7 +1285,7 @@ impl LinkCollector<'_, '_> {
module_id,
dis: disambiguator,
path_str: path_str.to_owned(),
extra_fragment,
extra_fragment: extra_fragment.clone(),
},
diag_info.clone(), // this struct should really be Copy, but Range is not :(
matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut),
@ -1343,7 +1356,7 @@ impl LinkCollector<'_, '_> {
Some(ItemLink {
link: ori_link.link.clone(),
link_text,
link_text: link_text.clone(),
did: res.def_id(self.cx.tcx),
fragment,
})
@ -1365,7 +1378,12 @@ impl LinkCollector<'_, '_> {
&diag_info,
)?;
let id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));
Some(ItemLink { link: ori_link.link.clone(), link_text, did: id, fragment })
Some(ItemLink {
link: ori_link.link.clone(),
link_text: link_text.clone(),
did: id,
fragment,
})
}
}
}

View file

@ -1,7 +1,7 @@
use crate::clean::Attributes;
use crate::core::ResolverCaches;
use crate::html::markdown::{markdown_links, MarkdownLink};
use crate::passes::collect_intra_doc_links::preprocess_link;
use crate::passes::collect_intra_doc_links::preprocessed_markdown_links;
use crate::passes::collect_intra_doc_links::PreprocessedMarkdownLink;
use rustc_ast::visit::{self, AssocCtxt, Visitor};
use rustc_ast::{self as ast, ItemKind};
@ -72,7 +72,7 @@ struct EarlyDocLinkResolver<'r, 'ra> {
resolver: &'r mut Resolver<'ra>,
current_mod: LocalDefId,
visited_mods: DefIdSet,
markdown_links: FxHashMap<String, Vec<MarkdownLink>>,
markdown_links: FxHashMap<String, Vec<PreprocessedMarkdownLink>>,
doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option<Res<ast::NodeId>>>,
traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
all_traits: Vec<DefId>,
@ -203,9 +203,12 @@ impl EarlyDocLinkResolver<'_, '_> {
let mut need_traits_in_scope = false;
for (doc_module, doc) in attrs.collapsed_doc_value_by_module_level() {
assert_eq!(doc_module, None);
for link in self.markdown_links.entry(doc).or_insert_with_key(|doc| markdown_links(doc))
{
if let Some(Ok(pinfo)) = preprocess_link(&link) {
let links = self
.markdown_links
.entry(doc)
.or_insert_with_key(|doc| preprocessed_markdown_links(doc));
for PreprocessedMarkdownLink(pp_link, _) in links {
if let Ok(pinfo) = pp_link {
// FIXME: Resolve the path in all namespaces and resolve its prefixes too.
let ns = TypeNS;
self.doc_link_resolutions