Skip to content

Commit 5fe6fa4

Browse files
Rollup merge of rust-lang#135771 - GuillaumeGomez:jump-to-def-perf, r=fmease
[rustdoc] Add support for associated items in "jump to def" feature Fixes rust-lang#135485. r? `@fmease`
2 parents dd9eb3d + 687ac3f commit 5fe6fa4

File tree

8 files changed

+236
-103
lines changed

8 files changed

+236
-103
lines changed

src/librustdoc/html/highlight.rs

Lines changed: 74 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,64 @@ fn string<T: Display, W: Write>(
10591059
}
10601060
}
10611061

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

1149+
let mut added_links = false;
10911150
let mut text_s = text.to_string();
10921151
if text_s.contains("::") {
1152+
let mut span = def_span.with_hi(def_span.lo());
10931153
text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
1154+
span = span.with_hi(span.hi() + BytePos(t.len() as _));
10941155
match t {
1156+
"::" => write!(&mut path, "::"),
10951157
"self" | "Self" => write!(
10961158
&mut path,
10971159
"<span class=\"{klass}\">{t}</span>",
@@ -1104,58 +1166,24 @@ fn string_without_closing_tag<T: Display>(
11041166
klass = Class::KeyWord.as_html(),
11051167
)
11061168
}
1107-
t => write!(&mut path, "{t}"),
1169+
t => {
1170+
if !t.is_empty()
1171+
&& generate_link_to_def(&mut path, t, klass, href_context, span, open_tag)
1172+
{
1173+
added_links = true;
1174+
write!(&mut path, "</a>")
1175+
} else {
1176+
write!(&mut path, "{t}")
1177+
}
1178+
}
11081179
}
11091180
.expect("Failed to build source HTML path");
1181+
span = span.with_lo(span.lo() + BytePos(t.len() as _));
11101182
path
11111183
});
11121184
}
11131185

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

src/librustdoc/html/render/span_map.rs

Lines changed: 72 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ use std::path::{Path, PathBuf};
22

33
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
44
use rustc_hir::def::{DefKind, Res};
5-
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
6-
use rustc_hir::intravisit::{self, Visitor};
7-
use rustc_hir::{
8-
ExprKind, HirId, Item, ItemKind, Mod, Node, Pat, PatExpr, PatExprKind, PatKind, QPath,
9-
};
5+
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
6+
use rustc_hir::intravisit::{self, Visitor, VisitorExt};
7+
use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath};
108
use rustc_middle::hir::nested_filter;
119
use rustc_middle::ty::TyCtxt;
1210
use rustc_span::hygiene::MacroKind;
@@ -67,7 +65,7 @@ struct SpanMapVisitor<'tcx> {
6765

6866
impl SpanMapVisitor<'_> {
6967
/// This function is where we handle `hir::Path` elements and add them into the "span map".
70-
fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
68+
fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) {
7169
match path.res {
7270
// FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
7371
// Would be nice to support them too alongside the other `DefKind`
@@ -79,24 +77,36 @@ impl SpanMapVisitor<'_> {
7977
LinkFromSrc::External(def_id)
8078
};
8179
// In case the path ends with generics, we remove them from the span.
82-
let span = path
83-
.segments
84-
.last()
85-
.map(|last| {
86-
// In `use` statements, the included item is not in the path segments.
87-
// However, it doesn't matter because you can't have generics on `use`
88-
// statements.
89-
if path.span.contains(last.ident.span) {
90-
path.span.with_hi(last.ident.span.hi())
91-
} else {
92-
path.span
93-
}
94-
})
95-
.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+
};
9699
self.matches.insert(span, link);
97100
}
98101
Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => {
99-
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)));
100110
}
101111
Res::PrimTy(p) => {
102112
// FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
@@ -189,31 +199,17 @@ impl SpanMapVisitor<'_> {
189199
self.matches.insert(span, link);
190200
}
191201
}
202+
}
192203

