rustdoc: Inline documentation of pub use

This commit teaches rustdoc to inline the documentation for the destination of a
`pub use` statement across crate boundaries. This is intended for the standard
library's facade to show the fact that the facade is just an implementation
detail rather than the api of the standard library itself.

This starts out by inlining traits and functions, but more items will come soon.
The current drawback of this system is that hyperlinks across crates sill go to
the original item's definition rather than the reexported location.
This commit is contained in:
Alex Crichton 2014-05-22 22:00:18 -07:00
parent 7d76d0ad44
commit 712118b9c0
2 changed files with 140 additions and 53 deletions

View File

@ -15,7 +15,7 @@ use syntax;
use syntax::ast;
use syntax::ast_util;
use syntax::attr;
use syntax::attr::AttributeMethods;
use syntax::attr::{AttributeMethods, AttrMetaMethods};
use syntax::codemap::Pos;
use syntax::parse::token::InternedString;
use syntax::parse::token;
@ -250,7 +250,8 @@ impl Clean<Item> for doctree::Module {
self.statics.clean().move_iter().collect(),
self.traits.clean().move_iter().collect(),
self.impls.clean().move_iter().collect(),
self.view_items.clean().move_iter().collect(),
self.view_items.clean().move_iter()
.flat_map(|s| s.move_iter()).collect(),
self.macros.clean().move_iter().collect()
);
@ -832,10 +833,6 @@ impl Clean<TraitMethod> for ty::Method {
core::Typed(ref tcx) => tcx,
core::NotTyped(_) => fail!(),
};
let mut attrs = Vec::new();
csearch::get_item_attrs(&tcx.sess.cstore, self.def_id, |v| {
attrs.extend(v.move_iter().map(|i| i.clean()));
});
let (self_, sig) = match self.explicit_self {
ast::SelfStatic => (ast::SelfStatic.clean(), self.fty.sig.clone()),
s => {
@ -861,7 +858,7 @@ impl Clean<TraitMethod> for ty::Method {
name: Some(self.ident.clean()),
visibility: Some(ast::Inherited),
def_id: self.def_id,
attrs: attrs,
attrs: load_attrs(tcx, self.def_id),
source: Span {
filename: "".to_strbuf(),
loline: 0, locol: 0, hiline: 0, hicol: 0,
@ -1404,21 +1401,105 @@ pub struct ViewItem {
pub inner: ViewItemInner,
}
impl Clean<Item> for ast::ViewItem {
fn clean(&self) -> Item {
Item {
name: None,
attrs: self.attrs.clean().move_iter().collect(),
source: self.span.clean(),
def_id: ast_util::local_def(0),
visibility: self.vis.clean(),
inner: ViewItemItem(ViewItem {
inner: self.node.clean()
}),
impl Clean<Vec<Item>> for ast::ViewItem {
fn clean(&self) -> Vec<Item> {
let denied = self.vis != ast::Public || self.attrs.iter().any(|a| {
a.name().get() == "doc" && match a.meta_item_list() {
Some(l) => attr::contains_name(l, "noinline"),
None => false,
}
});
let convert = |node: &ast::ViewItem_| {
Item {
name: None,
attrs: self.attrs.clean().move_iter().collect(),
source: self.span.clean(),
def_id: ast_util::local_def(0),
visibility: self.vis.clean(),
inner: ViewItemItem(ViewItem { inner: node.clean() }),
}
};
let mut ret = Vec::new();
match self.node {
ast::ViewItemUse(ref path) if !denied => {
match path.node {
ast::ViewPathGlob(..) => ret.push(convert(&self.node)),
ast::ViewPathList(ref a, ref list, ref b) => {
let remaining = list.iter().filter(|path| {
match try_inline(path.node.id) {
Some(item) => { ret.push(item); false }
None => true,
}
}).map(|a| a.clone()).collect::<Vec<ast::PathListIdent>>();
if remaining.len() > 0 {
let path = ast::ViewPathList(a.clone(),
remaining,
b.clone());
let path = syntax::codemap::dummy_spanned(path);
ret.push(convert(&ast::ViewItemUse(@path)));
}
}
ast::ViewPathSimple(_, _, id) => {
match try_inline(id) {
Some(item) => ret.push(item),
None => ret.push(convert(&self.node)),
}
}
}
}
ref n => ret.push(convert(n)),
}
return ret;
}
}
fn try_inline(id: ast::NodeId) -> Option<Item> {
let cx = super::ctxtkey.get().unwrap();
let tcx = match cx.maybe_typed {
core::Typed(ref tycx) => tycx,
core::NotTyped(_) => return None,
};
let def = match tcx.def_map.borrow().find(&id) {
Some(def) => *def,
None => return None,
};
let did = ast_util::def_id_of_def(def);
if ast_util::is_local(did) { return None }
let inner = match def {
ast::DefTrait(did) => TraitItem(build_external_trait(tcx, did)),
ast::DefFn(did, style) =>
FunctionItem(build_external_function(tcx, did, style)),
_ => return None,
};
let fqn = csearch::get_item_path(tcx, did);
Some(Item {
source: Span {
filename: "".to_strbuf(), loline: 0, locol: 0, hiline: 0, hicol: 0,
},
name: Some(fqn.last().unwrap().to_str().to_strbuf()),
attrs: load_attrs(tcx, did),
inner: inner,
visibility: Some(ast::Public),
def_id: did,
})
}
fn load_attrs(tcx: &ty::ctxt, did: ast::DefId) -> Vec<Attribute> {
let mut attrs = Vec::new();
csearch::get_item_attrs(&tcx.sess.cstore, did, |v| {
attrs.extend(v.move_iter().map(|item| {
let mut a = attr::mk_attr_outer(item);
// FIXME this isn't quite always true, it's just true about 99% of
// the time when dealing with documentation
if a.name().get() == "doc" && a.value_str().is_some() {
a.node.is_sugared_doc = true;
}
a.clean()
}));
});
attrs
}
#[deriving(Clone, Encodable, Decodable)]
pub enum ViewItemInner {
ExternCrate(String, Option<String>, ast::NodeId),
@ -1654,6 +1735,20 @@ fn build_external_trait(tcx: &ty::ctxt, did: ast::DefId) -> Trait {
}
}
fn build_external_function(tcx: &ty::ctxt,
did: ast::DefId,
style: ast::FnStyle) -> Function {
let t = csearch::get_type(tcx, did);
Function {
decl: match ty::get(t.ty).sty {
ty::ty_bare_fn(ref f) => f.sig.clean(),
_ => fail!("bad function"),
},
generics: t.generics.clean(),
fn_style: style,
}
}
fn resolve_use_source(path: Path, id: ast::NodeId) -> ImportSource {
ImportSource {
path: path,

View File

@ -157,9 +157,9 @@ pub struct Cache {
// Private fields only used when initially crawling a crate to build a cache
stack: Vec<String> ,
parent_stack: Vec<ast::NodeId> ,
search_index: Vec<IndexItem> ,
stack: Vec<String>,
parent_stack: Vec<ast::DefId>,
search_index: Vec<IndexItem>,
privmod: bool,
public_items: NodeSet,
@ -198,7 +198,7 @@ struct IndexItem {
name: String,
path: String,
desc: String,
parent: Option<ast::NodeId>,
parent: Option<ast::DefId>,
}
// TLS keys used to carry information around during rendering.
@ -302,7 +302,7 @@ pub fn run(mut krate: clean::Crate, dst: Path) -> io::IoResult<()> {
path: fqp.slice_to(fqp.len() - 1).connect("::")
.to_strbuf(),
desc: shorter(item.doc_value()).to_strbuf(),
parent: Some(pid),
parent: Some(did),
});
},
None => {}
@ -360,9 +360,8 @@ pub fn run(mut krate: clean::Crate, dst: Path) -> io::IoResult<()> {
try!(write!(&mut w, r#"],"paths":["#));
for (i, &nodeid) in pathid_to_nodeid.iter().enumerate() {
let def = ast_util::local_def(nodeid);
let &(ref fqp, short) = cache.paths.find(&def).unwrap();
for (i, &did) in pathid_to_nodeid.iter().enumerate() {
let &(ref fqp, short) = cache.paths.find(&did).unwrap();
if i > 0 {
try!(write!(&mut w, ","));
}
@ -730,14 +729,13 @@ impl DocFolder for Cache {
clean::VariantItem(..) => {
(Some(*self.parent_stack.last().unwrap()),
Some(self.stack.slice_to(self.stack.len() - 1)))
}
clean::MethodItem(..) => {
if self.parent_stack.len() == 0 {
(None, None)
} else {
let last = self.parent_stack.last().unwrap();
let did = ast_util::local_def(*last);
let did = *last;
let path = match self.paths.find(&did) {
Some(&(_, item_type::Trait)) =>
Some(self.stack.slice_to(self.stack.len() - 1)),
@ -766,9 +764,11 @@ impl DocFolder for Cache {
});
}
(Some(parent), None) if !self.privmod => {
// We have a parent, but we don't know where they're
// defined yet. Wait for later to index this item.
self.orphan_methods.push((parent, item.clone()))
if ast_util::is_local(parent) {
// We have a parent, but we don't know where they're
// defined yet. Wait for later to index this item.
self.orphan_methods.push((parent.node, item.clone()))
}
}
_ => {}
}
@ -789,19 +789,17 @@ impl DocFolder for Cache {
clean::TypedefItem(..) | clean::TraitItem(..) |
clean::FunctionItem(..) | clean::ModuleItem(..) |
clean::ForeignFunctionItem(..) => {
if ast_util::is_local(item.def_id) {
// Reexported items mean that the same id can show up twice
// in the rustdoc ast that we're looking at. We know,
// however, that a reexported item doesn't show up in the
// `public_items` map, so we can skip inserting into the
// paths map if there was already an entry present and we're
// not a public item.
let id = item.def_id.node;
if !self.paths.contains_key(&item.def_id) ||
self.public_items.contains(&id) {
self.paths.insert(item.def_id,
(self.stack.clone(), shortty(&item)));
}
// Reexported items mean that the same id can show up twice
// in the rustdoc ast that we're looking at. We know,
// however, that a reexported item doesn't show up in the
// `public_items` map, so we can skip inserting into the
// paths map if there was already an entry present and we're
// not a public item.
let id = item.def_id.node;
if !self.paths.contains_key(&item.def_id) ||
self.public_items.contains(&id) {
self.paths.insert(item.def_id,
(self.stack.clone(), shortty(&item)));
}
}
// link variants to their parent enum because pages aren't emitted
@ -817,20 +815,14 @@ impl DocFolder for Cache {
// Maintain the parent stack
let parent_pushed = match item.inner {
clean::TraitItem(..) | clean::EnumItem(..) | clean::StructItem(..) => {
if ast_util::is_local(item.def_id) {
self.parent_stack.push(item.def_id.node);
}
self.parent_stack.push(item.def_id);
true
}
clean::ImplItem(ref i) => {
match i.for_ {
clean::ResolvedPath{ did, .. } => {
if ast_util::is_local(did) {
self.parent_stack.push(did.node);
true
} else {
false
}
self.parent_stack.push(did);
true
}
_ => false
}