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>  </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>  </details>
This commit is contained in:
commit
421a2113a8
13 changed files with 345 additions and 14 deletions
|
|
@ -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 @@ impl<'a, 'tcx> Clean<Crate> for visit_ast::RustdocVisitor<'a, 'tcx> {
|
|||
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 @@ impl Attributes {
|
|||
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 @@ impl Clean<FunctionRetTy> for hir::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 @@ impl Clean<Item> for doctree::Trait {
|
|||
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 item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item,
|
|||
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 render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi
|
|||
};
|
||||
|
||||
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 render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLi
|
|||
|
||||
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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue