Skip to content

Commit e20b4d4

Browse files
Prevent private/hidden items to create ambiguities in intra-doc links
1 parent c9b4d1b commit e20b4d4

File tree

4 files changed

+184
-38
lines changed

4 files changed

+184
-38
lines changed

src/librustdoc/clean/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,8 @@ impl Item {
512512
pub(crate) fn links(&self, cx: &Context<'_>) -> Vec<RenderedLink> {
513513
use crate::html::format::{href, link_tooltip};
514514

515+
eprintln!("yyyyyyyy {self:?}");
516+
515517
let Some(links) = cx.cache().intra_doc_links.get(&self.item_id) else { return vec![] };
516518
links
517519
.iter()

src/librustdoc/core.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::clean::{self, ItemId};
3030
use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
3131
use crate::formats::cache::Cache;
3232
use crate::passes::Condition::*;
33+
use crate::passes::collect_intra_doc_links::{LinkCollector, Res as CRes};
3334
use crate::passes::{self};
3435

3536
pub(crate) struct DocContext<'tcx> {
@@ -440,6 +441,10 @@ pub(crate) fn run_global_ctxt(
440441
}
441442
}
442443

444+
let (mut krate, LinkCollector { visited_links, mut ambiguous_links, .. }) =
445+
tcx.sess.time("collect_intra_doc_links", || {
446+
passes::collect_intra_doc_links::collect_intra_doc_links(krate, &mut ctxt)
447+
});
443448
tcx.sess.time("check_lint_expectations", || tcx.check_expectations(Some(sym::rustdoc)));
444449

445450
if let Some(guar) = tcx.dcx().has_errors() {
@@ -448,6 +453,30 @@ pub(crate) fn run_global_ctxt(
448453

449454
krate = tcx.sess.time("create_format_cache", || Cache::populate(&mut ctxt, krate));
450455

456+
let mut collector =
457+
LinkCollector { cx: &mut ctxt, visited_links, ambiguous_links: Default::default() };
458+
459+
for ((item_id, path_str), info) in ambiguous_links.iter_mut() {
460+
info.resolved.retain(|(res, _)| match res {
461+
CRes::Def(_, def_id) => collector.validate_link(*def_id),
462+
CRes::Primitive(_) => true,
463+
});
464+
if info.resolved.len() == 1 {
465+
let (res, fragment) = info.resolved.pop().unwrap();
466+
let diag_info = info.diag_info.into_info();
467+
if let Some(link) = collector.compute_link(
468+
res,
469+
fragment,
470+
path_str,
471+
info.disambiguator,
472+
diag_info,
473+
&info.link_text,
474+
) {
475+
collector.save_link(*item_id, link);
476+
}
477+
}
478+
}
479+
451480
Ok((krate, ctxt.render_options, ctxt.cache))
452481
}
453482

src/librustdoc/passes/collect_intra_doc_links.rs

Lines changed: 153 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use rustc_data_structures::intern::Interned;
1414
use rustc_errors::{Applicability, Diag, DiagMessage};
1515
use rustc_hir::def::Namespace::*;
1616
use rustc_hir::def::{DefKind, Namespace, PerNS};
17-
use rustc_hir::def_id::{CRATE_DEF_ID, DefId};
17+
use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE};
1818
use rustc_hir::{Mutability, Safety};
1919
use rustc_middle::ty::{Ty, TyCtxt};
2020
use rustc_middle::{bug, span_bug, ty};
@@ -30,23 +30,30 @@ use smallvec::{SmallVec, smallvec};
3030
use tracing::{debug, info, instrument, trace};
3131

3232
use crate::clean::utils::find_nearest_parent_module;
33-
use crate::clean::{self, Crate, Item, ItemLink, PrimitiveType};
33+
use crate::clean::{self, Crate, Item, ItemId, ItemLink, PrimitiveType};
3434
use crate::core::DocContext;
3535
use crate::html::markdown::{MarkdownLink, MarkdownLinkRange, markdown_links};
3636
use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS};
37-
use crate::passes::Pass;
3837
use crate::visit::DocVisitor;
3938

