Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ declare_features! (
(internal, field_representing_type_raw, "CURRENT_RUSTC_VERSION", None),
/// Outputs useful `assert!` messages
(unstable, generic_assert, "1.63.0", None),
/// Allows using `arg@` rustdoc disambiguator
(unstable, intra_doc_arg, "CURRENT_RUSTC_VERSION", None),
/// Allows using the #[rustc_intrinsic] attribute.
(internal, intrinsics, "1.0.0", None),
/// Allows using `#[lang = ".."]` attribute for linking items to special compiler logic.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,7 @@ symbols! {
into_future,
into_iter,
into_try_type,
intra_doc_arg,
intra_doc_pointers,
intrinsics,
irrefutable_let_patterns,
Expand Down
24 changes: 19 additions & 5 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,9 @@ impl Item {
.iter()
.filter_map(|ItemLink { link: s, link_text, page_id: id, fragment }| {
debug!(?id);
if let Ok(HrefInfo { mut url, .. }) = href(*id, cx) {
if let Some(id) = id
&& let Ok(HrefInfo { mut url, .. }) = href(*id, cx)
{
debug!(?url);
match fragment {
Some(UrlFragment::Item(def_id)) => {
Expand All @@ -593,7 +595,14 @@ impl Item {
original_text: s.clone(),
new_text: link_text.clone(),
tooltip: link_tooltip(*id, fragment, cx).to_string(),
href: url,
href: Some(url),
})
} else if id.is_none() {
Some(RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
tooltip: format!("function argument {link_text}"),
href: None,
Comment thread
nik-rev marked this conversation as resolved.
})
} else {
None
Expand All @@ -616,7 +625,7 @@ impl Item {
.map(|ItemLink { link: s, link_text, .. }| RenderedLink {
original_text: s.clone(),
new_text: link_text.clone(),
href: String::new(),
href: Some(String::new()),
tooltip: String::new(),
})
.collect()
Expand Down Expand Up @@ -990,7 +999,10 @@ pub(crate) struct ItemLink {
/// The `DefId` of the Item whose **HTML Page** contains the item being
/// linked to. This will be different to `item_id` on item's that don't
/// have their own page, such as struct fields and enum variants.
pub(crate) page_id: DefId,
///
/// If `None`, no link will be generated, but link syntax will be
/// removed. For example, `[arg@n]` will become just `n`.
pub(crate) page_id: Option<DefId>,
/// The url fragment to append to the link
pub(crate) fragment: Option<UrlFragment>,
}
Expand All @@ -1003,7 +1015,9 @@ pub struct RenderedLink {
/// The text to display in the HTML
pub(crate) new_text: Box<str>,
/// The URL to put in the `href`
pub(crate) href: String,
///
/// If this is `None`, no link will be generated at all.
pub(crate) href: Option<String>,
/// The tooltip.
pub(crate) tooltip: String,
}
Expand Down
125 changes: 103 additions & 22 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
struct LinkReplacerInner<'a> {
links: &'a [RenderedLink],
shortcut_link: Option<&'a RenderedLink>,
/// If `true`, the next [`TagEnd::Link`] will be removed
remove_closing_link_tag: bool,
}

struct LinkReplacer<'a, I: Iterator<Item = Event<'a>>> {
Expand All @@ -371,7 +373,12 @@ struct LinkReplacer<'a, I: Iterator<Item = Event<'a>>> {

impl<'a, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, I> {
fn new(iter: I, links: &'a [RenderedLink]) -> Self {
LinkReplacer { iter, inner: { LinkReplacerInner { links, shortcut_link: None } } }
LinkReplacer {
iter,
inner: {
LinkReplacerInner { links, shortcut_link: None, remove_closing_link_tag: false }
},
}
}
}

Expand All @@ -384,7 +391,12 @@ struct SpannedLinkReplacer<'a, I: Iterator<Item = SpannedEvent<'a>>> {

impl<'a, I: Iterator<Item = SpannedEvent<'a>>> SpannedLinkReplacer<'a, I> {
fn new(iter: I, links: &'a [RenderedLink]) -> Self {
SpannedLinkReplacer { iter, inner: { LinkReplacerInner { links, shortcut_link: None } } }
SpannedLinkReplacer {
iter,
inner: {
LinkReplacerInner { links, shortcut_link: None, remove_closing_link_tag: false }
},
}
}
}

Expand All @@ -401,10 +413,22 @@ impl<'a> LinkReplacerInner<'a> {
title,
..
}) => {
// an empty destination URL ("") will be generated when
// rustdoc encounters argument disambiguation syntax: [`arg@foo`], which
// currently won't generate a link - so, the link syntax is simply stripped
if dest_url.is_empty() {
*event = Event::Text("".into());
self.remove_closing_link_tag = true;
return;
}

debug!("saw start of shortcut link to {dest_url} with title {title}");
// If this is a shortcut link, it was resolved by the broken_link_callback.
// So the URL will already be updated properly.
let link = self.links.iter().find(|&link| *link.href == **dest_url);
let link = self
.links
.iter()
.find(|&link| link.href.as_ref().is_none_or(|href| *href == **dest_url));
// Since this is an external iterator, we can't replace the inner text just yet.
// Store that we saw a link so we know to replace it later.
if let Some(link) = link {
Expand All @@ -416,11 +440,49 @@ impl<'a> LinkReplacerInner<'a> {
}
}
}
// Remove the end of link
Event::End(TagEnd::Link) if self.remove_closing_link_tag => {
*event = Event::Text("".into());
self.remove_closing_link_tag = false;
}
// Now that we're done with the shortcut link, don't replace any more text.
Event::End(TagEnd::Link) if self.shortcut_link.is_some() => {
debug!("saw end of shortcut link");
self.shortcut_link = None;
}
// We are currently inside of the link:
//
// [`arg@f`]
//
// And converting that into just:
//
// `f`
Event::Code(text) if self.remove_closing_link_tag => {
if let Some(link) = self.links.iter().find(|link| {
let original_text: &str = &*link.original_text;
let text: &str = text;

// compare contents of inline code block with contents of link. because
// the original link had an inline code block inside, we remove that from the comparison
//
// original link:
// [`fn@f`]
// ^^^^^^ original_text
//
// the Event::Code(text) we received:
// `fn@f`
// ^^^^ text
//
// to compare the 2 for equality, backticks have to be stripped.
let Some(original_text) = original_text.strip_circumfix("`", "`") else {
return false;
};

original_text == text
}) {
*text = CowStr::Borrowed(&link.new_text);
}
}
// Handle backticks in inline code blocks, but only if we're in the middle of a shortcut link.
// [`fn@f`]
Event::Code(text) => {
Expand All @@ -442,6 +504,23 @@ impl<'a> LinkReplacerInner<'a> {
debug!("replacing {text} with {new_text}", new_text = link.new_text);
*text = CowStr::Borrowed(&link.new_text);
}
};
}
// We are currently inside of the link:
//
// [arg@f]
//
// And converting that into just:
//
// f
Event::Text(text) if self.remove_closing_link_tag => {
if let Some(link) = self.links.iter().find(|link| {
let original_text: &str = &*link.original_text;
let text: &str = text;

original_text == text
}) {
*text = CowStr::Borrowed(&link.new_text);
}
}
// Replace plain text in links, but only in the middle of a shortcut link.
Expand All @@ -466,7 +545,13 @@ impl<'a> LinkReplacerInner<'a> {
if let Some(link) =
self.links.iter().find(|&link| *link.original_text == **dest_url)
{
*dest_url = CowStr::Borrowed(link.href.as_ref());
let Some(href) = link.href.as_ref() else {
// Remove link.
*event = Event::Text("".into());
self.remove_closing_link_tag = true;
return;
};
*dest_url = CowStr::Borrowed(href);
if title.is_empty() && !link.tooltip.is_empty() {
*title = CowStr::Borrowed(link.tooltip.as_ref());
}
Expand Down Expand Up @@ -1350,10 +1435,9 @@ impl<'a> Markdown<'a> {
} = self;

let replacer = move |broken_link: BrokenLink<'_>| {
links
.iter()
.find(|link| *link.original_text == *broken_link.reference)
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
links.iter().find(|link| *link.original_text == *broken_link.reference).map(|link| {
(link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into())
})
};

let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(replacer));
Expand Down Expand Up @@ -1428,10 +1512,9 @@ impl MarkdownWithToc<'_> {
return (Toc { entries: Vec::new() }, String::new());
}
let mut replacer = |broken_link: BrokenLink<'_>| {
links
.iter()
.find(|link| *link.original_text == *broken_link.reference)
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
links.iter().find(|link| *link.original_text == *broken_link.reference).map(|link| {
(link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into())
})
};

let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(&mut replacer));
Expand Down Expand Up @@ -1472,10 +1555,9 @@ impl<'a> MarkdownItemInfo<'a> {
}

let replacer = move |broken_link: BrokenLink<'_>| {
links
.iter()
.find(|link| *link.original_text == *broken_link.reference)
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
links.iter().find(|link| *link.original_text == *broken_link.reference).map(|link| {
(link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into())
})
};

let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(replacer));
Expand Down Expand Up @@ -1509,10 +1591,9 @@ impl MarkdownSummaryLine<'_> {
}

