@@ -194,12 +194,12 @@ pub fn attrs_to_doc_fragments<'a>(
194
194
for ( attr, item_id) in attrs {
195
195
if let Some ( ( doc_str, comment_kind) ) = attr. doc_str_and_comment_kind ( ) {
196
196
let doc = beautify_doc_string ( doc_str, comment_kind) ;
197
- let kind = if attr. is_doc_comment ( ) {
198
- DocFragmentKind :: SugaredDoc
197
+ let ( span , kind) = if attr. is_doc_comment ( ) {
198
+ ( attr . span , DocFragmentKind :: SugaredDoc )
199
199
} else {
200
- DocFragmentKind :: RawDoc
200
+ ( span_for_value ( attr ) , DocFragmentKind :: RawDoc )
201
201
} ;
202
- let fragment = DocFragment { span : attr . span , doc, kind, item_id, indent : 0 } ;
202
+ let fragment = DocFragment { span, doc, kind, item_id, indent : 0 } ;
203
203
doc_fragments. push ( fragment) ;
204
204
} else if !doc_only {
205
205
other_attrs. push ( attr. clone ( ) ) ;
@@ -211,6 +211,16 @@ pub fn attrs_to_doc_fragments<'a>(
211
211
( doc_fragments, other_attrs)
212
212
}
213
213
214
+ fn span_for_value ( attr : & ast:: Attribute ) -> Span {
215
+ if let ast:: AttrKind :: Normal ( normal) = & attr. kind
216
+ && let ast:: AttrArgs :: Eq ( _, ast:: AttrArgsEq :: Hir ( meta) ) = & normal. item . args
217
+ {
218
+ meta. span . with_ctxt ( attr. span . ctxt ( ) )
219
+ } else {
220
+ attr. span
221
+ }
222
+ }
223
+
214
224
/// Return the doc-comments on this item, grouped by the module they came from.
215
225
/// The module can be different if this is a re-export with added documentation.
216
226
///
@@ -482,15 +492,36 @@ pub fn span_of_fragments(fragments: &[DocFragment]) -> Option<Span> {
482
492
483
493
/// Attempts to match a range of bytes from parsed markdown to a `Span` in the source code.
484
494
///
485
- /// This method will return `None` if we cannot construct a span from the source map or if the
486
- /// fragments are not all sugared doc comments. It's difficult to calculate the correct span in
487
- /// that case due to escaping and other source features.
495
+ /// This method does not always work, because markdown bytes don't necessarily match source bytes,
496
+ /// like if escapes are used in the string. In this case, it returns `None`.
497
+ ///
498
+ /// This method will return `Some` only if:
499
+ ///
500
+ /// - The doc is made entirely from sugared doc comments, which cannot contain escapes
501
+ /// - The doc is entirely from a single doc fragment, with a string literal, exactly equal
502
+ /// - The doc comes from `include_str!`
488
503
pub fn source_span_for_markdown_range (
489
504
tcx : TyCtxt < ' _ > ,
490
505
markdown : & str ,
491
506
md_range : & Range < usize > ,
492
507
fragments : & [ DocFragment ] ,
493
508
) -> Option < Span > {
509
+ if let & [ fragment] = & fragments
510
+ && fragment. kind == DocFragmentKind :: RawDoc
511
+ && let Ok ( snippet) = tcx. sess . source_map ( ) . span_to_snippet ( fragment. span )
512
+ && snippet. trim_end ( ) == markdown. trim_end ( )
513
+ && let Ok ( md_range_lo) = u32:: try_from ( md_range. start )
514
+ && let Ok ( md_range_hi) = u32:: try_from ( md_range. end )
515
+ {
516
+ // Single fragment with string that contains same bytes as doc.
517
+ return Some ( Span :: new (
518
+ fragment. span . lo ( ) + rustc_span:: BytePos ( md_range_lo) ,
519
+ fragment. span . lo ( ) + rustc_span:: BytePos ( md_range_hi) ,
520
+ fragment. span . ctxt ( ) ,
521
+ fragment. span . parent ( ) ,
522
+ ) ) ;
523
+ }
524
+
494
525
let is_all_sugared_doc = fragments. iter ( ) . all ( |frag| frag. kind == DocFragmentKind :: SugaredDoc ) ;
495
526
496
527
if !is_all_sugared_doc {
0 commit comments