Generate documentation for auto-trait impls

A new section is added to both both struct and trait doc pages.

On struct/enum pages, a new 'Auto Trait Implementations' section displays any
synthetic implementations for auto traits. Currently, this is only done
for Send and Sync.

On trait pages, a new 'Auto Implementors' section displays all types
which automatically implement the trait. Effectively, this is a list of
all public types in the standard library.

Synthesized impls for a particular auto trait ('synthetic impls') take
into account generic bounds. For example, a type 'struct Foo<T>(T)' will
have 'impl<T> Send for Foo<T> where T: Send' generated for it.

Manual implementations of auto traits are also taken into account. If we have
the following types:

'struct Foo<T>(T)'
'struct Wrapper<T>(Foo<T>)'
'unsafe impl<T> Send for Wrapper<T>' // pretend that Wrapper<T> makes
this sound somehow

Then Wrapper will have the following impl generated:
'impl<T> Send for Wrapper<T>'
reflecting the fact that 'T: Send' need not hold for 'Wrapper<T>: Send'
to hold

Lifetimes, HRTBS, and projections (e.g. '<T as Iterator>::Item') are
taken into account by synthetic impls

However, if a type can *never* implement a particular auto trait
(e.g. 'struct MyStruct<T>(*const T)'), then a negative impl will be
generated (in this case, 'impl<T> !Send for MyStruct<T>')

All of this means that a user should be able to copy-paste a synthetic
impl into their code, without any observable changes in behavior
(assuming the rest of the program remains unchanged).
This commit is contained in:
Aaron Hill
2017-11-22 16:16:55 -05:00
parent 27a046e933
commit 6728f21d85
29 changed files with 2486 additions and 154 deletions

View File

