Skip to content

Commit 5810df7

Browse files
Better handling of paths in link to def feature
1 parent 895e35a commit 5810df7

7 files changed

+175
-91
lines changed

src/librustdoc/html/highlight.rs

+74-46
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,64 @@ fn string<T: Display, W: Write>(
10501050
}
10511051
}
10521052

1053+
fn generate_link_to_def(
1054+
out: &mut impl Write,
1055+
text_s: &str,
1056+
klass: Class,
1057+
href_context: &Option<HrefContext<'_, '_>>,
1058+
def_span: Span,
1059+
open_tag: bool,
1060+
) -> bool {
1061+
if let Some(href_context) = href_context
1062+
&& let Some(href) =
1063+
href_context.context.shared.span_correspondence_map.get(&def_span).and_then(|href| {
1064+
let context = href_context.context;
1065+
// FIXME: later on, it'd be nice to provide two links (if possible) for all items:
1066+
// one to the documentation page and one to the source definition.
1067+
// FIXME: currently, external items only generate a link to their documentation,
1068+
// a link to their definition can be generated using this:
1069+
// https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
1070+
match href {
1071+
LinkFromSrc::Local(span) => {
1072+
context.href_from_span_relative(*span, &href_context.current_href)
1073+
}
1074+
LinkFromSrc::External(def_id) => {
1075+
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
1076+
.ok()
1077+
.map(|(url, _, _)| url)
1078+
}
1079+
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
1080+
PrimitiveType::primitive_locations(context.tcx())[prim],
1081+
context,
1082+
Some(href_context.root_path),
1083+
)
1084+
.ok()
1085+
.map(|(url, _, _)| url),
1086+
LinkFromSrc::Doc(def_id) => {
1087+
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
1088+
.ok()
1089+
.map(|(doc_link, _, _)| doc_link)
1090+
}
1091+
}
1092+
})
1093+
{
1094+
if !open_tag {
1095+
// We're already inside an element which has the same klass, no need to give it
1096+
// again.
1097+
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
1098+
} else {
1099+
let klass_s = klass.as_html();
1100+
if klass_s.is_empty() {
1101+
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
1102+
} else {
1103+
write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap();
1104+
}
1105+
}
1106+
return true;
1107+
}
1108+
false
1109+
}
1110+
10531111
/// This function writes `text` into `out` with some modifications depending on `klass`:
10541112
///
10551113
/// * If `klass` is `None`, `text` is written into `out` with no modification.
@@ -1079,10 +1137,14 @@ fn string_without_closing_tag<T: Display>(
10791137
return Some("</span>");
10801138
};
10811139

1140+
let mut added_links = false;
10821141
let mut text_s = text.to_string();
10831142
if text_s.contains("::") {
1143+
let mut span = def_span.with_hi(def_span.lo());
10841144
text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
1145+
span = span.with_hi(span.hi() + BytePos(t.len() as _));
10851146
match t {
1147+
"::" => write!(&mut path, "::"),
10861148
"self" | "Self" => write!(
10871149
&mut path,
10881150
"<span class=\"{klass}\">{t}</span>",
@@ -1095,58 +1157,24 @@ fn string_without_closing_tag<T: Display>(
10951157
klass = Class::KeyWord.as_html(),
10961158
)
10971159
}
1098-
t => write!(&mut path, "{t}"),
1160+
t => {
1161+
if !t.is_empty()
1162+
&& generate_link_to_def(&mut path, t, klass, href_context, span, open_tag)
1163+
{
1164+
added_links = true;
1165+
write!(&mut path, "</a>")
1166+
} else {
1167+
write!(&mut path, "{t}")
1168+
}
1169+
}
10991170
}
11001171
.expect("Failed to build source HTML path");
1172+
span = span.with_lo(span.lo() + BytePos(t.len() as _));
11011173
path
11021174
});
11031175
}
11041176

