Skip to content

Commit

Permalink
Preserve non-local recursive Deref impls
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jryans committed Jan 8, 2021
1 parent 06ce97c commit 8eaf68f
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 36 deletions.
100 changes: 64 additions & 36 deletions src/librustdoc/passes/collect_trait_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::clean::*;
use crate::core::DocContext;
use crate::fold::DocFolder;

use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_middle::ty::DefIdTree;
use rustc_span::symbol::sym;
Expand Down Expand Up @@ -54,39 +54,6 @@ crate fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
}
}

let mut cleaner = BadImplStripper { prims, items: crate_items };

// scan through included items ahead of time to splice in Deref targets to the "valid" sets
for it in &new_items {
if let ImplItem(Impl { ref for_, ref trait_, ref items, .. }) = *it.kind {
if cleaner.keep_item(for_) && trait_.def_id() == cx.tcx.lang_items().deref_trait() {
let target = items
.iter()
.find_map(|item| match *item.kind {
TypedefItem(ref t, true) => Some(&t.type_),
_ => None,
})
.expect("Deref impl without Target type");

if let Some(prim) = target.primitive_type() {
cleaner.prims.insert(prim);
} else if let Some(did) = target.def_id() {
cleaner.items.insert(did);
}
}
}
}

new_items.retain(|it| {
if let ImplItem(Impl { ref for_, ref trait_, ref blanket_impl, .. }) = *it.kind {
cleaner.keep_item(for_)
|| trait_.as_ref().map_or(false, |t| cleaner.keep_item(t))
|| blanket_impl.is_some()
} else {
true
}
});

// `tcx.crates()` doesn't include the local crate, and `tcx.all_trait_implementations`
// doesn't work with it anyway, so pull them from the HIR map instead
for &trait_did in cx.tcx.all_traits(LOCAL_CRATE).iter() {
Expand Down Expand Up @@ -123,6 +90,63 @@ crate fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
}
}

let mut cleaner = BadImplStripper { prims, items: crate_items };

let mut type_did_to_deref_target: FxHashMap<DefId, &Type> = FxHashMap::default();
// Gather all type to `Deref` target edges.
for it in &new_items {
if let ImplItem(Impl { ref for_, ref trait_, ref items, .. }) = *it.kind {
if trait_.def_id() == cx.tcx.lang_items().deref_trait() {
let target = items.iter().find_map(|item| match *item.kind {
TypedefItem(ref t, true) => Some(&t.type_),
_ => None,
});
if let (Some(for_did), Some(target)) = (for_.def_id(), target) {
type_did_to_deref_target.insert(for_did, target);
}
}
}
}
// Follow all `Deref` targets of included items and recursively add them as valid
fn add_deref_target(
map: &FxHashMap<DefId, &Type>,
cleaner: &mut BadImplStripper,
type_did: &DefId,
) {
if let Some(target) = map.get(type_did) {
debug!("add_deref_target: type {:?}, target {:?}", type_did, target);
if let Some(target_prim) = target.primitive_type() {
cleaner.prims.insert(target_prim);
} else if let Some(target_did) = target.def_id() {
// `impl Deref<Target = S> for S`
if target_did == *type_did {
// Avoid infinite cycles
return;
}
cleaner.items.insert(target_did);
add_deref_target(map, cleaner, &target_did);
}
}
}
for type_did in type_did_to_deref_target.keys() {
// Since only the `DefId` portion of the `Type` instances is known to be same for both the
// `Deref` target type and the impl for type positions, this map of types is keyed by
// `DefId` and for convenience uses a special cleaner that accepts `DefId`s directly.
if cleaner.keep_impl_with_def_id(type_did) {
add_deref_target(&type_did_to_deref_target, &mut cleaner, type_did);
}
}

new_items.retain(|it| {
if let ImplItem(Impl { ref for_, ref trait_, ref blanket_impl, .. }) = *it.kind {
cleaner.keep_impl(for_)
|| trait_.as_ref().map_or(false, |t| cleaner.keep_impl(t))
|| blanket_impl.is_some()
} else {
true
}
});

if let Some(ref mut it) = krate.module {
if let ModuleItem(Module { ref mut items, .. }) = *it.kind {
items.extend(synth.impls);
Expand Down Expand Up @@ -192,16 +216,20 @@ struct BadImplStripper {
}

impl BadImplStripper {
fn keep_item(&self, ty: &Type) -> bool {
fn keep_impl(&self, ty: &Type) -> bool {
if let Generic(_) = ty {
// keep impls made on generics
true
} else if let Some(prim) = ty.primitive_type() {
self.prims.contains(&prim)
} else if let Some(did) = ty.def_id() {
self.items.contains(&did)
self.keep_impl_with_def_id(&did)
} else {
false
}
}

fn keep_impl_with_def_id(&self, did: &DefId) -> bool {
self.items.contains(did)
}
}
26 changes: 26 additions & 0 deletions src/test/rustdoc/deref-recursive-pathbuf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// ignore-tidy-linelength

// #26207: Show all methods reachable via Deref impls, recursing through multiple dereferencing
// levels and across multiple crates.

// @has 'foo/struct.Foo.html'
// @has '-' '//*[@id="deref-methods-PathBuf"]' 'Methods from Deref<Target = PathBuf>'
// @has '-' '//*[@class="impl-items"]//*[@id="method.as_path"]' 'pub fn as_path(&self)'
// @has '-' '//*[@id="deref-methods-Path"]' 'Methods from Deref<Target = Path>'
// @has '-' '//*[@class="impl-items"]//*[@id="method.exists"]' 'pub fn exists(&self)'
// @has '-' '//*[@class="sidebar-title"][@href="#deref-methods-PathBuf"]' 'Methods from Deref<Target=PathBuf>'
// @has '-' '//*[@class="sidebar-links"]/a[@href="#method.as_path"]' 'as_path'
// @has '-' '//*[@class="sidebar-title"][@href="#deref-methods-Path"]' 'Methods from Deref<Target=Path>'
// @has '-' '//*[@class="sidebar-links"]/a[@href="#method.exists"]' 'exists'

#![crate_name = "foo"]

use std::ops::Deref;
use std::path::PathBuf;

pub struct Foo(PathBuf);

impl Deref for Foo {
type Target = PathBuf;
fn deref(&self) -> &PathBuf { &self.0 }
}

0 comments on commit 8eaf68f

Please sign in to comment.