diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index ffe6fea7ea447..b00cefdddb524 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -482,16 +482,16 @@ impl Item { } pub(crate) fn links(&self, cx: &Context<'_>) -> Vec { - use crate::html::format::href; + use crate::html::format::{href, link_tooltip}; cx.cache() .intra_doc_links .get(&self.item_id) .map_or(&[][..], |v| v.as_slice()) .iter() - .filter_map(|ItemLink { link: s, link_text, page_id: did, ref fragment }| { - debug!(?did); - if let Ok((mut href, ..)) = href(*did, cx) { + .filter_map(|ItemLink { link: s, link_text, page_id: id, ref fragment }| { + debug!(?id); + if let Ok((mut href, ..)) = href(*id, cx) { debug!(?href); if let Some(ref fragment) = *fragment { fragment.render(&mut href, cx.tcx()) @@ -499,6 +499,7 @@ impl Item { Some(RenderedLink { original_text: s.clone(), new_text: link_text.clone(), + tooltip: link_tooltip(*id, fragment, cx), href, }) } else { @@ -523,6 +524,7 @@ impl Item { original_text: s.clone(), new_text: link_text.clone(), href: String::new(), + tooltip: String::new(), }) .collect() } @@ -1040,6 +1042,8 @@ pub struct RenderedLink { pub(crate) new_text: String, /// The URL to put in the `href` pub(crate) href: String, + /// The tooltip. + pub(crate) tooltip: String, } /// The attributes on an [`Item`], including attributes like `#[derive(...)]` and `#[inline]`, diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 8a7a8ea5fd1f2..314f061224940 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -34,6 +34,7 @@ use crate::clean::{ use crate::formats::item_type::ItemType; use crate::html::escape::Escape; use crate::html::render::Context; +use crate::passes::collect_intra_doc_links::UrlFragment; use super::url_parts_builder::estimate_item_path_byte_length; use super::url_parts_builder::UrlPartsBuilder; @@ -768,6 +769,21 @@ pub(crate) fn href_relative_parts<'fqp>( } } +pub(crate) fn link_tooltip(did: DefId, fragment: &Option, cx: &Context<'_>) -> String { + let cache = cx.cache(); + let Some((fqp, shortty)) = cache.paths.get(&did) + .or_else(|| cache.external_paths.get(&did)) + else { return String::new() }; + let fqp = fqp.iter().map(|sym| sym.as_str()).join("::"); + if let &Some(UrlFragment::Item(id)) = fragment { + let name = cx.tcx().item_name(id); + let descr = cx.tcx().def_kind(id).descr(id); + format!("{descr} {fqp}::{name}") + } else { + format!("{shortty} {fqp}") + } +} + /// Used to render a [`clean::Path`]. fn resolved_path<'cx>( w: &mut fmt::Formatter<'_>, diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 331804393938f..e4adee6ae4dfb 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -360,6 +360,9 @@ impl<'a, I: Iterator>> Iterator for LinkReplacer<'a, I> { trace!("it matched"); assert!(self.shortcut_link.is_none(), "shortcut links cannot be nested"); self.shortcut_link = Some(link); + if title.is_empty() && !link.tooltip.is_empty() { + *title = CowStr::Borrowed(link.tooltip.as_ref()); + } } } // Now that we're done with the shortcut link, don't replace any more text. @@ -410,9 +413,12 @@ impl<'a, I: Iterator>> Iterator for LinkReplacer<'a, I> { } // If this is a link, but not a shortcut link, // replace the URL, since the broken_link_callback was not called. - Some(Event::Start(Tag::Link(_, dest, _))) => { + Some(Event::Start(Tag::Link(_, dest, title))) => { if let Some(link) = self.links.iter().find(|&link| *link.original_text == **dest) { *dest = CowStr::Borrowed(link.href.as_ref()); + if title.is_empty() && !link.tooltip.is_empty() { + *title = CowStr::Borrowed(link.tooltip.as_ref()); + } } } // Anything else couldn't have been a valid Rust path, so no need to replace the text. @@ -976,7 +982,7 @@ impl Markdown<'_> { links .iter() .find(|link| link.original_text.as_str() == &*broken_link.reference) - .map(|link| (link.href.as_str().into(), link.new_text.as_str().into())) + .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) }; let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(&mut replacer)); @@ -1059,7 +1065,7 @@ impl MarkdownSummaryLine<'_> { links .iter() .find(|link| link.original_text.as_str() == &*broken_link.reference) - .map(|link| (link.href.as_str().into(), link.new_text.as_str().into())) + .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) }; let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer)) @@ -1106,7 +1112,7 @@ fn markdown_summary_with_limit( link_names .iter() .find(|link| link.original_text.as_str() == &*broken_link.reference) - .map(|link| (link.href.as_str().into(), link.new_text.as_str().into())) + .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) }; let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer)); @@ -1187,7 +1193,7 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin link_names .iter() .find(|link| link.original_text.as_str() == &*broken_link.reference) - .map(|link| (link.href.as_str().into(), link.new_text.as_str().into())) + .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) }; let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer)); diff --git a/tests/rustdoc/intra-doc/basic.rs b/tests/rustdoc/intra-doc/basic.rs index 39f5c298bc4a1..e2d3ef425cb45 100644 --- a/tests/rustdoc/intra-doc/basic.rs +++ b/tests/rustdoc/intra-doc/basic.rs @@ -1,21 +1,38 @@ // @has basic/index.html // @has - '//a/@href' 'struct.ThisType.html' +// @has - '//a/@title' 'struct basic::ThisType' // @has - '//a/@href' 'struct.ThisType.html#method.this_method' +// @has - '//a/@title' 'associated function basic::ThisType::this_method' // @has - '//a/@href' 'enum.ThisEnum.html' +// @has - '//a/@title' 'enum basic::ThisEnum' // @has - '//a/@href' 'enum.ThisEnum.html#variant.ThisVariant' +// @has - '//a/@title' 'variant basic::ThisEnum::ThisVariant' // @has - '//a/@href' 'trait.ThisTrait.html' +// @has - '//a/@title' 'trait basic::ThisTrait' // @has - '//a/@href' 'trait.ThisTrait.html#tymethod.this_associated_method' +// @has - '//a/@title' 'associated function basic::ThisTrait::this_associated_method' // @has - '//a/@href' 'trait.ThisTrait.html#associatedtype.ThisAssociatedType' +// @has - '//a/@title' 'associated type basic::ThisTrait::ThisAssociatedType' // @has - '//a/@href' 'trait.ThisTrait.html#associatedconstant.THIS_ASSOCIATED_CONST' +// @has - '//a/@title' 'associated constant basic::ThisTrait::THIS_ASSOCIATED_CONST' // @has - '//a/@href' 'trait.ThisTrait.html' +// @has - '//a/@title' 'trait basic::ThisTrait' // @has - '//a/@href' 'type.ThisAlias.html' +// @has - '//a/@title' 'type basic::ThisAlias' // @has - '//a/@href' 'union.ThisUnion.html' +// @has - '//a/@title' 'union basic::ThisUnion' // @has - '//a/@href' 'fn.this_function.html' +// @has - '//a/@title' 'fn basic::this_function' // @has - '//a/@href' 'constant.THIS_CONST.html' +// @has - '//a/@title' 'constant basic::THIS_CONST' // @has - '//a/@href' 'static.THIS_STATIC.html' +// @has - '//a/@title' 'static basic::THIS_STATIC' // @has - '//a/@href' 'macro.this_macro.html' +// @has - '//a/@title' 'macro basic::this_macro' // @has - '//a/@href' 'trait.SoAmbiguous.html' +// @has - '//a/@title' 'trait basic::SoAmbiguous' // @has - '//a/@href' 'fn.SoAmbiguous.html' +// @has - '//a/@title' 'fn basic::SoAmbiguous' //! In this crate we would like to link to: //! //! * [`ThisType`](ThisType)