Skip to content

Commit 949b98c

Browse files
committed
Auto merge of #95337 - petrochenkov:doclink3, r=camelid
rustdoc: Fix resolution of `crate`-relative paths in doc links Resolve `crate::foo` paths transparently to rustdoc, so their resolution no longer affects diagnostics and modules used for determining traits in scope. The proper solution is to account for the current `module_id`/`parent_scope` in `fn resolve_crate_root`, but it's a slightly larger compiler changes. This PR moves the code closer to it, but keeps it rustdoc-specific. Fixes #78696 Fixes #94924
2 parents a22cf2a + f5ee822 commit 949b98c

File tree

6 files changed

+53
-43
lines changed

6 files changed

+53
-43
lines changed

compiler/rustc_resolve/src/lib.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -3270,12 +3270,21 @@ impl<'a> Resolver<'a> {
32703270
&mut self,
32713271
path_str: &str,
32723272
ns: Namespace,
3273-
module_id: DefId,
3273+
mut module_id: DefId,
32743274
) -> Option<Res> {
32753275
let mut segments =
32763276
Vec::from_iter(path_str.split("::").map(Ident::from_str).map(Segment::from_ident));
3277-
if path_str.starts_with("::") {
3278-
segments[0].ident.name = kw::PathRoot;
3277+
if let Some(segment) = segments.first_mut() {
3278+
if segment.ident.name == kw::Crate {
3279+
// FIXME: `resolve_path` always resolves `crate` to the current crate root, but
3280+
// rustdoc wants it to resolve to the `module_id`'s crate root. This trick of
3281+
// replacing `crate` with `self` and changing the current module should achieve
3282+
// the same effect.
3283+
segment.ident.name = kw::SelfLower;
3284+
module_id = module_id.krate.as_def_id();
3285+
} else if segment.ident.name == kw::Empty {
3286+
segment.ident.name = kw::PathRoot;
3287+
}
32793288
}
32803289

32813290
let module = self.expect_module(module_id);

src/librustdoc/passes/collect_intra_doc_links.rs

+5-32
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_hir::def::{
99
Namespace::{self, *},
1010
PerNS,
1111
};
12-
use rustc_hir::def_id::{CrateNum, DefId, CRATE_DEF_ID};
12+
use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
1313
use rustc_hir::Mutability;
1414
use rustc_middle::ty::{DefIdTree, Ty, TyCtxt};
1515
use rustc_middle::{bug, span_bug, ty};
@@ -1043,16 +1043,11 @@ impl<'a, 'tcx> DocVisitor for LinkCollector<'a, 'tcx> {
10431043
// so we know which module it came from.
10441044
for (parent_module, doc) in item.attrs.collapsed_doc_value_by_module_level() {
10451045
debug!("combined_docs={}", doc);
1046-
1047-
let (krate, parent_node) = if let Some(id) = parent_module {
1048-
(id.krate, Some(id))
1049-
} else {
1050-
(item.def_id.krate(), parent_node)
1051-
};
10521046
// NOTE: if there are links that start in one crate and end in another, this will not resolve them.
10531047
// This is a degenerate case and it's not supported by rustdoc.
1048+
let parent_node = parent_module.or(parent_node);
10541049
for md_link in markdown_links(&doc) {
1055-
let link = self.resolve_link(&item, &doc, parent_node, krate, md_link);
1050+
let link = self.resolve_link(&item, &doc, parent_node, md_link);
10561051
if let Some(link) = link {
10571052
self.cx.cache.intra_doc_links.entry(item.def_id).or_default().push(link);
10581053
}
@@ -1187,7 +1182,6 @@ impl LinkCollector<'_, '_> {
11871182
item: &Item,
11881183
dox: &str,
11891184
parent_node: Option<DefId>,
1190-
krate: CrateNum,
11911185
ori_link: MarkdownLink,
11921186
) -> Option<ItemLink> {
11931187
trace!("considering link '{}'", ori_link.link);
@@ -1199,7 +1193,7 @@ impl LinkCollector<'_, '_> {
11991193
link_range: ori_link.range.clone(),
12001194
};
12011195

1202-
let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text } =
1196+
let PreprocessingInfo { ref path_str, disambiguator, extra_fragment, link_text } =
12031197
match preprocess_link(&ori_link)? {
12041198
Ok(x) => x,
12051199
Err(err) => {
@@ -1221,7 +1215,6 @@ impl LinkCollector<'_, '_> {
12211215
return None;
12221216
}
12231217
};
1224-
let mut path_str = &*path_str;
12251218

12261219
let inner_docs = item.inner_docs(self.cx.tcx);
12271220

@@ -1239,7 +1232,7 @@ impl LinkCollector<'_, '_> {
12391232
let base_node =
12401233
if item.is_mod() && inner_docs { self.mod_ids.last().copied() } else { parent_node };
12411234

1242-
let Some(mut module_id) = base_node else {
1235+
let Some(module_id) = base_node else {
12431236
// This is a bug.
12441237
debug!("attempting to resolve item without parent module: {}", path_str);
12451238
resolution_failure(
@@ -1252,26 +1245,6 @@ impl LinkCollector<'_, '_> {
12521245
return None;
12531246
};
12541247

1255-
let resolved_self;
1256-
let is_lone_crate = path_str == "crate";
1257-
if path_str.starts_with("crate::") || is_lone_crate {
1258-
use rustc_span::def_id::CRATE_DEF_INDEX;
1259-
1260-
// HACK(jynelson): rustc_resolve thinks that `crate` is the crate currently being documented.
1261-
// But rustdoc wants it to mean the crate this item was originally present in.
1262-
// To work around this, remove it and resolve relative to the crate root instead.
1263-
// HACK(jynelson)(2): If we just strip `crate::` then suddenly primitives become ambiguous
1264-
// (consider `crate::char`). Instead, change it to `self::`. This works because 'self' is now the crate root.
1265-
// FIXME(#78696): This doesn't always work.
1266-
if is_lone_crate {
1267-
path_str = "self";
1268-
} else {
1269-
resolved_self = format!("self::{}", &path_str["crate::".len()..]);
1270-
path_str = &resolved_self;
1271-
}
1272-
module_id = DefId { krate, index: CRATE_DEF_INDEX };
1273-
}
1274-
12751248
let (mut res, fragment) = self.resolve_with_disambiguator_cached(
12761249
ResolutionInfo {
12771250
item_id: item.def_id,

src/librustdoc/passes/collect_intra_doc_links/early.rs

-8
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ crate fn early_resolve_intra_doc_links(
3232
all_trait_impls: Default::default(),
3333
};
3434

35-
// Because of the `crate::` prefix, any doc comment can reference
36-
// the crate root's set of in-scope traits. This line makes sure
37-
// it's available.
38-
loader.add_traits_in_scope(CRATE_DEF_ID.to_def_id());
39-
4035
// Overridden `visit_item` below doesn't apply to the crate root,
4136
// so we have to visit its attributes and reexports separately.
4237
loader.load_links_in_attrs(&krate.attrs);
@@ -105,9 +100,6 @@ impl IntraLinkCrateLoader<'_, '_> {
105100
/// having impls in them.
106101
fn add_foreign_traits_in_scope(&mut self) {
107102
for cnum in Vec::from_iter(self.resolver.cstore().crates_untracked()) {
108-
// FIXME: Due to #78696 rustdoc can query traits in scope for any crate root.
109-
self.add_traits_in_scope(cnum.as_def_id());
110-
111103
let all_traits = Vec::from_iter(self.resolver.cstore().traits_in_crate_untracked(cnum));
112104
let all_trait_impls =
113105
Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#![deny(rustdoc::broken_intra_doc_links)]
2+
3+
/// [crate::DoesNotExist]
4+
//~^ ERROR unresolved link to `crate::DoesNotExist`
5+
pub struct Item;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: unresolved link to `crate::DoesNotExist`
2+
--> $DIR/crate-nonexistent.rs:3:6
3+
|
4+
LL | /// [crate::DoesNotExist]
5+
| ^^^^^^^^^^^^^^^^^^^ no item named `DoesNotExist` in module `crate_nonexistent`
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/crate-nonexistent.rs:1:9
9+
|
10+
LL | #![deny(rustdoc::broken_intra_doc_links)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to previous error
14+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
pub mod io {
2+
pub trait Read {
3+
fn read(&mut self);
4+
}
5+
}
6+
7+
pub mod bufreader {
8+
// @has crate_relative_assoc/bufreader/index.html '//a/@href' 'struct.TcpStream.html#method.read'
9+
//! [`crate::TcpStream::read`]
10+
use crate::io::Read;
11+
}
12+
13+
pub struct TcpStream;
14+
15+
impl crate::io::Read for TcpStream {
16+
fn read(&mut self) {}
17+
}

0 commit comments

Comments
 (0)