Skip to content

Commit 8eaf68f

Browse files
committed
Preserve non-local recursive Deref impls
This adjusts the `rustdoc` trait impl collection path to preserve `Deref` impls from other crates. This adds a first pass to map all of the `Deref` type to target edges and then recursively preserves all targets.
1 parent 06ce97c commit 8eaf68f

File tree

2 files changed

+90
-36
lines changed

2 files changed

+90
-36
lines changed

src/librustdoc/passes/collect_trait_impls.rs

+64-36
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ 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};
77
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
88
use rustc_middle::ty::DefIdTree;
99
use rustc_span::symbol::sym;
@@ -54,39 +54,6 @@ crate fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
5454
}
5555
}
5656

57-
let mut cleaner = BadImplStripper { prims, items: crate_items };
58-
59-
// scan through included items ahead of time to splice in Deref targets to the "valid" sets
60-
for it in &new_items {
61-
if let ImplItem(Impl { ref for_, ref trait_, ref items, .. }) = *it.kind {
62-
if cleaner.keep_item(for_) && trait_.def_id() == cx.tcx.lang_items().deref_trait() {
63-
let target = items
64-
.iter()
65-
.find_map(|item| match *item.kind {
66-
TypedefItem(ref t, true) => Some(&t.type_),
67-
_ => None,
68-
})
69-
.expect("Deref impl without Target type");
70-
71-
if let Some(prim) = target.primitive_type() {
72-
cleaner.prims.insert(prim);
73-
} else if let Some(did) = target.def_id() {
74-
cleaner.items.insert(did);
75-
}
76-
}
77-
}
78-
}
79-
80-
new_items.retain(|it| {
81-
if let ImplItem(Impl { ref for_, ref trait_, ref blanket_impl, .. }) = *it.kind {
82-
cleaner.keep_item(for_)
83-
|| trait_.as_ref().map_or(false, |t| cleaner.keep_item(t))
84-
|| blanket_impl.is_some()
85-
} else {
86-
true
87-
}
88-
});
89-
9057
// `tcx.crates()` doesn't include the local crate, and `tcx.all_trait_implementations`
9158
// doesn't work with it anyway, so pull them from the HIR map instead
9259
for &trait_did in cx.tcx.all_traits(LOCAL_CRATE).iter() {
@@ -123,6 +90,63 @@ crate fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
12390
}
12491
}
12592

93+
let mut cleaner = BadImplStripper { prims, items: crate_items };
94+
95+
let mut type_did_to_deref_target: FxHashMap<DefId, &Type> = FxHashMap::default();
96+
// Gather all type to `Deref` target edges.
97+
for it in &new_items {
98+
if let ImplItem(Impl { ref for_, ref trait_, ref items, .. }) = *it.kind {
99+
if trait_.def_id() == cx.tcx.lang_items().deref_trait() {
100+
let target = items.iter().find_map(|item| match *item.kind {
101+
TypedefItem(ref t, true) => Some(&t.type_),
102+
_ => None,
103+
});
104+
if let (Some(for_did), Some(target)) = (for_.def_id(), target) {
105+
type_did_to_deref_target.insert(for_did, target);
106+
}
107+
}
108+
}
109+
}
110+
// Follow all `Deref` targets of included items and recursively add them as valid
111+
fn add_deref_target(
112+
map: &FxHashMap<DefId, &Type>,
113+
cleaner: &mut BadImplStripper,
114+
type_did: &DefId,
115+
) {
116+
if let Some(target) = map.get(type_did) {
117+
debug!("add_deref_target: type {:?}, target {:?}", type_did, target);
118+
if let Some(target_prim) = target.primitive_type() {
119+
cleaner.prims.insert(target_prim);
120+
} else if let Some(target_did) = target.def_id() {
121+
// `impl Deref<Target = S> for S`
122+
if target_did == *type_did {
123+
// Avoid infinite cycles
124+
return;
125+
}
126+
cleaner.items.insert(target_did);
127+
add_deref_target(map, cleaner, &target_did);
128+
}
129+
}
130+
}
131+
for type_did in type_did_to_deref_target.keys() {
132+
// Since only the `DefId` portion of the `Type` instances is known to be same for both the
133+
// `Deref` target type and the impl for type positions, this map of types is keyed by
134+
// `DefId` and for convenience uses a special cleaner that accepts `DefId`s directly.
135+
if cleaner.keep_impl_with_def_id(type_did) {
136+
add_deref_target(&type_did_to_deref_target, &mut cleaner, type_did);
137+
}
138+
}
139+
140+
new_items.retain(|it| {
141+
if let ImplItem(Impl { ref for_, ref trait_, ref blanket_impl, .. }) = *it.kind {
142+
cleaner.keep_impl(for_)
143+
|| trait_.as_ref().map_or(false, |t| cleaner.keep_impl(t))
144+
|| blanket_impl.is_some()
145+
} else {
146+
true
147+
}
148+
});
149+
126150
if let Some(ref mut it) = krate.module {
127151
if let ModuleItem(Module { ref mut items, .. }) = *it.kind {
128152
items.extend(synth.impls);
@@ -192,16 +216,20 @@ struct BadImplStripper {
192216
}
193217

194218
impl BadImplStripper {
195-
fn keep_item(&self, ty: &Type) -> bool {
219+
fn keep_impl(&self, ty: &Type) -> bool {
196220
if let Generic(_) = ty {
197221
// keep impls made on generics
198222
true
199223
} else if let Some(prim) = ty.primitive_type() {
200224
self.prims.contains(&prim)
201225
} else if let Some(did) = ty.def_id() {
202-
self.items.contains(&did)
226+
self.keep_impl_with_def_id(&did)
203227
} else {
204228
false
205229
}
206230
}
231+
232+
fn keep_impl_with_def_id(&self, did: &DefId) -> bool {
233+
self.items.contains(did)
234+
}
207235
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// ignore-tidy-linelength
2+
3+
// #26207: Show all methods reachable via Deref impls, recursing through multiple dereferencing
4+
// levels and across multiple crates.
5+
6+
// @has 'foo/struct.Foo.html'
7+
// @has '-' '//*[@id="deref-methods-PathBuf"]' 'Methods from Deref<Target = PathBuf>'
8+
// @has '-' '//*[@class="impl-items"]//*[@id="method.as_path"]' 'pub fn as_path(&self)'
9+
// @has '-' '//*[@id="deref-methods-Path"]' 'Methods from Deref<Target = Path>'
10+
// @has '-' '//*[@class="impl-items"]//*[@id="method.exists"]' 'pub fn exists(&self)'
11+
// @has '-' '//*[@class="sidebar-title"][@href="#deref-methods-PathBuf"]' 'Methods from Deref<Target=PathBuf>'
12+
// @has '-' '//*[@class="sidebar-links"]/a[@href="#method.as_path"]' 'as_path'
13+
// @has '-' '//*[@class="sidebar-title"][@href="#deref-methods-Path"]' 'Methods from Deref<Target=Path>'
14+
// @has '-' '//*[@class="sidebar-links"]/a[@href="#method.exists"]' 'exists'
15+
16+
#![crate_name = "foo"]
17+
18+
use std::ops::Deref;
19+
use std::path::PathBuf;
20+
21+
pub struct Foo(PathBuf);
22+
23+
impl Deref for Foo {
24+
type Target = PathBuf;
25+
fn deref(&self) -> &PathBuf { &self.0 }
26+
}

0 commit comments

Comments
 (0)