@@ -184,10 +184,14 @@ impl<'a> RenderContext<'a> {
184184 ) -> HashMap < String , String > {
185185 let mut resolved = HashMap :: new ( ) ;
186186 for ( text, id) in links {
187+ // The links field keys may have backtick wrappers (`` `Builder` ``).
188+ // Strip them since preprocess_shortcut_links removes them from URLs.
189+ let clean_key = text. trim_matches ( '`' ) . to_string ( ) ;
187190 if let Some ( url) = self . resolve_reexport_url ( id, current_depth) {
188- // The links field keys may have backtick wrappers (`` `Builder` ``).
189- // Strip them since preprocess_shortcut_links removes them from URLs.
190- let clean_key = text. trim_matches ( '`' ) . to_string ( ) ;
191+ resolved. insert ( clean_key, url) ;
192+ } else if let Some ( url) = self . resolve_method_from_link_text ( & clean_key, current_depth) {
193+ // Fallback: parse "Type::method" from the link text when the
194+ // method ID isn't in our index (common for cross-crate methods).
191195 resolved. insert ( clean_key, url) ;
192196 }
193197 }
@@ -271,27 +275,81 @@ impl<'a> RenderContext<'a> {
271275 ) -> Option < String > {
272276 let method_name = method_item. name . as_deref ( ) ?;
273277
274- // Find the parent type by searching impl blocks.
275- for ( _impl_id, impl_item) in & self . krate . index {
276- let ItemEnum :: Impl ( impl_) = & impl_item. inner else {
278+ // Find the parent by searching impl blocks and trait definitions.
279+ for ( parent_id, parent_item) in & self . krate . index {
280+ match & parent_item. inner {
281+ ItemEnum :: Impl ( impl_) => {
282+ if !impl_. items . contains ( method_id) {
283+ continue ;
284+ }
285+ // Found the impl containing this method.
286+ if let Some ( type_id) = get_type_id ( & impl_. for_ ) {
287+ if let Some ( type_url) = self . resolve_reexport_url ( type_id, current_depth) {
288+ let base_url = type_url. split ( '#' ) . next ( ) . unwrap_or ( & type_url) ;
289+ return Some ( format ! ( "{}#method.{}" , base_url, method_name) ) ;
290+ }
291+ }
292+ return None ;
293+ }
294+ ItemEnum :: Trait ( trait_) => {
295+ if !trait_. items . contains ( method_id) {
296+ continue ;
297+ }
298+ // Found the trait containing this method.
299+ if let Some ( trait_url) = self . resolve_reexport_url ( parent_id, current_depth) {
300+ let base_url = trait_url. split ( '#' ) . next ( ) . unwrap_or ( & trait_url) ;
301+ return Some ( format ! ( "{}#method.{}" , base_url, method_name) ) ;
302+ }
303+ return None ;
304+ }
305+ _ => continue ,
306+ }
307+ }
308+
309+ None
310+ }
311+
312+ /// Resolve a "Type::method" pattern from link text.
313+ ///
314+ /// Fallback when the method ID isn't in our index (cross-crate methods).
315+ /// Parses the link text to find the parent type, resolves it, and appends
316+ /// a `#method.name` anchor.
317+ fn resolve_method_from_link_text (
318+ & self ,
319+ text : & str ,
320+ current_depth : usize ,
321+ ) -> Option < String > {
322+ // Split "Type::method" or "mod::Type::method" on last "::".
323+ let ( type_part, method_name) = text. rsplit_once ( "::" ) ?;
324+
325+ // Search krate.paths for the type by matching the last path segment,
326+ // preferring types/traits over modules.
327+ let type_name = type_part. rsplit ( "::" ) . next ( ) . unwrap_or ( type_part) ;
328+ let mut best: Option < ( String , usize ) > = None ;
329+
330+ for ( id, summary) in & self . krate . paths {
331+ let is_type = matches ! (
332+ summary. kind,
333+ ItemKind :: Struct | ItemKind :: Enum | ItemKind :: Trait | ItemKind :: Union
334+ ) ;
335+ if !is_type {
277336 continue ;
278- } ;
279- if !impl_ . items . contains ( method_id ) {
337+ }
338+ if summary . path . last ( ) . map ( |s| s . as_str ( ) ) != Some ( type_name ) {
280339 continue ;
281340 }
282- // Found the impl containing this method.
283- // Get the type ID from the impl's for_ type.
284- if let Some ( type_id ) = get_type_id ( & impl_ . for_ ) {
285- if let Some ( type_url ) = self . resolve_reexport_url ( type_id , current_depth ) {
286- // Strip any existing fragment .
287- let base_url = type_url . split ( '#' ) . next ( ) . unwrap_or ( & type_url ) ;
288- return Some ( format ! ( "{}#method.{}" , base_url , method_name ) ) ;
341+ if let Some ( type_url ) = self . resolve_reexport_url ( id , current_depth ) {
342+ let base_url = type_url . split ( '#' ) . next ( ) . unwrap_or ( & type_url ) ;
343+ let url = format ! ( "{}#method.{}" , base_url , method_name ) ;
344+ let path_len = summary . path . len ( ) ;
345+ // Prefer shorter paths (more likely the canonical public location) .
346+ if best . as_ref ( ) . map_or ( true , | ( _ , len ) | path_len < * len ) {
347+ best = Some ( ( url , path_len ) ) ;
289348 }
290349 }
291- break ;
292350 }
293351
294- None
352+ best . map ( | ( url , _ ) | url )
295353 }
296354
297355 /// Find a re-export URL for a local item by checking if it appears as a
@@ -503,6 +561,9 @@ mod tests {
503561 const ENUM_DEF : u32 = 7 ;
504562 const OK_VAR : u32 = 8 ;
505563 const ERR_VAR : u32 = 9 ;
564+ const TRAIT_DEF : u32 = 10 ;
565+ const TRAIT_METHOD : u32 = 11 ;
566+ const TRAIT_USE : u32 = 12 ;
506567
507568 /// Helper to build a minimal Item.
508569 fn item ( id : u32 , name : & str , vis : Visibility , inner : ItemEnum ) -> ( Id , Item ) {
@@ -574,6 +635,18 @@ mod tests {
574635 } )
575636 }
576637
638+ fn empty_trait ( method_ids : Vec < u32 > ) -> ItemEnum {
639+ ItemEnum :: Trait ( Trait {
640+ is_auto : false ,
641+ is_unsafe : false ,
642+ is_dyn_compatible : true ,
643+ items : method_ids. into_iter ( ) . map ( Id ) . collect ( ) ,
644+ generics : Generics { params : vec ! [ ] , where_predicates : vec ! [ ] } ,
645+ bounds : vec ! [ ] ,
646+ implementations : vec ! [ ] ,
647+ } )
648+ }
649+
577650 fn path_entry ( crate_id : u32 , path : & [ & str ] , kind : ItemKind ) -> ItemSummary {
578651 ItemSummary {
579652 crate_id,
@@ -617,7 +690,7 @@ mod tests {
617690
618691 // thread module.
619692 let ( id, i) = item ( THREAD_MOD , "thread" , Visibility :: Public ,
620- module ( false , vec ! [ JH_USE , SPAWN_USE , ENUM_USE ] ) ) ;
693+ module ( false , vec ! [ JH_USE , SPAWN_USE , ENUM_USE , TRAIT_USE ] ) ) ;
621694 index. insert ( id. clone ( ) , i) ;
622695 paths. insert ( id, path_entry ( 0 , & [ "mycrate" , "thread" ] , ItemKind :: Module ) ) ;
623696
@@ -666,6 +739,22 @@ mod tests {
666739 paths. insert ( id, path_entry ( 0 ,
667740 & [ "mycrate" , "_priv" , "MyEnum" , "Err" ] , ItemKind :: Variant ) ) ;
668741
742+ // MyTrait definition (in private module) with one method.
743+ let ( id, i) = item ( TRAIT_DEF , "MyTrait" , Visibility :: Public ,
744+ empty_trait ( vec ! [ TRAIT_METHOD ] ) ) ;
745+ index. insert ( id. clone ( ) , i) ;
746+ paths. insert ( id, path_entry ( 0 ,
747+ & [ "mycrate" , "_priv" , "MyTrait" ] , ItemKind :: Trait ) ) ;
748+
749+ // Trait method.
750+ let ( id, i) = item ( TRAIT_METHOD , "do_stuff" , Visibility :: Public , empty_fn ( ) ) ;
751+ index. insert ( id, i) ;
752+
753+ // Re-export: MyTrait.
754+ let ( id, i) = item ( TRAIT_USE , "MyTrait" , Visibility :: Public ,
755+ use_item ( "mycrate::_priv::MyTrait" , "MyTrait" , TRAIT_DEF ) ) ;
756+ index. insert ( id, i) ;
757+
669758 krate
670759 }
671760
@@ -732,6 +821,21 @@ mod tests {
732821 ) ;
733822 }
734823
824+ #[ test]
825+ fn test_resolve_trait_method_url ( ) {
826+ let krate = test_crate ( ) ;
827+ let config = test_config ( ) ;
828+ let ctx = RenderContext :: new ( & krate, & config) . unwrap ( ) ;
829+
830+ // Trait method do_stuff is defined inside MyTrait. It should resolve
831+ // to the trait page at the re-export location with a #method anchor.
832+ let url = ctx. resolve_reexport_url ( & Id ( TRAIT_METHOD ) , 2 ) ;
833+ assert_eq ! (
834+ url,
835+ Some ( "../../mycrate/thread/trait.MyTrait.html#method.do_stuff" . to_string( ) ) ,
836+ ) ;
837+ }
838+
735839 #[ test]
736840 fn test_build_breadcrumbs_item_page ( ) {
737841 // Item at std::thread::spawn, depth=2 (file at std/thread/fn.spawn.html).
0 commit comments