Skip to content

Commit

Permalink
Unrolled build for rust-lang#134432
Browse files Browse the repository at this point in the history
Rollup merge of rust-lang#134432 - GuillaumeGomez:intra-doc-in-footnotes, r=notriddle

Fix intra doc links not generated inside footnote definitions

Fixes rust-lang#132208.

The problem was that we were running the `Footnote` "pass" before the `LinkReplacer` one. Sadly, the change is bigger than it should because we can't specialize the `Iterator` trait implementation, forcing me to add a new type to handle the other `Iterator` kind (the one which still has the `Range`).

r? ``@notriddle``
  • Loading branch information
rust-timer authored Dec 19, 2024
2 parents a4079b2 + a01de76 commit 71f93a5
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 16 deletions.
66 changes: 50 additions & 16 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,35 +344,48 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
}

/// Make headings links with anchor IDs and build up TOC.
struct LinkReplacer<'a, I: Iterator<Item = Event<'a>>> {
inner: I,
struct LinkReplacerInner<'a> {
links: &'a [RenderedLink],
shortcut_link: Option<&'a RenderedLink>,
}

struct LinkReplacer<'a, I: Iterator<Item = Event<'a>>> {
iter: I,
inner: LinkReplacerInner<'a>,
}

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

impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
type Item = Event<'a>;
// FIXME: Once we have specialized trait impl (for `Iterator` impl on `LinkReplacer`),
// we can remove this type and move back `LinkReplacerInner` fields into `LinkReplacer`.
struct SpannedLinkReplacer<'a, I: Iterator<Item = SpannedEvent<'a>>> {
iter: I,
inner: LinkReplacerInner<'a>,
}

fn next(&mut self) -> Option<Self::Item> {
let mut event = self.inner.next();
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 } } }
}
}

impl<'a> LinkReplacerInner<'a> {
fn handle_event(&mut self, event: &mut Event<'a>) {
// Replace intra-doc links and remove disambiguators from shortcut links (`[fn@f]`).
match &mut event {
match event {
// This is a shortcut link that was resolved by the broken_link_callback: `[fn@f]`
// Remove any disambiguator.
Some(Event::Start(Tag::Link {
Event::Start(Tag::Link {
// [fn@f] or [fn@f][]
link_type: LinkType::ShortcutUnknown | LinkType::CollapsedUnknown,
dest_url,
title,
..
})) => {
}) => {
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.
Expand All @@ -389,13 +402,13 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
}
}
// Now that we're done with the shortcut link, don't replace any more text.
Some(Event::End(TagEnd::Link)) if self.shortcut_link.is_some() => {
Event::End(TagEnd::Link) if self.shortcut_link.is_some() => {
debug!("saw end of shortcut link");
self.shortcut_link = None;
}
// Handle backticks in inline code blocks, but only if we're in the middle of a shortcut link.
// [`fn@f`]
Some(Event::Code(text)) => {
Event::Code(text) => {
trace!("saw code {text}");
if let Some(link) = self.shortcut_link {
// NOTE: this only replaces if the code block is the *entire* text.
Expand All @@ -418,7 +431,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
}
// Replace plain text in links, but only in the middle of a shortcut link.
// [fn@f]
Some(Event::Text(text)) => {
Event::Text(text) => {
trace!("saw text {text}");
if let Some(link) = self.shortcut_link {
// NOTE: same limitations as `Event::Code`
Expand All @@ -434,7 +447,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> 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_url, title, .. })) => {
Event::Start(Tag::Link { dest_url, title, .. }) => {
if let Some(link) =
self.links.iter().find(|&link| *link.original_text == **dest_url)
{
Expand All @@ -447,12 +460,33 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
// Anything else couldn't have been a valid Rust path, so no need to replace the text.
_ => {}
}
}
}

impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
type Item = Event<'a>;

fn next(&mut self) -> Option<Self::Item> {
let mut event = self.iter.next();
if let Some(ref mut event) = event {
self.inner.handle_event(event);
}
// Yield the modified event
event
}
}

impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for SpannedLinkReplacer<'a, I> {
type Item = SpannedEvent<'a>;

fn next(&mut self) -> Option<Self::Item> {
let Some((mut event, range)) = self.iter.next() else { return None };
self.inner.handle_event(&mut event);
// Yield the modified event
Some((event, range))
}
}

/// Wrap HTML tables into `<div>` to prevent having the doc blocks width being too big.
struct TableWrapper<'a, I: Iterator<Item = Event<'a>>> {
inner: I,
Expand Down Expand Up @@ -1339,9 +1373,9 @@ impl<'a> Markdown<'a> {

ids.handle_footnotes(|ids, existing_footnotes| {
let p = HeadingLinks::new(p, None, ids, heading_offset);
let p = SpannedLinkReplacer::new(p, links);
let p = footnotes::Footnotes::new(p, existing_footnotes);
let p = LinkReplacer::new(p.map(|(ev, _)| ev), links);
let p = TableWrapper::new(p);
let p = TableWrapper::new(p.map(|(ev, _)| ev));
CodeBlocks::new(p, codes, edition, playground)
})
}
Expand Down
24 changes: 24 additions & 0 deletions tests/rustdoc/intra-doc/link-in-footnotes-132208.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Rustdoc has multiple passes and if the footnote pass is run before the link replacer
// one, intra doc links are not generated inside footnote definitions. This test
// therefore ensures that intra-doc link are correctly generated inside footnote
// definitions.
//
// Regression test for <https://github.com/rust-lang/rust/issues/132208>.

#![crate_name = "foo"]

//@ has 'foo/index.html'
//@ has - '//*[@class="docblock"]//a[@href="struct.Bar.html"]' 'a'
//@ has - '//*[@class="docblock"]//*[@class="footnotes"]//a[@href="struct.Foo.html"]' 'b'

//! [a]: crate::Bar
//! [b]: crate::Foo
//!
//! link in body: [a]
//!
//! see footnote[^1]
//!
//! [^1]: link in footnote: [b]
pub struct Bar;
pub struct Foo;

0 comments on commit 71f93a5

Please sign in to comment.