Skip to content

Commit f0d002b

Browse files
Correctly generate path for non-local items in source code pages
1 parent 98aa362 commit f0d002b

File tree

2 files changed

+165
-64
lines changed

2 files changed

+165
-64
lines changed

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

+140-52
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,7 +582,7 @@ 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

@@ -651,92 +652,179 @@ fn generate_macro_def_id_path(
651652
Ok((url, ItemType::Macro, fqp))
652653
}
653654

655+
fn generate_item_def_id_path(
656+
mut def_id: DefId,
657+
original_def_id: DefId,
658+
cx: &Context<'_>,
659+
root_path: Option<&str>,
660+
original_def_kind: DefKind,
661+
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
662+
use crate::rustc_trait_selection::infer::TyCtxtInferExt;
663+
use crate::rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
664+
use rustc_middle::traits::ObligationCause;
665+
666+
let tcx = cx.tcx();
667+
let crate_name = tcx.crate_name(def_id.krate);
668+
669+
// No need to try to infer the actual parent item if it's not an associated item from the `impl`
670+
// block.
671+
if def_id != original_def_id && matches!(tcx.def_kind(def_id), DefKind::Impl { .. }) {
672+
let infcx = tcx.infer_ctxt().build();
673+
def_id = infcx
674+
.at(&ObligationCause::dummy(), tcx.param_env(def_id))
675+
.query_normalize(ty::Binder::dummy(tcx.type_of(def_id).instantiate_identity()))
676+
.map(|resolved| infcx.resolve_vars_if_possible(resolved.value))
677+
.ok()
678+
.and_then(|normalized| normalized.skip_binder().ty_adt_def())
679+
.map(|adt| adt.did())
680+
.unwrap_or(def_id);
681+
}
682+
683+
let relative: Vec<Symbol> = tcx
684+
.def_path(def_id)
685+
.data
686+
.into_iter()
687+
.filter_map(|elem| {
688+
// extern blocks (and a few others things) have an empty name.
689+
match elem.data.get_opt_name() {
690+
Some(s) if !s.is_empty() => Some(s),
691+
_ => None,
692+
}
693+
})
694+
.collect();
695+
let fqp: Vec<Symbol> = once(crate_name).chain(relative).collect();
696+
697+
let def_kind = tcx.def_kind(def_id);
698+
let shortty = def_kind.into();
699+
let module_fqp = to_module_fqp(shortty, &fqp);
700+
let mut is_remote = false;
701+
702+
let url_parts = url_parts(cx.cache(), def_id, &module_fqp, &cx.current, &mut is_remote)?;
703+
let (url_parts, shortty, fqp) = make_href(root_path, shortty, url_parts, &fqp, is_remote)?;
704+
if def_id == original_def_id {
705+
return Ok((url_parts, shortty, fqp));
706+
}
707+
let kind = ItemType::from_def_kind(original_def_kind, Some(def_kind));
708+
Ok((format!("{url_parts}#{kind}.{}", tcx.item_name(original_def_id)), shortty, fqp))
709+
}
710+
711+
fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] {
712+
if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] }
713+
}
714+
715+
fn url_parts(
716+
cache: &Cache,
717+
def_id: DefId,
718+
module_fqp: &[Symbol],
719+
relative_to: &[Symbol],
720+
is_remote: &mut bool,
721+
) -> Result<UrlPartsBuilder, HrefError> {
722+
match cache.extern_locations[&def_id.krate] {
723+
ExternalLocation::Remote(ref s) => {
724+
*is_remote = true;
725+
let s = s.trim_end_matches('/');
726+
let mut builder = UrlPartsBuilder::singleton(s);
727+
builder.extend(module_fqp.iter().copied());
728+
Ok(builder)
729+
}
730+
ExternalLocation::Local => Ok(href_relative_parts(module_fqp, relative_to).collect()),
731+
ExternalLocation::Unknown => Err(HrefError::DocumentationNotBuilt),
732+
}
733+
}
734+
735+
fn make_href(
736+
root_path: Option<&str>,
737+
shortty: ItemType,
738+
mut url_parts: UrlPartsBuilder,
739+
fqp: &[Symbol],
740+
is_remote: bool,
741+
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
742+
if !is_remote && let Some(root_path) = root_path {
743+
let root = root_path.trim_end_matches('/');
744+
url_parts.push_front(root);
745+
}
746+
debug!(?url_parts);
747+
match shortty {
748+
ItemType::Module => {
749+
url_parts.push("index.html");
750+
}
751+
_ => {
752+
let prefix = shortty.as_str();
753+
let last = fqp.last().unwrap();
754+
url_parts.push_fmt(format_args!("{prefix}.{last}.html"));
755+
}
756+
}
757+
Ok((url_parts.finish(), shortty, fqp.to_vec()))
758+
}
759+
654760
pub(crate) fn href_with_root_path(
655-
did: DefId,
761+
original_did: DefId,
656762
cx: &Context<'_>,
657763
root_path: Option<&str>,
658764
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
659765
let tcx = cx.tcx();
660-
let def_kind = tcx.def_kind(did);
766+
let def_kind = tcx.def_kind(original_did);
661767
let did = match def_kind {
662768
DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => {
663769
// documented on their parent's page
664-
tcx.parent(did)
770+
tcx.parent(original_did)
665771
}
772+
// If this a constructor, we get the parent (either a struct or a variant) and then
773+
// generate the link for this item.
774+
DefKind::Ctor(..) => return href_with_root_path(tcx.parent(original_did), cx, root_path),
666775
DefKind::ExternCrate => {
667776
// Link to the crate itself, not the `extern crate` item.
668-
if let Some(local_did) = did.as_local() {
777+
if let Some(local_did) = original_did.as_local() {
669778
tcx.extern_mod_stmt_cnum(local_did).unwrap_or(LOCAL_CRATE).as_def_id()
670779
} else {
671-
did
780+
original_did
672781
}
673782
}
674-
_ => did,
783+
_ => original_did,
675784
};
676785
let cache = cx.cache();
677786
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-
}
681787

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);
788+
if !original_did.is_local() {
789+
// If we are generating an href for the "jump to def" feature, then the only case we want
790+
// to ignore is if the item is `doc(hidden)` because we can't link to it.
791+
if root_path.is_some() {
792+
if tcx.is_doc_hidden(original_did) {
793+
return Err(HrefError::Private);
794+
}
795+
} else if !cache.effective_visibilities.is_directly_public(tcx, did)
796+
&& !cache.document_private
797+
&& !cache.primitive_locations.values().any(|&id| id == did)
798+
{
799+
return Err(HrefError::Private);
800+
}
688801
}
689802