@@ -37,7 +37,7 @@ pub use self::ExternalLocation::*;
use std::borrow::Cow;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashSet};
use std::collections::{BTreeMap, HashSet, VecDeque};
use std::default::Default;
use std::error;
use std::fmt::{self, Display, Formatter, Write as FmtWrite};
@@ -169,6 +169,7 @@ pub enum ExternalLocation {
Unknown,
}
/// Metadata about implementations for a type or trait.
#[derive(Clone)]
pub struct Impl {
@@ -270,6 +271,18 @@ pub struct Cache {
/// generating explicit hyperlinks to other crates.
pub external_paths: FxHashMap<DefId, (Vec<String>, ItemType)>,
/// Maps local def ids of exported types to fully qualified paths.
/// Unlike 'paths', this mapping ignores any renames that occur
/// due to 'use' statements.
///
/// This map is used when writing out the special 'implementors'
/// javascript file. By using the exact path that the type
/// is declared with, we ensure that each path will be identical
/// to the path used if the corresponding type is inlined. By
/// doing this, we can detect duplicate impls on a trait page, and only display
/// the impl for the inlined type.
pub exact_paths: FxHashMap<DefId, Vec<String>>,
/// This map contains information about all known traits of this crate.
/// Implementations of a crate should inherit the documentation of the
/// parent trait if no extra documentation is specified, and default methods
@@ -322,6 +335,7 @@ pub struct RenderInfo {
pub inlined: FxHashSet<DefId>,
pub external_paths: ::core::ExternalPaths,
pub external_typarams: FxHashMap<DefId, String>,
pub exact_paths: FxHashMap<DefId, Vec<String>>,
pub deref_trait_did: Option<DefId>,
pub deref_mut_trait_did: Option<DefId>,
pub owned_box_did: Option<DefId>,
@@ -436,7 +450,9 @@ fn init_ids() -> FxHashMap<String, usize> {
"required-methods",
"provided-methods",
"implementors",
"synthetic-implementors",
"implementors-list",
"synthetic-implementors-list",
"methods",
"deref-methods",
"implementations",
@@ -507,6 +523,7 @@ pub fn run(mut krate: clean::Crate,
themes,
};
// If user passed in `--playground-url` arg, we fill in crate name here
if let Some(url) = playground_url {
markdown::PLAYGROUND.with(|slot| {
@@ -556,6 +573,7 @@ pub fn run(mut krate: clean::Crate,
inlined: _,
external_paths,
external_typarams,
exact_paths,
deref_trait_did,
deref_mut_trait_did,
owned_box_did,
@@ -568,6 +586,7 @@ pub fn run(mut krate: clean::Crate,
let mut cache = Cache {
impls: FxHashMap(),
external_paths,
exact_paths,
paths: FxHashMap(),
implementors: FxHashMap(),
stack: Vec::new(),
@@ -873,7 +892,10 @@ themePicker.onclick = function() {{
// should add it.
if !imp.impl_item.def_id.is_local() { continue }
have_impls = true;
write!(implementors, "{},", as_json(&imp.inner_impl().to_string())).unwrap();
write!(implementors, "{{text:{},synthetic:{},types:{}}},",
as_json(&imp.inner_impl().to_string()),
imp.inner_impl().synthetic,
as_json(&collect_paths_for_type(imp.inner_impl().for_.clone()))).unwrap();
}
implementors.push_str("];");
@@ -1856,8 +1878,7 @@ fn item_module(w: &mut fmt::Formatter, cx: &Context,
item: &clean::Item, items: &[clean::Item]) -> fmt::Result {
document(w, cx, item)?;
let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped())
.collect::<Vec<usize>>();
let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::<Vec<usize>>();
// the order of item types in the listing
fn reorder(ty: ItemType) -> u8 {
@@ -2201,6 +2222,50 @@ fn item_function(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
document(w, cx, it)
}
fn render_implementor(cx: &Context, implementor: &Impl, w: &mut fmt::Formatter,
implementor_dups: &FxHashMap<&str, (DefId, bool)>) -> Result<(), fmt::Error> {
write!(w, "<li>")?;
if let Some(l) = (Item { cx, item: &implementor.impl_item }).src_href() {
write!(w, "<div class='out-of-band'>")?;
write!(w, "<a class='srclink' href='{}' title='{}'>[src]</a>",
l, "goto source code")?;
write!(w, "</div>")?;
}
write!(w, "<code>")?;
// If there's already another implementor that has the same abbridged name, use the
// full path, for example in `std::iter::ExactSizeIterator`
let use_absolute = match implementor.inner_impl().for_ {
clean::ResolvedPath { ref path, is_generic: false, .. } |
clean::BorrowedRef {
type_: box clean::ResolvedPath { ref path, is_generic: false, .. },
..
} => implementor_dups[path.last_name()].1,
_ => false,
};
fmt_impl_for_trait_page(&implementor.inner_impl(), w, use_absolute)?;
for it in &implementor.inner_impl().items {
if let clean::TypedefItem(ref tydef, _) = it.inner {
write!(w, "<span class=\"where fmt-newline\"> ")?;
assoc_type(w, it, &vec![], Some(&tydef.type_), AssocItemLink::Anchor(None))?;
write!(w, ";</span>")?;
}
}
writeln!(w, "</code></li>")?;
Ok(())
}
fn render_impls(cx: &Context, w: &mut fmt::Formatter,
traits: Vec<&&Impl>,
containing_item: &clean::Item) -> Result<(), fmt::Error> {
for i in &traits {
let did = i.trait_did().unwrap();
let assoc_link = AssocItemLink::GotoSource(did, &i.inner_impl().provided_trait_methods);
render_impl(w, cx, i, assoc_link,
RenderMode::Normal, containing_item.stable_since(), true)?;
}
Ok(())
}
fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
t: &clean::Trait) -> fmt::Result {
let mut bounds = String::new();
@@ -2380,6 +2445,14 @@ fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
</h2>
<ul class='item-list' id='implementors-list'>
";
let synthetic_impl_header = "
<h2 id='synthetic-implementors' class='small-section-header'>
Auto implementors<a href='#synthetic-implementors' class='anchor'></a>
</h2>
<ul class='item-list' id='synthetic-implementors-list'>
";
if let Some(implementors) = cache.implementors.get(&it.def_id) {
// The DefId is for the first Type found with that name. The bool is
// if any Types with the same name but different DefId have been found.
@@ -2405,6 +2478,11 @@ fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
.partition::<Vec<_>, _>(|i| i.inner_impl().for_.def_id()
.map_or(true, |d| cache.paths.contains_key(&d)));
let (synthetic, concrete) = local.iter()
.partition::<Vec<_>, _>(|i| i.inner_impl().synthetic);
if !foreign.is_empty() {
write!(w, "
<h2 id='foreign-impls' class='small-section-header'>
@@ -2422,42 +2500,48 @@ fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
}
write!(w, "{}", impl_header)?;
for implementor in local {
write!(w, "<li>")?;
if let Some(l) = (Item { cx, item: &implementor.impl_item }).src_href() {
write!(w, "<div class='out-of-band'>")?;
write!(w, "<a class='srclink' href='{}' title='{}'>[src]</a>",
l, "goto source code")?;
write!(w, "</div>")?;
}
write!(w, "<code>")?;
// If there's already another implementor that has the same abbridged name, use the
// full path, for example in `std::iter::ExactSizeIterator`
let use_absolute = match implementor.inner_impl().for_ {
clean::ResolvedPath { ref path, is_generic: false, .. } |
clean::BorrowedRef {
type_: box clean::ResolvedPath { ref path, is_generic: false, .. },
..
} => implementor_dups[path.last_name()].1,
_ => false,
};
fmt_impl_for_trait_page(&implementor.inner_impl(), w, use_absolute)?;
for it in &implementor.inner_impl().items {
if let clean::TypedefItem(ref tydef, _) = it.inner {
write!(w, "<span class=\"where fmt-newline\"> ")?;
assoc_type(w, it, &vec![], Some(&tydef.type_), AssocItemLink::Anchor(None))?;
write!(w, ";</span>")?;
}
}
writeln!(w, "</code></li>")?;
for implementor in concrete {
render_implementor(cx, implementor, w, &implementor_dups)?;
}
write!(w, "</ul>")?;
if t.auto {
write!(w, "{}", synthetic_impl_header)?;
for implementor in synthetic {
render_implementor(cx, implementor, w, &implementor_dups)?;
}
write!(w, "</ul>")?;
}
write!(w, r#"<script type="text/javascript">
window.inlined_types=new Set();"#)?;
write!(w, r#"<script type="text/javascript" async
src="{root_path}/implementors/{path}/{ty}.{name}.js">
</script>"#,
root_path = vec![".."; cx.current.len()].join("/"),
path = if it.def_id.is_local() {
cx.current.join("/")
} else {
let (ref path, _) = cache.external_paths[&it.def_id];
path[..path.len() - 1].join("/")
},
ty = it.type_().css_class(),
name = *it.name.as_ref().unwrap())?;
} else {
// even without any implementations to write in, we still want the heading and list, so the
// implementors javascript file pulled in below has somewhere to write the impls into
write!(w, "{}", impl_header)?;
write!(w, "</ul>")?;
write!(w, r#"<script type="text/javascript">window.inlined_types=new Set();</script>"#)?;
if t.auto {
write!(w, "{}", synthetic_impl_header)?;
write!(w, "</ul>")?;
}
}
write!(w, "</ul>")?;
write!(w, r#"<script type="text/javascript" async
src="{root_path}/implementors/{path}/{ty}.{name}.js">
</script>"#,
@@ -3075,17 +3159,28 @@ fn render_assoc_items(w: &mut fmt::Formatter,
}).is_some();
render_deref_methods(w, cx, impl_, containing_item, has_deref_mut)?;
}
let (synthetic, concrete) = traits
.iter()
.partition::<Vec<_>, _>(|t| t.inner_impl().synthetic);
write!(w, "
<h2 id='implementations' class='small-section-header'>
Trait Implementations<a href='#implementations' class='anchor'></a>
</h2>
<div id='implementations-list'>
")?;
for i in &traits {
let did = i.trait_did().unwrap();
let assoc_link = AssocItemLink::GotoSource(did, &i.inner_impl().provided_trait_methods);
render_impl(w, cx, i, assoc_link,
RenderMode::Normal, containing_item.stable_since(), true)?;
}
render_impls(cx, w, concrete, containing_item)?;
write!(w, "</div>")?;
write!(w, "
<h2 id='synthetic-implementations' class='small-section-header'>
Auto Trait Implementations<a href='#synthetic-implementations' class='anchor'></a>
</h2>
<div id='synthetic-implementations-list'>
")?;
render_impls(cx, w, synthetic, containing_item)?;
write!(w, "</div>")?;
}
Ok(())
}
@@ -3586,32 +3681,48 @@ fn sidebar_assoc_items(it: &clean::Item) -> String {
}
}
}
let mut links = HashSet::new();
let ret = v.iter()
.filter_map(|i| {
let is_negative_impl = is_negative_impl(i.inner_impl());
if let Some(ref i) = i.inner_impl().trait_ {
let i_display = format!("{:#}", i);
let out = Escape(&i_display);
let encoded = small_url_encode(&format!("{:#}", i));
let generated = format!("<a href=\"#impl-{}\">{}{}</a>",
encoded,
if is_negative_impl { "!" } else { "" },
out);
if !links.contains(&generated) && links.insert(generated.clone()) {
Some(generated)
let format_impls = |impls: Vec<&Impl>| {
let mut links = HashSet::new();
impls.iter()
.filter_map(|i| {
let is_negative_impl = is_negative_impl(i.inner_impl());
if let Some(ref i) = i.inner_impl().trait_ {
let i_display = format!("{:#}", i);
let out = Escape(&i_display);
let encoded = small_url_encode(&format!("{:#}", i));
let generated = format!("<a href=\"#impl-{}\">{}{}</a>",
encoded,
if is_negative_impl { "!" } else { "" },
out);
if links.insert(generated.clone()) {
Some(generated)
} else {
None
}
} else {
None
}
} else {
None
}
})
.collect::<String>();
if !ret.is_empty() {
})
.collect::<String>()
};
let (synthetic, concrete) = v
.iter()
.partition::<Vec<_>, _>(|i| i.inner_impl().synthetic);
let concrete_format = format_impls(concrete);
let synthetic_format = format_impls(synthetic);
if !concrete_format.is_empty() {
out.push_str("<a class=\"sidebar-title\" href=\"#implementations\">\
Trait Implementations</a>");
out.push_str(&format!("<div class=\"sidebar-links\">{}</div>", ret));
out.push_str(&format!("<div class=\"sidebar-links\">{}</div>", concrete_format));
}
if !synthetic_format.is_empty() {
out.push_str("<a class=\"sidebar-title\" href=\"#synthetic-implementations\">\
Auto Trait Implementations</a>");
out.push_str(&format!("<div class=\"sidebar-links\">{}</div>", synthetic_format));
}
}
}
@@ -3734,7 +3845,7 @@ fn sidebar_trait(fmt: &mut fmt::Formatter, it: &clean::Item,
if let Some(implementors) = c.implementors.get(&it.def_id) {
let res = implementors.iter()
.filter(|i| i.inner_impl().for_.def_id()
.map_or(false, |d| !c.paths.contains_key(&d)))
.map_or(false, |d| !c.paths.contains_key(&d)))
.filter_map(|i| {
match extract_for_impl_name(&i.impl_item) {
Some((ref name, ref url)) => {
@@ -3755,6 +3866,10 @@ fn sidebar_trait(fmt: &mut fmt::Formatter, it: &clean::Item,
}
sidebar.push_str("<a class=\"sidebar-title\" href=\"#implementors\">Implementors</a>");
if t.auto {
sidebar.push_str("<a class=\"sidebar-title\" \
href=\"#synthetic-implementors\">Auto Implementors</a>");
}
sidebar.push_str(&sidebar_assoc_items(it));
@@ -3969,6 +4084,66 @@ fn get_index_type(clean_type: &clean::Type) -> Type {
t
}
/// Returns a list of all paths used in the type.
/// This is used to help deduplicate imported impls
/// for reexported types. If any of the contained
/// types are re-exported, we don't use the corresponding
/// entry from the js file, as inlining will have already
/// picked up the impl
fn collect_paths_for_type(first_ty: clean::Type) -> Vec<String> {
let mut out = Vec::new();
let mut visited = FxHashSet();
let mut work = VecDeque::new();
let cache = cache();
work.push_back(first_ty);
while let Some(ty) = work.pop_front() {
if !visited.insert(ty.clone()) {
continue;
}
match ty {
clean::Type::ResolvedPath { did, .. } => {
let get_extern = || cache.external_paths.get(&did).map(|s| s.0.clone());
let fqp = cache.exact_paths.get(&did).cloned().or_else(get_extern);
match fqp {
Some(path) => {
out.push(path.join("::"));
},
_ => {}
};
},
clean::Type::Tuple(tys) => {
work.extend(tys.into_iter());
},
clean::Type::Slice(ty) => {
work.push_back(*ty);
}
clean::Type::Array(ty, _) => {
work.push_back(*ty);
},
clean::Type::Unique(ty) => {
work.push_back(*ty);
},
clean::Type::RawPointer(_, ty) => {
work.push_back(*ty);
},
clean::Type::BorrowedRef { type_, .. } => {
work.push_back(*type_);
},
clean::Type::QPath { self_type, trait_, .. } => {
work.push_back(*self_type);
work.push_back(*trait_);
},
_ => {}
}
};
out
}
fn get_index_type_name(clean_type: &clean::Type, accept_generic: bool) -> Option<String> {
match *clean_type {
clean::ResolvedPath { ref path, .. } => {