Use doc comments from 'pub use' statements

Split off from #62855

Currently, rustdoc ignores any doc comments found on 'pub use'
statements. As described in issue #58700, this makes it impossible to
properly document procedural macros. Any doc comments must be written on
the procedural macro definition, which must occur in a dedicated
proc-macro crate. This means that any doc comments or doc tests cannot
reference items defined in re-exporting crate, despite the fact that
such items may be required to use the procedural macro.

To solve this issue, this commit allows doc comments to be written on
'pub use' statements. For consistency, this applies to *all* 'pub use'
statements, not just those importing procedural macros.

When inlining documentation, documentation on 'pub use' statements will
be prepended to the documentation of the inlined item. For example,
the following items:

```rust

mod other_mod {
    /// Doc comment from definition
    pub struct MyStruct;
}

/// Doc comment from 'pub use'
///
pub use other_mod::MyStruct;
```

will caues the documentation for the re-export of 'MyStruct' to be
rendered as:

```
Doc comment from 'pub use'
Doc comment from definition
```

Note the empty line in the 'pub use' doc comments - because doc comments
are concatenated as-is, this ensure that the doc comments on the
definition start on a new line.
This commit is contained in:
Aaron Hill 2019-07-27 14:18:50 -04:00
parent 0e9b465d72
commit 7ee9b7a410
No known key found for this signature in database
GPG Key ID: B4087E510E98B164
5 changed files with 73 additions and 24 deletions

View File

