about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2025-01-26 21:24:36 +0100
committerGuillaume Gomez <guillaume1.gomez@gmail.com>2025-08-10 12:00:08 +0200
commitf1649a4e4379aa0f5eb1181ca8e04c7790c21e5e (patch)
treeab973445f4979c964de2a935598a97f1a941ccc8
parent49044d0d4044960fbb96d47f51640bc5fa8004af (diff)
downloadrust-f1649a4e4379aa0f5eb1181ca8e04c7790c21e5e.tar.gz
rust-f1649a4e4379aa0f5eb1181ca8e04c7790c21e5e.zip
Better handling of paths in link to def feature
-rw-r--r--src/librustdoc/html/highlight.rs120
-rw-r--r--src/librustdoc/html/render/span_map.rs70
-rw-r--r--tests/rustdoc/jump-to-def-assoc-items.rs53
-rw-r--r--tests/rustdoc/jump-to-def/jump-to-def-doc-links-calls.rs4
-rw-r--r--tests/rustdoc/jump-to-def/jump-to-def-pats.rs10
-rw-r--r--tests/rustdoc/jump-to-def/jump-to-non-local-method.rs7
-rw-r--r--tests/rustdoc/source-code-pages/check-source-code-urls-to-def.rs2
7 files changed, 175 insertions, 91 deletions
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index 272180fb990..c88db697be1 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -1059,6 +1059,64 @@ fn string<T: Display, W: Write>(
     }
 }
 
+fn generate_link_to_def(
+    out: &mut impl Write,
+    text_s: &str,
+    klass: Class,
+    href_context: &Option<HrefContext<'_, '_>>,
+    def_span: Span,
+    open_tag: bool,
+) -> bool {
+    if let Some(href_context) = href_context
+        && let Some(href) =
+            href_context.context.shared.span_correspondence_map.get(&def_span).and_then(|href| {
+                let context = href_context.context;
+                // FIXME: later on, it'd be nice to provide two links (if possible) for all items:
+                // one to the documentation page and one to the source definition.
+                // FIXME: currently, external items only generate a link to their documentation,
+                // a link to their definition can be generated using this:
+                // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
+                match href {
+                    LinkFromSrc::Local(span) => {
+                        context.href_from_span_relative(*span, &href_context.current_href)
+                    }
+                    LinkFromSrc::External(def_id) => {
+                        format::href_with_root_path(*def_id, context, Some(href_context.root_path))
+                            .ok()
+                            .map(|(url, _, _)| url)
+                    }
+                    LinkFromSrc::Primitive(prim) => format::href_with_root_path(
+                        PrimitiveType::primitive_locations(context.tcx())[prim],
+                        context,
+                        Some(href_context.root_path),
+                    )
+                    .ok()
+                    .map(|(url, _, _)| url),
+                    LinkFromSrc::Doc(def_id) => {
+                        format::href_with_root_path(*def_id, context, Some(href_context.root_path))
+                            .ok()
+                            .map(|(doc_link, _, _)| doc_link)
+                    }
+                }
+            })
+    {
+        if !open_tag {
+            // We're already inside an element which has the same klass, no need to give it
+            // again.
+            write!(out, "<a href=\"{href}\">{text_s}").unwrap();
+        } else {
+            let klass_s = klass.as_html();
+            if klass_s.is_empty() {
+                write!(out, "<a href=\"{href}\">{text_s}").unwrap();
+            } else {
+                write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap();
+            }
+        }
+        return true;
+    }
+    false
+}
+
 /// This function writes `text` into `out` with some modifications depending on `klass`:
 ///
 /// * If `klass` is `None`, `text` is written into `out` with no modification.
@@ -1088,10 +1146,14 @@ fn string_without_closing_tag<T: Display>(
         return Some("</span>");
     };
 
+    let mut added_links = false;
     let mut text_s = text.to_string();
     if text_s.contains("::") {
+        let mut span = def_span.with_hi(def_span.lo());
         text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
+            span = span.with_hi(span.hi() + BytePos(t.len() as _));
             match t {
+                "::" => write!(&mut path, "::"),
                 "self" | "Self" => write!(
                     &mut path,
                     "<span class=\"{klass}\">{t}</span>",
@@ -1104,58 +1166,24 @@ fn string_without_closing_tag<T: Display>(
                         klass = Class::KeyWord.as_html(),
                     )
                 }
-                t => write!(&mut path, "{t}"),
+                t => {
+                    if !t.is_empty()
+                        && generate_link_to_def(&mut path, t, klass, href_context, span, open_tag)
+                    {
+                        added_links = true;
+                        write!(&mut path, "</a>")
+                    } else {
+                        write!(&mut path, "{t}")
+                    }
+                }
             }
             .expect("Failed to build source HTML path");
+            span = span.with_lo(span.lo() + BytePos(t.len() as _));
             path
         });
     }
 
