提交 421a2113 编写于 作者: B bors

Auto merge of #45039 - QuietMisdreavus:doc-spotlight, r=GuillaumeGomez,QuietMisdreavus

show in docs whether the return type of a function impls Iterator/Read/Write

Closes #25928

This PR makes it so that when rustdoc documents a function, it checks the return type to see whether it implements a handful of specific traits. If so, it will print the impl and any associated types. Rather than doing this via a whitelist within rustdoc, i chose to do this by a new `#[doc]` attribute parameter, so things like `Future` could tap into this if desired.

### Known shortcomings

~~The printing of impls currently uses the `where` class over the whole thing to shrink the font size relative to the function definition itself. Naturally, when the impl has a where clause of its own, it gets shrunken even further:~~ (This is no longer a problem because the design changed and rendered this concern moot.)

The lookup currently just looks at the top-level type, not looking inside things like Result or Option, which renders the spotlights on Read/Write a little less useful:

<details><summary>`File::{open, create}` don't have spotlight info (pic of old design)</summary>

![image](https://user-images.githubusercontent.com/5217170/31209495-e59d027e-a950-11e7-9998-ceefceb71c07.png)

</details>

All three of the initially spotlighted traits are generically implemented on `&mut` references. Rustdoc currently treats a `&mut T` reference-to-a-generic as an impl on the reference primitive itself. `&mut Self` counts as a generic in the eyes of rustdoc. All this combines to create this lovely scene on `Iterator::by_ref`:

<details><summary>`Iterator::by_ref` spotlights Iterator, Read, and Write (pic of old design)</summary>

![image](https://user-images.githubusercontent.com/5217170/31209554-50b271ca-a951-11e7-928b-4f83416c8681.png)

</details>
# `doc_spotlight`
The tracking issue for this feature is: [#45040]
The `doc_spotlight` feature allows the use of the `spotlight` parameter to the `#[doc]` attribute,
to "spotlight" a specific trait on the return values of functions. Adding a `#[doc(spotlight)]`
attribute to a trait definition will make rustdoc print extra information for functions which return
a type that implements that trait. This attribute is applied to the `Iterator`, `io::Read`, and
`io::Write` traits in the standard library.
You can do this on your own traits, like this:
```
#![feature(doc_spotlight)]
#[doc(spotlight)]
pub trait MyTrait {}
pub struct MyStruct;
impl MyTrait for MyStruct {}
/// The docs for this function will have an extra line about `MyStruct` implementing `MyTrait`,
/// without having to write that yourself!
pub fn my_fn() -> MyStruct { MyStruct }
```
This feature was originally implemented in PR [#45039].
[#45040]: https://github.com/rust-lang/rust/issues/45040
[#45039]: https://github.com/rust-lang/rust/pull/45039
......@@ -30,6 +30,7 @@ fn _assert_is_object_safe(_: &Iterator<Item=()>) {}
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_on_unimplemented = "`{Self}` is not an iterator; maybe try calling \
`.iter()` or a similar method"]
#[doc(spotlight)]
pub trait Iterator {
/// The type of the elements being iterated over.
#[stable(feature = "rust1", since = "1.0.0")]
......
......@@ -107,6 +107,24 @@
#![feature(const_unsafe_cell_new)]
#![feature(const_cell_new)]
#![feature(const_nonzero_new)]
#![cfg_attr(not(stage0), feature(doc_spotlight))]
#![cfg_attr(not(stage0), feature(const_min_value))]
#![cfg_attr(not(stage0), feature(const_max_value))]
#![cfg_attr(not(stage0), feature(const_atomic_bool_new))]
#![cfg_attr(not(stage0), feature(const_atomic_isize_new))]
#![cfg_attr(not(stage0), feature(const_atomic_usize_new))]
#![cfg_attr(not(stage0), feature(const_atomic_i8_new))]
#![cfg_attr(not(stage0), feature(const_atomic_u8_new))]
#![cfg_attr(not(stage0), feature(const_atomic_i16_new))]
#![cfg_attr(not(stage0), feature(const_atomic_u16_new))]
#![cfg_attr(not(stage0), feature(const_atomic_i32_new))]
#![cfg_attr(not(stage0), feature(const_atomic_u32_new))]
#![cfg_attr(not(stage0), feature(const_atomic_i64_new))]
#![cfg_attr(not(stage0), feature(const_atomic_u64_new))]
#![cfg_attr(not(stage0), feature(const_unsafe_cell_new))]
#![cfg_attr(not(stage0), feature(const_cell_new))]
#![cfg_attr(not(stage0), feature(const_nonzero_new))]
#[prelude_import]
#[allow(unused)]
......
......@@ -145,11 +145,13 @@ 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("spotlight");
clean::Trait {
unsafety: cx.tcx.trait_def(did).unsafety,
generics,
items: trait_items,
bounds: supertrait_bounds,
is_spotlight,
}
}
......
......@@ -151,7 +151,7 @@ fn clean(&self, cx: &DocContext) -> Crate {
match module.inner {
ModuleItem(ref module) => {
for it in &module.items {
if it.is_extern_crate() && it.attrs.has_doc_masked() {
if it.is_extern_crate() && it.attrs.has_doc_flag("masked") {
masked_crates.insert(it.def_id.krate);
}
}
......@@ -596,12 +596,12 @@ fn extract_cfg(mi: &ast::MetaItem) -> Option<&ast::MetaItem> {
None
}
pub fn has_doc_masked(&self) -> bool {
pub fn has_doc_flag(&self, flag: &str) -> bool {
for attr in &self.other_attrs {
if !attr.check_name("doc") { continue; }
if let Some(items) = attr.meta_item_list() {
if items.iter().filter_map(|i| i.meta_item()).any(|it| it.check_name("masked")) {
if items.iter().filter_map(|i| i.meta_item()).any(|it| it.check_name(flag)) {
return true;
}
}
......@@ -1331,19 +1331,31 @@ fn clean(&self, cx: &DocContext) -> FunctionRetTy {
}
}
impl GetDefId for FunctionRetTy {
fn def_id(&self) -> Option<DefId> {
match *self {
Return(ref ty) => ty.def_id(),
DefaultReturn => None,
}
}
}
#[derive(Clone, RustcEncodable, RustcDecodable, Debug)]
pub struct Trait {
pub unsafety: hir::Unsafety,
pub items: Vec<Item>,
pub generics: Generics,
pub bounds: Vec<TyParamBound>,
pub is_spotlight: bool,
}
impl Clean<Item> for doctree::Trait {
fn clean(&self, cx: &DocContext) -> Item {
let attrs = self.attrs.clean(cx);
let is_spotlight = attrs.has_doc_flag("spotlight");
Item {
name: Some(self.name.clean(cx)),
attrs: self.attrs.clean(cx),
attrs: attrs,
source: self.whence.clean(cx),
def_id: cx.tcx.hir.local_def_id(self.id),
visibility: self.vis.clean(cx),
......@@ -1354,6 +1366,7 @@ fn clean(&self, cx: &DocContext) -> Item {
items: self.items.clean(cx),
generics: self.generics.clean(cx),
bounds: self.bounds.clean(cx),
is_spotlight: is_spotlight,
}),
}
}
......
......@@ -2268,7 +2268,7 @@ fn item_function(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
AbiSpace(f.abi),
it.name.as_ref().unwrap(),
f.generics).len();
write!(w, "<pre class='rust fn'>")?;
write!(w, "{}<pre class='rust fn'>", render_spotlight_traits(it)?)?;
render_attributes(w, it)?;
write!(w, "{vis}{constness}{unsafety}{abi}fn \
{name}{generics}{decl}{where_clause}</pre>",
......@@ -2402,8 +2402,9 @@ fn trait_item(w: &mut fmt::Formatter, cx: &Context, m: &clean::Item, t: &clean::
let item_type = m.type_();
let id = derive_id(format!("{}.{}", item_type, name));
let ns_id = derive_id(format!("{}.{}", name, item_type.name_space()));
write!(w, "<h3 id='{id}' class='method'>\
write!(w, "{extra}<h3 id='{id}' class='method'>\
<span id='{ns_id}' class='invisible'><code>",
extra = render_spotlight_traits(m)?,
id = id,
ns_id = ns_id)?;
render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl)?;
......@@ -2605,10 +2606,10 @@ fn assoc_const(w: &mut fmt::Formatter,
Ok(())
}
fn assoc_type(w: &mut fmt::Formatter, it: &clean::Item,
bounds: &Vec<clean::TyParamBound>,
default: Option<&clean::Type>,
link: AssocItemLink) -> fmt::Result {
fn assoc_type<W: fmt::Write>(w: &mut W, it: &clean::Item,
bounds: &Vec<clean::TyParamBound>,
default: Option<&clean::Type>,
link: AssocItemLink) -> fmt::Result {
write!(w, "type <a href='{}' class=\"type\">{}</a>",
naive_assoc_href(it, link),
it.name.as_ref().unwrap())?;
......@@ -3239,6 +3240,69 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool) -> bool {
}
}
fn render_spotlight_traits(item: &clean::Item) -> Result<String, fmt::Error> {
let mut out = String::new();
match item.inner {
clean::FunctionItem(clean::Function { ref decl, .. }) |
clean::TyMethodItem(clean::TyMethod { ref decl, .. }) |
clean::MethodItem(clean::Method { ref decl, .. }) |
clean::ForeignFunctionItem(clean::Function { ref decl, .. }) => {
out = spotlight_decl(decl)?;
}
_ => {}
}
Ok(out)
}
fn spotlight_decl(decl: &clean::FnDecl) -> Result<String, fmt::Error> {
let mut out = String::new();
let mut trait_ = String::new();
if let Some(did) = decl.output.def_id() {
let c = cache();
if let Some(impls) = c.impls.get(&did) {
for i in impls {
let impl_ = i.inner_impl();
if impl_.trait_.def_id().and_then(|d| c.traits.get(&d))
.map_or(false, |t| t.is_spotlight) {
if out.is_empty() {
out.push_str(
&format!("<h3 class=\"important\">Important traits for {}</h3>\
<code class=\"content\">",
impl_.for_));
trait_.push_str(&format!("{}", impl_.for_));
}
//use the "where" class here to make it small
out.push_str(&format!("<span class=\"where fmt-newline\">{}</span>", impl_));
let t_did = impl_.trait_.def_id().unwrap();
for it in &impl_.items {
if let clean::TypedefItem(ref tydef, _) = it.inner {
out.push_str("<span class=\"where fmt-newline\"> ");
assoc_type(&mut out, it, &vec![],
Some(&tydef.type_),
AssocItemLink::GotoSource(t_did, &FxHashSet()))?;
out.push_str(";</span>");
}
}
}
}
}
}
if !out.is_empty() {
out.insert_str(0, &format!("<div class=\"important-traits\"><div class='tooltip'>ⓘ\
<span class='tooltiptext'>Important traits for {}</span></div>\
<div class=\"content hidden\">",
trait_));
out.push_str("</code></div></div>");
}
Ok(out)
}
fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLink,
render_mode: RenderMode, outer_version: Option<&str>,
show_def_docs: bool) -> fmt::Result {
......@@ -3280,12 +3344,14 @@ fn doc_impl_item(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item,
};
match item.inner {
clean::MethodItem(..) | clean::TyMethodItem(..) => {
clean::MethodItem(clean::Method { ref decl, .. }) |
clean::TyMethodItem(clean::TyMethod{ ref decl, .. }) => {
// Only render when the method is not static or we allow static methods
if render_method_item {
let id = derive_id(format!("{}.{}", item_type, name));
let ns_id = derive_id(format!("{}.{}", name, item_type.name_space()));
write!(w, "<h4 id='{}' class=\"{}\">", id, item_type)?;
write!(w, "{}", spotlight_decl(decl)?)?;
write!(w, "<span id='{}' class='invisible'>", ns_id)?;
write!(w, "<code>")?;
render_assoc_item(w, item, link.anchor(&id), ItemType::Impl)?;
......@@ -3332,6 +3398,7 @@ fn doc_impl_item(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item,
if render_method_item || render_mode == RenderMode::Normal {
let prefix = render_assoc_const_value(item);
if !is_default_item {
if let Some(t) = trait_ {
// The trait item may have been stripped so we might not
......
......@@ -216,6 +216,7 @@
var help = document.getElementById("help");
switch (getVirtualKey(ev)) {
case "Escape":
hideModal();
var search = document.getElementById("search");
if (!hasClass(help, "hidden")) {
displayHelp(false, ev);
......@@ -229,6 +230,7 @@
case "s":
case "S":
displayHelp(false, ev);
hideModal();
ev.preventDefault();
focusSearchBar();
break;
......@@ -241,6 +243,7 @@
case "?":
if (ev.shiftKey) {
hideModal();
displayHelp(true, ev);
}
break;
......@@ -1713,6 +1716,31 @@
}
});
function showModal(content) {
var modal = document.createElement('div');
modal.id = "important";
addClass(modal, 'modal');
modal.innerHTML = '<div class="modal-content"><div class="close" id="modal-close">✕</div>' +
'<div class="whiter"></div><span class="docblock">' + content +
'</span></div>';
document.getElementsByTagName('body')[0].appendChild(modal);
document.getElementById('modal-close').onclick = hideModal;
modal.onclick = hideModal;
}
function hideModal() {
var modal = document.getElementById("important");
if (modal) {
modal.parentNode.removeChild(modal);
}
}
onEach(document.getElementsByClassName('important-traits'), function(e) {
e.onclick = function() {
showModal(e.lastElementChild.innerHTML);
};
});
var search_input = document.getElementsByClassName("search-input")[0];
if (search_input) {
......
......@@ -89,7 +89,7 @@ h2 {
h3 {
font-size: 1.3em;
}
h1, h2, h3:not(.impl):not(.method):not(.type):not(.tymethod), h4:not(.method):not(.type):not(.tymethod):not(.associatedconstant) {
h1, h2, h3:not(.impl):not(.method):not(.type):not(.tymethod):not(.important), h4:not(.method):not(.type):not(.tymethod):not(.associatedconstant) {
font-weight: 500;
margin: 20px 0 15px 0;
padding-bottom: 6px;
......@@ -141,9 +141,12 @@ code, pre {
border-radius: 3px;
padding: 0 0.2em;
}
.docblock pre code, .docblock-short pre code {
.docblock pre code, .docblock-short pre code, .docblock code.spotlight {
padding: 0;
}
.docblock code.spotlight :last-child {
padding-bottom: 0.6em;
}
pre {
padding: 14px;
}
......@@ -435,10 +438,11 @@ h4 > code, h3 > code, .invisible > code {
font-size: 0.8em;
}
.content .methods > div { margin-left: 40px; }
.content .methods > div:not(.important-traits) { margin-left: 40px; }
.content .impl-items .docblock, .content .impl-items .stability {
margin-left: 40px;
margin-bottom: .6em;
}
.content .impl-items .method, .content .impl-items > .type, .impl-items > .associatedconstant {
margin-left: 20px;
......@@ -951,3 +955,102 @@ pre.rust {
color: #888;
font-size: 16px;
}
.important-traits {
cursor: pointer;
z-index: 2;
}
h4 > .important-traits {
position: absolute;
left: -44px;
top: 2px;
}
.modal {
position: fixed;
width: 100vw;
height: 100vh;
background-color: rgba(0,0,0,0.3);
z-index: 10000;
top: 0;
left: 0;
}
.modal-content {
display: block;
max-width: 60%;
min-width: 200px;
background-color: #eee;
padding: 8px;
top: 40%;
position: absolute;
left: 50%;
transform: translate(-50%, -40%);
border: 1px solid #999;
border-radius: 4px;
border-top-right-radius: 0;
}
.modal-content > .docblock {
margin: 0;
}
h3.important {
margin: 0;
margin-bottom: 13px;
font-size: 19px;
}
.modal-content > .docblock > code.content {
margin: 0;
padding: 0;
font-size: 20px;
}
.modal-content > .close {
position: absolute;
font-weight: 900;
right: -25px;
top: -1px;
font-size: 18px;
background-color: #eee;
width: 25px;
padding-right: 2px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
text-align: center;
border: 1px solid #999;
border-right: 0;
cursor: pointer;
}
.modal-content > .close:hover {
background-color: #ff1f1f;
color: white;
}
.modal-content > .whiter {
height: 25px;
position: absolute;
width: 3px;
background-color: #eee;
right: -2px;
top: 0px;
}
.modal-content > .close:hover + .whiter {
background-color: #ff1f1f;
}
#main > div.important-traits {
position: absolute;
left: -24px;
margin-top: 16px;
}
.content > .methods > div.important-traits {
position: absolute;
left: -42px;
margin-top: 2px;
}
\ No newline at end of file
......@@ -470,6 +470,7 @@ fn read_to_end<R: Read + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> Result<usize>
/// [`&[u8]`]: primitive.slice.html
///
#[stable(feature = "rust1", since = "1.0.0")]
#[doc(spotlight)]
pub trait Read {
/// Pull some bytes from this source into the specified buffer, returning
/// how many bytes were read.
......@@ -988,6 +989,7 @@ pub fn initialize(&self, buf: &mut [u8]) {
/// # }
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[doc(spotlight)]
pub trait Write {
/// Write a buffer into this object, returning how many bytes were written.
///
......
......@@ -329,6 +329,7 @@
#![feature(vec_push_all)]
#![feature(doc_cfg)]
#![feature(doc_masked)]
#![feature(doc_spotlight)]
#![cfg_attr(test, feature(update_panic_count))]
#![cfg_attr(windows, feature(const_atomic_ptr_new))]
......
......@@ -381,6 +381,8 @@ pub fn new() -> Features {
(active, doc_cfg, "1.21.0", Some(43781)),
// #[doc(masked)]
(active, doc_masked, "1.21.0", Some(44027)),
// #[doc(spotlight)]
(active, doc_spotlight, "1.22.0", Some(45040)),
// allow `#[must_use]` on functions and comparison operators (RFC 1940)
(active, fn_must_use, "1.21.0", Some(43302)),
......@@ -1300,6 +1302,10 @@ fn visit_attribute(&mut self, attr: &ast::Attribute) {
gate_feature_post!(&self, doc_masked, attr.span,
"#[doc(masked)] is experimental"
);
} else if content.iter().any(|c| c.check_name("spotlight")) {
gate_feature_post!(&self, doc_spotlight, attr.span,
"#[doc(spotlight)] is experimental"
);
}
}
}
......
// Copyright 2017 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.
#[doc(spotlight)] //~ ERROR: #[doc(spotlight)] is experimental
trait SomeTrait {}
fn main() {}
// Copyright 2017 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.
#![feature(doc_spotlight)]
pub struct Wrapper<T> {
inner: T,
}
impl<T: SomeTrait> SomeTrait for Wrapper<T> {}
#[doc(spotlight)]
pub trait SomeTrait {
// @has doc_spotlight/trait.SomeTrait.html
// @has - '//code[@class="content"]' 'impl<T: SomeTrait> SomeTrait for Wrapper<T>'
fn wrap_me(self) -> Wrapper<Self> where Self: Sized {
Wrapper {
inner: self,
}
}
}
pub struct SomeStruct;
impl SomeTrait for SomeStruct {}
impl SomeStruct {
// @has doc_spotlight/struct.SomeStruct.html
// @has - '//code[@class="content"]' 'impl SomeTrait for SomeStruct'
// @has - '//code[@class="content"]' 'impl<T: SomeTrait> SomeTrait for Wrapper<T>'
pub fn new() -> SomeStruct {
SomeStruct
}
}
// @has doc_spotlight/fn.bare_fn.html
// @has - '//code[@class="content"]' 'impl SomeTrait for SomeStruct'
pub fn bare_fn() -> SomeStruct {
SomeStruct
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册