@@ -514,20 +514,25 @@ pub fn span_of_fragments(fragments: &[DocFragment]) -> Option<Span> {
514514/// This method does not always work, because markdown bytes don't necessarily match source bytes,
515515/// like if escapes are used in the string. In this case, it returns `None`.
516516///
517- /// This method will return `Some` only if:
517+ /// `markdown` is typically the entire documentation for an item,
518+ /// after combining fragments.
519+ ///
520+ /// This method will return `Some` only if one of the following is true:
518521///
519522/// - The doc is made entirely from sugared doc comments, which cannot contain escapes
520523/// - The doc is entirely from a single doc fragment, with a string literal, exactly equal
521524/// - The doc comes from `include_str!`
525+ /// - The doc includes exactly one substring matching `markdown[md_range]` which is contained in a single doc fragment.
522526pub fn source_span_for_markdown_range (
523527 tcx : TyCtxt < ' _ > ,
524528 markdown : & str ,
525529 md_range : & Range < usize > ,
526530 fragments : & [ DocFragment ] ,
527531) -> Option < Span > {
532+ let span_to_snippet = |span| tcx. sess . source_map ( ) . span_to_snippet ( span) ;
528533 if let & [ fragment] = & fragments
529534 && fragment. kind == DocFragmentKind :: RawDoc
530- && let Ok ( snippet) = tcx . sess . source_map ( ) . span_to_snippet ( fragment. span )
535+ && let Ok ( snippet) = span_to_snippet ( fragment. span )
531536 && snippet. trim_end ( ) == markdown. trim_end ( )
532537 && let Ok ( md_range_lo) = u32:: try_from ( md_range. start )
533538 && let Ok ( md_range_hi) = u32:: try_from ( md_range. end )
@@ -544,6 +549,38 @@ pub fn source_span_for_markdown_range(
544549 let is_all_sugared_doc = fragments. iter ( ) . all ( |frag| frag. kind == DocFragmentKind :: SugaredDoc ) ;
545550
546551 if !is_all_sugared_doc {
552+ // this case ignores the markdown outside of the range so that it can
553+ // work in cases where the markdown is made from several different
554+ // doc fragments, but the target range does not span across multiple
555+ // fragments.
556+ let mut match_data = None ;
557+ let pat = & markdown[ md_range. clone ( ) ] ;
558+ // this heirustic doesn't make sense with a zero-sized range.
559+ if pat. len ( ) == 0 {
560+ return None ;
561+ }
562+ for ( i, fragment) in fragments. iter ( ) . enumerate ( ) {
563+ if let Ok ( snippet) = span_to_snippet ( fragment. span )
564+ && let Some ( match_start) = snippet. find ( pat)
565+ {
566+ // if there is either a match in a previous fragment,
567+ // or multiple matches in this fragment,
568+ // there is ambiguity.
569+ if match_data. is_none ( ) && !snippet[ match_start + 1 ..] . contains ( pat) {
570+ match_data = Some ( ( i, match_start) ) ;
571+ } else {
572+ // heirustic produced ambiguity, return nothing.
573+ return None ;
574+ }
575+ }
576+ }
577+ if let Some ( ( i, match_start) ) = match_data {
578+ use rustc_span:: BytePos ;
579+ let mut sp = fragments[ i] . span ;
580+ sp = sp. with_lo ( sp. lo ( ) + BytePos ( match_start as u32 ) ) ;
581+ sp = sp. with_hi ( sp. lo ( ) + BytePos ( ( md_range. end - md_range. start ) as u32 ) ) ;
582+ return Some ( sp) ;
583+ }
547584 return None ;
548585 }
549586
0 commit comments