-    if let Some(href_context) = href_context
-        && let Some(href) = href_context.context.shared.span_correspondence_map.get(&def_span)
-        && let Some(href) = {
-            let context = href_context.context;
-            // FIXME: later on, it'd be nice to provide two links (if possible) for all items:
-            // one to the documentation page and one to the source definition.
-            // FIXME: currently, external items only generate a link to their documentation,
-            // a link to their definition can be generated using this:
-            // https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
-            match href {
-                LinkFromSrc::Local(span) => {
-                    context.href_from_span_relative(*span, &href_context.current_href)
-                }
-                LinkFromSrc::External(def_id) => {
-                    format::href_with_root_path(*def_id, context, Some(href_context.root_path))
-                        .ok()
-                        .map(|(url, _, _)| url)
-                }
-                LinkFromSrc::Primitive(prim) => format::href_with_root_path(
-                    PrimitiveType::primitive_locations(context.tcx())[prim],
-                    context,
-                    Some(href_context.root_path),
-                )
-                .ok()
-                .map(|(url, _, _)| url),
-                LinkFromSrc::Doc(def_id) => {
-                    format::href_with_root_path(*def_id, context, Some(href_context.root_path))
-                        .ok()
-                        .map(|(doc_link, _, _)| doc_link)
-                }
-            }
-        }
-    {
-        if !open_tag {
-            // We're already inside an element which has the same klass, no need to give it
-            // again.
-            write!(out, "<a href=\"{href}\">{text_s}").unwrap();
-        } else {
-            let klass_s = klass.as_html();
-            if klass_s.is_empty() {
-                write!(out, "<a href=\"{href}\">{text_s}").unwrap();
-            } else {
-                write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap();
-            }
-        }
+    if !added_links && generate_link_to_def(out, &text_s, klass, href_context, def_span, open_tag) {
         return Some("</a>");
     }
     if !open_tag {
diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs
index ebbc61db688..342cf5073f7 100644
--- a/src/librustdoc/html/render/span_map.rs
+++ b/src/librustdoc/html/render/span_map.rs
@@ -65,7 +65,7 @@ struct SpanMapVisitor<'tcx> {
 
 impl SpanMapVisitor<'_> {
     /// This function is where we handle `hir::Path` elements and add them into the "span map".
-    fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
+    fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) {
         match path.res {
             // FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
             // Would be nice to support them too alongside the other `DefKind`
@@ -77,24 +77,36 @@ impl SpanMapVisitor<'_> {
                     LinkFromSrc::External(def_id)
                 };
                 // In case the path ends with generics, we remove them from the span.
-                let span = path
-                    .segments
-                    .last()
-                    .map(|last| {
-                        // In `use` statements, the included item is not in the path segments.
-                        // However, it doesn't matter because you can't have generics on `use`
-                        // statements.
-                        if path.span.contains(last.ident.span) {
-                            path.span.with_hi(last.ident.span.hi())
-                        } else {
-                            path.span
-                        }
-                    })
-                    .unwrap_or(path.span);
+                let span = if only_use_last_segment
+                    && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
+                {
+                    path_span
+                } else {
+                    path.segments
+                        .last()
+                        .map(|last| {
+                            // In `use` statements, the included item is not in the path segments.
+                            // However, it doesn't matter because you can't have generics on `use`
+                            // statements.
+                            if path.span.contains(last.ident.span) {
+                                path.span.with_hi(last.ident.span.hi())
+                            } else {
+                                path.span
+                            }
+                        })
+                        .unwrap_or(path.span)
+                };
                 self.matches.insert(span, link);
             }
             Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => {
-                self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
+                let path_span = if only_use_last_segment
+                    && let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
+                {
+                    path_span
+                } else {
+                    path.span
+                };
+                self.matches.insert(path_span, LinkFromSrc::Local(clean::Span::new(span)));
             }
             Res::PrimTy(p) => {
                 // FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
@@ -200,37 +212,41 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
         if self.handle_macro(path.span) {
             return;
         }
-        self.handle_path(path);
+        self.handle_path(path, false);
         intravisit::walk_path(self, path);
     }
 
-    fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, span: Span) {
+    fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: Span) {
         match *qpath {
             QPath::TypeRelative(qself, path) => {
                 if matches!(path.res, Res::Err) {
                     let tcx = self.tcx;
-                    let hir = tcx.hir();
-                    let body_id = hir.enclosing_body_owner(id);
-                    let typeck_results = tcx.typeck_body(hir.body_owned_by(body_id).id());
+                    let body_id = tcx.hir_enclosing_body_owner(id);
+                    let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
                     let path = rustc_hir::Path {
                         // We change the span to not include parens.
-                        span: span.with_hi(path.ident.span.hi()),
+                        span: path.ident.span,
                         res: typeck_results.qpath_res(qpath, id),
                         segments: &[],
                     };
-                    self.handle_path(&path);
+                    self.handle_path(&path, false);
                 } else {
-                    self.infer_id(path.hir_id, Some(id), span);
+                    self.infer_id(path.hir_id, Some(id), path.ident.span);
                 }
 
-                rustc_ast::visit::try_visit!(self.visit_ty(qself));
+                if let Some(qself) = qself.try_as_ambig_ty() {
+                    rustc_ast::visit::try_visit!(self.visit_ty(qself));
+                }
                 self.visit_path_segment(path);
             }
             QPath::Resolved(maybe_qself, path) => {
-                self.handle_path(path);
+                self.handle_path(path, true);
 
+                let maybe_qself = maybe_qself.and_then(|maybe_qself| maybe_qself.try_as_ambig_ty());
                 rustc_ast::visit::visit_opt!(self, visit_ty, maybe_qself);
-                self.visit_path(path, id)
+                if !self.handle_macro(path.span) {
+                    intravisit::walk_path(self, path);
+                }
             }
             _ => {}
         }
diff --git a/tests/rustdoc/jump-to-def-assoc-items.rs b/tests/rustdoc/jump-to-def-assoc-items.rs
index 1229c363172..8cbf9906283 100644
--- a/tests/rustdoc/jump-to-def-assoc-items.rs
+++ b/tests/rustdoc/jump-to-def-assoc-items.rs
@@ -1,15 +1,54 @@
+// This test ensures that patterns also get a link generated.
+
 //@ compile-flags: -Zunstable-options --generate-link-to-definition
 
 #![crate_name = "foo"]
 
-pub enum Ty {
-    Var,
+//@ has 'src/foo/jump-to-def-assoc-items.rs.html'
+
+pub trait Trait {
+    type T;
+}
+pub trait Another {
+    type T;
+    const X: u32;
 }
 
-//@ has 'src/foo/jump-to-def-assoc-items.rs.html'
-//@ has - '//a[@href="#6"]' 'Self::Var'
-impl Ty {
-    fn f() {
-        let _ = Self::Var;
+pub struct Foo;
+
+impl Foo {
+    pub fn new() -> Self { Foo }
+}
+
+pub struct C;
+
+impl C {
+    pub fn wat() {}
+}
+
+pub struct Bar;
+impl Trait for Bar {
+    type T = Foo;
+}
+impl Another for Bar {
+    type T = C;
+    const X: u32 = 12;
+}
+
+pub fn bar() {
+    //@ has - '//a[@href="#20"]' 'new'
+    <Bar as Trait>::T::new();
+    //@ has - '//a[@href="#26"]' 'wat'
+    <Bar as Another>::T::wat();
+
+    match 12u32 {
+        //@ has - '//a[@href="#14"]' 'X'
+        <Bar as Another>::X => {}
+        _ => {}
     }
 }
+
+pub struct Far {
+        //@ has - '//a[@href="#10"]' 'T'
+    x: <Bar as Trait>::T,
+}
diff --git a/tests/rustdoc/jump-to-def/jump-to-def-doc-links-calls.rs b/tests/rustdoc/jump-to-def/jump-to-def-doc-links-calls.rs
index 61856978773..55e59f23b6f 100644
--- a/tests/rustdoc/jump-to-def/jump-to-def-doc-links-calls.rs
+++ b/tests/rustdoc/jump-to-def/jump-to-def-doc-links-calls.rs
@@ -8,7 +8,7 @@
 pub struct Bar;
 
 impl std::default::Default for Bar {
-    //@ has - '//a[@href="#20-22"]' 'Self::new'
+    //@ has - '//a[@href="#20-22"]' 'new'
     fn default() -> Self {
         Self::new()
     }
@@ -16,7 +16,7 @@ impl std::default::Default for Bar {
 
 //@ has - '//a[@href="#8"]' 'Bar'
 impl Bar {
-     //@ has - '//a[@href="#24-26"]' 'Self::bar'
+     //@ has - '//a[@href="#24-26"]' 'bar'
      pub fn new()-> Self {
          Self::bar()
      }
diff --git a/tests/rustdoc/jump-to-def/jump-to-def-pats.rs b/tests/rustdoc/jump-to-def/jump-to-def-pats.rs
index 147902b44cf..852eba208db 100644
--- a/tests/rustdoc/jump-to-def/jump-to-def-pats.rs
+++ b/tests/rustdoc/jump-to-def/jump-to-def-pats.rs
@@ -30,13 +30,13 @@ pub fn foo() -> Result<(), ()> {
 impl<T, E> fmt::Display for MyEnum<T, E> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            //@ has - '//a[@href="#12"]' 'Self::Ok'
+            //@ has - '//a[@href="#12"]' 'Ok'
             Self::Ok(_) => f.write_str("MyEnum::Ok"),
-            //@ has - '//a[@href="#13"]' 'MyEnum::Err'
+            //@ has - '//a[@href="#13"]' 'Err'
             MyEnum::Err(_) => f.write_str("MyEnum::Err"),
-            //@ has - '//a[@href="#14"]' 'Self::Some'
+            //@ has - '//a[@href="#14"]' 'Some'
             Self::Some(_) => f.write_str("MyEnum::Some"),
-            //@ has - '//a[@href="#15"]' 'Self::None'
+            //@ has - '//a[@href="#15"]' 'None'
             Self::None => f.write_str("MyEnum::None"),
         }
     }
@@ -45,7 +45,7 @@ impl<T, E> fmt::Display for MyEnum<T, E> {
 impl X {
     fn p(&self) -> &str {
         match self {
-            //@ has - '//a[@href="#19"]' 'Self::A'
+            //@ has - '//a[@href="#19"]' 'A'
             Self::A => "X::A",
         }
     }
diff --git a/tests/rustdoc/jump-to-def/jump-to-non-local-method.rs b/tests/rustdoc/jump-to-def/jump-to-non-local-method.rs
index e2f530425f0..1d6d6b8d18f 100644
--- a/tests/rustdoc/jump-to-def/jump-to-non-local-method.rs
+++ b/tests/rustdoc/jump-to-def/jump-to-non-local-method.rs
@@ -21,9 +21,10 @@ pub fn bar2<T: Read>(readable: T) {
 }
 
 pub fn bar() {
-    //@ has - '//a[@href="{{channel}}/core/sync/atomic/struct.AtomicIsize.html#method.new"]' 'AtomicIsize::new'
+    //@ has - '//a[@href="{{channel}}/core/sync/atomic/struct.AtomicIsize.html"]' 'AtomicIsize'
+    //@ has - '//a[@href="{{channel}}/core/sync/atomic/struct.AtomicIsize.html#method.new"]' 'new'
     let _ = AtomicIsize::new(0);
-    //@ has - '//a[@href="#48"]' 'local_private'
+    //@ has - '//a[@href="#49"]' 'local_private'
     local_private();
 }
 
@@ -39,7 +40,7 @@ pub fn macro_call() -> Result<(), ()> {
 }
 
 pub fn variant() {
-    //@ has - '//a[@href="{{channel}}/core/cmp/enum.Ordering.html#variant.Less"]' 'Ordering::Less'
+    //@ has - '//a[@href="{{channel}}/core/cmp/enum.Ordering.html#variant.Less"]' 'Less'
     let _ = Ordering::Less;
     //@ has - '//a[@href="{{channel}}/core/marker/struct.PhantomData.html"]' 'PhantomData'
     let _: PhantomData::<usize> = PhantomData;
diff --git a/tests/rustdoc/source-code-pages/check-source-code-urls-to-def.rs b/tests/rustdoc/source-code-pages/check-source-code-urls-to-def.rs
index d701b88bf9f..a7b944fa2f6 100644
--- a/tests/rustdoc/source-code-pages/check-source-code-urls-to-def.rs
+++ b/tests/rustdoc/source-code-pages/check-source-code-urls-to-def.rs
@@ -34,7 +34,7 @@ fn babar() {}
 // The 5 links to line 23 and the line 23 itself.
 //@ count - '//pre[@class="rust"]//a[@href="#23"]' 6
 //@ has - '//pre[@class="rust"]//a[@href="../../source_code/struct.SourceCode.html"]' \
-//        'source_code::SourceCode'
+//        'SourceCode'
 pub fn foo(a: u32, b: &str, c: String, d: Foo, e: bar::Bar, f: source_code::SourceCode) {
     let x = 12;
     let y: Foo = Foo;