let mut replacer = |broken_link: BrokenLink<'_>| {
links
.iter()
.find(|link| *link.original_text == *broken_link.reference)
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
links.iter().find(|link| *link.original_text == *broken_link.reference).map(|link| {
(link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into())
})
};

let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer))
Expand Down Expand Up @@ -1559,7 +1640,7 @@ fn markdown_summary_with_limit(
link_names
.iter()
.find(|link| *link.original_text == *broken_link.reference)
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
.map(|link| (link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into()))
};

let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer));
Expand Down Expand Up @@ -1640,7 +1721,7 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin
link_names
.iter()
.find(|link| *link.original_text == *broken_link.reference)
.map(|link| (link.href.as_str().into(), link.tooltip.as_str().into()))
.map(|link| (link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into()))
};

let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer));
Expand Down
8 changes: 4 additions & 4 deletions src/librustdoc/json/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ impl JsonRenderer<'_> {
.get(&item.item_id)
.into_iter()
.flatten()
.map(|clean::ItemLink { link, page_id, fragment, .. }| {
.flat_map(|clean::ItemLink { link, page_id, fragment, .. }| {
let id = match fragment {
Some(UrlFragment::Item(frag_id)) => *frag_id,
Some(UrlFragment::Item(frag_id)) => Some(*frag_id),
// FIXME: Pass the `UserWritten` segment to JSON consumer.
Some(UrlFragment::UserWritten(_)) | None => *page_id,
Some(UrlFragment::UserWritten(_)) | None => page_id.as_ref().copied(),
};

(String::from(&**link), self.id_from_item_default(id.into()))
id.map(|id| (String::from(&**link), self.id_from_item_default(id.into())))
})
.collect();
let docs = item.opt_doc_value();
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#![feature(iter_intersperse)]
#![feature(iter_order_by)]
#![feature(rustc_private)]
#![feature(strip_circumfix)]
#![feature(test)]
#![feature(trim_prefix_suffix)]
#![recursion_limit = "256"]
Expand Down
Loading
Loading