about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/passes/collect_trait_impls.rs100
-rw-r--r--src/test/rustdoc/deref-recursive-pathbuf.rs26
2 files changed, 90 insertions, 36 deletions
diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs
index 9b0ae09cb3f..7b5e9e5905f 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<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);
@@ -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 00000000000..759e881aab4
--- /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<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 }
+}