Skip to content

Commit 16cf13f

Browse files
authored
Rollup merge of rust-lang#120596 - GuillaumeGomez:jump-to-def-non-local-link, r=notriddle
[rustdoc] Correctly generate path for non-local items in source code pages While browsing some crates using the "jump to def" feature, I realized that a lot of items didn't have a link generated. The reason is because we only cache foreign items if they appear in the documented API. This means that for the others, we need to infer them. r? ``@notriddle``
2 parents fd8bc48 + 898bb7f commit 16cf13f

File tree

4 files changed

+219
-79
lines changed

4 files changed

+219
-79
lines changed

src/librustdoc/clean/inline.rs

+16-3
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,28 @@ pub(crate) fn load_attrs<'hir>(cx: &DocContext<'hir>, did: DefId) -> &'hir [ast:
191191
cx.tcx.get_attrs_unchecked(did)
192192
}
193193

194+
pub(crate) fn item_relative_path(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<Symbol> {
195+
tcx.def_path(def_id)
196+
.data
197+
.into_iter()
198+
.filter_map(|elem| {
199+
// extern blocks (and a few others things) have an empty name.
200+
match elem.data.get_opt_name() {
201+
Some(s) if !s.is_empty() => Some(s),
202+
_ => None,
203+
}
204+
})
205+
.collect()
206+
}
207+
194208
/// Record an external fully qualified name in the external_paths cache.
195209
///
196210
/// These names are used later on by HTML rendering to generate things like
197211
/// source links back to the original item.
198212
pub(crate) fn record_extern_fqn(cx: &mut DocContext<'_>, did: DefId, kind: ItemType) {
199213
let crate_name = cx.tcx.crate_name(did.krate);
200214

201-
let relative =
202-
cx.tcx.def_path(did).data.into_iter().filter_map(|elem| elem.data.get_opt_name());
215+
let relative = item_relative_path(cx.tcx, did);
203216
let fqn = if let ItemType::Macro = kind {
204217
// Check to see if it is a macro 2.0 or built-in macro
205218
if matches!(
@@ -210,7 +223,7 @@ pub(crate) fn record_extern_fqn(cx: &mut DocContext<'_>, did: DefId, kind: ItemT
210223
) {
211224
once(crate_name).chain(relative).collect()
212225
} else {
213-
vec![crate_name, relative.last().expect("relative was empty")]
226+
vec![crate_name, *relative.last().expect("relative was empty")]
214227
}
215228
} else {
216229
once(crate_name).chain(relative).collect()

src/librustdoc/formats/item_type.rs

+25-12
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::fmt;
44

55
use serde::{Serialize, Serializer};
66

7-
use rustc_hir::def::DefKind;
7+
use rustc_hir::def::{CtorOf, DefKind};
88
use rustc_span::hygiene::MacroKind;
99

1010
use crate::clean;
@@ -115,7 +115,15 @@ impl<'a> From<&'a clean::Item> for ItemType {
115115

116116
impl From<DefKind> for ItemType {
117117
fn from(other: DefKind) -> Self {
118-
match other {
118+
Self::from_def_kind(other, None)
119+
}
120+
}
121+
122+
impl ItemType {
123+
/// Depending on the parent kind, some variants have a different translation (like a `Method`
124+
/// becoming a `TyMethod`).
125+
pub(crate) fn from_def_kind(kind: DefKind, parent_kind: Option<DefKind>) -> Self {
126+
match kind {
119127
DefKind::Enum => Self::Enum,
120128
DefKind::Fn => Self::Function,
121129
DefKind::Mod => Self::Module,
@@ -131,30 +139,35 @@ impl From<DefKind> for ItemType {
131139
MacroKind::Attr => ItemType::ProcAttribute,
132140
MacroKind::Derive => ItemType::ProcDerive,
133141
},
134-
DefKind::ForeignTy
135-
| DefKind::Variant
136-
| DefKind::AssocTy
137-
| DefKind::TyParam
142+
DefKind::ForeignTy => Self::ForeignType,
143+
DefKind::Variant => Self::Variant,
144+
DefKind::Field => Self::StructField,
145+
DefKind::AssocTy => Self::AssocType,
146+
DefKind::AssocFn => {
147+
if let Some(DefKind::Trait) = parent_kind {
148+
Self::TyMethod
149+
} else {
150+
Self::Method
151+
}
152+
}
153+
DefKind::Ctor(CtorOf::Struct, _) => Self::Struct,
154+
DefKind::Ctor(CtorOf::Variant, _) => Self::Variant,
155+
DefKind::AssocConst => Self::AssocConst,
156+
DefKind::TyParam
138157
| DefKind::ConstParam
139-
| DefKind::Ctor(..)
140-
| DefKind::AssocFn
141-
| DefKind::AssocConst
142158
| DefKind::ExternCrate
143159
| DefKind::Use
144160
| DefKind::ForeignMod
145161
| DefKind::AnonConst
146162
| DefKind::InlineConst
147163
| DefKind::OpaqueTy
148-
| DefKind::Field
149164
| DefKind::LifetimeParam
150165
| DefKind::GlobalAsm
151166
| DefKind::Impl { .. }
152167
| DefKind::Closure => Self::ForeignType,
153168
}
154169
}
155-
}
156170

157-
impl ItemType {
158171
pub(crate) fn as_str(&self) -> &'static str {
159172
match *self {
160173
ItemType::Module => "mod",

src/librustdoc/html/format.rs

+130-64
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use crate::clean::{
3232
self, types::ExternalLocation, utils::find_nearest_parent_module, ExternalCrate, ItemId,
3333
PrimitiveType,
3434
};
35+
use crate::formats::cache::Cache;
3536
use crate::formats::item_type::ItemType;
3637
use crate::html::escape::Escape;
3738
use crate::html::render::Context;
@@ -581,22 +582,11 @@ fn generate_macro_def_id_path(
581582
cx: &Context<'_>,
582583
root_path: Option<&str>,
583584
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
584-
let tcx = cx.shared.tcx;
585+
let tcx = cx.tcx();
585586
let crate_name = tcx.crate_name(def_id.krate);
586587
let cache = cx.cache();
587588

588-
let fqp: Vec<Symbol> = tcx
589-
.def_path(def_id)
590-
.data
591-
.into_iter()
592-
.filter_map(|elem| {
593-
// extern blocks (and a few others things) have an empty name.
594-
match elem.data.get_opt_name() {
595-
Some(s) if !s.is_empty() => Some(s),
596-
_ => None,
597-
}
598-
})
599-
.collect();
589+
let fqp = clean::inline::item_relative_path(tcx, def_id);
600590
let mut relative = fqp.iter().copied();
601591
let cstore = CStore::from_tcx(tcx);
602592
// We need this to prevent a `panic` when this function is used from intra doc links...
@@ -651,92 +641,168 @@ fn generate_macro_def_id_path(
651641
Ok((url, ItemType::Macro, fqp))
652642
}
653643

644+
fn generate_item_def_id_path(
645+
mut def_id: DefId,
646+
original_def_id: DefId,
647+
cx: &Context<'_>,
648+
root_path: Option<&str>,
649+
original_def_kind: DefKind,
650+
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
651+
use crate::rustc_trait_selection::infer::TyCtxtInferExt;
652+
use crate::rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
653+
use rustc_middle::traits::ObligationCause;
654+
655+
let tcx = cx.tcx();
656+
let crate_name = tcx.crate_name(def_id.krate);
657+
658+
// No need to try to infer the actual parent item if it's not an associated item from the `impl`
659+
// block.
660+
if def_id != original_def_id && matches!(tcx.def_kind(def_id), DefKind::Impl { .. }) {
661+
let infcx = tcx.infer_ctxt().build();
662+
def_id = infcx
663+
.at(&ObligationCause::dummy(), tcx.param_env(def_id))
664+
.query_normalize(ty::Binder::dummy(tcx.type_of(def_id).instantiate_identity()))
665+
.map(|resolved| infcx.resolve_vars_if_possible(resolved.value))
666+
.ok()
667+
.and_then(|normalized| normalized.skip_binder().ty_adt_def())
668+
.map(|adt| adt.did())
669+
.unwrap_or(def_id);
670+
}
671+
672+
let relative = clean::inline::item_relative_path(tcx, def_id);
673+
let fqp: Vec<Symbol> = once(crate_name).chain(relative).collect();
674+
675+
let def_kind = tcx.def_kind(def_id);
676+
let shortty = def_kind.into();
677+
let module_fqp = to_module_fqp(shortty, &fqp);
678+
let mut is_remote = false;
679+
680+
let url_parts = url_parts(cx.cache(), def_id, &module_fqp, &cx.current, &mut is_remote)?;
681+
let (url_parts, shortty, fqp) = make_href(root_path, shortty, url_parts, &fqp, is_remote)?;
682+
if def_id == original_def_id {
683+
return Ok((url_parts, shortty, fqp));
684+
}
685+
let kind = ItemType::from_def_kind(original_def_kind, Some(def_kind));
686+
Ok((format!("{url_parts}#{kind}.{}", tcx.item_name(original_def_id)), shortty, fqp))
687+
}
688+
689+
fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
690+
if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
691+
}
692+
693+
fn url_parts(
694+
cache: &Cache,
695+
def_id: DefId,
696+
module_fqp: &[Symbol],
697+
relative_to: &[Symbol],
698+
is_remote: &mut bool,
699+
) -> Result<UrlPartsBuilder, HrefError> {
700+
match cache.extern_locations[&def_id.krate] {
701+
ExternalLocation::Remote(ref s) => {
702+
*is_remote = true;
703+
let s = s.trim_end_matches('/');
704+
let mut builder = UrlPartsBuilder::singleton(s);
705+
builder.extend(module_fqp.iter().copied());
706+
Ok(builder)
707+
}
708+
ExternalLocation::Local => Ok(href_relative_parts(module_fqp, relative_to).collect()),
709+
ExternalLocation::Unknown => Err(HrefError::DocumentationNotBuilt),
710+
}
711+
}
712+
713+
fn make_href(
714+
root_path: Option<&str>,
715+
shortty: ItemType,
716+
mut url_parts: UrlPartsBuilder,
717+
fqp: &[Symbol],
718+
is_remote: bool,
719+
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
720+
if !is_remote && let Some(root_path) = root_path {
721+
let root = root_path.trim_end_matches('/');
722+
url_parts.push_front(root);
723+
}
724+
debug!(?url_parts);
725+
match shortty {
726+
ItemType::Module => {
727+
url_parts.push("index.html");
728+
}
729+
_ => {
730+
let prefix = shortty.as_str();
731+
let last = fqp.last().unwrap();
732+
url_parts.push_fmt(format_args!("{prefix}.{last}.html"));
733+
}
734+
}
735+
Ok((url_parts.finish(), shortty, fqp.to_vec()))
736+
}
737+
654738
pub(crate) fn href_with_root_path(
655-
did: DefId,
739+
original_did: DefId,
656740
cx: &Context<'_>,
657741
root_path: Option<&str>,
658742
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
659743
let tcx = cx.tcx();
660-
let def_kind = tcx.def_kind(did);
744+
let def_kind = tcx.def_kind(original_did);
661745
let did = match def_kind {
662746
DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
663747
// documented on their parent's page
664-
tcx.parent(did)
748+
tcx.parent(original_did)
665749
}
750+
// If this a constructor, we get the parent (either a struct or a variant) and then
751+
// generate the link for this item.
752+
DefKind::Ctor(..) => return href_with_root_path(tcx.parent(original_did), cx, root_path),
666753
DefKind::ExternCrate => {
667754
// Link to the crate itself, not the `extern crate` item.
668-
if let Some(local_did) = did.as_local() {
755+
if let Some(local_did) = original_did.as_local() {
669756
tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id()
670757
} else {
671-
did
758+
original_did
672759
}
673760
}
674-
_ => did,
761+
_ => original_did,
675762
};
676763
let cache = cx.cache();
677764
let relative_to = &cx.current;
678-
fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
679-
if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
680-
}
681765

682-
if !did.is_local()
683-
&& !cache.effective_visibilities.is_directly_public(tcx, did)
684-
&& !cache.document_private
685-
&& !cache.primitive_locations.values().any(|&id| id == did)
686-
{
687-
return Err(HrefError::Private);
766+
if !original_did.is_local() {
767+
// If we are generating an href for the "jump to def" feature, then the only case we want
768+
// to ignore is if the item is `doc(hidden)` because we can't link to it.
769+
if root_path.is_some() {
770+
if tcx.is_doc_hidden(original_did) {
771+
return Err(HrefError::Private);
772+
}
773+
} else if !cache.effective_visibilities.is_directly_public(tcx, did)
774+
&& !cache.document_private
775+
&& !cache.primitive_locations.values().any(|&id| id == did)
776+
{
777+
return Err(HrefError::Private);
778+
}
688779
}
689780

690781
let mut is_remote = false;
691-
let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
782+
let (fqp, shortty, url_parts) = match cache.paths.get(&did) {
692783
Some(&(ref fqp, shortty)) => (fqp, shortty, {
693784
let module_fqp = to_module_fqp(shortty, fqp.as_slice());
694785
debug!(?fqp, ?shortty, ?module_fqp);
695786
href_relative_parts(module_fqp, relative_to).collect()
696787
}),
697788
None => {
698-
if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&did) {
789+
// Associated items are handled differently with "jump to def". The anchor is generated
790+
// directly here whereas for intra-doc links, we have some extra computation being
791+
// performed there.
792+
let def_id_to_get = if root_path.is_some() { original_did } else { did };
793+
if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&def_id_to_get) {
699794
let module_fqp = to_module_fqp(shortty, fqp);
700-
(
701-
fqp,
702-
shortty,
703-
match cache.extern_locations[&did.krate] {
704-
ExternalLocation::Remote(ref s) => {
705-
is_remote = true;
706-
let s = s.trim_end_matches('/');
707-
let mut builder = UrlPartsBuilder::singleton(s);
708-
builder.extend(module_fqp.iter().copied());
709-
builder
710-
}
711-
ExternalLocation::Local => {
712-
href_relative_parts(module_fqp, relative_to).collect()
713-
}
714-
ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
715-
},
716-
)
795+
(fqp, shortty, url_parts(cache, did, module_fqp, relative_to, &mut is_remote)?)
717796
} else if matches!(def_kind, DefKind::Macro(_)) {
718797
return generate_macro_def_id_path(did, cx, root_path);
798+
} else if did.is_local() {
799+
return Err(HrefError::Private);
719800
} else {
720-
return Err(HrefError::NotInExternalCache);
801+
return generate_item_def_id_path(did, original_did, cx, root_path, def_kind);
721802
}
722803
}
723804
};
724-
if !is_remote && let Some(root_path) = root_path {
725-
let root = root_path.trim_end_matches('/');
726-
url_parts.push_front(root);
727-
}
728-
debug!(?url_parts);
729-
match shortty {
730-
ItemType::Module => {
731-
url_parts.push("index.html");
732-
}
733-
_ => {
734-
let prefix = shortty.as_str();
735-
let last = fqp.last().unwrap();
736-
url_parts.push_fmt(format_args!("{prefix}.{last}.html"));
737-
}
738-
}
739-
Ok((url_parts.finish(), shortty, fqp.to_vec()))
805+
make_href(root_path, shortty, url_parts, fqp, is_remote)
740806
}
741807

742808
pub(crate) fn href(

0 commit comments

Comments
 (0)