diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 9b0ae09cb3fd5..7b5e9e5905f33 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -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; @@ -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() { @@ -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 = 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, + 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 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); @@ -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) + } } diff --git a/src/test/rustdoc/deref-recursive-pathbuf.rs b/src/test/rustdoc/deref-recursive-pathbuf.rs new file mode 100644 index 0000000000000..759e881aab415 --- /dev/null +++ b/src/test/rustdoc/deref-recursive-pathbuf.rs @@ -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' +// @has '-' '//*[@class="impl-items"]//*[@id="method.as_path"]' 'pub fn as_path(&self)' +// @has '-' '//*[@id="deref-methods-Path"]' 'Methods from Deref' +// @has '-' '//*[@class="impl-items"]//*[@id="method.exists"]' 'pub fn exists(&self)' +// @has '-' '//*[@class="sidebar-title"][@href="#deref-methods-PathBuf"]' 'Methods from Deref' +// @has '-' '//*[@class="sidebar-links"]/a[@href="#method.as_path"]' 'as_path' +// @has '-' '//*[@class="sidebar-title"][@href="#deref-methods-Path"]' 'Methods from Deref' +// @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 } +}