1105-
if let Some(href_context) = href_context
1106-
&& let Some(href) = href_context.context.shared.span_correspondence_map.get(&def_span)
1107-
&& let Some(href) = {
1108-
let context = href_context.context;
1109-
// FIXME: later on, it'd be nice to provide two links (if possible) for all items:
1110-
// one to the documentation page and one to the source definition.
1111-
// FIXME: currently, external items only generate a link to their documentation,
1112-
// a link to their definition can be generated using this:
1113-
// https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
1114-
match href {
1115-
LinkFromSrc::Local(span) => {
1116-
context.href_from_span_relative(*span, &href_context.current_href)
1117-
}
1118-
LinkFromSrc::External(def_id) => {
1119-
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
1120-
.ok()
1121-
.map(|(url, _, _)| url)
1122-
}
1123-
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
1124-
PrimitiveType::primitive_locations(context.tcx())[prim],
1125-
context,
1126-
Some(href_context.root_path),
1127-
)
1128-
.ok()
1129-
.map(|(url, _, _)| url),
1130-
LinkFromSrc::Doc(def_id) => {
1131-
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
1132-
.ok()
1133-
.map(|(doc_link, _, _)| doc_link)
1134-
}
1135-
}
1136-
}
1137-
{
1138-
if !open_tag {
1139-
// We're already inside an element which has the same klass, no need to give it
1140-
// again.
1141-
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
1142-
} else {
1143-
let klass_s = klass.as_html();
1144-
if klass_s.is_empty() {
1145-
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
1146-
} else {
1147-
write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap();
1148-
}
1149-
}
1177+
if !added_links && generate_link_to_def(out, &text_s, klass, href_context, def_span, open_tag) {
11501178
return Some("</a>");
11511179
}
11521180
if !open_tag {

src/librustdoc/html/render/span_map.rs

+43-27
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ struct SpanMapVisitor<'tcx> {
6565

6666
impl SpanMapVisitor<'_> {
6767
/// This function is where we handle `hir::Path` elements and add them into the "span map".
68-
fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
68+
fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) {
6969
match path.res {
7070
// FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
7171
// Would be nice to support them too alongside the other `DefKind`
@@ -77,24 +77,36 @@ impl SpanMapVisitor<'_> {
7777
LinkFromSrc::External(def_id)
7878
};
7979
// In case the path ends with generics, we remove them from the span.
80-
let span = path
81-
.segments
82-
.last()
83-
.map(|last| {
84-
// In `use` statements, the included item is not in the path segments.
85-
// However, it doesn't matter because you can't have generics on `use`
86-
// statements.
87-
if path.span.contains(last.ident.span) {
88-
path.span.with_hi(last.ident.span.hi())
89-
} else {
90-
path.span
91-
}
92-
})
93-
.unwrap_or(path.span);
80+
let span = if only_use_last_segment
81+
&& let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
82+
{
83+
path_span
84+
} else {
85+
path.segments
86+
.last()
87+
.map(|last| {
88+
// In `use` statements, the included item is not in the path segments.
89+
// However, it doesn't matter because you can't have generics on `use`
90+
// statements.
91+
if path.span.contains(last.ident.span) {
92+
path.span.with_hi(last.ident.span.hi())
93+
} else {
94+
path.span
95+
}
96+
})
97+
.unwrap_or(path.span)
98+
};
9499
self.matches.insert(span, link);
95100
}
96101
Res::Local(_) if let Some(span) = self.tcx.hir().res_span(path.res) => {
97-
self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
102+
let path_span = if only_use_last_segment
103+
&& let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
104+
{
105+
path_span
106+
} else {
107+
path.span
108+
};
109+
self.matches.insert(path_span, LinkFromSrc::Local(clean::Span::new(span)));
98110
}
99111
Res::PrimTy(p) => {
100112
// FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
@@ -200,37 +212,41 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
200212
if self.handle_macro(path.span) {
201213
return;
202214
}
203-
self.handle_path(path);
215+
self.handle_path(path, false);
204216
intravisit::walk_path(self, path);
205217
}
206218

207-
fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, span: Span) {
219+
fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: Span) {
208220
match *qpath {
209221
QPath::TypeRelative(qself, path) => {
210222
if matches!(path.res, Res::Err) {
211223
let tcx = self.tcx;
212-
let hir = tcx.hir();
213-
let body_id = hir.enclosing_body_owner(id);
214-
let typeck_results = tcx.typeck_body(hir.body_owned_by(body_id).id());
224+
let body_id = tcx.hir_enclosing_body_owner(id);
225+
let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
215226
let path = rustc_hir::Path {
216227
// We change the span to not include parens.
217-
span: span.with_hi(path.ident.span.hi()),
228+
span: path.ident.span,
218229
res: typeck_results.qpath_res(qpath, id),
219230
segments: &[],
220231
};
221-
self.handle_path(&path);
232+
self.handle_path(&path, false);
222233
} else {
223-
self.infer_id(path.hir_id, Some(id), span);
234+
self.infer_id(path.hir_id, Some(id), path.ident.span);
224235
}
225236

226-
rustc_ast::visit::try_visit!(self.visit_ty(qself));
237+
if let Some(qself) = qself.try_as_ambig_ty() {
238+
rustc_ast::visit::try_visit!(self.visit_ty(qself));
239+
}
227240
self.visit_path_segment(path);
228241
}
229242
QPath::Resolved(maybe_qself, path) => {
230-
self.handle_path(path);
243+
self.handle_path(path, true);
231244

245+
let maybe_qself = maybe_qself.and_then(|maybe_qself| maybe_qself.try_as_ambig_ty());
232246
rustc_ast::visit::visit_opt!(self, visit_ty, maybe_qself);
233-
self.visit_path(path, id)
247+
if !self.handle_macro(path.span) {
248+
intravisit::walk_path(self, path);
249+
}
234250
}
235251
_ => {}
236252
}

tests/rustdoc/check-source-code-urls-to-def.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ fn babar() {}
3434
// The 5 links to line 23 and the line 23 itself.
3535
//@ count - '//pre[@class="rust"]//a[@href="#23"]' 6
3636
//@ has - '//pre[@class="rust"]//a[@href="../../source_code/struct.SourceCode.html"]' \
37-
// 'source_code::SourceCode'
37+
// 'SourceCode'
3838
pub fn foo(a: u32, b: &str, c: String, d: Foo, e: bar::Bar, f: source_code::SourceCode) {
3939
let x = 12;
4040
let y: Foo = Foo;
+46-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,54 @@
1+
// This test ensures that patterns also get a link generated.
2+
13
//@ compile-flags: -Zunstable-options --generate-link-to-definition
24

35
#![crate_name = "foo"]
46

5-
pub enum Ty {
6-
Var,
7+
//@ has 'src/foo/jump-to-def-assoc-items.rs.html'
8+
9+
pub trait Trait {
10+
type T;
11+
}
12+
pub trait Another {
13+
type T;
14+
const X: u32;
715
}
816

9-
//@ has 'src/foo/jump-to-def-assoc-items.rs.html'
10-
//@ has - '//a[@href="#6"]' 'Self::Var'
11-
impl Ty {
12-
fn f() {
13-
let _ = Self::Var;
17+
pub struct Foo;
18+
19+
impl Foo {
20+
pub fn new() -> Self { Foo }
21+
}
22+
23+
pub struct C;
24+
25+
impl C {
26+
pub fn wat() {}
27+
}
28+
29+
pub struct Bar;
30+
impl Trait for Bar {
31+
type T = Foo;
32+
}
33+
impl Another for Bar {
34+
type T = C;
35+
const X: u32 = 12;
36+
}
37+
38+
pub fn bar() {
39+
//@ has - '//a[@href="#20"]' 'new'
40+
<Bar as Trait>::T::new();
41+
//@ has - '//a[@href="#26"]' 'wat'
42+
<Bar as Another>::T::wat();
43+
44+
match 12u32 {
45+
//@ has - '//a[@href="#14"]' 'X'
46+
<Bar as Another>::X => {}
47+
_ => {}
1448
}
1549
}
50+
51+
pub struct Far {
52+
//@ has - '//a[@href="#10"]' 'T'
53+
x: <Bar as Trait>::T,
54+
}

tests/rustdoc/jump-to-def-doc-links-calls.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
pub struct Bar;
99

1010
impl std::default::Default for Bar {
11-
//@ has - '//a[@href="#20-22"]' 'Self::new'
11+
//@ has - '//a[@href="#20-22"]' 'new'
1212
fn default() -> Self {
1313
Self::new()
1414
}
1515
}
1616

1717
//@ has - '//a[@href="#8"]' 'Bar'
1818
impl Bar {
19-
//@ has - '//a[@href="#24-26"]' 'Self::bar'
19+
//@ has - '//a[@href="#24-26"]' 'bar'
2020
pub fn new()-> Self {
2121
Self::bar()
2222
}

tests/rustdoc/jump-to-def-pats.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ pub fn foo() -> Result<(), ()> {
3030
impl<T, E> fmt::Display for MyEnum<T, E> {
3131
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3232
match self {
33-
//@ has - '//a[@href="#12"]' 'Self::Ok'
33+
//@ has - '//a[@href="#12"]' 'Ok'
3434
Self::Ok(_) => f.write_str("MyEnum::Ok"),
35-
//@ has - '//a[@href="#13"]' 'MyEnum::Err'
35+
//@ has - '//a[@href="#13"]' 'Err'
3636
MyEnum::Err(_) => f.write_str("MyEnum::Err"),
37-
//@ has - '//a[@href="#14"]' 'Self::Some'
37+
//@ has - '//a[@href="#14"]' 'Some'
3838
Self::Some(_) => f.write_str("MyEnum::Some"),
39-
//@ has - '//a[@href="#15"]' 'Self::None'
39+
//@ has - '//a[@href="#15"]' 'None'
4040
Self::None => f.write_str("MyEnum::None"),
4141
}
4242
}
@@ -45,7 +45,7 @@ impl<T, E> fmt::Display for MyEnum<T, E> {
4545
impl X {
4646
fn p(&self) -> &str {
4747
match self {
48-
//@ has - '//a[@href="#19"]' 'Self::A'
48+
//@ has - '//a[@href="#19"]' 'A'
4949
Self::A => "X::A",
5050
}
5151
}

0 commit comments

Comments
 (0)