40-
pub(crate) const COLLECT_INTRA_DOC_LINKS: Pass = Pass {
41-
name: "collect-intra-doc-links",
42-
run: collect_intra_doc_links,
43-
description: "resolves intra-doc links",
44-
};
45-
46-
fn collect_intra_doc_links(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
47-
let mut collector = LinkCollector { cx, visited_links: FxHashMap::default() };
39+
pub(crate) fn collect_intra_doc_links<'a, 'tcx>(
40+
krate: Crate,
41+
cx: &'a mut DocContext<'tcx>,
42+
) -> (Crate, LinkCollector<'a, 'tcx>) {
43+
let mut collector = LinkCollector {
44+
cx,
45+
visited_links: FxHashMap::default(),
46+
ambiguous_links: FxHashMap::default(),
47+
};
4848
collector.visit_crate(&krate);
49-
krate
49+
(krate, collector)
50+
}
51+
52+
pub(crate) struct AmbiguousLinks {
53+
pub(crate) disambiguator: Option<Disambiguator>,
54+
pub(crate) link_text: Box<str>,
55+
pub(crate) diag_info: OwnedDiagnosticInfo,
56+
pub(crate) resolved: Vec<(Res, Option<UrlFragment>)>,
5057
}
5158

5259
fn filter_assoc_items_by_name_and_namespace<'a>(
@@ -61,7 +68,7 @@ fn filter_assoc_items_by_name_and_namespace<'a>(
6168
}
6269

6370
#[derive(Copy, Clone, Debug, Hash, PartialEq)]
64-
enum Res {
71+
pub(crate) enum Res {
6572
Def(DefKind, DefId),
6673
Primitive(PrimitiveType),
6774
}
@@ -233,7 +240,7 @@ impl UrlFragment {
233240
}
234241

235242
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
236-
struct ResolutionInfo {
243+
pub(crate) struct ResolutionInfo {
237244
item_id: DefId,
238245
module_id: DefId,
239246
dis: Option<Disambiguator>,
@@ -242,18 +249,54 @@ struct ResolutionInfo {
242249
}
243250

244251
#[derive(Clone)]
245-
struct DiagnosticInfo<'a> {
252+
pub(crate) struct DiagnosticInfo<'a> {
246253
item: &'a Item,
247254
dox: &'a str,
248255
ori_link: &'a str,
249256
link_range: MarkdownLinkRange,
250257
}
251258

252-
struct LinkCollector<'a, 'tcx> {
253-
cx: &'a mut DocContext<'tcx>,
259+
pub(crate) struct OwnedDiagnosticInfo {
260+
item: Item,
261+
ori_link: String,
262+
link_range: MarkdownLinkRange,
263+
}
264+
265+
impl From<DiagnosticInfo<'_>> for OwnedDiagnosticInfo {
266+
fn from(f: DiagnosticInfo<'_>) -> Self {
267+
Self {
268+
item: f.item.clone(),
269+
ori_link: f.ori_link.to_string(),
270+
link_range: f.link_range.clone(),
271+
}
272+
}
273+
}
274+
275+
impl OwnedDiagnosticInfo {
276+
pub(crate) fn into_info(&self) -> DiagnosticInfo<'_> {
277+
DiagnosticInfo {
278+
item: &self.item,
279+
ori_link: &self.ori_link,
280+
dox: "",
281+
link_range: self.link_range.clone(),
282+
}
283+
}
284+
}
285+
286+
pub(crate) struct LinkCollector<'a, 'tcx> {
287+
pub(crate) cx: &'a mut DocContext<'tcx>,
254288
/// Cache the resolved links so we can avoid resolving (and emitting errors for) the same link.
255289
/// The link will be `None` if it could not be resolved (i.e. the error was cached).
256-
visited_links: FxHashMap<ResolutionInfo, Option<(Res, Option<UrlFragment>)>>,
290+
pub(crate) visited_links: FxHashMap<ResolutionInfo, Option<(Res, Option<UrlFragment>)>>,
291+
/// These links are ambiguous. We need for the cache to have its paths filled. Unfortunately,
292+
/// if we run the `LinkCollector` pass after `Cache::populate`, a lot of items that we need
293+
/// to go through will be removed, making a lot of intra-doc links to not be inferred.
294+
///
295+
/// So instead, we store the ambiguous links and we wait for cache paths to be filled before
296+
/// inferring them (if possible).
297+
///
298+
/// Key is `(item ID, path str)`.
299+
pub(crate) ambiguous_links: FxHashMap<(ItemId, String), AmbiguousLinks>,
257300
}
258301

259302
impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
@@ -981,14 +1024,17 @@ impl LinkCollector<'_, '_> {
9811024
_ => find_nearest_parent_module(self.cx.tcx, item_id).unwrap(),
9821025
};
9831026
for md_link in preprocessed_markdown_links(&doc) {
984-
let link = self.resolve_link(&doc, item, item_id, module_id, &md_link);
985-
if let Some(link) = link {
986-
self.cx.cache.intra_doc_links.entry(item.item_id).or_default().insert(link);
1027+
if let Some(link) = self.resolve_link(&doc, item, item_id, module_id, &md_link) {
1028+
self.save_link(item.item_id, link);
9871029
}
9881030
}
9891031
}
9901032
}
9911033

