@@ -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 ) ;
@@ -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`.
504517fn 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
521538fn 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
628652pub ( 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.
0 commit comments