@ -24,6 +24,8 @@ use crate::clean::{
use super::Clean;
type Attrs<'hir> = rustc::ty::Attributes<'hir>;
/// Attempt to inline a definition into this AST.
///
/// This function will fetch the definition specified, and if it is
@ -40,6 +42,7 @@ pub fn try_inline(
cx: &DocContext<'_>,
res: Res,
name: ast::Name,
attrs: Option<Attrs<'_>>,
visited: &mut FxHashSet<DefId>
) -> Option<Vec<clean::Item>> {
let did = if let Some(did) = res.opt_def_id() {
@ -49,10 +52,13 @@ pub fn try_inline(
};
if did.is_local() { return None }
let mut ret = Vec::new();
let attrs_clone = attrs.clone();
let inner = match res {
Res::Def(DefKind::Trait, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Trait);
ret.extend(build_impls(cx, did));
ret.extend(build_impls(cx, did, attrs));
clean::TraitItem(build_external_trait(cx, did))
}
Res::Def(DefKind::Fn, did) => {
@ -61,27 +67,27 @@ pub fn try_inline(
}
Res::Def(DefKind::Struct, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Struct);
ret.extend(build_impls(cx, did));
ret.extend(build_impls(cx, did, attrs));
clean::StructItem(build_struct(cx, did))
}
Res::Def(DefKind::Union, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Union);
ret.extend(build_impls(cx, did));
ret.extend(build_impls(cx, did, attrs));
clean::UnionItem(build_union(cx, did))
}
Res::Def(DefKind::TyAlias, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Typedef);
ret.extend(build_impls(cx, did));
ret.extend(build_impls(cx, did, attrs));
clean::TypedefItem(build_type_alias(cx, did), false)
}
Res::Def(DefKind::Enum, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Enum);
ret.extend(build_impls(cx, did));
ret.extend(build_impls(cx, did, attrs));
clean::EnumItem(build_enum(cx, did))
}
Res::Def(DefKind::ForeignTy, did) => {
record_extern_fqn(cx, did, clean::TypeKind::Foreign);
ret.extend(build_impls(cx, did));
ret.extend(build_impls(cx, did, attrs));
clean::ForeignTypeItem
}
// Never inline enum variants but leave them shown as re-exports.
@ -113,11 +119,15 @@ pub fn try_inline(
}
_ => return None,
};
let target_attrs = load_attrs(cx, did);
let attrs = merge_attrs(cx, target_attrs, attrs_clone);
cx.renderinfo.borrow_mut().inlined.insert(did);
ret.push(clean::Item {
source: cx.tcx.def_span(did).clean(cx),
name: Some(name.clean(cx)),
attrs: load_attrs(cx, did),
attrs,
inner,
visibility: Some(clean::Public),
stability: cx.tcx.lookup_stability(did).clean(cx),
@ -144,8 +154,8 @@ pub fn try_inline_glob(cx: &DocContext<'_>, res: Res, visited: &mut FxHashSet<De
}
}
pub fn load_attrs(cx: &DocContext<'_>, did: DefId) -> clean::Attributes {
cx.tcx.get_attrs(did).clean(cx)
pub fn load_attrs<'hir>(cx: &DocContext<'hir>, did: DefId) -> Attrs<'hir> {
cx.tcx.get_attrs(did)
}
/// Record an external fully qualified name in the external_paths cache.
@ -187,7 +197,7 @@ pub fn build_external_trait(cx: &DocContext<'_>, did: DefId) -> clean::Trait {
let generics = (cx.tcx.generics_of(did), &predicates).clean(cx);
let generics = filter_non_trait_generics(did, generics);
let (generics, supertrait_bounds) = separate_supertrait_bounds(generics);
let is_spotlight = load_attrs(cx, did).has_doc_flag(sym::spotlight);
let is_spotlight = load_attrs(cx, did).clean(cx).has_doc_flag(sym::spotlight);
let is_auto = cx.tcx.trait_is_auto(did);
clean::Trait {
auto: auto_trait,
@ -274,23 +284,41 @@ fn build_type_alias(cx: &DocContext<'_>, did: DefId) -> clean::Typedef {
}
}
pub fn build_impls(cx: &DocContext<'_>, did: DefId) -> Vec<clean::Item> {
pub fn build_impls(cx: &DocContext<'_>, did: DefId, attrs: Option<Attrs<'_>>) -> Vec<clean::Item> {
let tcx = cx.tcx;
let mut impls = Vec::new();
for &did in tcx.inherent_impls(did).iter() {
build_impl(cx, did, &mut impls);
build_impl(cx, did, attrs.clone(), &mut impls);
}
impls
}
pub fn build_impl(cx: &DocContext<'_>, did: DefId, ret: &mut Vec<clean::Item>) {
fn merge_attrs(cx: &DocContext<'_>, attrs: Attrs<'_>, other_attrs: Option<Attrs<'_>>
) -> clean::Attributes {
let mut merged_attrs: Vec<ast::Attribute> = Vec::with_capacity(attrs.len());
// If we have additional attributes (from a re-export),
// always insert them first. This ensure that re-export
// doc comments show up before the original doc comments
// when we render them.
if let Some(a) = other_attrs {
merged_attrs.extend(a.iter().cloned());
}
merged_attrs.extend(attrs.to_vec());
merged_attrs.clean(cx)
}
pub fn build_impl(cx: &DocContext<'_>, did: DefId, attrs: Option<Attrs<'_>>,
ret: &mut Vec<clean::Item>
) {
if !cx.renderinfo.borrow_mut().inlined.insert(did) {
return
}
let attrs = load_attrs(cx, did);
let attrs = merge_attrs(cx, load_attrs(cx, did), attrs);
let tcx = cx.tcx;
let associated_trait = tcx.impl_trait_ref(did);
@ -416,7 +444,7 @@ fn build_module(
let def_id = item.res.def_id();
if item.vis == ty::Visibility::Public {
if did == def_id || !visited.insert(def_id) { continue }
if let Some(i) = try_inline(cx, item.res, item.ident.name, visited) {
if let Some(i) = try_inline(cx, item.res, item.ident.name, None, visited) {
items.extend(i)
}
}

View File

@ -2429,7 +2429,7 @@ impl Clean<Item> for ty::AssocItem {
stability: get_stability(cx, self.def_id),
deprecation: get_deprecation(cx, self.def_id),
def_id: self.def_id,
attrs: inline::load_attrs(cx, self.def_id),
attrs: inline::load_attrs(cx, self.def_id).clean(cx),
source: cx.tcx.def_span(self.def_id).clean(cx),
inner,
}
@ -3372,7 +3372,7 @@ impl Clean<Item> for ty::VariantDef {
};
Item {
name: Some(self.ident.clean(cx)),
attrs: inline::load_attrs(cx, self.def_id),
attrs: inline::load_attrs(cx, self.def_id).clean(cx),
source: cx.tcx.def_span(self.def_id).clean(cx),
visibility: Some(Inherited),
def_id: self.def_id,
@ -3856,7 +3856,7 @@ fn build_deref_target_impls(cx: &DocContext<'_>,
let primitive = match *target {
ResolvedPath { did, .. } if did.is_local() => continue,
ResolvedPath { did, .. } => {
ret.extend(inline::build_impls(cx, did));
ret.extend(inline::build_impls(cx, did, None));
continue
}
_ => match target.primitive_type() {
@ -3894,7 +3894,7 @@ fn build_deref_target_impls(cx: &DocContext<'_>,
};
if let Some(did) = did {
if !did.is_local() {
inline::build_impl(cx, did, ret);
inline::build_impl(cx, did, None, ret);
}
}
}
@ -3921,7 +3921,11 @@ impl Clean<Vec<Item>> for doctree::ExternCrate<'_> {
},
);
if let Some(items) = inline::try_inline(cx, res, self.name, &mut visited) {
if let Some(items) = inline::try_inline(
cx, res, self.name,
Some(rustc::ty::Attributes::Borrowed(self.attrs)),
&mut visited
) {
return items;
}
}
@ -3981,7 +3985,11 @@ impl Clean<Vec<Item>> for doctree::Import<'_> {
}
if !denied {
let mut visited = FxHashSet::default();
if let Some(items) = inline::try_inline(cx, path.res, name, &mut visited) {
if let Some(items) = inline::try_inline(
cx, path.res, name,
Some(rustc::ty::Attributes::Borrowed(self.attrs)),
&mut visited
) {
return items;
}
}

View File

@ -30,7 +30,7 @@ pub fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
for &cnum in cx.tcx.crates().iter() {
for &did in cx.tcx.all_trait_implementations(cnum).iter() {
inline::build_impl(cx, did, &mut new_items);
inline::build_impl(cx, did, None, &mut new_items);
}
}
@ -66,7 +66,7 @@ pub fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
for def_id in primitive_impls.iter().filter_map(|&def_id| def_id) {
if !def_id.is_local() {
inline::build_impl(cx, def_id, &mut new_items);
inline::build_impl(cx, def_id, None, &mut new_items);
// FIXME(eddyb) is this `doc(hidden)` check needed?
if !cx.tcx.get_attrs(def_id).lists(sym::doc).has_word(sym::hidden) {
@ -119,7 +119,7 @@ pub fn collect_trait_impls(krate: Crate, cx: &DocContext<'_>) -> Crate {
for &trait_did in cx.all_traits.iter() {
for &impl_node in cx.tcx.hir().trait_impls(trait_did) {
let impl_did = cx.tcx.hir().local_def_id(impl_node);
inline::build_impl(cx, impl_did, &mut new_items);
inline::build_impl(cx, impl_did, None, &mut new_items);
}
}

View File

@ -0,0 +1,9 @@
// aux-build:add-docs.rs
extern crate inner;
// @has add_docs/struct.MyStruct.html
// @has add_docs/struct.MyStruct.html "Doc comment from 'pub use', Doc comment from definition"
/// Doc comment from 'pub use',
pub use inner::MyStruct;

View File

@ -0,0 +1,4 @@
#![crate_name = "inner"]
/// Doc comment from definition
pub struct MyStruct;