diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 0c0920ae63e4e..1f67f5b946e48 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -517,6 +517,7 @@ impl clean::GenericArgs { } // Possible errors when computing href link source for a `DefId` +#[derive(Debug)] pub(crate) enum HrefError { /// This item is known to rustdoc, but from a crate that does not have documentation generated. /// diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 480728b179790..fc0df613b7647 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -728,11 +728,13 @@ fn string( LinkFromSrc::Local(span) => context .href_from_span(*span, true) .map(|s| format!("{}{}", context_info.root_path, s)), - LinkFromSrc::External(def_id) => { - format::href_with_root_path(*def_id, context, Some(context_info.root_path)) - .ok() - .map(|(url, _, _)| url) - } + LinkFromSrc::External(span) => context.href_from_span(*span, true).map(|s| { + if s.starts_with("http://") || s.starts_with("https://") { + s + } else { + format!("{}{}", context_info.root_path, s) + } + }), LinkFromSrc::Primitive(prim) => format::href_with_root_path( PrimitiveType::primitive_locations(context.tcx())[prim], context, @@ -740,6 +742,11 @@ fn string( ) .ok() .map(|(url, _, _)| url), + LinkFromSrc::Doc(def_id) => { + format::href_with_root_path(*def_id, context, Some(&context_info.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 68e2f0cf9c06c..d2e87929a59df 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -116,7 +116,6 @@ pub(crate) struct SharedContext<'tcx> { /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of /// the crate. redirections: Option>>, - /// Correspondance map used to link types used in the source code pages to allow to click on /// links to jump to the type's definition. pub(crate) span_correspondance_map: FxHashMap, @@ -336,7 +335,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())) + } }; sources::clean_path(&src_root, file, false, |component| { diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 86961dc3bf149..31ec9e7658775 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -1,11 +1,12 @@ +use crate::clean::types::rustc_span; use crate::clean::{self, 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::Span; @@ -22,8 +23,9 @@ use std::path::{Path, PathBuf}; #[derive(Debug)] pub(crate) enum LinkFromSrc { Local(clean::Span), - External(DefId), + External(clean::Span), Primitive(PrimitiveType), + Doc(DefId), } /// This function will do at most two things: @@ -64,7 +66,7 @@ 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<'_>, path_span: Option) { - let info = match path.res { + match path.res { // FIXME: For now, we only handle `DefKind` if it's not `DefKind::TyParam` or // `DefKind::Macro`. Would be nice to support them too alongside the other `DefKind` // (such as primitive types!). @@ -72,23 +74,44 @@ impl<'tcx> SpanMapVisitor<'tcx> { if matches!(kind, DefKind::Macro(_)) { return; } - Some(def_id) + let span = rustc_span(def_id, self.tcx); + let link = if def_id.as_local().is_some() { + LinkFromSrc::Local(span) + } else { + LinkFromSrc::External(span) + }; + self.matches.insert(path_span.unwrap_or(path.span), link); + } + Res::Local(_) => { + if let Some(span) = self.tcx.hir().res_span(path.res) { + self.matches.insert( + path_span.unwrap_or(path.span), + LinkFromSrc::Local(clean::Span::new(span)), + ); + } } - Res::Local(_) => None, Res::PrimTy(p) => { // FIXME: Doesn't handle "path-like" primitives like arrays or tuples. let span = path_span.unwrap_or(path.span); self.matches.insert(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.unwrap_or(path.span), LinkFromSrc::Local(clean::Span::new(span))); - } else if let Some(def_id) = info { - self.matches.insert(path_span.unwrap_or(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(def_id) = self.tcx.hir().opt_local_def_id(hir_id) { + if let Some(span) = self.tcx.def_ident_span(def_id) { + let cspan = clean::Span::new(span); + let def_id = def_id.to_def_id(); + // If the span isn't from the current crate, we ignore it. + if cspan.is_dummy() || cspan.cnum(self.tcx.sess) != LOCAL_CRATE { + return; + } + self.matches.insert(span, LinkFromSrc::Doc(def_id)); + } } } } @@ -117,6 +140,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); } @@ -134,13 +160,13 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { 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 span = rustc_span(def_id, self.tcx); + let link = if def_id.as_local().is_some() { + LinkFromSrc::Local(span) + } else { + LinkFromSrc::External(span) + }; + self.matches.insert(segment.ident.span, link); } } } @@ -151,4 +177,28 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { self.handle_path(path, None); intravisit::walk_use(self, path, id); } + + 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/src/test/rustdoc-gui/source-anchor-scroll.goml b/src/test/rustdoc-gui/source-anchor-scroll.goml index 4e51c8dcac0af..b82c67c92a272 100644 --- a/src/test/rustdoc-gui/source-anchor-scroll.goml +++ b/src/test/rustdoc-gui/source-anchor-scroll.goml @@ -7,11 +7,11 @@ 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="../../src/link_to_definition/lib.rs.html#4-6"]' assert-property: ("html", {"scrollTop": "125"}) -click: '//a[text() = "bar"]' +click: '//a[text() = "bar" and @href="../../src/link_to_definition/lib.rs.html#27-35"]' assert-property: ("html", {"scrollTop": "166"}) -click: '//a[text() = "sub_fn"]' +click: '//a[text() = "sub_fn" and @href="../../src/link_to_definition/lib.rs.html#1-3"]' assert-property: ("html", {"scrollTop": "53"}) // We now check that clicking on lines doesn't change the scroll diff --git a/src/test/rustdoc/check-source-code-urls-to-def.rs b/src/test/rustdoc/check-source-code-urls-to-def.rs index 12c5df2871cf5..c24834cb99731 100644 --- a/src/test/rustdoc/check-source-code-urls-to-def.rs +++ b/src/test/rustdoc/check-source-code-urls-to-def.rs @@ -28,11 +28,11 @@ impl Foo { fn babar() {} -// @has - '//a/@href' '/struct.String.html' +// @has - '//a/@href' '/string.rs.html' // @has - '//a/@href' '/primitive.u32.html' // @has - '//a/@href' '/primitive.str.html' // @count - '//a[@href="../../src/foo/check-source-code-urls-to-def.rs.html#23"]' 5 -// @has - '//a[@href="../../source_code/struct.SourceCode.html"]' 'source_code::SourceCode' +// @has - '//a[@href="../../src/source_code/source_code.rs.html#1"]' '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; diff --git a/src/test/rustdoc/jump-to-def-doc-links.rs b/src/test/rustdoc/jump-to-def-doc-links.rs new file mode 100644 index 0000000000000..014d5803299cb --- /dev/null +++ b/src/test/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 {}