Skip to content

Commit 87ba9a5

Browse files
authored
Rollup merge of #148080 - GuillaumeGomez:fix-jump-def-links, r=lolbinarycat
[rustdoc] Fix invalid jump to def macro link generation Follow-up of #147820. I realized that when there was no intra-doc link linking to the same item, then the generated link for macros in jump to def would be invalid. To make the code less redundant, I merged the "registering" of items and the href generation use the same code for macros. r? `````@notriddle`````
2 parents b98d127 + 4d3d3b3 commit 87ba9a5

File tree

8 files changed

+118
-71
lines changed

8 files changed

+118
-71
lines changed

src/librustdoc/clean/inline.rs

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,30 @@ pub(crate) fn item_relative_path(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<Symbol>
227227
tcx.def_path(def_id).data.into_iter().filter_map(|elem| elem.data.get_opt_name()).collect()
228228
}
229229

230+
/// Get the public Rust path to an item. This is used to generate the URL to the item's page.
231+
///
232+
/// In particular: we handle macro differently: if it's not a macro 2.0 oe a built-in macro, then
233+
/// it is generated at the top-level of the crate and its path will be `[crate_name, macro_name]`.
234+
pub(crate) fn get_item_path(tcx: TyCtxt<'_>, def_id: DefId, kind: ItemType) -> Vec<Symbol> {
235+
let crate_name = tcx.crate_name(def_id.krate);
236+
let relative = item_relative_path(tcx, def_id);
237+
238+
if let ItemType::Macro = kind {
239+
// Check to see if it is a macro 2.0 or built-in macro
240+
// More information in <https://rust-lang.github.io/rfcs/1584-macros.html>.
241+
if matches!(
242+
CStore::from_tcx(tcx).load_macro_untracked(def_id, tcx),
243+
LoadedMacro::MacroDef { def, .. } if !def.macro_rules
244+
) {
245+
once(crate_name).chain(relative).collect()
246+
} else {
247+
vec![crate_name, *relative.last().expect("relative was empty")]
248+
}
249+
} else {
250+
once(crate_name).chain(relative).collect()
251+
}
252+
}
253+
230254
/// Record an external fully qualified name in the external_paths cache.
231255
///
232256
/// These names are used later on by HTML rendering to generate things like
@@ -240,27 +264,12 @@ pub(crate) fn record_extern_fqn(cx: &mut DocContext<'_>, did: DefId, kind: ItemT
240264
return;
241265
}
242266

243-
let crate_name = cx.tcx.crate_name(did.krate);
244-
245-
let relative = item_relative_path(cx.tcx, did);
246-
let fqn = if let ItemType::Macro = kind {
247-
// Check to see if it is a macro 2.0 or built-in macro
248-
if matches!(
249-
CStore::from_tcx(cx.tcx).load_macro_untracked(did, cx.tcx),
250-
LoadedMacro::MacroDef { def, .. } if !def.macro_rules
251-
) {
252-
once(crate_name).chain(relative).collect()
253-
} else {
254-
vec![crate_name, *relative.last().expect("relative was empty")]
255-
}
256-
} else {
257-
once(crate_name).chain(relative).collect()
258-
};
267+
let item_path = get_item_path(cx.tcx, did, kind);
259268

260269
if did.is_local() {
261-
cx.cache.exact_paths.insert(did, fqn);
270+
cx.cache.exact_paths.insert(did, item_path);
262271
} else {
263-
cx.cache.external_paths.insert(did, (fqn, kind));
272+
cx.cache.external_paths.insert(did, (item_path, kind));
264273
}
265274
}
266275

src/librustdoc/clean/types.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use crate::clean::utils::{is_literal_expr, print_evaluated_const};
4040
use crate::core::DocContext;
4141
use crate::formats::cache::Cache;
4242
use crate::formats::item_type::ItemType;
43+
use crate::html::format::HrefInfo;
4344
use crate::html::render::Context;
4445
use crate::passes::collect_intra_doc_links::UrlFragment;
4546

