//! [RFC 1946]: https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md
use pulldown_cmark::LinkType;
+use rustc_ast::util::comments::may_have_doc_links;
use rustc_data_structures::{fx::FxHashMap, intern::Interned, stable_set::FxHashSet};
use rustc_errors::{Applicability, Diagnostic};
use rustc_hir::def::Namespace::*;
}
/// A link failed to resolve.
-#[derive(Debug)]
+#[derive(Clone, Debug)]
enum ResolutionFailure<'a> {
/// This resolved, but with the wrong namespace.
WrongNamespace {
Dummy,
}
-#[derive(Debug)]
+#[derive(Clone, Debug)]
enum MalformedGenerics {
/// This link has unbalanced angle brackets.
///
}
}
+#[derive(Clone, Copy)]
enum AnchorFailure {
/// User error: `[std#x#y]` is not valid
MultipleAnchors,
// Resolver doesn't know about true, false, and types that aren't paths (e.g. `()`).
let result = self
.cx
- .enter_resolver(|resolver| resolver.resolve_rustdoc_path(path_str, ns, module_id))
+ .resolver_caches
+ .doc_link_resolutions
+ .get(&(Symbol::intern(path_str), ns, module_id))
+ .copied()
+ .unwrap_or_else(|| {
+ self.cx.enter_resolver(|resolver| {
+ resolver.resolve_rustdoc_path(path_str, ns, module_id)
+ })
+ })
.and_then(|res| res.try_into().ok())
.or_else(|| resolve_primitive(path_str, ns))
.or_else(|| self.resolve_macro_rules(path_str, ns));
// In the presence of re-exports, this is not the same as the module of the item.
// Rather than merging all documentation into one, resolve it one attribute at a time
// so we know which module it came from.
- for (parent_module, doc) in item.attrs.collapsed_doc_value_by_module_level() {
+ for (parent_module, doc) in item.attrs.prepare_to_doc_link_resolution() {
+ if !may_have_doc_links(&doc) {
+ continue;
+ }
debug!("combined_docs={}", doc);
// NOTE: if there are links that start in one crate and end in another, this will not resolve them.
// This is a degenerate case and it's not supported by rustdoc.
let parent_node = parent_module.or(parent_node);
- for md_link in markdown_links(&doc) {
+ let mut tmp_links = self
+ .cx
+ .resolver_caches
+ .markdown_links
+ .take()
+ .expect("`markdown_links` are already borrowed");
+ if !tmp_links.contains_key(&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);
if let Some(link) = link {
self.cx.cache.intra_doc_links.entry(item.item_id).or_default().push(link);
}
}
+ self.cx.resolver_caches.markdown_links = Some(tmp_links);
}
if item.is_mod() {
}
}
-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>,
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;
}))
}
+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.
///
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 {
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);
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),
}
Some(ItemLink {
- link: ori_link.link,
- link_text,
+ link: ori_link.link.clone(),
+ link_text: link_text.clone(),
did: res.def_id(self.cx.tcx),
fragment,
})
&diag_info,
)?;
let id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));
- Some(ItemLink { link: ori_link.link, link_text, did: id, fragment })
+ Some(ItemLink {
+ link: ori_link.link.clone(),
+ link_text: link_text.clone(),
+ did: id,
+ fragment,
+ })
}
}
}