Skip to content

Commit 47614ab

Browse files
authored
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`
2 parents a19ad5e + a01de76 commit 47614ab

File tree

2 files changed

+74
-16
lines changed

2 files changed

+74
-16
lines changed

Diff for: src/librustdoc/html/markdown.rs

+50-16
Original file line numberDiff line numberDiff line change
@@ -344,35 +344,48 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
344344
}
345345

346346
/// Make headings links with anchor IDs and build up TOC.
347-
struct LinkReplacer<'a, I: Iterator<Item = Event<'a>>> {
348-
inner: I,
347+
struct LinkReplacerInner<'a> {
349348
links: &'a [RenderedLink],
350349
shortcut_link: Option<&'a RenderedLink>,
351350
}
352351

352+
struct LinkReplacer<'a, I: Iterator<Item = Event<'a>>> {
353+
iter: I,
354+
inner: LinkReplacerInner<'a>,
355+
}
356+
353357
impl<'a, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, I> {
354358
fn new(iter: I, links: &'a [RenderedLink]) -> Self {
355-
LinkReplacer { inner: iter, links, shortcut_link: None }
359+
LinkReplacer { iter, inner: { LinkReplacerInner { links, shortcut_link: None } } }
356360
}
357361
}
358362

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

362-
fn next(&mut self) -> Option<Self::Item> {
363-
let mut event = self.inner.next();
370+
impl<'a, I: Iterator<Item = SpannedEvent<'a>>> SpannedLinkReplacer<'a, I> {
371+
fn new(iter: I, links: &'a [RenderedLink]) -> Self {
372+
SpannedLinkReplacer { iter, inner: { LinkReplacerInner { links, shortcut_link: None } } }
373+
}
374+
}
364375

376+
impl<'a> LinkReplacerInner<'a> {
377+
fn handle_event(&mut self, event: &mut Event<'a>) {
365378
// Replace intra-doc links and remove disambiguators from shortcut links (`[fn@f]`).
366-
match &mut event {
379+
match event {
367380
// This is a shortcut link that was resolved by the broken_link_callback: `[fn@f]`
368381
// Remove any disambiguator.
369-
Some(Event::Start(Tag::Link {
382+
Event::Start(Tag::Link {
370383
// [fn@f] or [fn@f][]
371384
link_type: LinkType::ShortcutUnknown | LinkType::CollapsedUnknown,
372385
dest_url,
373386
title,
374387
..
375-
})) => {
388+
}) => {
376389
debug!("saw start of shortcut link to {dest_url} with title {title}");
377390
// If this is a shortcut link, it was resolved by the broken_link_callback.
378391
// So the URL will already be updated properly.
@@ -389,13 +402,13 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
389402
}
390403
}
391404
// Now that we're done with the shortcut link, don't replace any more text.
392-
Some(Event::End(TagEnd::Link)) if self.shortcut_link.is_some() => {
405+
Event::End(TagEnd::Link) if self.shortcut_link.is_some() => {
393406
debug!("saw end of shortcut link");
394407
self.shortcut_link = None;
395408
}
396409
// Handle backticks in inline code blocks, but only if we're in the middle of a shortcut link.
397410
// [`fn@f`]
398-
Some(Event::Code(text)) => {
411+
Event::Code(text) => {
399412
trace!("saw code {text}");
400413
if let Some(link) = self.shortcut_link {
401414
// NOTE: this only replaces if the code block is the *entire* text.
@@ -418,7 +431,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
418431
}
419432
// Replace plain text in links, but only in the middle of a shortcut link.
420433
// [fn@f]
421-
Some(Event::Text(text)) => {
434+
Event::Text(text) => {
422435
trace!("saw text {text}");
423436
if let Some(link) = self.shortcut_link {
424437
// NOTE: same limitations as `Event::Code`
@@ -434,7 +447,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
434447
}
435448
// If this is a link, but not a shortcut link,
436449
// replace the URL, since the broken_link_callback was not called.
437-
Some(Event::Start(Tag::Link { dest_url, title, .. })) => {
450+
Event::Start(Tag::Link { dest_url, title, .. }) => {
438451
if let Some(link) =
439452
self.links.iter().find(|&link| *link.original_text == **dest_url)
440453
{
@@ -447,12 +460,33 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
447460
// Anything else couldn't have been a valid Rust path, so no need to replace the text.
448461
_ => {}
449462
}
463+
}
464+
}
465+
466+
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
467+
type Item = Event<'a>;
450468

469+
fn next(&mut self) -> Option<Self::Item> {
470+
let mut event = self.iter.next();
471+
if let Some(ref mut event) = event {
472+
self.inner.handle_event(event);
473+
}
451474
// Yield the modified event
452475
event
453476
}
454477
}
455478

479+
impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for SpannedLinkReplacer<'a, I> {
480+
type Item = SpannedEvent<'a>;
481+
482+
fn next(&mut self) -> Option<Self::Item> {
483+
let Some((mut event, range)) = self.iter.next() else { return None };
484+
self.inner.handle_event(&mut event);
485+
// Yield the modified event
486+
Some((event, range))
487+
}
488+
}
489+
456490
/// Wrap HTML tables into `<div>` to prevent having the doc blocks width being too big.
457491
struct TableWrapper<'a, I: Iterator<Item = Event<'a>>> {
458492
inner: I,
@@ -1339,9 +1373,9 @@ impl<'a> Markdown<'a> {
13391373

13401374
ids.handle_footnotes(|ids, existing_footnotes| {
13411375
let p = HeadingLinks::new(p, None, ids, heading_offset);
1376+
let p = SpannedLinkReplacer::new(p, links);
13421377
let p = footnotes::Footnotes::new(p, existing_footnotes);
1343-
let p = LinkReplacer::new(p.map(|(ev, _)| ev), links);
1344-
let p = TableWrapper::new(p);
1378+
let p = TableWrapper::new(p.map(|(ev, _)| ev));
13451379
CodeBlocks::new(p, codes, edition, playground)
13461380
})
13471381
}

Diff for: tests/rustdoc/intra-doc/link-in-footnotes-132208.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Rustdoc has multiple passes and if the footnote pass is run before the link replacer
2+
// one, intra doc links are not generated inside footnote definitions. This test
3+
// therefore ensures that intra-doc link are correctly generated inside footnote
4+
// definitions.
5+
//
6+
// Regression test for <https://github.com/rust-lang/rust/issues/132208>.
7+
8+
#![crate_name = "foo"]
9+
10+
//@ has 'foo/index.html'
11+
//@ has - '//*[@class="docblock"]//a[@href="struct.Bar.html"]' 'a'
12+
//@ has - '//*[@class="docblock"]//*[@class="footnotes"]//a[@href="struct.Foo.html"]' 'b'
13+
14+
//! [a]: crate::Bar
15+
//! [b]: crate::Foo
16+
//!
17+
//! link in body: [a]
18+
//!
19+
//! see footnote[^1]
20+
//!
21+
//! [^1]: link in footnote: [b]
22+
23+
pub struct Bar;
24+
pub struct Foo;

0 commit comments

Comments
 (0)