diff options
| author | Guillaume Gomez <guillaume1.gomez@gmail.com> | 2025-01-26 21:24:36 +0100 |
|---|---|---|
| committer | Guillaume Gomez <guillaume1.gomez@gmail.com> | 2025-08-10 12:00:08 +0200 |
| commit | f1649a4e4379aa0f5eb1181ca8e04c7790c21e5e (patch) | |
| tree | ab973445f4979c964de2a935598a97f1a941ccc8 | |
| parent | 49044d0d4044960fbb96d47f51640bc5fa8004af (diff) | |
| download | rust-f1649a4e4379aa0f5eb1181ca8e04c7790c21e5e.tar.gz rust-f1649a4e4379aa0f5eb1181ca8e04c7790c21e5e.zip | |
Better handling of paths in link to def feature
| -rw-r--r-- | src/librustdoc/html/highlight.rs | 120 | ||||
| -rw-r--r-- | src/librustdoc/html/render/span_map.rs | 70 | ||||
| -rw-r--r-- | tests/rustdoc/jump-to-def-assoc-items.rs | 53 | ||||
| -rw-r--r-- | tests/rustdoc/jump-to-def/jump-to-def-doc-links-calls.rs | 4 | ||||
| -rw-r--r-- | tests/rustdoc/jump-to-def/jump-to-def-pats.rs | 10 | ||||
| -rw-r--r-- | tests/rustdoc/jump-to-def/jump-to-non-local-method.rs | 7 | ||||
| -rw-r--r-- | tests/rustdoc/source-code-pages/check-source-code-urls-to-def.rs | 2 |
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; |