690803
let mut is_remote = false;
691-
let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
804+
let (fqp, shortty, url_parts) = match cache.paths.get(&did) {
692805
Some(&(ref fqp, shortty)) => (fqp, shortty, {
693806
let module_fqp = to_module_fqp(shortty, fqp.as_slice());
694807
debug!(?fqp, ?shortty, ?module_fqp);
695808
href_relative_parts(module_fqp, relative_to).collect()
696809
}),
697810
None => {
698-
if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&did) {
811+
// Associated items are handled differently with "jump to def". The anchor is generated
812+
// directly here whereas for intra-doc links, we have some extra computation being
813+
// performed there.
814+
let def_id_to_get = if root_path.is_some() { original_did } else { did };
815+
if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&def_id_to_get) {
699816
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-
)
817+
(fqp, shortty, url_parts(cache, did, module_fqp, relative_to, &mut is_remote)?)
717818
} else if matches!(def_kind, DefKind::Macro(_)) {
718819
return generate_macro_def_id_path(did, cx, root_path);
820+
} else if did.is_local() {
821+
return Err(HrefError::Private);
719822
} else {
720-
return Err(HrefError::NotInExternalCache);
823+
return generate_item_def_id_path(did, original_did, cx, root_path, def_kind);
721824
}
722825
}
723826
};
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()))
827+
make_href(root_path, shortty, url_parts, fqp, is_remote)
740828
}
741829

742830
pub(crate) fn href(

0 commit comments

Comments
 (0)