Skip to content

Commit ed502bf

Browse files
authored
Unrolled build for #135771
Rollup merge of #135771 - GuillaumeGomez:jump-to-def-perf, r=fmease [rustdoc] Add support for associated items in "jump to def" feature Fixes #135485. r? ``@fmease``
2 parents eabf390 + 25e767b commit ed502bf

File tree

9 files changed

+262
-103
lines changed

9 files changed

+262
-103
lines changed

src/librustdoc/html/highlight.rs

Lines changed: 74 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,64 @@ fn string<W: Write>(
12621262
}
12631263
}
12641264

1265+
fn generate_link_to_def(
1266+
out: &mut impl Write,
1267+
text_s: &str,
1268+
klass: Class,
1269+
href_context: &Option<HrefContext<'_, '_>>,
1270+
def_span: Span,
1271+
open_tag: bool,
1272+
) -> bool {
1273+
if let Some(href_context) = href_context
1274+
&& let Some(href) =
1275+
href_context.context.shared.span_correspondence_map.get(&def_span).and_then(|href| {
1276+
let context = href_context.context;
1277+
// FIXME: later on, it'd be nice to provide two links (if possible) for all items:
1278+
// one to the documentation page and one to the source definition.
1279+
// FIXME: currently, external items only generate a link to their documentation,
1280+
// a link to their definition can be generated using this:
1281+
// https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
1282+
match href {
1283+
LinkFromSrc::Local(span) => {
1284+
context.href_from_span_relative(*span, &href_context.current_href)
1285+
}
1286+
LinkFromSrc::External(def_id) => {
1287+
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
1288+
.ok()
1289+
.map(|(url, _, _)| url)
1290+
}
1291+
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
1292+
PrimitiveType::primitive_locations(context.tcx())[prim],
1293+
context,
1294+
Some(href_context.root_path),
1295+
)
1296+
.ok()
1297+
.map(|(url, _, _)| url),
1298+
LinkFromSrc::Doc(def_id) => {
1299+
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
1300+
.ok()
1301+
.map(|(doc_link, _, _)| doc_link)
1302+
}
1303+
}
1304+
})
1305+
{
1306+
if !open_tag {
1307+
// We're already inside an element which has the same klass, no need to give it
1308+
// again.
1309+
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
1310+
} else {
1311+
let klass_s = klass.as_html();
1312+
if klass_s.is_empty() {
1313+
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
1314+
} else {
1315+
write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap();
1316+
}
1317+
}
1318+
return true;
1319+
}
1320+
false
1321+
}
1322+
12651323
/// This function writes `text` into `out` with some modifications depending on `klass`:
12661324
///
12671325
/// * If `klass` is `None`, `text` is written into `out` with no modification.
@@ -1291,10 +1349,14 @@ fn string_without_closing_tag<T: Display>(
12911349
return Some("</span>");
12921350
};
12931351

1352+
let mut added_links = false;
12941353
let mut text_s = text.to_string();
12951354
if text_s.contains("::") {
1355+
let mut span = def_span.with_hi(def_span.lo());
12961356
text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
1357+
span = span.with_hi(span.hi() + BytePos(t.len() as _));
12971358
match t {
1359+
"::" => write!(&mut path, "::"),
12981360
"self" | "Self" => write!(
12991361
&mut path,
13001362
"<span class=\"{klass}\">{t}</span>",
@@ -1307,58 +1369,24 @@ fn string_without_closing_tag<T: Display>(
13071369
klass = Class::KeyWord.as_html(),
13081370
)
13091371
}
1310-
t => write!(&mut path, "{t}"),
1372+
t => {
1373+
if !t.is_empty()
1374+
&& generate_link_to_def(&mut path, t, klass, href_context, span, open_tag)
1375+
{
1376+
added_links = true;
1377+
write!(&mut path, "</a>")
1378+
} else {
1379+
write!(&mut path, "{t}")
1380+
}
1381+
}
13111382
}
13121383
.expect("Failed to build source HTML path");
1384+
span = span.with_lo(span.lo() + BytePos(t.len() as _));
13131385
path
13141386
});
13151387
}
13161388

1317-
if let Some(href_context) = href_context
1318-
&& let Some(href) = href_context.context.shared.span_correspondence_map.get(&def_span)
1319-
&& let Some(href) = {
1320-
let context = href_context.context;
1321-
// FIXME: later on, it'd be nice to provide two links (if possible) for all items:
1322-
// one to the documentation page and one to the source definition.
1323-
// FIXME: currently, external items only generate a link to their documentation,
1324-
// a link to their definition can be generated using this:
1325-
// https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
1326-
match href {
1327-
LinkFromSrc::Local(span) => {
1328-
context.href_from_span_relative(*span, &href_context.current_href)
1329-
}
1330-
LinkFromSrc::External(def_id) => {
1331-
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
1332-
.ok()
1333-
.map(|(url, _, _)| url)
1334-
}
1335-
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
1336-
PrimitiveType::primitive_locations(context.tcx())[prim],
1337-
context,
1338-
Some(href_context.root_path),
1339-
)
1340-
.ok()
1341-
.map(|(url, _, _)| url),
1342-
LinkFromSrc::Doc(def_id) => {
1343-
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
1344-
.ok()
1345-
.map(|(doc_link, _, _)| doc_link)
1346-
}
1347-
}
1348-
}
1349-
{
1350-
if !open_tag {
1351-
// We're already inside an element which has the same klass, no need to give it
1352-
// again.
1353-
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
1354-
} else {
1355-
let klass_s = klass.as_html();
1356-
if klass_s.is_empty() {
1357-
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
1358-
} else {
1359-
write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap();
1360-
}
1361-
}
1389+
if !added_links && generate_link_to_def(out, &text_s, klass, href_context, def_span, open_tag) {
13621390
return Some("</a>");
13631391
}
13641392
if !open_tag {

src/librustdoc/html/render/span_map.rs

Lines changed: 78 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,23 @@ 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+
// FIXME: associated type impl items don't have an associated body, so we don't handle
209+
// them currently.
210+
if let Node::ImplItem(impl_item) = node
211+
&& matches!(impl_item.kind, rustc_hir::ImplItemKind::Type(_))
212+
{
213+
return None;
214+
} else if let Some((def_id, _)) = node.associated_body() {
215+
return Some(def_id);
215216
}
216217
}
218+
None
217219
}
218220

219221
impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
@@ -227,12 +229,42 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
227229
if self.handle_macro(path.span) {
228230
return;
229231
}
230-
self.handle_path(path);
232+
self.handle_path(path, false);
231233
intravisit::walk_path(self, path);
232234
}
233235

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

238270
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+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This test ensures that associated types don't crash rustdoc jump to def.
2+
3+
//@ compile-flags: -Zunstable-options --generate-link-to-definition
4+
5+
6+
#![crate_name = "foo"]
7+
8+
//@ has 'src/foo/jump-to-def-ice-assoc-types.rs.html'
9+
10+
pub trait Trait {
11+
type Node;
12+
}
13+
14+
pub fn y<G: Trait>() {
15+
struct X<G>(G);
16+
17+
impl<G: Trait> Trait for X<G> {
18+
type Node = G::Node;
19+
}
20+
}

0 commit comments

Comments
 (0)