Skip to content

Commit 052b44b

Browse files
committed
Auto merge of #153160 - arferreira:fix-relative-extern-url-root-path, r=<try>
Fix relative extern URL depth on source pages try-job: dist-x86_64-linux-alt
2 parents d1ee5e5 + 1369e07 commit 052b44b

File tree

6 files changed

+89
-59
lines changed

6 files changed

+89
-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);
@@ -400,16 +400,16 @@ fn generate_macro_def_id_path(
400400

401401
let url = match cache.extern_locations[&def_id.krate] {
402402
ExternalLocation::Remote { ref url, is_absolute } => {
403-
let mut prefix = remote_url_prefix(url, is_absolute, cx.current.len());
403+
let depth = jump_to_def_path_depth.unwrap_or(cx.current.len());
404+
let mut prefix = remote_url_prefix(url, is_absolute, depth);
404405
prefix.extend(module_path.iter().copied());
405406
prefix.push_fmt(format_args!("{}.{last}.html", item_type.as_str()));
406407
prefix.finish()
407408
}
408409
ExternalLocation::Local => {
409-
// `root_path` always end with a `/`.
410410
format!(
411411
"{root_path}{path}/{item_type}.{last}.html",
412-
root_path = root_path.unwrap_or(""),
412+
root_path = "../".repeat(jump_to_def_path_depth.unwrap_or(0)),
413413
path = fmt::from_fn(|f| module_path.iter().joined("/", f)),
414414
item_type = item_type.as_str(),
415415
)
@@ -426,7 +426,7 @@ fn generate_item_def_id_path(
426426
mut def_id: DefId,
427427
original_def_id: DefId,
428428
cx: &Context<'_>,
429-
root_path: Option<&str>,
429+
jump_to_def_path_depth: Option<usize>,
430430
) -> Result<HrefInfo, HrefError> {
431431
use rustc_middle::traits::ObligationCause;
432432
use rustc_trait_selection::infer::TyCtxtInferExt;
@@ -455,8 +455,9 @@ fn generate_item_def_id_path(
455455
let shortty = ItemType::from_def_id(def_id, tcx);
456456
let module_fqp = to_module_fqp(shortty, &fqp);
457457

458-
let (parts, is_absolute) = url_parts(cx.cache(), def_id, module_fqp, &cx.current)?;
459-
let mut url = make_href(root_path, shortty, parts, &fqp, is_absolute);
458+
let (parts, is_remote) =
459+
url_parts(cx.cache(), def_id, module_fqp, &cx.current, jump_to_def_path_depth)?;
460+
let mut url = make_href(jump_to_def_path_depth, shortty, parts, &fqp, is_remote);
460461

461462
if def_id != original_def_id {
462463
let kind = ItemType::from_def_id(original_def_id, tcx);
@@ -501,34 +502,50 @@ fn remote_url_prefix(url: &str, is_absolute: bool, depth: usize) -> UrlPartsBuil
501502
}
502503
}
503504

505+
/// Returns `(url_parts, is_remote)` where `is_remote` indicates the URL points to an
506+
/// external location and should not use relative URLs.
507+
///
508+
/// This function tries to generate minimal, relative URLs. When generating an intra-doc
509+
/// link from one item page to another, it compares the paths of both items and generates
510+
/// exactly the right number of "../"'s to reach the deepest common parent, then fills in
511+
/// the rest of the path. This is done by comparing `module_fqp` with `relative_to`.
512+
///
513+
/// When generating a link for jump-to-def across crates, that won't work, because sources are
514+
/// stored under `src/<crate>/<path>.rs`. When linking jump-to-def for another crate, we just
515+
/// write enough "../"'s to reach the doc root, then generate the entire path for `module_fqp`.
516+
/// The right number of "../"'s is stored in `jump_to_def_path_depth`.
504517
fn url_parts(
505518
cache: &Cache,
506519
def_id: DefId,
507520
module_fqp: &[Symbol],
508521
relative_to: &[Symbol],
522+
jump_to_def_path_depth: Option<usize>,
509523
) -> Result<(UrlPartsBuilder, bool), HrefError> {
510524
match cache.extern_locations[&def_id.krate] {
511525
ExternalLocation::Remote { ref url, is_absolute } => {
512-
let mut builder = remote_url_prefix(url, is_absolute, relative_to.len());
526+
let depth = jump_to_def_path_depth.unwrap_or(relative_to.len());
527+
let mut builder = remote_url_prefix(url, is_absolute, depth);
513528
builder.extend(module_fqp.iter().copied());
514-
Ok((builder, is_absolute))
529+
// Remote URLs (both absolute and relative) already encode the correct path;
530+
// no additional depth prefix should be applied.
531+
Ok((builder, true))
515532
}
516533
ExternalLocation::Local => Ok((href_relative_parts(module_fqp, relative_to), false)),
517534
ExternalLocation::Unknown => Err(HrefError::DocumentationNotBuilt),
518535
}
519536
}
520537

521538
fn make_href(
522-
root_path: Option<&str>,
539+
jump_to_def_path_depth: Option<usize>,
523540
shortty: ItemType,
524541
mut url_parts: UrlPartsBuilder,
525542
fqp: &[Symbol],
526-
is_absolute: bool,
543+
is_remote: bool,
527544
) -> String {
528-
// FIXME: relative extern URLs may break when prefixed with root_path
529-
if !is_absolute && let Some(root_path) = root_path {
530-
let root = root_path.trim_end_matches('/');
531-
url_parts.push_front(root);
545+
// Remote URLs already have the correct depth via `remote_url_prefix`;
546+
// only local items need `jump_to_def_path_depth` to bridge from source pages to the doc tree.
547+
if !is_remote && let Some(depth) = jump_to_def_path_depth {
548+
url_parts.push_front(&iter::repeat_n("..", depth).join("/"));
532549
}
533550
debug!(?url_parts);
534551
match shortty {
@@ -543,10 +560,10 @@ fn make_href(
543560
url_parts.finish()
544561
}
545562

546-
pub(crate) fn href_with_root_path(
563+
pub(crate) fn href_with_jump_to_def_path_depth(
547564
original_did: DefId,
548565
cx: &Context<'_>,
549-
root_path: Option<&str>,
566+
jump_to_def_path_depth: Option<usize>,
550567
) -> Result<HrefInfo, HrefError> {
551568
let tcx = cx.tcx();
552569
let def_kind = tcx.def_kind(original_did);
@@ -557,7 +574,13 @@ pub(crate) fn href_with_root_path(
557574
}
558575
// If this a constructor, we get the parent (either a struct or a variant) and then
559576
// generate the link for this item.
560-
DefKind::Ctor(..) => return href_with_root_path(tcx.parent(original_did), cx, root_path),
577+
DefKind::Ctor(..) => {
578+
return href_with_jump_to_def_path_depth(
579+
tcx.parent(original_did),
580+
cx,
581+
jump_to_def_path_depth,
582+
);
583+
}
561584
DefKind::ExternCrate => {
562585
// Link to the crate itself, not the `extern crate` item.
563586
if let Some(local_did) = original_did.as_local() {
@@ -577,7 +600,7 @@ pub(crate) fn href_with_root_path(
577600
if !original_did.is_local() {
578601
// If we are generating an href for the "jump to def" feature, then the only case we want
579602
// to ignore is if the item is `doc(hidden)` because we can't link to it.
580-
if root_path.is_some() {
603+
if jump_to_def_path_depth.is_some() {
581604
if tcx.is_doc_hidden(original_did) {
582605
return Err(HrefError::Private);
583606
}
@@ -589,7 +612,7 @@ pub(crate) fn href_with_root_path(
589612
}
590613
}
591614

592-
let (fqp, shortty, url_parts, is_absolute) = match cache.paths.get(&did) {
615+
let (fqp, shortty, url_parts, is_remote) = match cache.paths.get(&did) {
593616
Some(&(ref fqp, shortty)) => (
594617
fqp,
595618
shortty,
@@ -604,29 +627,30 @@ pub(crate) fn href_with_root_path(
604627
// Associated items are handled differently with "jump to def". The anchor is generated
605628
// directly here whereas for intra-doc links, we have some extra computation being
606629
// performed there.
607-
let def_id_to_get = if root_path.is_some() { original_did } else { did };
630+
let def_id_to_get = if jump_to_def_path_depth.is_some() { original_did } else { did };
608631
if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&def_id_to_get) {
609632
let module_fqp = to_module_fqp(shortty, fqp);
610-
let (parts, is_absolute) = url_parts(cache, did, module_fqp, relative_to)?;
611-
(fqp, shortty, parts, is_absolute)
633+
let (parts, is_remote) =
634+
url_parts(cache, did, module_fqp, relative_to, jump_to_def_path_depth)?;
635+
(fqp, shortty, parts, is_remote)
612636
} else if matches!(def_kind, DefKind::Macro(_)) {
613-
return generate_macro_def_id_path(did, cx, root_path);
637+
return generate_macro_def_id_path(did, cx, jump_to_def_path_depth);
614638
} else if did.is_local() {
615639
return Err(HrefError::Private);
616640
} else {
617-
return generate_item_def_id_path(did, original_did, cx, root_path);
641+
return generate_item_def_id_path(did, original_did, cx, jump_to_def_path_depth);
618642
}
619643
}
620644
};
621645
Ok(HrefInfo {
622-
url: make_href(root_path, shortty, url_parts, fqp, is_absolute),
646+
url: make_href(jump_to_def_path_depth, shortty, url_parts, fqp, is_remote),
623647
kind: shortty,
624648
rust_path: fqp.clone(),
625649
})
626650
}
627651

628652
pub(crate) fn href(did: DefId, cx: &Context<'_>) -> Result<HrefInfo, HrefError> {
629-
href_with_root_path(did, cx, None)
653+
href_with_jump_to_def_path_depth(did, cx, None)
630654
}
631655

632656
/// 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: 13 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,28 @@ 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+
// Tracks the extra directory depth accumulated during path traversal.
197+
// Starts at 0; `PathBuf::pop()` on empty is a no-op, so we mirror that
198+
// with `saturating_sub`. The base depth of 2 (for `src/<crate>/`) is added after.
199+
let extra_depth = Cell::new(0usize);
197200

198201
clean_path(
199202
&shared.src_root,
200203
&p,
201204
|component| {
202205
cur.borrow_mut().push(component);
203-
root_path.borrow_mut().push("..");
206+
extra_depth.set(extra_depth.get() + 1);
204207
},
205208
|| {
206209
cur.borrow_mut().pop();
207-
root_path.borrow_mut().pop();
210+
extra_depth.set(extra_depth.get().saturating_sub(1));
208211
},
209212
);
210213

214+
let jump_to_def_path_depth = 2 + extra_depth.get();
215+
211216
let src_fname = p.file_name().expect("source has no filename").to_os_string();
212217
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-
}
221218
let mut file_path = Path::new(&self.crate_name).join(&*cur.borrow());
222219
file_path.push(&fname);
223220
fname.push(".html");
@@ -228,6 +225,7 @@ impl SourceCollector<'_, '_> {
228225

229226
let title = format!("{} - source", src_fname.to_string_lossy());
230227
let desc = format!("Source of the Rust file `{}`.", p.to_string_lossy());
228+
let root_path = "../".repeat(jump_to_def_path_depth);
231229
let page = layout::Page {
232230
title: &title,
233231
short_title: &src_fname.to_string_lossy(),
@@ -251,7 +249,7 @@ impl SourceCollector<'_, '_> {
251249
contents,
252250
file_span,
253251
self.cx,
254-
&root_path,
252+
jump_to_def_path_depth,
255253
&highlight::DecorationInfo::default(),
256254
&source_context,
257255
)
@@ -330,7 +328,7 @@ pub(crate) fn print_src(
330328
s: &str,
331329
file_span: rustc_span::Span,
332330
context: &Context<'_>,
333-
root_path: &str,
331+
jump_to_def_path_depth: usize,
334332
decoration_info: &highlight::DecorationInfo,
335333
source_context: &SourceContext<'_>,
336334
) -> fmt::Result {
@@ -359,7 +357,7 @@ pub(crate) fn print_src(
359357
Some(highlight::HrefContext {
360358
context,
361359
file_span: file_span.into(),
362-
root_path,
360+
jump_to_def_path_depth,
363361
current_href,
364362
}),
365363
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
@@ -1204,7 +1204,7 @@ impl LinkCollector<'_, '_> {
12041204

12051205
/// Returns `true` if a link could be generated from the given intra-doc information.
12061206
///
1207-
/// This is a very light version of `format::href_with_root_path` since we're only interested
1207+
/// This is a very light version of `format::href_with_jump_to_def_path_depth` since we're only interested
12081208
/// about whether we can generate a link to an item or not.
12091209
///
12101210
/// * 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)