diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index c943d3ad4d056..83eb8bc76d208 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -1050,6 +1050,64 @@ fn string(
}
}
+fn generate_link_to_def(
+ out: &mut impl Write,
+ text_s: &str,
+ klass: Class,
+ href_context: &Option>,
+ 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, "{text_s}").unwrap();
+ } else {
+ let klass_s = klass.as_html();
+ if klass_s.is_empty() {
+ write!(out, "{text_s}").unwrap();
+ } else {
+ write!(out, "{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.
@@ -1079,10 +1137,14 @@ fn string_without_closing_tag(
return Some("");
};
+ 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,
"{t}",
@@ -1095,58 +1157,24 @@ fn string_without_closing_tag(
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, "")
+ } 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, "{text_s}").unwrap();
- } else {
- let klass_s = klass.as_html();
- if klass_s.is_empty() {
- write!(out, "{text_s}").unwrap();
- } else {
- write!(out, "{text_s}").unwrap();
- }
- }
+ if !added_links && generate_link_to_def(out, &text_s, klass, href_context, def_span, open_tag) {
return Some("");
}
if !open_tag {
diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs
index 846d3ad310ce4..505797ccc0b2f 100644
--- a/src/librustdoc/html/render/span_map.rs
+++ b/src/librustdoc/html/render/span_map.rs
@@ -3,10 +3,8 @@ use std::path::{Path, PathBuf};
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
-use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::{
- ExprKind, HirId, Item, ItemKind, Mod, Node, Pat, PatExpr, PatExprKind, PatKind, QPath,
-};
+use rustc_hir::intravisit::{self, Visitor, VisitorExt};
+use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::TyCtxt;
use rustc_span::hygiene::MacroKind;
@@ -67,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`
@@ -79,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.
@@ -189,31 +199,6 @@ impl SpanMapVisitor<'_> {
self.matches.insert(span, link);
}
}
-
- fn handle_pat(&mut self, p: &Pat<'_>) {
- let mut check_qpath = |qpath, hir_id| match qpath {
- QPath::TypeRelative(_, path) if matches!(path.res, Res::Err) => {
- self.infer_id(path.hir_id, Some(hir_id), qpath.span());
- }
- QPath::Resolved(_, path) => self.handle_path(path),
- _ => {}
- };
- match p.kind {
- PatKind::Binding(_, _, _, Some(p)) => self.handle_pat(p),
- PatKind::Struct(qpath, _, _) | PatKind::TupleStruct(qpath, _, _) => {
- check_qpath(qpath, p.hir_id)
- }
- PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, .. }) => {
- check_qpath(*qpath, *hir_id)
- }
- PatKind::Or(pats) => {
- for pat in pats {
- self.handle_pat(pat);
- }
- }
- _ => {}
- }
- }
}
impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
@@ -227,12 +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_pat(&mut self, p: &Pat<'tcx>) {
- self.handle_pat(p);
+ 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 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: path.ident.span,
+ res: typeck_results.qpath_res(qpath, id),
+ segments: &[],
+ };
+ self.handle_path(&path, false);
+ } else {
+ self.infer_id(path.hir_id, Some(id), path.ident.span);
+ }
+
+ rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself));
+ self.visit_path_segment(path);
+ }
+ QPath::Resolved(maybe_qself, path) => {
+ self.handle_path(path, true);
+
+ rustc_ast::visit::visit_opt!(self, visit_ty_unambig, maybe_qself);
+ if !self.handle_macro(path.span) {
+ intravisit::walk_path(self, path);
+ }
+ }
+ _ => {}
+ }
}
fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
diff --git a/tests/rustdoc/check-source-code-urls-to-def.rs b/tests/rustdoc/check-source-code-urls-to-def.rs
index d701b88bf9fd0..a7b944fa2f6fc 100644
--- a/tests/rustdoc/check-source-code-urls-to-def.rs
+++ b/tests/rustdoc/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;
diff --git a/tests/rustdoc/jump-to-def-assoc-items.rs b/tests/rustdoc/jump-to-def-assoc-items.rs
new file mode 100644
index 0000000000000..8cbf990628386
--- /dev/null
+++ b/tests/rustdoc/jump-to-def-assoc-items.rs
@@ -0,0 +1,54 @@
+// This test ensures that patterns also get a link generated.
+
+//@ compile-flags: -Zunstable-options --generate-link-to-definition
+
+#![crate_name = "foo"]
+
+//@ has 'src/foo/jump-to-def-assoc-items.rs.html'
+
+pub trait Trait {
+ type T;
+}
+pub trait Another {
+ type T;
+ const X: u32;
+}
+
+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'
+ ::T::new();
+ //@ has - '//a[@href="#26"]' 'wat'
+ ::T::wat();
+
+ match 12u32 {
+ //@ has - '//a[@href="#14"]' 'X'
+ ::X => {}
+ _ => {}
+ }
+}
+
+pub struct Far {
+ //@ has - '//a[@href="#10"]' 'T'
+ x: ::T,
+}
diff --git a/tests/rustdoc/jump-to-def-doc-links-calls.rs b/tests/rustdoc/jump-to-def-doc-links-calls.rs
index 618569787733c..55e59f23b6f28 100644
--- a/tests/rustdoc/jump-to-def-doc-links-calls.rs
+++ b/tests/rustdoc/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-pats.rs b/tests/rustdoc/jump-to-def-pats.rs
index 147902b44cf5c..852eba208db01 100644
--- a/tests/rustdoc/jump-to-def-pats.rs
+++ b/tests/rustdoc/jump-to-def-pats.rs
@@ -30,13 +30,13 @@ pub fn foo() -> Result<(), ()> {
impl fmt::Display for MyEnum {
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 fmt::Display for MyEnum {
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-non-local-method.rs b/tests/rustdoc/jump-to-non-local-method.rs
index e2f530425f0db..1d6d6b8d18faa 100644
--- a/tests/rustdoc/jump-to-non-local-method.rs
+++ b/tests/rustdoc/jump-to-non-local-method.rs
@@ -21,9 +21,10 @@ pub fn bar2(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:: = PhantomData;