Skip to content

Commit

Permalink
Rollup merge of #90183 - GuillaumeGomez:recurse-deref, r=jyn514
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
GuillaumeGomez committed Oct 30, 2021
2 parents ec83b95 + 78b6045 commit 7349440
Show file tree
Hide file tree
Showing 9 changed files with 315 additions and 31 deletions.
9 changes: 7 additions & 2 deletions src/librustdoc/html/render/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::rc::Rc;
use std::sync::mpsc::{channel, Receiver};

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

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

/// Shared mutable state used in [`Context`] and elsewhere.
crate struct SharedContext<'tcx> {
Expand Down Expand Up @@ -513,6 +516,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
dst,
render_redirect_pages: false,
id_map: RefCell::new(id_map),
deref_id_map: RefCell::new(FxHashMap::default()),
shared: Rc::new(scx),
include_sources,
};
Expand All @@ -536,6 +540,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
current: self.current.clone(),
dst: self.dst.clone(),
render_redirect_pages: self.render_redirect_pages,
deref_id_map: RefCell::new(FxHashMap::default()),
id_map: RefCell::new(IdMap::new()),
shared: Rc::clone(&self.shared),
include_sources: self.include_sources,
Expand Down
94 changes: 78 additions & 16 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,19 @@ fn render_assoc_items(
containing_item: &clean::Item,
it: DefId,
what: AssocItemRender<'_>,
) {
let mut derefs = FxHashSet::default();
derefs.insert(it);
render_assoc_items_inner(w, cx, containing_item, it, what, &mut derefs)
}

fn render_assoc_items_inner(
w: &mut Buffer,
cx: &Context<'_>,
containing_item: &clean::Item,
it: DefId,
what: AssocItemRender<'_>,
derefs: &mut FxHashSet<DefId>,
) {
info!("Documenting associated items of {:?}", containing_item.name);
let cache = cx.cache();
Expand All @@ -1063,31 +1076,39 @@ fn render_assoc_items(
};
let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| i.inner_impl().trait_.is_none());
if !non_trait.is_empty() {
let mut tmp_buf = Buffer::empty_from(w);
let render_mode = match what {
AssocItemRender::All => {
w.write_str(
tmp_buf.write_str(
"<h2 id=\"implementations\" class=\"small-section-header\">\
Implementations<a href=\"#implementations\" class=\"anchor\"></a>\
</h2>",
);
RenderMode::Normal
}
AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => {
let id =
cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx))));
if let Some(def_id) = type_.def_id(cx.cache()) {
cx.deref_id_map.borrow_mut().insert(def_id, id.clone());
}
write!(
w,
"<h2 id=\"deref-methods\" class=\"small-section-header\">\
tmp_buf,
"<h2 id=\"{id}\" class=\"small-section-header\">\
<span>Methods from {trait_}&lt;Target = {type_}&gt;</span>\
<a href=\"#deref-methods\" class=\"anchor\"></a>\
<a href=\"#{id}\" class=\"anchor\"></a>\
</h2>",
id = id,
trait_ = trait_.print(cx),
type_ = type_.print(cx),
);
RenderMode::ForDeref { mut_: deref_mut_ }
}
};
let mut impls_buf = Buffer::empty_from(w);
for i in &non_trait {
render_impl(
w,
&mut impls_buf,
cx,
i,
containing_item,
Expand All @@ -1104,18 +1125,27 @@ fn render_assoc_items(
},
);
}
if !impls_buf.is_empty() {
w.push_buffer(tmp_buf);
w.push_buffer(impls_buf);
}
}
if let AssocItemRender::DerefFor { .. } = what {
return;
}

if !traits.is_empty() {
let deref_impl =
traits.iter().find(|t| t.trait_did() == cx.tcx().lang_items().deref_trait());
if let Some(impl_) = deref_impl {
let has_deref_mut =
traits.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait());
render_deref_methods(w, cx, impl_, containing_item, has_deref_mut);
render_deref_methods(w, cx, impl_, containing_item, has_deref_mut, derefs);
}

// If we were already one level into rendering deref methods, we don't want to render
// anything after recursing into any further deref methods above.
if let AssocItemRender::DerefFor { .. } = what {
return;
}

