rustdoc: Inline methods inhereted through Deref

Whenever a type implements Deref, rustdoc will now add a section to the "methods
available" sections for "Methods from Deref<Target=Foo>", listing all the
inherent methods of the type `Foo`.

Closes #19190
This commit is contained in:
Alex Crichton 2015-04-13 16:23:32 -07:00
parent 8fb31f75c9
commit 71c1b5b704
10 changed files with 333 additions and 66 deletions

View File

@ -218,15 +218,17 @@ fn build_type(cx: &DocContext, tcx: &ty::ctxt, did: ast::DefId) -> clean::ItemEn
})
}
fn build_impls(cx: &DocContext, tcx: &ty::ctxt,
did: ast::DefId) -> Vec<clean::Item> {
pub fn build_impls(cx: &DocContext, tcx: &ty::ctxt,
did: ast::DefId) -> Vec<clean::Item> {
ty::populate_implementations_for_type_if_necessary(tcx, did);
let mut impls = Vec::new();
match tcx.inherent_impls.borrow().get(&did) {
None => {}
Some(i) => {
impls.extend(i.iter().map(|&did| { build_impl(cx, tcx, did) }));
for &did in i.iter() {
build_impl(cx, tcx, did, &mut impls);
}
}
}
@ -247,9 +249,9 @@ fn build_impls(cx: &DocContext, tcx: &ty::ctxt,
fn populate_impls(cx: &DocContext, tcx: &ty::ctxt,
def: decoder::DefLike,
impls: &mut Vec<Option<clean::Item>>) {
impls: &mut Vec<clean::Item>) {
match def {
decoder::DlImpl(did) => impls.push(build_impl(cx, tcx, did)),
decoder::DlImpl(did) => build_impl(cx, tcx, did, impls),
decoder::DlDef(def::DefMod(did)) => {
csearch::each_child_of_item(&tcx.sess.cstore,
did,
@ -262,14 +264,15 @@ fn build_impls(cx: &DocContext, tcx: &ty::ctxt,
}
}
impls.into_iter().filter_map(|a| a).collect()
return impls;
}
fn build_impl(cx: &DocContext,
tcx: &ty::ctxt,
did: ast::DefId) -> Option<clean::Item> {
pub fn build_impl(cx: &DocContext,
tcx: &ty::ctxt,
did: ast::DefId,
ret: &mut Vec<clean::Item>) {
if !cx.inlined.borrow_mut().as_mut().unwrap().insert(did) {
return None
return
}
let attrs = load_attrs(cx, tcx, did);
@ -278,13 +281,13 @@ fn build_impl(cx: &DocContext,
// If this is an impl for a #[doc(hidden)] trait, be sure to not inline
let trait_attrs = load_attrs(cx, tcx, t.def_id);
if trait_attrs.iter().any(|a| is_doc_hidden(a)) {
return None
return
}
}
// If this is a defaulted impl, then bail out early here
if csearch::is_default_impl(&tcx.sess.cstore, did) {
return Some(clean::Item {
return ret.push(clean::Item {
inner: clean::DefaultImplItem(clean::DefaultImpl {
// FIXME: this should be decoded
unsafety: ast::Unsafety::Normal,
@ -352,19 +355,25 @@ fn build_impl(cx: &DocContext,
})
}
}
}).collect();
}).collect::<Vec<_>>();
let polarity = csearch::get_impl_polarity(tcx, did);
let ty = ty::lookup_item_type(tcx, did);
return Some(clean::Item {
let trait_ = associated_trait.clean(cx).map(|bound| {
match bound {
clean::TraitBound(polyt, _) => polyt.trait_,
clean::RegionBound(..) => unreachable!(),
}
});
if let Some(clean::ResolvedPath { did, .. }) = trait_ {
if Some(did) == cx.deref_trait_did.get() {
super::build_deref_target_impls(cx, &trait_items, ret);
}
}
ret.push(clean::Item {
inner: clean::ImplItem(clean::Impl {
unsafety: ast::Unsafety::Normal, // FIXME: this should be decoded
derived: clean::detect_derived(&attrs),
trait_: associated_trait.clean(cx).map(|bound| {
match bound {
clean::TraitBound(polyt, _) => polyt.trait_,
clean::RegionBound(..) => unreachable!(),
}
}),
trait_: trait_,
for_: ty.ty.clean(cx),
generics: (&ty.generics, &predicates, subst::TypeSpace).clean(cx),
items: trait_items,

View File

@ -128,6 +128,10 @@ impl<'a, 'tcx> Clean<Crate> for visit_ast::RustdocVisitor<'a, 'tcx> {
fn clean(&self, cx: &DocContext) -> Crate {
use rustc::session::config::Input;
if let Some(t) = cx.tcx_opt() {
cx.deref_trait_did.set(t.lang_items.deref_trait());
}
let mut externs = Vec::new();
cx.sess().cstore.iter_crate_data(|n, meta| {
externs.push((n, meta.clean(cx)));
@ -321,7 +325,7 @@ impl Item {
attr::Unstable => "unstable".to_string(),
attr::Stable => String::new(),
};
if s.deprecated_since.len() > 0 {
if !s.deprecated_since.is_empty() {
base.push_str(" deprecated");
}
base
@ -387,7 +391,7 @@ impl Clean<Item> for doctree::Module {
items.extend(self.statics.iter().map(|x| x.clean(cx)));
items.extend(self.constants.iter().map(|x| x.clean(cx)));
items.extend(self.traits.iter().map(|x| x.clean(cx)));
items.extend(self.impls.iter().map(|x| x.clean(cx)));
items.extend(self.impls.iter().flat_map(|x| x.clean(cx).into_iter()));
items.extend(self.macros.iter().map(|x| x.clean(cx)));
items.extend(self.def_traits.iter().map(|x| x.clean(cx)));
@ -2186,9 +2190,21 @@ fn detect_derived<M: AttrMetaMethods>(attrs: &[M]) -> bool {
attr::contains_name(attrs, "automatically_derived")
}
impl Clean<Item> for doctree::Impl {
fn clean(&self, cx: &DocContext) -> Item {
Item {
impl Clean<Vec<Item>> for doctree::Impl {
fn clean(&self, cx: &DocContext) -> Vec<Item> {
let mut ret = Vec::new();
let trait_ = self.trait_.clean(cx);
let items = self.items.clean(cx);
// If this impl block is an implementation of the Deref trait, then we
// need to try inlining the target's inherent impl blocks as well.
if let Some(ResolvedPath { did, .. }) = trait_ {
if Some(did) == cx.deref_trait_did.get() {
build_deref_target_impls(cx, &items, &mut ret);
}
}
ret.push(Item {
name: None,
attrs: self.attrs.clean(cx),
source: self.whence.clean(cx),
@ -2198,12 +2214,66 @@ impl Clean<Item> for doctree::Impl {
inner: ImplItem(Impl {
unsafety: self.unsafety,
generics: self.generics.clean(cx),
trait_: self.trait_.clean(cx),
trait_: trait_,
for_: self.for_.clean(cx),
items: self.items.clean(cx),
items: items,
derived: detect_derived(&self.attrs),
polarity: Some(self.polarity.clean(cx)),
}),
});
return ret;
}
}
fn build_deref_target_impls(cx: &DocContext,
items: &[Item],
ret: &mut Vec<Item>) {
let tcx = match cx.tcx_opt() {
Some(t) => t,
None => return,
};
for item in items {
let target = match item.inner {
TypedefItem(ref t) => &t.type_,
_ => continue,
};
let primitive = match *target {
ResolvedPath { did, .. } if ast_util::is_local(did) => continue,
ResolvedPath { did, .. } => {
ret.extend(inline::build_impls(cx, tcx, did));
continue
}
_ => match target.primitive_type() {
Some(prim) => prim,
None => continue,
}
};
let did = match primitive {
Isize => tcx.lang_items.isize_impl(),
I8 => tcx.lang_items.i8_impl(),
I16 => tcx.lang_items.i16_impl(),
I32 => tcx.lang_items.i32_impl(),
I64 => tcx.lang_items.i64_impl(),
Usize => tcx.lang_items.usize_impl(),
U8 => tcx.lang_items.u8_impl(),
U16 => tcx.lang_items.u16_impl(),
U32 => tcx.lang_items.u32_impl(),
U64 => tcx.lang_items.u64_impl(),
F32 => tcx.lang_items.f32_impl(),
F64 => tcx.lang_items.f64_impl(),
Char => tcx.lang_items.char_impl(),
Bool => None,
Str => tcx.lang_items.str_impl(),
Slice => tcx.lang_items.slice_impl(),
Array => tcx.lang_items.slice_impl(),
PrimitiveTuple => None,
PrimitiveRawPointer => tcx.lang_items.const_ptr_impl(),
};
if let Some(did) = did {
if !ast_util::is_local(did) {
inline::build_impl(cx, tcx, did, ret);
}
}
}
}

View File

@ -20,7 +20,7 @@ use rustc_resolve as resolve;
use syntax::{ast, ast_map, codemap, diagnostic};
use std::cell::RefCell;
use std::cell::{RefCell, Cell};
use std::collections::{HashMap, HashSet};
use visit_ast::RustdocVisitor;
@ -48,6 +48,7 @@ pub struct DocContext<'tcx> {
pub external_typarams: RefCell<Option<HashMap<ast::DefId, String>>>,
pub inlined: RefCell<Option<HashSet<ast::DefId>>>,
pub populated_crate_impls: RefCell<HashSet<ast::CrateNum>>,
pub deref_trait_did: Cell<Option<ast::DefId>>,
}
impl<'tcx> DocContext<'tcx> {
@ -77,6 +78,7 @@ pub struct CrateAnalysis {
pub external_paths: ExternalPaths,
pub external_typarams: RefCell<Option<HashMap<ast::DefId, String>>>,
pub inlined: RefCell<Option<HashSet<ast::DefId>>>,
pub deref_trait_did: Option<ast::DefId>,
}
pub type Externs = HashMap<String, Vec<String>>;
@ -147,15 +149,17 @@ pub fn run_core(search_paths: SearchPaths, cfgs: Vec<String>, externs: Externs,
external_paths: RefCell::new(Some(HashMap::new())),
inlined: RefCell::new(Some(HashSet::new())),
populated_crate_impls: RefCell::new(HashSet::new()),
deref_trait_did: Cell::new(None),
};
debug!("crate: {:?}", ctxt.krate);
let analysis = CrateAnalysis {
let mut analysis = CrateAnalysis {
exported_items: exported_items,
public_items: public_items,
external_paths: RefCell::new(None),
external_typarams: RefCell::new(None),
inlined: RefCell::new(None),
deref_trait_did: None,
};
let krate = {
@ -170,5 +174,6 @@ pub fn run_core(search_paths: SearchPaths, cfgs: Vec<String>, externs: Externs,
*analysis.external_typarams.borrow_mut() = map;
let map = ctxt.inlined.borrow_mut().take();
*analysis.inlined.borrow_mut() = map;
analysis.deref_trait_did = ctxt.deref_trait_did.get();
(krate, analysis)
}

View File

@ -209,6 +209,7 @@ pub struct Cache {
privmod: bool,
remove_priv: bool,
public_items: NodeSet,
deref_trait_did: Option<ast::DefId>,
// In rare case where a structure is defined in one module but implemented
// in another, if the implementing module is parsed before defining module,
@ -396,6 +397,7 @@ pub fn run(mut krate: clean::Crate,
public_items: public_items,
orphan_methods: Vec::new(),
traits: mem::replace(&mut krate.external_traits, HashMap::new()),
deref_trait_did: analysis.as_ref().and_then(|a| a.deref_trait_did),
typarams: analysis.as_ref().map(|a| {
a.external_typarams.borrow_mut().take().unwrap()
}).unwrap_or(HashMap::new()),
@ -403,8 +405,6 @@ pub fn run(mut krate: clean::Crate,
a.inlined.borrow_mut().take().unwrap()
}).unwrap_or(HashSet::new()),
};
cache.stack.push(krate.name.clone());
krate = cache.fold_crate(krate);
// Cache where all our extern crates are located
for &(n, ref e) in &krate.externs {
@ -427,6 +427,9 @@ pub fn run(mut krate: clean::Crate,
cache.primitive_locations.insert(prim, ast::LOCAL_CRATE);
}
cache.stack.push(krate.name.clone());
krate = cache.fold_crate(krate);
// Build our search index
let index = try!(build_index(&krate, &mut cache));
@ -1069,8 +1072,11 @@ impl DocFolder for Cache {
}
ref t => {
t.primitive_type().map(|p| {
ast_util::local_def(p.to_node_id())
t.primitive_type().and_then(|t| {
self.primitive_locations.get(&t).map(|n| {
let id = t.to_node_id();
ast::DefId { krate: *n, node: id }
})
})
}
};
@ -1684,12 +1690,12 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
fn short_stability(item: &clean::Item, show_reason: bool) -> Option<String> {
item.stability.as_ref().and_then(|stab| {
let reason = if show_reason && stab.reason.len() > 0 {
let reason = if show_reason && !stab.reason.is_empty() {
format!(": {}", stab.reason)
} else {
String::new()
};
let text = if stab.deprecated_since.len() > 0 {
let text = if !stab.deprecated_since.is_empty() {
let since = if show_reason {
format!(" since {}", Escape(&stab.deprecated_since))
} else {
@ -1865,7 +1871,7 @@ fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
}
// If there are methods directly on this trait object, render them here.
try!(render_methods(w, it));
try!(render_methods(w, it.def_id, MethodRender::All));
let cache = cache();
try!(write!(w, "
@ -1995,7 +2001,7 @@ fn item_struct(w: &mut fmt::Formatter, it: &clean::Item,
try!(write!(w, "</table>"));
}
}
render_methods(w, it)
render_methods(w, it.def_id, MethodRender::All)
}
fn item_enum(w: &mut fmt::Formatter, it: &clean::Item,
@ -2094,7 +2100,7 @@ fn item_enum(w: &mut fmt::Formatter, it: &clean::Item,
try!(write!(w, "</table>"));
}
try!(render_methods(w, it));
try!(render_methods(w, it.def_id, MethodRender::All));
Ok(())
}
@ -2183,27 +2189,61 @@ enum MethodLink {
GotoSource(ast::DefId),
}
fn render_methods(w: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result {
let v = match cache().impls.get(&it.def_id) {
Some(v) => v.clone(),
enum MethodRender<'a> {
All,
DerefFor { trait_: &'a clean::Type, type_: &'a clean::Type },
}
fn render_methods(w: &mut fmt::Formatter,
it: ast::DefId,
what: MethodRender) -> fmt::Result {
let c = cache();
let v = match c.impls.get(&it) {
Some(v) => v,
None => return Ok(()),
};
let (non_trait, traits): (Vec<_>, _) = v.into_iter()
.partition(|i| i.impl_.trait_.is_none());
let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| {
i.impl_.trait_.is_none()
});
if !non_trait.is_empty() {
try!(write!(w, "<h2 id='methods'>Methods</h2>"));
let render_header = match what {
MethodRender::All => {
try!(write!(w, "<h2 id='methods'>Methods</h2>"));
true
}
MethodRender::DerefFor { trait_, type_ } => {
try!(write!(w, "<h2 id='deref-methods'>Methods from \
{}&lt;Target={}&gt;</h2>", trait_, type_));
false
}
};
for i in &non_trait {
try!(render_impl(w, i, MethodLink::Anchor));
try!(render_impl(w, i, MethodLink::Anchor, render_header));
}
}
if let MethodRender::DerefFor { .. } = what {
return Ok(())
}
if !traits.is_empty() {
let deref_impl = traits.iter().find(|t| {
match *t.impl_.trait_.as_ref().unwrap() {
clean::ResolvedPath { did, .. } => {
Some(did) == c.deref_trait_did
}
_ => false
}
});
if let Some(impl_) = deref_impl {
try!(render_deref_methods(w, impl_));
}
try!(write!(w, "<h2 id='implementations'>Trait \
Implementations</h2>"));
let (derived, manual): (Vec<_>, _) = traits.into_iter()
.partition(|i| i.impl_.derived);
let (derived, manual): (Vec<_>, _) = traits.iter().partition(|i| {
i.impl_.derived
});
for i in &manual {
let did = i.trait_did().unwrap();
try!(render_impl(w, i, MethodLink::GotoSource(did)));
try!(render_impl(w, i, MethodLink::GotoSource(did), true));
}
if !derived.is_empty() {
try!(write!(w, "<h3 id='derived_implementations'>\
@ -2211,27 +2251,52 @@ fn render_methods(w: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result {
</h3>"));
for i in &derived {
let did = i.trait_did().unwrap();
try!(render_impl(w, i, MethodLink::GotoSource(did)));
try!(render_impl(w, i, MethodLink::GotoSource(did), true));
}
}
}
Ok(())
}
fn render_impl(w: &mut fmt::Formatter, i: &Impl, link: MethodLink)
-> fmt::Result {
try!(write!(w, "<h3 class='impl'><code>impl{} ",
i.impl_.generics));
if let Some(clean::ImplPolarity::Negative) = i.impl_.polarity {
try!(write!(w, "!"));
fn render_deref_methods(w: &mut fmt::Formatter, impl_: &Impl) -> fmt::Result {
let deref_type = impl_.impl_.trait_.as_ref().unwrap();
let target = impl_.impl_.items.iter().filter_map(|item| {
match item.inner {
clean::TypedefItem(ref t) => Some(&t.type_),
_ => None,
}
}).next().unwrap();
let what = MethodRender::DerefFor { trait_: deref_type, type_: target };
match *target {
clean::ResolvedPath { did, .. } => render_methods(w, did, what),
_ => {
if let Some(prim) = target.primitive_type() {
if let Some(c) = cache().primitive_locations.get(&prim) {
let did = ast::DefId { krate: *c, node: prim.to_node_id() };
try!(render_methods(w, did, what));
}
}
Ok(())
}
}
if let Some(ref ty) = i.impl_.trait_ {
try!(write!(w, "{} for ", *ty));
}
try!(write!(w, "{}{}</code></h3>", i.impl_.for_,
WhereClause(&i.impl_.generics)));
if let Some(ref dox) = i.dox {
try!(write!(w, "<div class='docblock'>{}</div>", Markdown(dox)));
}
fn render_impl(w: &mut fmt::Formatter, i: &Impl, link: MethodLink,
render_header: bool) -> fmt::Result {
if render_header {
try!(write!(w, "<h3 class='impl'><code>impl{} ",
i.impl_.generics));
if let Some(clean::ImplPolarity::Negative) = i.impl_.polarity {
try!(write!(w, "!"));
}
if let Some(ref ty) = i.impl_.trait_ {
try!(write!(w, "{} for ", *ty));
}
try!(write!(w, "{}{}</code></h3>", i.impl_.for_,
WhereClause(&i.impl_.generics)));
if let Some(ref dox) = i.dox {
try!(write!(w, "<div class='docblock'>{}</div>", Markdown(dox)));
}
}
fn doctraititem(w: &mut fmt::Formatter, item: &clean::Item,
@ -2393,7 +2458,7 @@ fn item_primitive(w: &mut fmt::Formatter,
it: &clean::Item,
_p: &clean::PrimitiveType) -> fmt::Result {
try!(document(w, it));
render_methods(w, it)
render_methods(w, it.def_id, MethodRender::All)
}
fn get_basic_keywords() -> &'static str {

View File

@ -481,9 +481,12 @@ em.stab.unstable { background: #FFF5D6; border-color: #FFC600; }
em.stab.deprecated { background: #F3DFFF; border-color: #7F0087; }
em.stab {
display: inline-block;
border-width: 2px;
border-width: 1px;
border-style: solid;
padding: 5px;
padding: 3px;
margin-bottom: 5px;
font-size: 90%;
font-style: normal;
}
em.stab p {
display: inline;
@ -492,6 +495,7 @@ em.stab p {
.module-item .stab {
border-width: 0;
padding: 0;
margin: 0;
background: inherit !important;
}

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::cell::RefCell;
use std::cell::{RefCell, Cell};
use std::collections::{HashSet, HashMap};
use std::dynamic_lib::DynamicLibrary;
use std::env;
@ -92,6 +92,7 @@ pub fn run(input: &str,
external_typarams: RefCell::new(None),
inlined: RefCell::new(None),
populated_crate_impls: RefCell::new(HashSet::new()),
deref_trait_did: Cell::new(None),
};
let mut v = RustdocVisitor::new(&ctx, None);

View File

@ -0,0 +1,30 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::ops::Deref;
pub struct Foo;
impl Deref for Foo {
type Target = i32;
fn deref(&self) -> &i32 { loop {} }
}
pub struct Bar;
pub struct Baz;
impl Baz {
pub fn baz(&self) {}
}
impl Deref for Bar {
type Target = Baz;
fn deref(&self) -> &Baz { loop {} }
}

View File

@ -0,0 +1,22 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::ops::Deref;
pub struct Bar;
impl Deref for Bar {
type Target = i32;
fn deref(&self) -> &i32 { loop {} }
}
// @has issue_19190_2/struct.Bar.html
// @has - '//*[@id="method.count_ones"]' 'fn count_ones(self) -> u32'

View File

@ -0,0 +1,35 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// aux-build:issue-19190-3.rs
// ignore-android
extern crate issue_19190_3;
use std::ops::Deref;
use issue_19190_3::Baz;
// @has issue_19190_3/struct.Foo.html
// @has - '//*[@id="method.count_ones"]' 'fn count_ones(self) -> u32'
pub use issue_19190_3::Foo;
// @has issue_19190_3/struct.Bar.html
// @has - '//*[@id="method.baz"]' 'fn baz(&self)'
pub use issue_19190_3::Bar;
// @has issue_19190_3/struct.MyBar.html
// @has - '//*[@id="method.baz"]' 'fn baz(&self)'
pub struct MyBar;
impl Deref for MyBar {
type Target = Baz;
fn deref(&self) -> &Baz { loop {} }
}

View File

@ -0,0 +1,26 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::ops::Deref;
pub struct Foo;
pub struct Bar;
impl Foo {
pub fn foo(&self) {}
}
impl Deref for Bar {
type Target = Foo;
fn deref(&self) -> &Foo { loop {} }
}
// @has issue_19190/struct.Bar.html
// @has - '//*[@id="method.foo"]' 'fn foo(&self)'