Skip to content

Commit 7349440

Browse files
Rollup merge of #90183 - GuillaumeGomez:recurse-deref, r=jyn514
Show all Deref implementations recursively Fixes #87783. This is a re-implementation of #80653, so taking the original PR comment: This changes `rustdoc` to recursively follow `Deref` targets so that methods from all levels are added to the rendered output. This implementation displays the methods from all levels in the expanded state with separate sections for each level. ![image](https://user-images.githubusercontent.com/279572/103482863-46723b00-4ddb-11eb-972b-c463351a425c.png) cc `@camelid` r? `@jyn514`
2 parents ec83b95 + 78b6045 commit 7349440

9 files changed

+315
-31
lines changed

src/librustdoc/html/render/context.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::rc::Rc;
66
use std::sync::mpsc::{channel, Receiver};
77

88
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
9-
use rustc_hir::def_id::LOCAL_CRATE;
9+
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
1010
use rustc_middle::ty::TyCtxt;
1111
use rustc_session::Session;
1212
use rustc_span::edition::Edition;
@@ -54,6 +54,9 @@ crate struct Context<'tcx> {
5454
/// real location of an item. This is used to allow external links to
5555
/// publicly reused items to redirect to the right location.
5656
pub(super) render_redirect_pages: bool,
57+
/// Tracks section IDs for `Deref` targets so they match in both the main
58+
/// body and the sidebar.
59+
pub(super) deref_id_map: RefCell<FxHashMap<DefId, String>>,
5760
/// The map used to ensure all generated 'id=' attributes are unique.
5861
pub(super) id_map: RefCell<IdMap>,
5962
/// Shared mutable state.
@@ -70,7 +73,7 @@ crate struct Context<'tcx> {
7073