let (synthetic, concrete): (Vec<&&Impl>, Vec<&&Impl>) =
traits.iter().partition(|t| t.inner_impl().synthetic);
let (blanket_impl, concrete): (Vec<&&Impl>, _) =
Expand Down Expand Up @@ -1167,6 +1197,7 @@ fn render_deref_methods(
impl_: &Impl,
container_item: &clean::Item,
deref_mut: bool,
derefs: &mut FxHashSet<DefId>,
) {
let cache = cx.cache();
let deref_type = impl_.inner_impl().trait_.as_ref().unwrap();
Expand All @@ -1188,16 +1219,16 @@ fn render_deref_methods(
if let Some(did) = target.def_id(cache) {
if let Some(type_did) = impl_.inner_impl().for_.def_id(cache) {
// `impl Deref<Target = S> for S`
if did == type_did {
if did == type_did || !derefs.insert(did) {
// Avoid infinite cycles
return;
}
}
render_assoc_items(w, cx, container_item, did, what);
render_assoc_items_inner(w, cx, container_item, did, what, derefs);
} else {
if let Some(prim) = target.primitive_type() {
if let Some(&did) = cache.primitive_locations.get(&prim) {
render_assoc_items(w, cx, container_item, did, what);
render_assoc_items_inner(w, cx, container_item, did, what, derefs);
}
}
}
Expand Down Expand Up @@ -1987,7 +2018,9 @@ fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) {
if let Some(impl_) =
v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait())
{
sidebar_deref_methods(cx, out, impl_, v);
let mut derefs = FxHashSet::default();
derefs.insert(did);
sidebar_deref_methods(cx, out, impl_, v, &mut derefs);
}

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

fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &[Impl]) {
fn sidebar_deref_methods(
cx: &Context<'_>,
out: &mut Buffer,
impl_: &Impl,
v: &[Impl],
derefs: &mut FxHashSet<DefId>,
) {
let c = cx.cache();

debug!("found Deref: {:?}", impl_);
Expand All @@ -2078,7 +2117,7 @@ fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &[
if let Some(did) = target.def_id(c) {
if let Some(type_did) = impl_.inner_impl().for_.def_id(c) {
// `impl Deref<Target = S> for S`
if did == type_did {
if did == type_did || !derefs.insert(did) {
// Avoid infinite cycles
return;
}
Expand All @@ -2102,9 +2141,17 @@ fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &[
})
.collect::<Vec<_>>();
if !ret.is_empty() {
let map;
let id = if let Some(target_def_id) = real_target.def_id(c) {
map = cx.deref_id_map.borrow();
map.get(&target_def_id).expect("Deref section without derived id")
} else {
"deref-methods"
};
write!(
out,
"<h3 class=\"sidebar-title\"><a href=\"#deref-methods\">Methods from {}&lt;Target={}&gt;</a></h3>",
"<h3 class=\"sidebar-title\"><a href=\"#{}\">Methods from {}&lt;Target={}&gt;</a></h3>",
id,
Escape(&format!("{:#}", impl_.inner_impl().trait_.as_ref().unwrap().print(cx))),
Escape(&format!("{:#}", real_target.print(cx))),
);
Expand All @@ -2117,6 +2164,21 @@ fn sidebar_deref_methods(cx: &Context<'_>, out: &mut Buffer, impl_: &Impl, v: &[
out.push_str("</div>");
}
}

// Recurse into any further impls that might exist for `target`
if let Some(target_did) = target.def_id_no_primitives() {
if let Some(target_impls) = c.impls.get(&target_did) {
if let Some(target_deref_impl) = target_impls.iter().find(|i| {
i.inner_impl()
.trait_
.as_ref()
.map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait())
.unwrap_or(false)
}) {
sidebar_deref_methods(cx, out, target_deref_impl, target_impls, derefs);
}
}
}
}
}

Expand Down
52 changes: 43 additions & 9 deletions src/librustdoc/passes/collect_trait_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ 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;
use rustc_middle::ty::DefIdTree;
use rustc_span::symbol::sym;

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

let mut cleaner = BadImplStripper { prims, items: crate_items };
let mut type_did_to_deref_target: FxHashMap<DefId, &Type> = FxHashMap::default();

// 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_no_primitives() {
// `impl Deref<Target = S> for S`
if target_did == type_did {
// Avoid infinite cycles
return;
}
cleaner.items.insert(target_did.into());
add_deref_target(map, cleaner, target_did);
}
}
}

// 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_impl(for_)
&& trait_.as_ref().map(|t| t.def_id()) == cx.tcx.lang_items().deref_trait()
if trait_.as_ref().map(|t| t.def_id()) == cx.tcx.lang_items().deref_trait()
&& cleaner.keep_impl(for_, true)
{
let target = items
.iter()
Expand All @@ -71,16 +95,26 @@ crate fn collect_trait_impls(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
} else if let Some(did) = target.def_id(&cx.cache) {
cleaner.items.insert(did.into());
}
if let Some(for_did) = for_.def_id_no_primitives() {
if type_did_to_deref_target.insert(for_did, target).is_none() {
// 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(for_did.into()) {
add_deref_target(&type_did_to_deref_target, &mut cleaner, for_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_with_def_id(t.def_id().into()))
cleaner.keep_impl(
for_,
trait_.as_ref().map(|t| t.def_id()) == cx.tcx.lang_items().deref_trait(),
) || trait_.as_ref().map_or(false, |t| cleaner.keep_impl_with_def_id(t.def_id().into()))
|| blanket_impl.is_some()
} else {
true
Expand Down Expand Up @@ -179,14 +213,14 @@ struct BadImplStripper {
}

impl BadImplStripper {
fn keep_impl(&self, ty: &Type) -> bool {
fn keep_impl(&self, ty: &Type, is_deref: bool) -> 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_no_primitives() {
self.keep_impl_with_def_id(did.into())
is_deref || self.keep_impl_with_def_id(did.into())
} else {
false
}
Expand Down
19 changes: 19 additions & 0 deletions src/test/rustdoc-ui/recursive-deref-ice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// check-pass

// ICE found in https://github.com/rust-lang/rust/issues/83123

pub struct Attribute;

pub struct Map<'hir> {}
impl<'hir> Map<'hir> {
pub fn attrs(&self) -> &'hir [Attribute] { &[] }
}

pub struct List<T>(T);

impl<T> std::ops::Deref for List<T> {
type Target = [T];
fn deref(&self) -> &[T] {
&[]
}
}
25 changes: 25 additions & 0 deletions src/test/rustdoc/deref-recursive-pathbuf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// #26207: Show all methods reachable via Deref impls, recursing through multiple dereferencing
// levels and across multiple crates.
// For `Deref` on non-foreign types, look at `deref-recursive.rs`.

// @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"]/a[@href="#deref-methods-PathBuf"]' 'Methods from Deref<Target=PathBuf>'
// @has '-' '//*[@class="sidebar-links"]/a[@href="#method.as_path"]' 'as_path'
// @has '-' '//*[@class="sidebar-title"]/a[@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 }
}
Loading

0 comments on commit 7349440

Please sign in to comment.