@@ -519,16 +520,16 @@ impl Item {
519520
.iter()
520521
.filter_map(|ItemLink { link: s, link_text, page_id: id, fragment }| {
521522
debug!(?id);
522-
if let Ok((mut href, ..)) = href(*id, cx) {
523-
debug!(?href);
523+
if let Ok(HrefInfo { mut url, .. }) = href(*id, cx) {
524+
debug!(?url);
524525
if let Some(ref fragment) = *fragment {
525-
fragment.render(&mut href, cx.tcx())
526+
fragment.render(&mut url, cx.tcx())
526527
}
527528
Some(RenderedLink {
528529
original_text: s.clone(),
529530
new_text: link_text.clone(),
530531
tooltip: link_tooltip(*id, fragment, cx).to_string(),
531-
href,
532+
href: url,
532533
})
533534
} else {
534535
None

src/librustdoc/display.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,18 @@ pub(crate) trait Joined: IntoIterator {
1010
///
1111
/// The performance of `joined` is slightly better than `format`, since it doesn't need to use a `Cell` to keep track of whether [`fmt`](Display::fmt)
1212
/// was already called (`joined`'s API doesn't allow it be called more than once).
13-
fn joined(self, sep: impl Display, f: &mut Formatter<'_>) -> fmt::Result;
13+
fn joined(&mut self, sep: impl Display, f: &mut Formatter<'_>) -> fmt::Result;
1414
}
1515

1616
impl<I, T> Joined for I
1717
where
18-
I: IntoIterator<Item = T>,
18+
I: Iterator<Item = T>,
1919
T: Display,
2020
{
21-
fn joined(self, sep: impl Display, f: &mut Formatter<'_>) -> fmt::Result {
22-
let mut iter = self.into_iter();
23-
let Some(first) = iter.next() else { return Ok(()) };
21+
fn joined(&mut self, sep: impl Display, f: &mut Formatter<'_>) -> fmt::Result {
22+
let Some(first) = self.next() else { return Ok(()) };
2423
first.fmt(f)?;
25-
for item in iter {
24+
for item in self {
2625
sep.fmt(f)?;
2726
item.fmt(f)?;
2827
}

src/librustdoc/html/format.rs

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ use rustc_abi::ExternAbi;
1717
use rustc_ast::join_path_syms;
1818
use rustc_data_structures::fx::FxHashSet;
1919
use rustc_hir as hir;
20-
use rustc_hir::def::DefKind;
20+
use rustc_hir::def::{DefKind, MacroKinds};
2121
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
2222
use rustc_hir::{ConstStability, StabilityLevel, StableSince};
23-
use rustc_metadata::creader::{CStore, LoadedMacro};
23+
use rustc_metadata::creader::CStore;
2424
use rustc_middle::ty::{self, TyCtxt, TypingMode};
2525
use rustc_span::symbol::kw;
2626
use rustc_span::{Symbol, sym};
@@ -349,47 +349,56 @@ pub(crate) enum HrefError {
349349
UnnamableItem,
350350
}
351351

352+
/// Type representing information of an `href` attribute.
353+
pub(crate) struct HrefInfo {
354+
/// URL to the item page.
355+
pub(crate) url: String,
356+
/// Kind of the item (used to generate the `title` attribute).
357+
pub(crate) kind: ItemType,
358+
/// Rust path to the item (used to generate the `title` attribute).
359+
pub(crate) rust_path: Vec<Symbol>,
360+
}
361+
352362
/// This function is to get the external macro path because they are not in the cache used in
353363
/// `href_with_root_path`.
354364
fn generate_macro_def_id_path(
355365
def_id: DefId,
356366
cx: &Context<'_>,
357367
root_path: Option<&str>,
358-
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
368+
) -> Result<HrefInfo, HrefError> {
359369
let tcx = cx.tcx();
360370
let crate_name = tcx.crate_name(def_id.krate);
361371
let cache = cx.cache();
362372

363-
let fqp = clean::inline::item_relative_path(tcx, def_id);
364-
let mut relative = fqp.iter().copied();
365373
let cstore = CStore::from_tcx(tcx);
366374
// We need this to prevent a `panic` when this function is used from intra doc links...
367375
if !cstore.has_crate_data(def_id.krate) {
368376
debug!("No data for crate {crate_name}");
369377
return Err(HrefError::NotInExternalCache);
370378
}
371-
// Check to see if it is a macro 2.0 or built-in macro.
372-
// More information in <https://rust-lang.github.io/rfcs/1584-macros.html>.
373-
let is_macro_2 = match cstore.load_macro_untracked(def_id, tcx) {
374-
// If `def.macro_rules` is `true`, then it's not a macro 2.0.
375-
LoadedMacro::MacroDef { def, .. } => !def.macro_rules,
376-
_ => false,
379+
let DefKind::Macro(kinds) = tcx.def_kind(def_id) else {
380+
unreachable!();
377381
};
378-
379-
let mut path = if is_macro_2 {
380-
once(crate_name).chain(relative).collect()
382+
let item_type = if kinds == MacroKinds::DERIVE {
383+
ItemType::ProcDerive
384+
} else if kinds == MacroKinds::ATTR {
385+
ItemType::ProcAttribute
381386
} else {
382-
vec![crate_name, relative.next_back().unwrap()]
387+
ItemType::Macro
383388
};
389+
let mut path = clean::inline::get_item_path(tcx, def_id, item_type);
384390
if path.len() < 2 {
385391
// The minimum we can have is the crate name followed by the macro name. If shorter, then
386392
// it means that `relative` was empty, which is an error.
387393
debug!("macro path cannot be empty!");
388394
return Err(HrefError::NotInExternalCache);
389395
}
390396

391-
if let Some(last) = path.last_mut() {
392-
*last = Symbol::intern(&format!("macro.{last}.html"));
397+
// FIXME: Try to use `iter().chain().once()` instead.
398+
let mut prev = None;
399+
if let Some(last) = path.pop() {
400+
path.push(Symbol::intern(&format!("{}.{last}.html", item_type.as_str())));
401+
prev = Some(last);
393402
}
394403

395404
let url = match cache.extern_locations[&def_id.krate] {
@@ -410,7 +419,11 @@ fn generate_macro_def_id_path(
410419
return Err(HrefError::NotInExternalCache);
411420
}
412421
};
413-
Ok((url, ItemType::Macro, fqp))
422+
if let Some(prev) = prev {
423+
path.pop();
424+
path.push(prev);
425+
}
426+
Ok(HrefInfo { url, kind: item_type, rust_path: path })
414427
}
415428

416429
fn generate_item_def_id_path(
@@ -419,7 +432,7 @@ fn generate_item_def_id_path(
419432
cx: &Context<'_>,
420433
root_path: Option<&str>,
421434
original_def_kind: DefKind,
422-
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
435+
) -> Result<HrefInfo, HrefError> {
423436
use rustc_middle::traits::ObligationCause;
424437
use rustc_trait_selection::infer::TyCtxtInferExt;
425438
use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
@@ -455,7 +468,7 @@ fn generate_item_def_id_path(
455468
let kind = ItemType::from_def_kind(original_def_kind, Some(def_kind));
456469
url_parts = format!("{url_parts}#{kind}.{}", tcx.item_name(original_def_id))
457470
};
458-
Ok((url_parts, shortty, fqp))
471+
Ok(HrefInfo { url: url_parts, kind: shortty, rust_path: fqp })
459472
}
460473

461474
/// Checks if the given defid refers to an item that is unnamable, such as one defined in a const block.
@@ -530,7 +543,7 @@ pub(crate) fn href_with_root_path(
530543
original_did: DefId,
531544
cx: &Context<'_>,
532545
root_path: Option<&str>,
533-
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
546+
) -> Result<HrefInfo, HrefError> {
534547
let tcx = cx.tcx();
535548
let def_kind = tcx.def_kind(original_did);
536549
let did = match def_kind {
@@ -596,14 +609,14 @@ pub(crate) fn href_with_root_path(
596609
}
597610
}
598611
};
599-
let url_parts = make_href(root_path, shortty, url_parts, fqp, is_remote);
600-
Ok((url_parts, shortty, fqp.clone()))
612+
Ok(HrefInfo {
613+
url: make_href(root_path, shortty, url_parts, fqp, is_remote),
614+
kind: shortty,
615+
rust_path: fqp.clone(),
616+
})
601617
}
602618

603-
pub(crate) fn href(
604-
did: DefId,
605-
cx: &Context<'_>,
606-
) -> Result<(String, ItemType, Vec<Symbol>), HrefError> {
619+
pub(crate) fn href(did: DefId, cx: &Context<'_>) -> Result<HrefInfo, HrefError> {
607620
href_with_root_path(did, cx, None)
608621
}
609622

@@ -690,12 +703,12 @@ fn resolved_path(
690703
} else {
691704
let path = fmt::from_fn(|f| {
692705
if use_absolute {
693-
if let Ok((_, _, fqp)) = href(did, cx) {
706+
if let Ok(HrefInfo { rust_path, .. }) = href(did, cx) {
694707
write!(
695708
f,
696709
"{path}::{anchor}",
697-
path = join_path_syms(&fqp[..fqp.len() - 1]),
698-
anchor = print_anchor(did, *fqp.last().unwrap(), cx)
710+
path = join_path_syms(&rust_path[..rust_path.len() - 1]),
711+
anchor = print_anchor(did, *rust_path.last().unwrap(), cx)
699712
)
700713
} else {
701714
write!(f, "{}", last.name)
@@ -824,12 +837,11 @@ fn print_higher_ranked_params_with_space(
824837

825838
pub(crate) fn print_anchor(did: DefId, text: Symbol, cx: &Context<'_>) -> impl Display {
826839
fmt::from_fn(move |f| {
827-
let parts = href(did, cx);
828-
if let Ok((url, short_ty, fqp)) = parts {
840+
if let Ok(HrefInfo { url, kind, rust_path }) = href(did, cx) {
829841
write!(
830842
f,
831-
r#"<a class="{short_ty}" href="{url}" title="{short_ty} {path}">{text}</a>"#,
832-
path = join_path_syms(fqp),
843+
r#"<a class="{kind}" href="{url}" title="{kind} {path}">{text}</a>"#,
844+
path = join_path_syms(rust_path),
833845
text = EscapeBodyText(text.as_str()),
834846
)
835847
} else {
@@ -1056,14 +1068,14 @@ fn print_qpath_data(qpath_data: &clean::QPathData, cx: &Context<'_>) -> impl Dis
10561068
None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()),
10571069
};
10581070

1059-
if let Some((url, _, path)) = parent_href {
1071+
if let Some(HrefInfo { url, rust_path, .. }) = parent_href {
10601072
write!(
10611073
f,
10621074
"<a class=\"associatedtype\" href=\"{url}#{shortty}.{name}\" \
10631075
title=\"type {path}::{name}\">{name}</a>",
10641076
shortty = ItemType::AssocType,
10651077
name = assoc.name,
1066-
path = join_path_syms(path),
1078+
path = join_path_syms(rust_path),
10671079
)
10681080
} else {
10691081
write!(f, "{}", assoc.name)

src/librustdoc/html/highlight.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use super::format;
2121
use crate::clean::PrimitiveType;
2222
use crate::display::Joined as _;
2323
use crate::html::escape::EscapeBodyText;
24+
use crate::html::format::HrefInfo;
2425
use crate::html::macro_expansion::ExpandedCode;
2526
use crate::html::render::span_map::{DUMMY_SP, Span};
2627
use crate::html::render::{Context, LinkFromSrc};
@@ -1357,19 +1358,19 @@ fn generate_link_to_def(
13571358
LinkFromSrc::External(def_id) => {
13581359
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
13591360
.ok()
1360-
.map(|(url, _, _)| url)
1361+
.map(|HrefInfo { url, .. }| url)
13611362
}
13621363
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
13631364
PrimitiveType::primitive_locations(context.tcx())[prim],
13641365
context,
13651366
Some(href_context.root_path),
13661367
)
13671368
.ok()
1368-
.map(|(url, _, _)| url),
1369+
.map(|HrefInfo { url, .. }| url),
13691370
LinkFromSrc::Doc(def_id) => {
13701371
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
13711372
.ok()
1372-
.map(|(doc_link, _, _)| doc_link)
1373+
.map(|HrefInfo { url, .. }| url)
13731374
}
13741375
}
13751376
})

src/librustdoc/html/render/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ use crate::formats::cache::Cache;
7474
use crate::formats::item_type::ItemType;
7575
use crate::html::escape::Escape;
7676
use crate::html::format::{
77-
Ending, HrefError, PrintWithSpace, full_print_fn_decl, href, print_abi_with_space,
77+
Ending, HrefError, HrefInfo, PrintWithSpace, full_print_fn_decl, href, print_abi_with_space,
7878
print_constness_with_space, print_default_space, print_generic_bounds, print_generics,
7979
print_impl, print_path, print_type, print_where_clause, visibility_print_with_space,
8080
};
@@ -982,7 +982,7 @@ fn assoc_href_attr(
982982
};
983983

984984
match href(did.expect_def_id(), cx) {
985-
Ok((url, ..)) => Href::Url(url, item_type),
985+
Ok(HrefInfo { url, .. }) => Href::Url(url, item_type),
986986
// The link is broken since it points to an external crate that wasn't documented.
987987
// Do not create any link in such case. This is better than falling back to a
988988
// dummy anchor like `#{item_type}.{name}` representing the `id` of *this* impl item

tests/rustdoc/jump-to-def/assoc-items.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ impl C {
2626
pub fn wat() {}
2727
}
2828

29-
//@ has - '//a[@href="{{channel}}/core/fmt/macros/macro.Debug.html"]' 'Debug'
30-
//@ has - '//a[@href="{{channel}}/core/cmp/macro.PartialEq.html"]' 'PartialEq'
29+
// These two links must not change and in particular must contain `/derive.`!
30+
//@ has - '//a[@href="{{channel}}/core/fmt/macros/derive.Debug.html"]' 'Debug'
31+
//@ has - '//a[@href="{{channel}}/core/cmp/derive.PartialEq.html"]' 'PartialEq'
3132
#[derive(Debug, PartialEq)]
3233
pub struct Bar;
3334
impl Trait for Bar {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// This test ensures that the same link is generated in both intra-doc links
2+
// and in jump to def links.
3+
4+
//@ compile-flags: -Zunstable-options --generate-link-to-definition
5+
6+
#![crate_name = "foo"]
7+
8+
// First we check intra-doc links.
9+
//@ has 'foo/struct.Bar.html'
10+
//@ has - '//a[@href="{{channel}}/core/fmt/macros/derive.Debug.html"]' 'Debug'
11+
//@ has - '//a[@href="{{channel}}/core/cmp/derive.PartialEq.html"]' 'PartialEq'
12+
13+
// We also check the "title" attributes.
14+
//@ has - '//a[@href="{{channel}}/core/fmt/macros/derive.Debug.html"]/@title' 'derive core::fmt::macros::Debug'
15+
//@ has - '//a[@href="{{channel}}/core/cmp/derive.PartialEq.html"]/@title' 'derive core::cmp::PartialEq'
16+
17+
// Then we check that they are the same in jump to def.
18+
19+
/// [Debug][derive@Debug] and [PartialEq][derive@PartialEq]
20+
//@ has 'src/foo/derive-macro.rs.html'
21+
//@ has - '//a[@href="{{channel}}/core/fmt/macros/derive.Debug.html"]' 'Debug'
22+
//@ has - '//a[@href="{{channel}}/core/cmp/derive.PartialEq.html"]' 'PartialEq'
23+
#[derive(Debug, PartialEq)]
24+
pub struct Bar;

0 commit comments

Comments
 (0)