Skip to content

Commit bfeb902

Browse files
committed
Fix relative extern URL depth on source pages
1 parent 6a979b3 commit bfeb902

File tree

6 files changed

+84
-59
lines changed

6 files changed

+84
-59
lines changed

src/librustdoc/html/format.rs

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -360,11 +360,11 @@ pub(crate) struct HrefInfo {
360360
}
361361

362362
/// This function is to get the external macro path because they are not in the cache used in
363-
/// `href_with_root_path`.
363+
/// `href_with_jump_to_def_path_depth`.
364364
fn generate_macro_def_id_path(
365365
def_id: DefId,
366366
cx: &Context<'_>,
367-
root_path: Option<&str>,
367+
jump_to_def_path_depth: Option<usize>,
368368
) -> Result<HrefInfo, HrefError> {
369369
let tcx = cx.tcx();
370370
let crate_name = tcx.crate_name(def_id.krate);
@@ -403,15 +403,15 @@ fn generate_macro_def_id_path(
403403

404404
let url = match cache.extern_locations[&def_id.krate] {
405405
ExternalLocation::Remote { ref url, is_absolute } => {
406-
let mut prefix = remote_url_prefix(url, is_absolute, cx.current.len());
406+
let depth = jump_to_def_path_depth.unwrap_or(cx.current.len());
407+
let mut prefix = remote_url_prefix(url, is_absolute, depth);
407408
prefix.extend(path.iter().copied());
408409
prefix.finish()
409410
}
410411
ExternalLocation::Local => {
411-
// `root_path` always end with a `/`.
412412
format!(
413413
"{root_path}{path}",
414-
root_path = root_path.unwrap_or(""),
414+
root_path = "../".repeat(jump_to_def_path_depth.unwrap_or(0)),
415415
path = fmt::from_fn(|f| path.iter().joined("/", f))
416416
)
417417
}
@@ -431,7 +431,7 @@ fn generate_item_def_id_path(
431431
mut def_id: DefId,
432432
original_def_id: DefId,
433433
cx: &Context<'_>,
434-
root_path: Option<&str>,
434+
jump_to_def_path_depth: Option<usize>,
435435
) -> Result<HrefInfo, HrefError> {
436436
use rustc_middle::traits::ObligationCause;
437437
use rustc_trait_selection::infer::TyCtxtInferExt;
@@ -460,8 +460,9 @@ fn generate_item_def_id_path(
460460
let shortty = ItemType::from_def_id(def_id, tcx);
461461
let module_fqp = to_module_fqp(shortty, &fqp);
462462

463-
let (parts, is_absolute) = url_parts(cx.cache(), def_id, module_fqp, &cx.current)?;
464-
let mut url = make_href(root_path, shortty, parts, &fqp, is_absolute);
463+
let (parts, is_remote) =
464+
url_parts(cx.cache(), def_id, module_fqp, &cx.current, jump_to_def_path_depth)?;
465+
let mut url = make_href(jump_to_def_path_depth, shortty, parts, &fqp, is_remote);
465466

466467
if def_id != original_def_id {
467468
let kind = ItemType::from_def_id(original_def_id, tcx);
@@ -506,34 +507,50 @@ fn remote_url_prefix(url: &str, is_absolute: bool, depth: usize) -> UrlPartsBuil
506507
}
507508
}
508509

510+
/// Returns `(url_parts, is_remote)` where `is_remote` indicates the URL points to an
511+
/// external location and should not use relative URLs.
512+
///
513+
/// This function tries to generate minimal, relative URLs. When generating an intra-doc
514+
/// link from one item page to another, it compares the paths of both items and generates
515+
/// exactly the right number of "../"'s to reach the deepest common parent, then fills in
516+
/// the rest of the path. This is done by comparing `module_fqp` with `relative_to`.
517+
///
518+
/// When generating a link for jump-to-def across crates, that won't work, because sources are
519+
/// stored under "src/[crate]/[path].rs". When linking jump-to-def for another crate, we just
520+
/// write enough "../"'s to reach the doc root, then generate the entire path for `module_fqp`.
521+
/// The right number of "../"'s is stored in `jump_to_def_path_depth`.
509522
fn url_parts(
510523
cache: &Cache,
511524
def_id: DefId,
512525
module_fqp: &[Symbol],
513526
relative_to: &[Symbol],
527+
jump_to_def_path_depth: Option<usize>,
514528
) -> Result<(UrlPartsBuilder, bool), HrefError> {
515529
match cache.extern_locations[&def_id.krate] {
516530
ExternalLocation::Remote { ref url, is_absolute } => {
517-
let mut builder = remote_url_prefix(url, is_absolute, relative_to.len());
531+
let depth = jump_to_def_path_depth.unwrap_or(relative_to.len());
532+
let mut builder = remote_url_prefix(url, is_absolute, depth);
518533
builder.extend(module_fqp.iter().copied());
519-
Ok((builder, is_absolute))
534+
// Remote URLs (both absolute and relative) already encode the correct path;
535+
// no additional depth prefix should be applied.
536+
Ok((builder, true))
520537
}
521538
ExternalLocation::Local => Ok((href_relative_parts(module_fqp, relative_to), false)),
522539
ExternalLocation::Unknown => Err(HrefError::DocumentationNotBuilt),
523540
}
524541
}
525542

526543
fn make_href(
527-
root_path: Option<&str>,
544+
jump_to_def_path_depth: Option<usize>,
528545
shortty: ItemType,
529546
mut url_parts: UrlPartsBuilder,
530547
fqp: &[Symbol],
531-
is_absolute: bool,
548+
is_remote: bool,
532549
) -> String {
533-
// FIXME: relative extern URLs may break when prefixed with root_path
534-
if !is_absolute && let Some(root_path) = root_path {
535-
let root = root_path.trim_end_matches('/');
536-
url_parts.push_front(root);
550+
// Remote URLs already have the correct depth via `remote_url_prefix`;
551+
// only local items need `jump_to_def_path_depth` to bridge from source pages to the doc tree.
552+
if !is_remote && let Some(depth) = jump_to_def_path_depth {
553+
url_parts.push_front(&iter::repeat_n("..", depth).join("/"));
537554
}
538555
debug!(?url_parts);
539556
match shortty {
@@ -548,10 +565,10 @@ fn make_href(
548565
url_parts.finish()
549566
}
550567

551-
pub(crate) fn href_with_root_path(
568+
pub(crate) fn href_with_jump_to_def_path_depth(
552569
original_did: DefId,
553570
cx: &Context<'_>,
554-
root_path: Option<&str>,
571+
jump_to_def_path_depth: Option<usize>,
555572
) -> Result<HrefInfo, HrefError> {
556573
let tcx = cx.tcx();
557574
let def_kind = tcx.def_kind(original_did);
@@ -562,7 +579,13 @@ pub(crate) fn href_with_root_path(
562579
}
563580
// If this a constructor, we get the parent (either a struct or a variant) and then
564581
// generate the link for this item.
565-
DefKind::Ctor(..) => return href_with_root_path(tcx.parent(original_did), cx, root_path),
582+
DefKind::Ctor(..) => {
583+
return href_with_jump_to_def_path_depth(
584+
tcx.parent(original_did),
585+
cx,
586+
jump_to_def_path_depth,
587+
);
588+
}
566589
DefKind::ExternCrate => {
567590
// Link to the crate itself, not the `extern crate` item.
568591
if let Some(local_did) = original_did.as_local() {
@@ -582,7 +605,7 @@ pub(crate) fn href_with_root_path(
582605
if !original_did.is_local() {
583606
// If we are generating an href for the "jump to def" feature, then the only case we want
584607
// to ignore is if the item is `doc(hidden)` because we can't link to it.
585-
if root_path.is_some() {
608+
if jump_to_def_path_depth.is_some() {
586609
if tcx.is_doc_hidden(original_did) {
587610
return Err(HrefError::Private);
588611
}
@@ -594,7 +617,7 @@ pub(crate) fn href_with_root_path(
594617
}
595618
}
596619

597-
let (fqp, shortty, url_parts, is_absolute) = match cache.paths.get(&did) {
620+
let (fqp, shortty, url_parts, is_remote) = match cache.paths.get(&did) {
598621
Some(&(ref fqp, shortty)) => (
599622
fqp,
600623
shortty,
@@ -609,29 +632,30 @@ pub(crate) fn href_with_root_path(
609632
// Associated items are handled differently with "jump to def". The anchor is generated
610633
// directly here whereas for intra-doc links, we have some extra computation being
611634
// performed there.
612-
let def_id_to_get = if root_path.is_some() { original_did } else { did };
635+
let def_id_to_get = if jump_to_def_path_depth.is_some() { original_did } else { did };
613636
if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&def_id_to_get) {
614637
let module_fqp = to_module_fqp(shortty, fqp);
615-
let (parts, is_absolute) = url_parts(cache, did, module_fqp, relative_to)?;
616-
(fqp, shortty, parts, is_absolute)
638+
let (parts, is_remote) =
639+
url_parts(cache, did, module_fqp, relative_to, jump_to_def_path_depth)?;
640+
(fqp, shortty, parts, is_remote)
617641
} else if matches!(def_kind, DefKind::Macro(_)) {
618-
return generate_macro_def_id_path(did, cx, root_path);
642+
return generate_macro_def_id_path(did, cx, jump_to_def_path_depth);
619643
} else if did.is_local() {
620644
return Err(HrefError::Private);
621645
} else {
622-
return generate_item_def_id_path(did, original_did, cx, root_path);
646+
return generate_item_def_id_path(did, original_did, cx, jump_to_def_path_depth);
623647
}
624648
}
625649
};
626650
Ok(HrefInfo {
627-
url: make_href(root_path, shortty, url_parts, fqp, is_absolute),
651+
url: make_href(jump_to_def_path_depth, shortty, url_parts, fqp, is_remote),
628652
kind: shortty,
629653
rust_path: fqp.clone(),
630654
})
631655
}
632656

633657
pub(crate) fn href(did: DefId, cx: &Context<'_>) -> Result<HrefInfo, HrefError> {
634-
href_with_root_path(did, cx, None)
658+
href_with_jump_to_def_path_depth(did, cx, None)
635659
}
636660

637661
/// Both paths should only be modules.

src/librustdoc/html/highlight.rs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub(crate) struct HrefContext<'a, 'tcx> {
3333
pub(crate) file_span: Span,
3434
/// This field is used to know "how far" from the top of the directory we are to link to either
3535
/// documentation pages or other source pages.
36-
pub(crate) root_path: &'a str,
36+
pub(crate) jump_to_def_path_depth: usize,
3737
/// This field is used to calculate precise local URLs.
3838
pub(crate) current_href: String,
3939
}
@@ -1396,23 +1396,27 @@ fn generate_link_to_def(
13961396
LinkFromSrc::Local(span) => {
13971397
context.href_from_span_relative(*span, &href_context.current_href)
13981398
}
1399-
LinkFromSrc::External(def_id) => {
1400-
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
1401-
.ok()
1402-
.map(|HrefInfo { url, .. }| url)
1403-
}
1404-
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
1399+
LinkFromSrc::External(def_id) => format::href_with_jump_to_def_path_depth(
1400+
*def_id,
1401+
context,
1402+
Some(href_context.jump_to_def_path_depth),
1403+
)
1404+
.ok()
1405+
.map(|HrefInfo { url, .. }| url),
1406+
LinkFromSrc::Primitive(prim) => format::href_with_jump_to_def_path_depth(
14051407
PrimitiveType::primitive_locations(context.tcx())[prim],
14061408
context,
1407-
Some(href_context.root_path),
1409+
Some(href_context.jump_to_def_path_depth),
1410+
)
1411+
.ok()
1412+
.map(|HrefInfo { url, .. }| url),
1413+
LinkFromSrc::Doc(def_id) => format::href_with_jump_to_def_path_depth(
1414+
*def_id,
1415+
context,
1416+
Some(href_context.jump_to_def_path_depth),
14081417
)
14091418
.ok()
14101419
.map(|HrefInfo { url, .. }| url),
1411-
LinkFromSrc::Doc(def_id) => {
1412-
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
1413-
.ok()
1414-
.map(|HrefInfo { url, .. }| url)
1415-
}
14161420
}
14171421
})
14181422
{

src/librustdoc/html/render/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2802,7 +2802,7 @@ fn render_call_locations<W: fmt::Write>(
28022802
contents_subset,
28032803
file_span,
28042804
cx,
2805-
&cx.root_path(),
2805+
cx.current.len(),
28062806
&highlight::DecorationInfo(decoration_info),
28072807
&sources::SourceContext::Embedded(sources::ScrapedInfo {
28082808
needs_expansion,

src/librustdoc/html/sources.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::cell::RefCell;
1+
use std::cell::{Cell, RefCell};
22
use std::ffi::OsStr;
33
use std::path::{Component, Path, PathBuf};
44
use std::{fmt, fs};
@@ -193,31 +193,23 @@ impl SourceCollector<'_, '_> {
193193
let shared = &self.cx.shared;
194194
// Create the intermediate directories
195195
let cur = RefCell::new(PathBuf::new());
196-
let root_path = RefCell::new(PathBuf::new());
196+
let jump_to_def_path_depth = Cell::new(2usize);
197197

198198
clean_path(
199199
&shared.src_root,
200200
&p,
201201
|component| {
202202
cur.borrow_mut().push(component);
203-
root_path.borrow_mut().push("..");
203+
jump_to_def_path_depth.set(jump_to_def_path_depth.get() + 1);
204204
},
205205
|| {
206206
cur.borrow_mut().pop();
207-
root_path.borrow_mut().pop();
207+
jump_to_def_path_depth.set(jump_to_def_path_depth.get() - 1);
208208
},
209209
);
210210

211211
let src_fname = p.file_name().expect("source has no filename").to_os_string();
212212
let mut fname = src_fname.clone();
213-
214-
let root_path = PathBuf::from("../../").join(root_path.into_inner());
215-
let mut root_path = root_path.to_string_lossy();
216-
if let Some(c) = root_path.as_bytes().last()
217-
&& *c != b'/'
218-
{
219-
root_path += "/";
220-
}
221213
let mut file_path = Path::new(&self.crate_name).join(&*cur.borrow());
222214
file_path.push(&fname);
223215
fname.push(".html");
@@ -228,6 +220,7 @@ impl SourceCollector<'_, '_> {
228220

229221
let title = format!("{} - source", src_fname.to_string_lossy());
230222
let desc = format!("Source of the Rust file `{}`.", p.to_string_lossy());
223+
let root_path = "../".repeat(jump_to_def_path_depth.get());
231224
let page = layout::Page {
232225
title: &title,
233226
short_title: &src_fname.to_string_lossy(),
@@ -251,7 +244,7 @@ impl SourceCollector<'_, '_> {
251244
contents,
252245
file_span,
253246
self.cx,
254-
&root_path,
247+
jump_to_def_path_depth.get(),
255248
&highlight::DecorationInfo::default(),
256249
&source_context,
257250
)
@@ -330,7 +323,7 @@ pub(crate) fn print_src(
330323
s: &str,
331324
file_span: rustc_span::Span,
332325
context: &Context<'_>,
333-
root_path: &str,
326+
jump_to_def_path_depth: usize,
334327
decoration_info: &highlight::DecorationInfo,
335328
source_context: &SourceContext<'_>,
336329
) -> fmt::Result {
@@ -359,7 +352,7 @@ pub(crate) fn print_src(
359352
Some(highlight::HrefContext {
360353
context,
361354
file_span: file_span.into(),
362-
root_path,
355+
jump_to_def_path_depth,
363356
current_href,
364357
}),
365358
Some(decoration_info),

src/librustdoc/passes/collect_intra_doc_links.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1200,7 +1200,7 @@ impl LinkCollector<'_, '_> {
12001200

12011201
/// Returns `true` if a link could be generated from the given intra-doc information.
12021202
///
1203-
/// This is a very light version of `format::href_with_root_path` since we're only interested
1203+
/// This is a very light version of `format::href_with_jump_to_def_path_depth` since we're only interested
12041204
/// about whether we can generate a link to an item or not.
12051205
///
12061206
/// * If `original_did` is local, then we check if the item is reexported or public.

tests/rustdoc-html/extern/extern-html-root-url-relative.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ pub mod nested {
2121
pub mod intra_doc_link {
2222
}
2323

24-
// link-to-definition
24+
// link-to-definition: source pages live under src/<crate_name>/ so they need
25+
// ../../ to reach the doc root. Previously these links were incorrectly generated
26+
// as ../../../ (one level too deep) which passed the `has` check by substring match.
2527
//@ has src/extern_html_root_url_relative/extern-html-root-url-relative.rs.html
2628
//@ has - '//a/@href' '../../core/iter/index.html'
2729
//@ has - '//a/@href' '../../core/future/index.html'
30+
//@ !has - '//a/@href' '../../../core/iter/index.html'
31+
//@ !has - '//a/@href' '../../../core/future/index.html'

0 commit comments

Comments
 (0)