Skip to content

Commit ed39e6d

Browse files
authored
Rollup merge of #76078 - jyn514:no-disambiguator, r=manishearth
Remove disambiguators from intra doc link text Closes #65354. r? @Manishearth The commits are mostly atomic, but there might be some mix between them here and there. I recommend reading 'refactor ItemLink' and 'refactor RenderedLink' on their own though, lots of churn without any logic changes.
2 parents 86cf797 + 18c14fd commit ed39e6d

File tree

5 files changed

+229
-40
lines changed

5 files changed

+229
-40
lines changed

src/librustdoc/clean/types.rs

+43-10
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ impl Item {
118118
self.attrs.collapsed_doc_value()
119119
}
120120

121-
pub fn links(&self) -> Vec<(String, String)> {
121+
pub fn links(&self) -> Vec<RenderedLink> {
122122
self.attrs.links(&self.def_id.krate)
123123
}
124124

@@ -425,10 +425,38 @@ pub struct Attributes {
425425
pub cfg: Option<Arc<Cfg>>,
426426
pub span: Option<rustc_span::Span>,
427427
/// map from Rust paths to resolved defs and potential URL fragments
428-
pub links: Vec<(String, Option<DefId>, Option<String>)>,
428+
pub links: Vec<ItemLink>,
429429
pub inner_docs: bool,
430430
}
431431

432+
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
433+
/// A link that has not yet been rendered.
434+
///
435+
/// This link will be turned into a rendered link by [`Attributes::links`]
436+
pub struct ItemLink {
437+
/// The original link written in the markdown
438+
pub(crate) link: String,
439+
/// The link text displayed in the HTML.
440+
///
441+
/// This may not be the same as `link` if there was a disambiguator
442+
/// in an intra-doc link (e.g. \[`fn@f`\])
443+
pub(crate) link_text: String,
444+
pub(crate) did: Option<DefId>,
445+
/// The url fragment to append to the link
446+
pub(crate) fragment: Option<String>,
447+
}
448+
449+
pub struct RenderedLink {
450+
/// The text the link was original written as.
451+
///
452+
/// This could potentially include disambiguators and backticks.
453+
pub(crate) original_text: String,
454+
/// The text to display in the HTML
455+
pub(crate) new_text: String,
456+
/// The URL to put in the `href`
457+
pub(crate) href: String,
458+
}
459+
432460
impl Attributes {
433461
/// Extracts the content from an attribute `#[doc(cfg(content))]`.
434462
pub fn extract_cfg(mi: &ast::MetaItem) -> Option<&ast::MetaItem> {
@@ -605,21 +633,25 @@ impl Attributes {
605633
/// Gets links as a vector
606634
///
607635
/// Cache must be populated before call
608-
pub fn links(&self, krate: &CrateNum) -> Vec<(String, String)> {
636+
pub fn links(&self, krate: &CrateNum) -> Vec<RenderedLink> {
609637
use crate::html::format::href;
610638
use crate::html::render::CURRENT_DEPTH;
611639

612640
self.links
613641
.iter()
614-
.filter_map(|&(ref s, did, ref fragment)| {
615-
match did {
642+
.filter_map(|ItemLink { link: s, link_text, did, fragment }| {
643+
match *did {
616644
Some(did) => {
617645
if let Some((mut href, ..)) = href(did) {
618646
if let Some(ref fragment) = *fragment {
619647
href.push_str("#");
620648
href.push_str(fragment);
621649
}
622-
Some((s.clone(), href))
650+
Some(RenderedLink {
651+
original_text: s.clone(),
652+
new_text: link_text.clone(),
653+
href,
654+
})
623655
} else {
624656
None
625657
}
@@ -639,16 +671,17 @@ impl Attributes {
639671
};
640672
// This is a primitive so the url is done "by hand".
641673
let tail = fragment.find('#').unwrap_or_else(|| fragment.len());
642-
Some((
643-
s.clone(),
644-
format!(
674+
Some(RenderedLink {
675+
original_text: s.clone(),
676+
new_text: link_text.clone(),
677+
href: format!(
645678
"{}{}std/primitive.{}.html{}",
646679
url,
647680
if !url.ends_with('/') { "/" } else { "" },
648681
&fragment[..tail],
649682
&fragment[tail..]
650683
),
651-
))
684+
})
652685
} else {
653686
panic!("This isn't a primitive?!");
654687
}

src/librustdoc/html/markdown.rs

+97-20
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use std::fmt::Write;
3434
use std::ops::Range;
3535
use std::str;
3636

37+
use crate::clean::RenderedLink;
3738
use crate::doctest;
3839
use crate::html::highlight;
3940
use crate::html::toc::TocBuilder;
@@ -52,7 +53,7 @@ fn opts() -> Options {
5253
pub struct Markdown<'a>(
5354
pub &'a str,
5455
/// A list of link replacements.
55-
pub &'a [(String, String)],
56+
pub &'a [RenderedLink],
5657
/// The current list of used header IDs.
5758
pub &'a mut IdMap,
5859
/// Whether to allow the use of explicit error codes in doctest lang strings.
@@ -78,7 +79,7 @@ pub struct MarkdownHtml<'a>(
7879
pub &'a Option<Playground>,
7980
);
8081
/// A tuple struct like `Markdown` that renders only the first paragraph.
81-
pub struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [(String, String)]);
82+
pub struct MarkdownSummaryLine<'a>(pub &'a str, pub &'a [RenderedLink]);
8283

8384
#[derive(Copy, Clone, PartialEq, Debug)]
8485
pub enum ErrorCodes {
@@ -337,31 +338,107 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
337338
}
338339

339340
/// Make headings links with anchor IDs and build up TOC.
340-
struct LinkReplacer<'a, 'b, I: Iterator<Item = Event<'a>>> {
341+
struct LinkReplacer<'a, I: Iterator<Item = Event<'a>>> {
341342
inner: I,
342-
links: &'b [(String, String)],
343+
links: &'a [RenderedLink],
344+
shortcut_link: Option<&'a RenderedLink>,
343345
}
344346

345-
impl<'a, 'b, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, 'b, I> {
346-
fn new(iter: I, links: &'b [(String, String)]) -> Self {
347-
LinkReplacer { inner: iter, links }
347+
impl<'a, I: Iterator<Item = Event<'a>>> LinkReplacer<'a, I> {
348+
fn new(iter: I, links: &'a [RenderedLink]) -> Self {
349+
LinkReplacer { inner: iter, links, shortcut_link: None }
348350
}
349351
}
350352

351-
impl<'a, 'b, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, 'b, I> {
353+
impl<'a, I: Iterator<Item = Event<'a>>> Iterator for LinkReplacer<'a, I> {
352354
type Item = Event<'a>;
353355

354356
fn next(&mut self) -> Option<Self::Item> {
355-
let event = self.inner.next();
356-
if let Some(Event::Start(Tag::Link(kind, dest, text))) = event {
357-
if let Some(&(_, ref replace)) = self.links.iter().find(|link| link.0 == *dest) {
358-
Some(Event::Start(Tag::Link(kind, replace.to_owned().into(), text)))
359-
} else {
360-
Some(Event::Start(Tag::Link(kind, dest, text)))
357+
use pulldown_cmark::LinkType;
358+
359+
let mut event = self.inner.next();
360+
361+
// Replace intra-doc links and remove disambiguators from shortcut links (`[fn@f]`).
362+
match &mut event {
363+
// This is a shortcut link that was resolved by the broken_link_callback: `[fn@f]`
364+
// Remove any disambiguator.
365+
Some(Event::Start(Tag::Link(
366+
// [fn@f] or [fn@f][]
367+
LinkType::ShortcutUnknown | LinkType::CollapsedUnknown,
368+
dest,
369+
title,
370+
))) => {
371+
debug!("saw start of shortcut link to {} with title {}", dest, title);
372+
// If this is a shortcut link, it was resolved by the broken_link_callback.
373+
// So the URL will already be updated properly.
374+
let link = self.links.iter().find(|&link| *link.href == **dest);
375+
// Since this is an external iterator, we can't replace the inner text just yet.
376+
// Store that we saw a link so we know to replace it later.
377+
if let Some(link) = link {
378+
trace!("it matched");
379+
assert!(self.shortcut_link.is_none(), "shortcut links cannot be nested");
380+
self.shortcut_link = Some(link);
381+
}
361382
}
362-
} else {
363-
event
383+
// Now that we're done with the shortcut link, don't replace any more text.
384+
Some(Event::End(Tag::Link(
385+
LinkType::ShortcutUnknown | LinkType::CollapsedUnknown,
386+
dest,
387+
_,
388+
))) => {
389+
debug!("saw end of shortcut link to {}", dest);
390+
if self.links.iter().find(|&link| *link.href == **dest).is_some() {
391+
assert!(self.shortcut_link.is_some(), "saw closing link without opening tag");
392+
self.shortcut_link = None;
393+
}
394+
}
395+
// Handle backticks in inline code blocks, but only if we're in the middle of a shortcut link.
396+
// [`fn@f`]
397+
Some(Event::Code(text)) => {
398+
trace!("saw code {}", text);
399+
if let Some(link) = self.shortcut_link {
400+
trace!("original text was {}", link.original_text);
401+
// NOTE: this only replaces if the code block is the *entire* text.
402+
// If only part of the link has code highlighting, the disambiguator will not be removed.
403+
// e.g. [fn@`f`]
404+
// This is a limitation from `collect_intra_doc_links`: it passes a full link,
405+
// and does not distinguish at all between code blocks.
406+
// So we could never be sure we weren't replacing too much:
407+
// [fn@my_`f`unc] is treated the same as [my_func()] in that pass.
408+
//
409+
// NOTE: &[1..len() - 1] is to strip the backticks
410+
if **text == link.original_text[1..link.original_text.len() - 1] {
411+
debug!("replacing {} with {}", text, link.new_text);
412+
*text = CowStr::Borrowed(&link.new_text);
413+
}
414+
}
415+
}
416+
// Replace plain text in links, but only in the middle of a shortcut link.
417+
// [fn@f]
418+
Some(Event::Text(text)) => {
419+
trace!("saw text {}", text);
420+
if let Some(link) = self.shortcut_link {
421+
trace!("original text was {}", link.original_text);
422+
// NOTE: same limitations as `Event::Code`
423+
if **text == *link.original_text {
424+
debug!("replacing {} with {}", text, link.new_text);
425+
*text = CowStr::Borrowed(&link.new_text);
426+
}
427+
}
428+
}
429+
// If this is a link, but not a shortcut link,
430+
// replace the URL, since the broken_link_callback was not called.
431+
Some(Event::Start(Tag::Link(_, dest, _))) => {
432+
if let Some(link) = self.links.iter().find(|&link| *link.original_text == **dest) {
433+
*dest = CowStr::Borrowed(link.href.as_ref());
434+
}
435+
}
436+
// Anything else couldn't have been a valid Rust path, so no need to replace the text.
437+
_ => {}
364438
}
439+
440+
// Yield the modified event
441+
event
365442
}
366443
}
367444

@@ -855,8 +932,8 @@ impl Markdown<'_> {
855932
return String::new();
856933
}
857934
let replacer = |_: &str, s: &str| {
858-
if let Some(&(_, ref replace)) = links.iter().find(|link| &*link.0 == s) {
859-
Some((replace.clone(), s.to_owned()))
935+
if let Some(link) = links.iter().find(|link| &*link.original_text == s) {
936+
Some((link.href.clone(), link.new_text.clone()))
860937
} else {
861938
None
862939
}
@@ -933,8 +1010,8 @@ impl MarkdownSummaryLine<'_> {
9331010
}
9341011

9351012
let replacer = |_: &str, s: &str| {
936-
if let Some(&(_, ref replace)) = links.iter().find(|link| &*link.0 == s) {
937-
Some((replace.clone(), s.to_owned()))
1013+
if let Some(link) = links.iter().find(|link| &*link.original_text == s) {
1014+
Some((link.href.clone(), link.new_text.clone()))
9381015
} else {
9391016
None
9401017
}

src/librustdoc/html/render/mod.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,8 @@ use rustc_span::symbol::{sym, Symbol};
6363
use serde::ser::SerializeSeq;
6464
use serde::{Serialize, Serializer};
6565

66-
use crate::clean::{self, AttributesExt, Deprecation, GetDefId, SelfTy, TypeKind};
67-
use crate::config::RenderInfo;
68-
use crate::config::RenderOptions;
66+
use crate::clean::{self, AttributesExt, Deprecation, GetDefId, RenderedLink, SelfTy, TypeKind};
67+
use crate::config::{RenderInfo, RenderOptions};
6968
use crate::docfs::{DocFS, PathError};
7069
use crate::doctree;
7170
use crate::error::Error;
@@ -1774,7 +1773,7 @@ fn render_markdown(
17741773
w: &mut Buffer,
17751774
cx: &Context,
17761775
md_text: &str,
1777-
links: Vec<(String, String)>,
1776+
links: Vec<RenderedLink>,
17781777
prefix: &str,
17791778
is_hidden: bool,
17801779
) {

src/librustdoc/passes/collect_intra_doc_links.rs

+35-6
Original file line numberDiff line numberDiff line change
@@ -697,11 +697,12 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
697697
// This is an anchor to an element of the current page, nothing to do in here!
698698
continue;
699699
}
700-
(parts[0].to_owned(), Some(parts[1].to_owned()))
700+
(parts[0], Some(parts[1].to_owned()))
701701
} else {
702-
(parts[0].to_owned(), None)
702+
(parts[0], None)
703703
};
704704
let resolved_self;
705+
let link_text;
705706
let mut path_str;
706707
let disambiguator;
707708
let (mut res, mut fragment) = {
@@ -718,6 +719,12 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
718719
continue;
719720
}
720721

722+
// We stripped `()` and `!` when parsing the disambiguator.
723+
// Add them back to be displayed, but not prefix disambiguators.
724+
link_text = disambiguator
725+
.map(|d| d.display_for(path_str))
726+
.unwrap_or_else(|| path_str.to_owned());
727+
721728
// In order to correctly resolve intra-doc-links we need to
722729
// pick a base AST node to work from. If the documentation for
723730
// this module came from an inner comment (//!) then we anchor
@@ -906,7 +913,12 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
906913
if let Res::PrimTy(_) = res {
907914
match disambiguator {
908915
Some(Disambiguator::Primitive | Disambiguator::Namespace(_)) | None => {
909-
item.attrs.links.push((ori_link, None, fragment))
916+
item.attrs.links.push(ItemLink {
917+
link: ori_link,
918+
link_text: path_str.to_owned(),
919+
did: None,
920+
fragment,
921+
});
910922
}
911923
Some(other) => {
912924
report_mismatch(other, Disambiguator::Primitive);
@@ -957,7 +969,12 @@ impl<'a, 'tcx> DocFolder for LinkCollector<'a, 'tcx> {
957969
}
958970
}
959971
let id = register_res(cx, res);
960-
item.attrs.links.push((ori_link, Some(id), fragment));
972+
item.attrs.links.push(ItemLink {
973+
link: ori_link,
974+
link_text,
975+
did: Some(id),
976+
fragment,
977+
});
961978
}
962979
}
963980

@@ -985,6 +1002,18 @@ enum Disambiguator {
9851002
}
9861003

9871004
impl Disambiguator {
1005+
/// The text that should be displayed when the path is rendered as HTML.
1006+
///
1007+
/// NOTE: `path` is not the original link given by the user, but a name suitable for passing to `resolve`.
1008+
fn display_for(&self, path: &str) -> String {
1009+
match self {
1010+
// FIXME: this will have different output if the user had `m!()` originally.
1011+
Self::Kind(DefKind::Macro(MacroKind::Bang)) => format!("{}!", path),
1012+
Self::Kind(DefKind::Fn) => format!("{}()", path),
1013+
_ => path.to_owned(),
1014+
}
1015+
}
1016+
9881017
/// (disambiguator, path_str)
9891018
fn from_str(link: &str) -> Result<(Self, &str), ()> {
9901019
use Disambiguator::{Kind, Namespace as NS, Primitive};
@@ -1037,7 +1066,7 @@ impl Disambiguator {
10371066
}
10381067

10391068
/// Return (description of the change, suggestion)
1040-
fn display_for(self, path_str: &str) -> (&'static str, String) {
1069+
fn suggestion_for(self, path_str: &str) -> (&'static str, String) {
10411070
const PREFIX: &str = "prefix with the item kind";
10421071
const FUNCTION: &str = "add parentheses";
10431072
const MACRO: &str = "add an exclamation mark";
@@ -1292,7 +1321,7 @@ fn suggest_disambiguator(
12921321
sp: Option<rustc_span::Span>,
12931322
link_range: &Option<Range<usize>>,
12941323
) {
1295-
let (action, mut suggestion) = disambiguator.display_for(path_str);
1324+
let (action, mut suggestion) = disambiguator.suggestion_for(path_str);
12961325
let help = format!("to link to the {}, {}", disambiguator.descr(), action);
12971326

12981327
if let Some(sp) = sp {

0 commit comments

Comments
 (0)