diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index c94968b4817cb..f87dd0e79b703 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -988,6 +988,11 @@ fn string_without_closing_tag(
)
.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)
+ }
}
})
{
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 4c47626363518..cc0e6b85ae1c1 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -349,7 +349,12 @@ impl<'tcx> Context<'tcx> {
let e = ExternalCrate { crate_num: cnum };
(e.name(self.tcx()), e.src_root(self.tcx()))
}
- ExternalLocation::Unknown => return None,
+ ExternalLocation::Unknown => {
+ let e = ExternalCrate { crate_num: cnum };
+ let name = e.name(self.tcx());
+ root = name.to_string();
+ (name, e.src_root(self.tcx()))
+ }
};
let href = RefCell::new(PathBuf::new());
diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs
index eb9262f472b67..ac587bf6008de 100644
--- a/src/librustdoc/html/render/span_map.rs
+++ b/src/librustdoc/html/render/span_map.rs
@@ -1,11 +1,11 @@
-use crate::clean::{self, PrimitiveType};
+use crate::clean::{self, rustc_span, PrimitiveType};
use crate::html::sources;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{DefKind, Res};
-use rustc_hir::def_id::DefId;
+use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::{ExprKind, HirId, Mod, Node};
+use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::TyCtxt;
use rustc_span::hygiene::MacroKind;
@@ -25,6 +25,7 @@ pub(crate) enum LinkFromSrc {
Local(clean::Span),
External(DefId),
Primitive(PrimitiveType),
+ Doc(DefId),
}
/// This function will do at most two things:
@@ -65,24 +66,43 @@ struct SpanMapVisitor<'tcx> {
impl<'tcx> SpanMapVisitor<'tcx> {
/// 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<'_>) {
- let info = match path.res {
+ 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`
// (such as primitive types!).
- Res::Def(kind, def_id) if kind != DefKind::TyParam => Some(def_id),
- Res::Local(_) => None,
+ Res::Def(kind, def_id) if kind != DefKind::TyParam => {
+ let link = if def_id.as_local().is_some() {
+ LinkFromSrc::Local(rustc_span(def_id, self.tcx))
+ } else {
+ LinkFromSrc::External(def_id)
+ };
+ self.matches.insert(path.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)));
+ }
+ }
Res::PrimTy(p) => {
// FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
self.matches.insert(path.span, LinkFromSrc::Primitive(PrimitiveType::from(p)));
- return;
}
- Res::Err => return,
- _ => return,
- };
- if let Some(span) = self.tcx.hir().res_span(path.res) {
- self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
- } else if let Some(def_id) = info {
- self.matches.insert(path.span, LinkFromSrc::External(def_id));
+ Res::Err => {}
+ _ => {}
+ }
+ }
+
+ /// Used to generate links on items' definition to go to their documentation page.
+ pub(crate) fn extract_info_from_hir_id(&mut self, hir_id: HirId) {
+ if let Some(Node::Item(item)) = self.tcx.hir().find(hir_id) {
+ if let Some(span) = self.tcx.def_ident_span(item.owner_id) {
+ let cspan = clean::Span::new(span);
+ // If the span isn't from the current crate, we ignore it.
+ if cspan.inner().is_dummy() || cspan.cnum(self.tcx.sess) != LOCAL_CRATE {
+ return;
+ }
+ self.matches.insert(span, LinkFromSrc::Doc(item.owner_id.to_def_id()));
+ }
}
}
@@ -117,10 +137,13 @@ impl<'tcx> SpanMapVisitor<'tcx> {
_ => return true,
};
let link_from_src = match data.macro_def_id {
- Some(macro_def_id) if macro_def_id.is_local() => {
- LinkFromSrc::Local(clean::Span::new(data.def_site))
+ Some(macro_def_id) => {
+ if macro_def_id.is_local() {
+ LinkFromSrc::Local(clean::Span::new(data.def_site))
+ } else {
+ LinkFromSrc::External(macro_def_id)
+ }
}
- Some(macro_def_id) => LinkFromSrc::External(macro_def_id),
None => return true,
};
let new_span = data.call_site;
@@ -160,6 +183,9 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)),
);
}
+ } else {
+ // If it's a "mod foo {}", we want to look to its documentation page.
+ self.extract_info_from_hir_id(id);
}
intravisit::walk_mod(self, m, id);
}
@@ -176,13 +202,12 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
.tcx
.typeck_body(hir.maybe_body_owned_by(body_id).expect("a body which isn't a body"));
if let Some(def_id) = typeck_results.type_dependent_def_id(expr.hir_id) {
- self.matches.insert(
- segment.ident.span,
- match hir.span_if_local(def_id) {
- Some(span) => LinkFromSrc::Local(clean::Span::new(span)),
- None => LinkFromSrc::External(def_id),
- },
- );
+ let link = if def_id.as_local().is_some() {
+ LinkFromSrc::Local(rustc_span(def_id, self.tcx))
+ } else {
+ LinkFromSrc::External(def_id)
+ };
+ self.matches.insert(segment.ident.span, link);
}
} else if self.handle_macro(expr.span) {
// We don't want to go deeper into the macro.
@@ -190,4 +215,28 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
}
intravisit::walk_expr(self, expr);
}
+
+ fn visit_item(&mut self, item: &'tcx Item<'tcx>) {
+ match item.kind {
+ ItemKind::Static(_, _, _)
+ | ItemKind::Const(_, _)
+ | ItemKind::Fn(_, _, _)
+ | ItemKind::Macro(_, _)
+ | ItemKind::TyAlias(_, _)
+ | ItemKind::Enum(_, _)
+ | ItemKind::Struct(_, _)
+ | ItemKind::Union(_, _)
+ | ItemKind::Trait(_, _, _, _, _)
+ | ItemKind::TraitAlias(_, _) => self.extract_info_from_hir_id(item.hir_id()),
+ ItemKind::Impl(_)
+ | ItemKind::Use(_, _)
+ | ItemKind::ExternCrate(_)
+ | ItemKind::ForeignMod { .. }
+ | ItemKind::GlobalAsm(_)
+ | ItemKind::OpaqueTy(_)
+ // We already have "visit_mod" above so no need to check it here.
+ | ItemKind::Mod(_) => {}
+ }
+ intravisit::walk_item(self, item);
+ }
}
diff --git a/tests/rustdoc-gui/source-anchor-scroll.goml b/tests/rustdoc-gui/source-anchor-scroll.goml
index 67f1497e70ce6..0e4913cafb2ab 100644
--- a/tests/rustdoc-gui/source-anchor-scroll.goml
+++ b/tests/rustdoc-gui/source-anchor-scroll.goml
@@ -7,11 +7,11 @@ set-window-size: (600, 800)
// We check that the scroll is at the top first.
assert-property: ("html", {"scrollTop": "0"})
-click: '//a[text() = "barbar"]'
+click: '//a[text() = "barbar" and @href="#5-7"]'
assert-property: ("html", {"scrollTop": "149"})
-click: '//a[text() = "bar"]'
+click: '//a[text() = "bar" and @href="#28-36"]'
assert-property: ("html", {"scrollTop": "180"})
-click: '//a[text() = "sub_fn"]'
+click: '//a[text() = "sub_fn" and @href="#2-4"]'
assert-property: ("html", {"scrollTop": "77"})
// We now check that clicking on lines doesn't change the scroll
diff --git a/tests/rustdoc/check-source-code-urls-to-def.rs b/tests/rustdoc/check-source-code-urls-to-def.rs
index 41b9d41fa4410..b803c7e9e86c7 100644
--- a/tests/rustdoc/check-source-code-urls-to-def.rs
+++ b/tests/rustdoc/check-source-code-urls-to-def.rs
@@ -14,10 +14,10 @@ extern crate source_code;
#[path = "auxiliary/source-code-bar.rs"]
pub mod bar;
-// @count - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#5"]' 4
+// @count - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#5-7"]' 4
use bar::Bar;
-// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#13"]' 'self'
-// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'Trait'
+// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#13-17"]' 'self'
+// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14-16"]' 'Trait'
use bar::sub::{self, Trait};
pub struct Foo;
@@ -32,7 +32,8 @@ fn babar() {}
// @has - '//pre[@class="rust"]//a/@href' '/primitive.u32.html'
// @has - '//pre[@class="rust"]//a/@href' '/primitive.str.html'
// @count - '//pre[@class="rust"]//a[@href="#23"]' 5
-// @has - '//pre[@class="rust"]//a[@href="../../source_code/struct.SourceCode.html"]' 'source_code::SourceCode'
+// @has - '//pre[@class="rust"]//a[@href="../../source_code/struct.SourceCode.html"]' \
+// 'source_code::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;
@@ -42,15 +43,15 @@ pub fn foo(a: u32, b: &str, c: String, d: Foo, e: bar::Bar, f: source_code::Sour
y.hello();
}
-// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'bar::sub::Trait'
-// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'Trait'
+// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14-16"]' 'bar::sub::Trait'
+// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14-16"]' 'Trait'
pub fn foo2(t: &T, v: &V, b: bool) {}
pub trait AnotherTrait {}
pub trait WhyNot {}
-// @has - '//pre[@class="rust"]//a[@href="#49"]' 'AnotherTrait'
-// @has - '//pre[@class="rust"]//a[@href="#50"]' 'WhyNot'
+// @has - '//pre[@class="rust"]//a[@href="#50"]' 'AnotherTrait'
+// @has - '//pre[@class="rust"]//a[@href="#51"]' 'WhyNot'
pub fn foo3(t: &T, v: &V)
where
T: AnotherTrait,
@@ -59,7 +60,7 @@ where
pub trait AnotherTrait2 {}
-// @has - '//pre[@class="rust"]//a[@href="#60"]' 'AnotherTrait2'
+// @has - '//pre[@class="rust"]//a[@href="#61"]' 'AnotherTrait2'
pub fn foo4() {
let x: Vec = Vec::new();
}
diff --git a/tests/rustdoc/jump-to-def-doc-links.rs b/tests/rustdoc/jump-to-def-doc-links.rs
new file mode 100644
index 0000000000000..014d5803299cb
--- /dev/null
+++ b/tests/rustdoc/jump-to-def-doc-links.rs
@@ -0,0 +1,51 @@
+// compile-flags: -Zunstable-options --generate-link-to-definition
+
+#![crate_name = "foo"]
+
+// @has 'src/foo/jump-to-def-doc-links.rs.html'
+
+// @has - '//a[@href="../../foo/struct.Bar.html"]' 'Bar'
+// @has - '//a[@href="../../foo/struct.Foo.html"]' 'Foo'
+pub struct Bar; pub struct Foo;
+
+// @has - '//a[@href="../../foo/enum.Enum.html"]' 'Enum'
+pub enum Enum {
+ Variant1(String),
+ Variant2(u8),
+}
+
+// @has - '//a[@href="../../foo/struct.Struct.html"]' 'Struct'
+pub struct Struct {
+ pub a: u8,
+ b: Foo,
+}
+
+impl Struct {
+ pub fn foo() {}
+ pub fn foo2(&self) {}
+ fn bar() {}
+ fn bar(&self) {}
+}
+
+// @has - '//a[@href="../../foo/trait.Trait.html"]' 'Trait'
+pub trait Trait {
+ fn foo();
+}
+
+impl Trait for Struct {
+ fn foo() {}
+}
+
+// @has - '//a[@href="../../foo/union.Union.html"]' 'Union'
+pub union Union {
+ pub a: u16,
+ pub f: u32,
+}
+
+// @has - '//a[@href="../../foo/fn.bar.html"]' 'bar'
+pub fn bar(b: Bar) {
+ let x = Foo;
+}
+
+// @has - '//a[@href="../../foo/bar/index.html"]' 'bar'
+pub mod bar {}