@@ -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 `.
364364fn 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`.
509522fn 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
526543fn 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
633657pub ( 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.
0 commit comments