1034+
pub(crate) fn save_link(&mut self, item_id: ItemId, link: ItemLink) {
1035+
self.cx.cache.intra_doc_links.entry(item_id).or_default().insert(link);
1036+
}
1037+
9921038
/// This is the entry point for resolving an intra-doc link.
9931039
///
9941040
/// FIXME(jynelson): this is way too many arguments
@@ -1012,7 +1058,7 @@ impl LinkCollector<'_, '_> {
10121058
pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?;
10131059
let disambiguator = *disambiguator;
10141060

1015-
let (mut res, fragment) = self.resolve_with_disambiguator_cached(
1061+
let mut resolved = self.resolve_with_disambiguator_cached(
10161062
ResolutionInfo {
10171063
item_id,
10181064
module_id,
@@ -1028,6 +1074,75 @@ impl LinkCollector<'_, '_> {
10281074
false,
10291075
)?;
10301076

1077+
if resolved.len() > 1 {
1078+
let links = AmbiguousLinks {
1079+
disambiguator,
1080+
link_text: link_text.clone(),
1081+
diag_info: diag_info.into(),
1082+
resolved,
1083+
};
1084+
self.ambiguous_links.insert((item.item_id, path_str.to_string()), links);
1085+
None
1086+
} else if let Some((res, fragment)) = resolved.pop() {
1087+
self.compute_link(res, fragment, path_str, disambiguator, diag_info, link_text)
1088+
} else {
1089+
None
1090+
}
1091+
}
1092+
1093+
/// Returns `true` if a link could be generated from the given intra-doc information.
1094+
///
1095+
/// This is a very light version of `format::href_with_root_path` since we're only interested
1096+
/// about whether we can generate a link to an item or not.
1097+
///
1098+
/// * If `original_did` is local, then we check if the item is reexported or public.
1099+
/// * If `original_did` is not local, then we check if the crate it comes from is a direct
1100+
/// public dependency.
1101+
pub(crate) fn validate_link(&self, original_did: DefId) -> bool {
1102+
let tcx = self.cx.tcx;
1103+
let def_kind = tcx.def_kind(original_did);
1104+
let did = match def_kind {
1105+
DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
1106+
// documented on their parent's page
1107+
tcx.parent(original_did)
1108+
}
1109+
// If this a constructor, we get the parent (either a struct or a variant) and then
1110+
// generate the link for this item.
1111+
DefKind::Ctor(..) => return self.validate_link(tcx.parent(original_did)),
1112+
DefKind::ExternCrate => {
1113+
// Link to the crate itself, not the `extern crate` item.
1114+
if let Some(local_did) = original_did.as_local() {
1115+
tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id()
1116+
} else {
1117+
original_did
1118+
}
1119+
}
1120+
_ => original_did,
1121+
};
1122+
1123+
let cache = &self.cx.cache;
1124+
if !original_did.is_local()
1125+
&& !cache.effective_visibilities.is_directly_public(tcx, did)
1126+
&& !cache.document_private
1127+
&& !cache.primitive_locations.values().any(|&id| id == did)
1128+
{
1129+
return false;
1130+
}
1131+
1132+
cache.paths.get(&did).is_some()
1133+
|| cache.external_paths.get(&did).is_some()
1134+
|| !did.is_local()
1135+
}
1136+
1137+
pub(crate) fn compute_link(
1138+
&mut self,
1139+
mut res: Res,
1140+
fragment: Option<UrlFragment>,
1141+
path_str: &str,
1142+
disambiguator: Option<Disambiguator>,
1143+
diag_info: DiagnosticInfo<'_>,
1144+
link_text: &Box<str>,
1145+
) -> Option<ItemLink> {
10311146
// Check for a primitive which might conflict with a module
10321147
// Report the ambiguity and require that the user specify which one they meant.
10331148
// FIXME: could there ever be a primitive not in the type namespace?
@@ -1073,7 +1188,7 @@ impl LinkCollector<'_, '_> {
10731188
}
10741189

10751190
res.def_id(self.cx.tcx).map(|page_id| ItemLink {
1076-
link: Box::<str>::from(&*ori_link.link),
1191+
link: Box::<str>::from(&*diag_info.ori_link),
10771192
link_text: link_text.clone(),
10781193
page_id,
10791194
fragment,
@@ -1095,7 +1210,7 @@ impl LinkCollector<'_, '_> {
10951210

10961211
let page_id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));
10971212
Some(ItemLink {
1098-
link: Box::<str>::from(&*ori_link.link),
1213+
link: Box::<str>::from(&*diag_info.ori_link),
10991214
link_text: link_text.clone(),
11001215
page_id,
11011216
fragment,
@@ -1208,10 +1323,10 @@ impl LinkCollector<'_, '_> {
12081323
// If this call is intended to be recoverable, then pass true to silence.
12091324
// This is only recoverable when path is failed to resolved.
12101325
recoverable: bool,
1211-
) -> Option<(Res, Option<UrlFragment>)> {
1326+
) -> Option<Vec<(Res, Option<UrlFragment>)>> {
12121327
if let Some(res) = self.visited_links.get(&key) {
12131328
if res.is_some() || cache_errors {
1214-
return res.clone();
1329+
return res.clone().map(|r| vec![r]);
12151330
}
12161331
}
12171332

@@ -1242,25 +1357,28 @@ impl LinkCollector<'_, '_> {
12421357
candidates = vec![candidate];
12431358
}
12441359

1245-
if let &[(res, def_id)] = candidates.as_slice() {
1360+
let mut resolved = Vec::with_capacity(candidates.len());
1361+
for (res, def_id) in candidates.as_slice() {
12461362
let fragment = match (&key.extra_fragment, def_id) {
12471363
(Some(_), Some(def_id)) => {
1248-
report_anchor_conflict(self.cx, diag, def_id);
1364+
report_anchor_conflict(self.cx, diag, *def_id);
12491365
return None;
12501366
}
12511367
(Some(u_frag), None) => Some(UrlFragment::UserWritten(u_frag.clone())),
1252-
(None, Some(def_id)) => Some(UrlFragment::Item(def_id)),
1368+
(None, Some(def_id)) => Some(UrlFragment::Item(*def_id)),
12531369
(None, None) => None,
12541370
};
1255-
let r = Some((res, fragment));
1256-
self.visited_links.insert(key, r.clone());
1257-
return r;
1371+
let r = (res.clone(), fragment.clone());
1372+
self.visited_links.insert(key.clone(), Some(r.clone()));
1373+
resolved.push(r);
12581374
}
12591375

1260-
if cache_errors {
1376+
if resolved.is_empty() && cache_errors {
12611377
self.visited_links.insert(key, None);
1378+
None
1379+
} else {
1380+
Some(resolved)
12621381
}
1263-
None
12641382
}
12651383

12661384
/// After parsing the disambiguator, resolve the main part of the link.
@@ -1416,7 +1534,7 @@ fn should_ignore_link(path_str: &str) -> bool {
14161534

14171535
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
14181536
/// Disambiguators for a link.
1419-
enum Disambiguator {
1537+
pub(crate) enum Disambiguator {
14201538
/// `prim@`
14211539
///
14221540
/// This is buggy, see <https://github.com/rust-lang/rust/pull/77875#discussion_r503583103>

src/librustdoc/passes/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ mod propagate_doc_cfg;
2424
pub(crate) use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
2525

2626
pub(crate) mod collect_intra_doc_links;
27-
pub(crate) use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS;
2827

2928
mod check_doc_test_visibility;
3029
pub(crate) use self::check_doc_test_visibility::CHECK_DOC_TEST_VISIBILITY;
@@ -75,7 +74,6 @@ pub(crate) const PASSES: &[Pass] = &[
7574
STRIP_PRIVATE,
7675
STRIP_PRIV_IMPORTS,
7776
PROPAGATE_DOC_CFG,
78-
COLLECT_INTRA_DOC_LINKS,
7977
COLLECT_TRAIT_IMPLS,
8078
CALCULATE_DOC_COVERAGE,
8179
RUN_LINTS,
@@ -89,7 +87,6 @@ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[
8987
ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
9088
ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),
9189
ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate),
92-
ConditionalPass::always(COLLECT_INTRA_DOC_LINKS),
9390
ConditionalPass::always(PROPAGATE_DOC_CFG),
9491
ConditionalPass::always(RUN_LINTS),
9592
];

0 commit comments

Comments
 (0)