7174
// `Context` is cloned a lot, so we don't want the size to grow unexpectedly.
7275
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
73-
rustc_data_structures::static_assert_size!(Context<'_>, 104);
76+
rustc_data_structures::static_assert_size!(Context<'_>, 144);
7477

7578
/// Shared mutable state used in [`Context`] and elsewhere.
7679
crate struct SharedContext<'tcx> {
@@ -513,6 +516,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
513516
dst,
514517
render_redirect_pages: false,
515518
id_map: RefCell::new(id_map),
519+
deref_id_map: RefCell::new(FxHashMap::default()),
516520
shared: Rc::new(scx),
517521
include_sources,
518522
};
@@ -536,6 +540,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
536540
current: self.current.clone(),
537541
dst: self.dst.clone(),
538542
render_redirect_pages: self.render_redirect_pages,
543+
deref_id_map: RefCell::new(FxHashMap::default()),
539544
id_map: RefCell::new(IdMap::new()),
540545
shared: Rc::clone(&self.shared),
541546
include_sources: self.include_sources,

src/librustdoc/html/render/mod.rs

+78-16
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,19 @@ fn render_assoc_items(
10541054
containing_item: &clean::Item,
10551055
it: DefId,
10561056
what: AssocItemRender<'_>,
1057+
) {
1058+
let mut derefs = FxHashSet::default();
1059+
derefs.insert(it);
1060+
render_assoc_items_inner(w, cx, containing_item, it, what, &mut derefs)
1061+
}
1062+
1063+
fn render_assoc_items_inner(
1064+
w: &mut Buffer,
1065+
cx: &Context<'_>,
1066+
containing_item: &clean::Item,
1067+
it: DefId,
1068+
what: AssocItemRender<'_>,
1069+
derefs: &mut FxHashSet<DefId>,
10571070
) {
10581071
info!("Documenting associated items of {:?}", containing_item.name);
10591072
let cache = cx.cache();
@@ -1063,31 +1076,39 @@ fn render_assoc_items(
10631076
};
10641077
let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none());
10651078
if !non_trait.is_empty() {
1079+
let mut tmp_buf = Buffer::empty_from(w);
10661080
let render_mode = match what {
10671081
AssocItemRender::All => {
1068-
w.write_str(
1082+
tmp_buf.write_str(
10691083
"<h2 id=\"implementations\" class=\"small-section-header\">\
10701084
Implementations<a href=\"#implementations\" class=\"anchor\"></a>\
10711085
</h2>",
10721086
);
10731087
RenderMode::Normal
10741088
}
10751089
AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => {
1090+
let id =
1091+
cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx))));
1092+
if let Some(def_id) = type_.def_id(cx.cache()) {
1093+
cx.deref_id_map.borrow_mut().insert(def_id, id.clone());
1094+
}
10761095
write!(
1077-
w,
1078-
"<h2 id=\"deref-methods\" class=\"small-section-header\">\
1096+
tmp_buf,
1097+
"<h2 id=\"{id}\" class=\"small-section-header\">\
10791098
<span>Methods from {trait_}&lt;Target = {type_}&gt;</span>\
1080-
<a href=\"#deref-methods\" class=\"anchor\"></a>\
1099+
<a href=\"#{id}\" class=\"anchor\"></a>\
10811100
</h2>",
1101+
id = id,
10821102
trait_ = trait_.print(cx),
10831103
type_ = type_.print(cx),
10841104
);
10851105
RenderMode::ForDeref { mut_: deref_mut_ }
10861106
}
10871107
};
1108+
let mut impls_buf = Buffer::empty_from(w);
10881109
for i in &non_trait {
10891110
render_impl(
1090-
w,
1111+
&mut impls_buf,
10911112
cx,
10921113
i,
10931114
containing_item,
@@ -1104,18 +1125,27 @@ fn render_assoc_items(
11041125
},
11051126
);
11061127
}
1128+
if !impls_buf.is_empty() {
1129+
w.push_buffer(tmp_buf);
1130+
w.push_buffer(impls_buf);
1131+
}
11071132
}
1108-
if let AssocItemRender::DerefFor { .. } = what {
1109-
return;
1110-
}
1133+
11111134
if !traits.is_empty() {
11121135
let deref_impl =
11131136
traits.iter().find(|t| t.trait_did() == cx.tcx().lang_items().deref_trait());
11141137
if let Some(impl_) = deref_impl {
11151138
let has_deref_mut =
11161139
traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait());
1117-
render_deref_methods(w, cx, impl_, containing_item, has_deref_mut);
1140+
render_deref_methods(w, cx, impl_, containing_item, has_deref_mut, derefs);
1141+
}
1142+
1143+
// If we were already one level into rendering deref methods, we don't want to render
1144+
// anything after recursing into any further deref methods above.
1145+
if let AssocItemRender::DerefFor { .. } = what {
1146+
return;
11181147
}
1148+
11191149
let (synthetic, concrete): (Vec<&&Impl>, Vec<&&Impl>) =
11201150
traits.iter().partition(|t| t.inner_impl().synthetic);
11211151
let (blanket_impl, concrete): (Vec<&&Impl>, _) =
@@ -1167,6 +1197,7 @@ fn render_deref_methods(
11671197
impl_: &Impl,
11681198
container_item: &clean::Item,
11691199
deref_mut: bool,
1200+
derefs: &mut FxHashSet<DefId>,
11701201
) {
11711202
let cache = cx.cache();
11721203
let deref_type = impl_.inner_impl().trait_.as_ref().unwrap();
@@ -1188,16 +1219,16 @@ fn render_deref_methods(
11881219
if let Some(did) = target.def_id(cache) {
11891220
if let Some(type_did) = impl_.inner_impl().for_.def_id(cache) {
11901221
// `impl Deref<Target = S> for S`
1191-
if did == type_did {
1222+
if did == type_did || !derefs.insert(did) {
11921223
// Avoid infinite cycles
11931224
return;
11941225
}
11951226
}
1196-
render_assoc_items(w, cx, container_item, did, what);
1227+
render_assoc_items_inner(w, cx, container_item, did, what, derefs);
11971228
} else {
11981229
if let Some(prim) = target.primitive_type() {
11991230
if let Some(&did) = cache.primitive_locations.get(&prim) {
1200-
render_assoc_items(w, cx, container_item, did, what);
1231+
render_assoc_items_inner(w, cx, container_item, did, what, derefs);
12011232
}
12021233
}
12031234
}
@@ -1987,7 +2018,9 @@ fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) {
19872018
if let Some(impl_) =
19882019
v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait())
19892020
{
1990-
sidebar_deref_methods(cx, out, impl_, v);
2021+
let mut derefs = FxHashSet::default();
2022+
derefs.insert(did);
2023+
sidebar_deref_methods(cx, out, impl_, v, &mut derefs);
19912024
}
19922025

19932026
let format_impls = |impls: Vec<&Impl>| {
@@ -2061,7 +2094,13 @@ fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) {
20612094
}
20622095
}
20632096

2064-
fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &[Impl]) {
2097+
fn sidebar_deref_methods(
2098+
cx: &Context<'_>,
2099+
out: &mut Buffer,
2100+
impl_: &Impl,
2101+
v: &[Impl],
2102+
derefs: &mut FxHashSet<DefId>,
2103+
) {
20652104
let c = cx.cache();
20662105

20672106
debug!("found Deref: {:?}", impl_);
@@ -2078,7 +2117,7 @@ fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &[
20782117
if let Some(did) = target.def_id(c) {
20792118
if let Some(type_did) = impl_.inner_impl().for_.def_id(c) {
20802119
// `impl Deref<Target = S> for S`
2081-
if did == type_did {
2120+
if did == type_did || !derefs.insert(did) {
20822121
// Avoid infinite cycles
20832122
return;
20842123
}
@@ -2102,9 +2141,17 @@ fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &[
21022141
})
21032142
.collect::<Vec<_>>();
21042143
if !ret.is_empty() {
2144+
let map;
2145+
let id = if let Some(target_def_id) = real_target.def_id(c) {
2146+
map = cx.deref_id_map.borrow();
2147+
map.get(&target_def_id).expect("Deref section without derived id")
2148+
} else {
2149+
"deref-methods"
2150+
};
21052151
write!(
21062152
out,
2107-
"<h3 class=\"sidebar-title\"><a href=\"#deref-methods\">Methods from {}&lt;Target={}&gt;</a></h3>",
2153+
"<h3 class=\"sidebar-title\"><a href=\"#{}\">Methods from {}&lt;Target={}&gt;</a></h3>",
2154+
id,
21082155
Escape(&format!("{:#}", impl_.inner_impl().trait_.as_ref().unwrap().print(cx))),
21092156
Escape(&format!("{:#}", real_target.print(cx))),
21102157
);
@@ -2117,6 +2164,21 @@ fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &[
21172164
out.push_str("</div>");
21182165
}
21192166
}
2167+
2168+
// Recurse into any further impls that might exist for `target`
2169+
if let Some(target_did) = target.def_id_no_primitives() {
2170+
if let Some(target_impls) = c.impls.get(&target_did) {
2171+
if let Some(target_deref_impl) = target_impls.iter().find(|i| {
2172+
i.inner_impl()
2173+
.trait_
2174+
.as_ref()
2175+
.map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait())
2176+
.unwrap_or(false)
2177+
}) {
2178+
sidebar_deref_methods(cx, out, target_deref_impl, target_impls, derefs);
2179+
}
2180+
}
2181+
}
21202182
}
21212183
}
21222184

src/librustdoc/passes/collect_trait_impls.rs

+43-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use crate::clean::*;
33
use crate::core::DocContext;
44
use crate::fold::DocFolder;
55

6-
use rustc_data_structures::fx::FxHashSet;
6+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
7+
use rustc_hir::def_id::DefId;
78
use rustc_middle::ty::DefIdTree;
89
use rustc_span::symbol::sym;
910

@@ -51,12 +52,35 @@ crate fn collect_trait_impls(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
5152
}
5253

5354
let mut cleaner = BadImplStripper { prims, items: crate_items };
55+
let mut type_did_to_deref_target: FxHashMap<DefId, &Type> = FxHashMap::default();
56+
57+
// Follow all `Deref` targets of included items and recursively add them as valid
58+
fn add_deref_target(
59+
map: &FxHashMap<DefId, &Type>,
60+
cleaner: &mut BadImplStripper,
61+
type_did: DefId,
62+
) {
63+
if let Some(target) = map.get(&type_did) {
64+
debug!("add_deref_target: type {:?}, target {:?}", type_did, target);
65+
if let Some(target_prim) = target.primitive_type() {
66+
cleaner.prims.insert(target_prim);
67+
} else if let Some(target_did) = target.def_id_no_primitives() {
68+
// `impl Deref<Target = S> for S`
69+
if target_did == type_did {
70+
// Avoid infinite cycles
71+
return;
72+
}
73+
cleaner.items.insert(target_did.into());
74+
add_deref_target(map, cleaner, target_did);
75+
}
76+
}
77+
}
5478

5579
// scan through included items ahead of time to splice in Deref targets to the "valid" sets
5680
for it in &new_items {
5781
if let ImplItem(Impl { ref for_, ref trait_, ref items, .. }) = *it.kind {
58-
if cleaner.keep_impl(for_)
59-
&& trait_.as_ref().map(|t| t.def_id()) == cx.tcx.lang_items().deref_trait()
82+
if trait_.as_ref().map(|t| t.def_id()) == cx.tcx.lang_items().deref_trait()
83+
&& cleaner.keep_impl(for_, true)
6084
{
6185
let target = items
6286
.iter()
@@ -71,16 +95,26 @@ crate fn collect_trait_impls(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
7195
} else if let Some(did) = target.def_id(&cx.cache) {
7296
cleaner.items.insert(did.into());
7397
}
98+
if let Some(for_did) = for_.def_id_no_primitives() {
99+
if type_did_to_deref_target.insert(for_did, target).is_none() {
100+
// Since only the `DefId` portion of the `Type` instances is known to be same for both the
101+
// `Deref` target type and the impl for type positions, this map of types is keyed by
102+
// `DefId` and for convenience uses a special cleaner that accepts `DefId`s directly.
103+
if cleaner.keep_impl_with_def_id(for_did.into()) {
104+
add_deref_target(&type_did_to_deref_target, &mut cleaner, for_did);
105+
}
106+
}
107+
}
74108
}
75109
}
76110
}
77111

78112
new_items.retain(|it| {
79113
if let ImplItem(Impl { ref for_, ref trait_, ref blanket_impl, .. }) = *it.kind {
80-
cleaner.keep_impl(for_)
81-
|| trait_
82-
.as_ref()
83-
.map_or(false, |t| cleaner.keep_impl_with_def_id(t.def_id().into()))
114+
cleaner.keep_impl(
115+
for_,
116+
trait_.as_ref().map(|t| t.def_id()) == cx.tcx.lang_items().deref_trait(),
117+
) || trait_.as_ref().map_or(false, |t| cleaner.keep_impl_with_def_id(t.def_id().into()))
84118
|| blanket_impl.is_some()
85119
} else {
86120
true
@@ -179,14 +213,14 @@ struct BadImplStripper {
179213
}
180214

181215
impl BadImplStripper {
182-
fn keep_impl(&self, ty: &Type) -> bool {
216+
fn keep_impl(&self, ty: &Type, is_deref: bool) -> bool {
183217
if let Generic(_) = ty {
184218
// keep impls made on generics
185219
true
186220
} else if let Some(prim) = ty.primitive_type() {
187221
self.prims.contains(&prim)
188222
} else if let Some(did) = ty.def_id_no_primitives() {
189-
self.keep_impl_with_def_id(did.into())
223+
is_deref || self.keep_impl_with_def_id(did.into())
190224
} else {
191225
false
192226
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// check-pass
2+
3+
// ICE found in https://github.com/rust-lang/rust/issues/83123
4+
5+
pub struct Attribute;
6+
7+
pub struct Map<'hir> {}
8+
impl<'hir> Map<'hir> {
9+
pub fn attrs(&self) -> &'hir [Attribute] { &[] }
10+
}
11+
12+
pub struct List<T>(T);
13+
14+
impl<T> std::ops::Deref for List<T> {
15+
type Target = [T];
16+
fn deref(&self) -> &[T] {
17+
&[]
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// #26207: Show all methods reachable via Deref impls, recursing through multiple dereferencing
2+
// levels and across multiple crates.
3+
// For `Deref` on non-foreign types, look at `deref-recursive.rs`.
4+
5+
// @has 'foo/struct.Foo.html'
6+
// @has '-' '//*[@id="deref-methods-PathBuf"]' 'Methods from Deref<Target = PathBuf>'
7+
// @has '-' '//*[@class="impl-items"]//*[@id="method.as_path"]' 'pub fn as_path(&self)'
8+
// @has '-' '//*[@id="deref-methods-Path"]' 'Methods from Deref<Target = Path>'
9+
// @has '-' '//*[@class="impl-items"]//*[@id="method.exists"]' 'pub fn exists(&self)'
10+
// @has '-' '//*[@class="sidebar-title"]/a[@href="#deref-methods-PathBuf"]' 'Methods from Deref<Target=PathBuf>'
11+
// @has '-' '//*[@class="sidebar-links"]/a[@href="#method.as_path"]' 'as_path'
12+
// @has '-' '//*[@class="sidebar-title"]/a[@href="#deref-methods-Path"]' 'Methods from Deref<Target=Path>'
13+
// @has '-' '//*[@class="sidebar-links"]/a[@href="#method.exists"]' 'exists'
14+
15+
#![crate_name = "foo"]
16+
17+
use std::ops::Deref;
18+
use std::path::PathBuf;
19+
20+
pub struct Foo(PathBuf);
21+
22+
impl Deref for Foo {
23+
type Target = PathBuf;
24+
fn deref(&self) -> &PathBuf { &self.0 }
25+
}

0 commit comments

Comments
 (0)