about summary refs log tree commit diff
path: root/compiler/rustc_resolve/src/rustdoc.rs
diff options
context:
space:
mode:
authorMichael Howell <michael@notriddle.com>2024-03-29 12:31:34 -0700
committerMichael Howell <michael@notriddle.com>2024-03-29 13:31:35 -0700
commit98642da6a9dda39e711b7f3520b0b6d40c1cd043 (patch)
treebc3834326dc016ab7bb22636218b753f03c8aaf1 /compiler/rustc_resolve/src/rustdoc.rs
parentd74804636fa57e80d1e213fa9d2d65b27216b515 (diff)
downloadrust-98642da6a9dda39e711b7f3520b0b6d40c1cd043.tar.gz
rust-98642da6a9dda39e711b7f3520b0b6d40c1cd043.zip
rustdoc: point at span in `include_str!`-ed md file
Diffstat (limited to 'compiler/rustc_resolve/src/rustdoc.rs')
-rw-r--r--compiler/rustc_resolve/src/rustdoc.rs45
1 files changed, 38 insertions, 7 deletions
diff --git a/compiler/rustc_resolve/src/rustdoc.rs b/compiler/rustc_resolve/src/rustdoc.rs
index 0ebcad3cdb8..0bc7579918c 100644
--- a/compiler/rustc_resolve/src/rustdoc.rs
+++ b/compiler/rustc_resolve/src/rustdoc.rs
@@ -194,12 +194,12 @@ pub fn attrs_to_doc_fragments<'a>(
     for (attr, item_id) in attrs {
         if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() {
             let doc = beautify_doc_string(doc_str, comment_kind);
-            let kind = if attr.is_doc_comment() {
-                DocFragmentKind::SugaredDoc
+            let (span, kind) = if attr.is_doc_comment() {
+                (attr.span, DocFragmentKind::SugaredDoc)
             } else {
-                DocFragmentKind::RawDoc
+                (span_for_value(attr), DocFragmentKind::RawDoc)
             };
-            let fragment = DocFragment { span: attr.span, doc, kind, item_id, indent: 0 };
+            let fragment = DocFragment { span, doc, kind, item_id, indent: 0 };
             doc_fragments.push(fragment);
         } else if !doc_only {
             other_attrs.push(attr.clone());
@@ -211,6 +211,16 @@ pub fn attrs_to_doc_fragments<'a>(
     (doc_fragments, other_attrs)
 }
 
+fn span_for_value(attr: &ast::Attribute) -> Span {
+    if let ast::AttrKind::Normal(normal) = &attr.kind
+        && let ast::AttrArgs::Eq(_, ast::AttrArgsEq::Hir(meta)) = &normal.item.args
+    {
+        meta.span.with_ctxt(attr.span.ctxt())
+    } else {
+        attr.span
+    }
+}
+
 /// Return the doc-comments on this item, grouped by the module they came from.
 /// The module can be different if this is a re-export with added documentation.
 ///
@@ -482,15 +492,36 @@ pub fn span_of_fragments(fragments: &[DocFragment]) -> Option<Span> {
 
 /// Attempts to match a range of bytes from parsed markdown to a `Span` in the source code.
 ///
-/// This method will return `None` if we cannot construct a span from the source map or if the
-/// fragments are not all sugared doc comments. It's difficult to calculate the correct span in
-/// that case due to escaping and other source features.
+/// This method does not always work, because markdown bytes don't necessarily match source bytes,
+/// like if escapes are used in the string. In this case, it returns `None`.
+///
+/// This method will return `Some` only if:
+///
+/// - The doc is made entirely from sugared doc comments, which cannot contain escapes
+/// - The doc is entirely from a single doc fragment, with a string literal, exactly equal
+/// - The doc comes from `include_str!`
 pub fn source_span_for_markdown_range(
     tcx: TyCtxt<'_>,
     markdown: &str,
     md_range: &Range<usize>,
     fragments: &[DocFragment],
 ) -> Option<Span> {
+    if let &[fragment] = &fragments
+        && fragment.kind == DocFragmentKind::RawDoc
+        && let Ok(snippet) = tcx.sess.source_map().span_to_snippet(fragment.span)
+        && snippet.trim_end() == markdown.trim_end()
+        && let Ok(md_range_lo) = u32::try_from(md_range.start)
+        && let Ok(md_range_hi) = u32::try_from(md_range.end)
+    {
+        // Single fragment with string that contains same bytes as doc.
+        return Some(Span::new(
+            fragment.span.lo() + rustc_span::BytePos(md_range_lo),
+            fragment.span.lo() + rustc_span::BytePos(md_range_hi),
+            fragment.span.ctxt(),
+            fragment.span.parent(),
+        ));
+    }
+
     let is_all_sugared_doc = fragments.iter().all(|frag| frag.kind == DocFragmentKind::SugaredDoc);
 
     if !is_all_sugared_doc {