193-
fn handle_pat(&mut self, p: &Pat<'_>) {
194-
let mut check_qpath = |qpath, hir_id| match qpath {
195-
QPath::TypeRelative(_, path) if matches!(path.res, Res::Err) => {
196-
self.infer_id(path.hir_id, Some(hir_id), qpath.span());
197-
}
198-
QPath::Resolved(_, path) => self.handle_path(path),
199-
_ => {}
200-
};
201-
match p.kind {
202-
PatKind::Binding(_, _, _, Some(p)) => self.handle_pat(p),
203-
PatKind::Struct(qpath, _, _) | PatKind::TupleStruct(qpath, _, _) => {
204-
check_qpath(qpath, p.hir_id)
205-
}
206-
PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, .. }) => {
207-
check_qpath(*qpath, *hir_id)
208-
}
209-
PatKind::Or(pats) => {
210-
for pat in pats {
211-
self.handle_pat(pat);
212-
}
213-
}
214-
_ => {}
204+
// This is a reimplementation of `hir_enclosing_body_owner` which allows to fail without
205+
// panicking.
206+
fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<LocalDefId> {
207+
for (_, node) in tcx.hir_parent_iter(hir_id) {
208+
if let Some((def_id, _)) = node.associated_body() {
209+
return Some(def_id);
215210
}
216211
}
212+
None
217213
}
218214

219215
impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
@@ -227,12 +223,42 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
227223
if self.handle_macro(path.span) {
228224
return;
229225
}
230-
self.handle_path(path);
226+
self.handle_path(path, false);
231227
intravisit::walk_path(self, path);
232228
}
233229

234-
fn visit_pat(&mut self, p: &Pat<'tcx>) {
235-
self.handle_pat(p);
230+
fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: Span) {
231+
match *qpath {
232+
QPath::TypeRelative(qself, path) => {
233+
if matches!(path.res, Res::Err) {
234+
let tcx = self.tcx;
235+
if let Some(body_id) = hir_enclosing_body_owner(tcx, id) {
236+
let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
237+
let path = rustc_hir::Path {
238+
// We change the span to not include parens.
239+
span: path.ident.span,
240+
res: typeck_results.qpath_res(qpath, id),
241+
segments: &[],
242+
};
243+
self.handle_path(&path, false);
244+
}
245+
} else {
246+
self.infer_id(path.hir_id, Some(id), path.ident.span);
247+
}
248+
249+
rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself));
250+
self.visit_path_segment(path);
251+
}
252+
QPath::Resolved(maybe_qself, path) => {
253+
self.handle_path(path, true);
254+
255+
rustc_ast::visit::visit_opt!(self, visit_ty_unambig, maybe_qself);
256+
if !self.handle_macro(path.span) {
257+
intravisit::walk_path(self, path);
258+
}
259+
}
260+
_ => {}
261+
}
236262
}
237263

238264
fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// This test ensures that patterns also get a link generated.
2+
3+
//@ compile-flags: -Zunstable-options --generate-link-to-definition
4+
5+
#![crate_name = "foo"]
6+
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;
15+
}
16+
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+
_ => {}
48+
}
49+
}
50+
51+
pub struct Far {
52+
//@ has - '//a[@href="#10"]' 'T'
53+
x: <Bar as Trait>::T,
54+
}

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// This test ensures that items with no body don't panic when generating
2+
// jump to def links.
3+
4+
//@ compile-flags: -Zunstable-options --generate-link-to-definition
5+
6+
#![crate_name = "foo"]
7+
8+
//@ has 'src/foo/jump-to-def-ice.rs.html'
9+
10+
pub trait A {
11+
type T;
12+
type U;
13+
}
14+
15+
impl A for () {
16+
type T = Self::U;
17+
type U = ();
18+
}
19+
20+
pub trait C {
21+
type X;
22+
}
23+
24+
pub struct F<T: C>(pub T::X);

0 commit comments

Comments
 (0)