Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reintroduce spotlight/"important traits" feature #74111

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/doc/rustdoc/src/unstable-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,27 @@ Book][unstable-doc-cfg] and [its tracking issue][issue-doc-cfg].
[unstable-doc-cfg]: ../unstable-book/language-features/doc-cfg.html
[issue-doc-cfg]: https://github.com/rust-lang/rust/issues/43781

### Adding your trait to the "Important Traits" dialog

Rustdoc keeps a list of a few traits that are believed to be "fundamental" to a given type when
implemented on it. These traits are intended to be the primary interface for their types, and are
often the only thing available to be documented on their types. For this reason, Rustdoc will track
when a given type implements one of these traits and call special attention to it when a function
returns one of these types. This is the "Important Traits" dialog, visible as a circle-i button next
to the function, which, when clicked, shows the dialog.

In the standard library, the traits that qualify for inclusion are `Iterator`, `io::Read`, and
`io::Write`. However, rather than being implemented as a hard-coded list, these traits have a
special marker attribute on them: `#[doc(spotlight)]`. This means that you could apply this
attribute to your own trait to include it in the "Important Traits" dialog in documentation.

The `#[doc(spotlight)]` attribute currently requires the `#![feature(doc_spotlight)]` feature gate.
For more information, see [its chapter in the Unstable Book][unstable-spotlight] and [its tracking
issue][issue-spotlight].

[unstable-spotlight]: ../unstable-book/language-features/doc-spotlight.html
[issue-spotlight]: https://github.com/rust-lang/rust/issues/45040

### Exclude certain dependencies from documentation

The standard library uses several dependencies which, in turn, use several types and traits from the
Expand Down
30 changes: 30 additions & 0 deletions src/doc/unstable-book/src/language-features/doc-spotlight.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# `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
1 change: 1 addition & 0 deletions src/libcore/future/future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::task::{Context, Poll};
/// `.await` the value.
///
/// [`Waker`]: ../task/struct.Waker.html
#[doc(spotlight)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[stable(feature = "futures_api", since = "1.36.0")]
#[lang = "future_trait"]
Expand Down
1 change: 1 addition & 0 deletions src/libcore/iter/traits/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ fn _assert_is_object_safe(_: &dyn Iterator<Item = ()>) {}
label = "`{Self}` is not an iterator",
message = "`{Self}` is not an iterator"
)]
#[doc(spotlight)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub trait Iterator {
/// The type of the elements being iterated over.
Expand Down
1 change: 1 addition & 0 deletions src/libcore/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
#![feature(custom_inner_attributes)]
#![feature(decl_macro)]
#![feature(doc_cfg)]
#![cfg_attr(not(bootstrap), feature(doc_spotlight))]
#![feature(extern_types)]
#![feature(fundamental)]
#![feature(intrinsics)]
Expand Down
1 change: 1 addition & 0 deletions src/librustc_ast_passes/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
include => external_doc
cfg => doc_cfg
masked => doc_masked
spotlight => doc_spotlight
alias => doc_alias
keyword => doc_keyword
);
Expand Down
3 changes: 3 additions & 0 deletions src/librustc_feature/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,9 @@ declare_features! (
/// Allows `#[doc(masked)]`.
(active, doc_masked, "1.21.0", Some(44027), None),

/// Allows `#[doc(spotlight)]`.
(active, doc_spotlight, "1.22.0", Some(45040), None),

/// Allows `#[doc(include = "some-file")]`.
(active, external_doc, "1.22.0", Some(44732), None),

Expand Down
2 changes: 2 additions & 0 deletions src/librustc_span/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ symbols! {
doc_cfg,
doc_keyword,
doc_masked,
doc_spotlight,
doctest,
document_private_items,
dotdoteq_in_patterns,
Expand Down Expand Up @@ -797,6 +798,7 @@ symbols! {
Some,
specialization,
speed,
spotlight,
sqrtf32,
sqrtf64,
sse4a_target_feature,
Expand Down
4 changes: 3 additions & 1 deletion src/librustdoc/clean/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use rustc_metadata::creader::LoadedMacro;
use rustc_middle::ty;
use rustc_mir::const_eval::is_min_const_fn;
use rustc_span::hygiene::MacroKind;
use rustc_span::symbol::Symbol;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span;

use crate::clean::{self, GetDefId, ToSource, TypeKind};
Expand Down Expand Up @@ -194,13 +194,15 @@ 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).clean(cx).has_doc_flag(sym::spotlight);
let is_auto = cx.tcx.trait_is_auto(did);
clean::Trait {
auto: auto_trait,
unsafety: cx.tcx.trait_def(did).unsafety,
generics,
items: trait_items,
bounds: supertrait_bounds,
is_spotlight,
is_auto,
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/librustdoc/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,7 @@ impl Clean<FnRetTy> for hir::FnRetTy<'_> {
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(sym::spotlight);
Item {
name: Some(self.name.clean(cx)),
attrs,
Expand All @@ -1021,6 +1022,7 @@ impl Clean<Item> for doctree::Trait<'_> {
items: self.items.iter().map(|ti| ti.clean(cx)).collect(),
generics: self.generics.clean(cx),
bounds: self.bounds.clean(cx),
is_spotlight,
is_auto: self.is_auto.clean(cx),
}),
}
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,7 @@ pub struct Trait {
pub items: Vec<Item>,
pub generics: Generics,
pub bounds: Vec<GenericBound>,
pub is_spotlight: bool,
pub is_auto: bool,
}

Expand Down
12 changes: 12 additions & 0 deletions src/librustdoc/html/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,22 @@ impl Buffer {
Buffer { for_html: false, buffer: String::new() }
}

crate fn is_empty(&self) -> bool {
self.buffer.is_empty()
}

crate fn into_inner(self) -> String {
self.buffer
}

crate fn insert_str(&mut self, idx: usize, s: &str) {
self.buffer.insert_str(idx, s);
}

crate fn push_str(&mut self, s: &str) {
self.buffer.push_str(s);
}

// Intended for consumption by write! and writeln! (std::fmt) but without
// the fmt::Result return type imposed by fmt::Write (and avoiding the trait
// import).
Expand Down
71 changes: 67 additions & 4 deletions src/librustdoc/html/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2389,7 +2389,7 @@ fn item_function(w: &mut Buffer, cx: &Context, it: &clean::Item, f: &clean::Func
write!(
w,
"{vis}{constness}{asyncness}{unsafety}{abi}fn \
{name}{generics}{decl}{where_clause}</pre>",
{name}{generics}{decl}{spotlight}{where_clause}</pre>",
vis = it.visibility.print_with_space(),
constness = f.header.constness.print_with_space(),
asyncness = f.header.asyncness.print_with_space(),
Expand All @@ -2399,7 +2399,8 @@ fn item_function(w: &mut Buffer, cx: &Context, it: &clean::Item, f: &clean::Func
generics = f.generics.print(),
where_clause = WhereClause { gens: &f.generics, indent: 0, end_newline: true },
decl = Function { decl: &f.decl, header_len, indent: 0, asyncness: f.header.asyncness }
.print()
.print(),
spotlight = spotlight_decl(&f.decl),
);
document(w, cx, it)
}
Expand Down Expand Up @@ -2587,7 +2588,12 @@ fn item_trait(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::Trait)
let item_type = m.type_();
let id = cx.derive_id(format!("{}.{}", item_type, name));
let ns_id = cx.derive_id(format!("{}.{}", name, item_type.name_space()));
write!(w, "<h3 id='{id}' class='method'><code id='{ns_id}'>", id = id, ns_id = ns_id);
write!(
w,
"<h3 id='{id}' class='method'><code id='{ns_id}'>",
id = id,
ns_id = ns_id
);
render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl);
write!(w, "</code>");
render_stability_since(w, m, t);
Expand Down Expand Up @@ -2901,7 +2907,7 @@ fn render_assoc_item(
write!(
w,
"{}{}{}{}{}{}{}fn <a href='{href}' class='fnname'>{name}</a>\
{generics}{decl}{where_clause}",
{generics}{decl}{spotlight}{where_clause}",
if parent == ItemType::Trait { " " } else { "" },
meth.visibility.print_with_space(),
header.constness.print_with_space(),
Expand All @@ -2913,6 +2919,7 @@ fn render_assoc_item(
name = name,
generics = g.print(),
decl = Function { decl: d, header_len, indent, asyncness: header.asyncness }.print(),
spotlight = spotlight_decl(&d),
where_clause = WhereClause { gens: g, indent, end_newline }
)
}
Expand Down Expand Up @@ -3550,6 +3557,62 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool) -> bool {
}
}

fn spotlight_decl(decl: &clean::FnDecl) -> String {
let mut out = Buffer::html();
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().map_or(false, |d| c.traits[&d].is_spotlight) {
if out.is_empty() {
out.push_str(&format!(
"<h3 class=\"important\">Important traits for {}</h3>\
<code class=\"content\">",
impl_.for_.print()
));
trait_.push_str(&impl_.for_.print().to_string());
}

//use the "where" class here to make it small
out.push_str(&format!(
"<span class=\"where fmt-newline\">{}</span>",
impl_.print()
));
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,
&[],
Some(&tydef.type_),
AssocItemLink::GotoSource(t_did, &FxHashSet::default()),
"",
);
out.push_str(";</span>");
}
}
}
}
}
}

if !out.is_empty() {
out.insert_str(
0,
"<span class=\"important-traits\"><span class=\"important-traits-tooltip\">ⓘ<div class='important-traits-tooltiptext'><span class=\"docblock\">"

);
out.push_str("</code></span></div></span></span>");
}

out.into_inner()
}

fn render_impl(
w: &mut Buffer,
cx: &Context,
Expand Down
13 changes: 13 additions & 0 deletions src/librustdoc/html/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ function defocusSearchBar() {
function handleEscape(ev) {
var help = getHelpElement();
var search = getSearchElement();
hideModal();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you add the hideModal calls BTW?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't intend to. Will fix.

if (hasClass(help, "hidden") === false) {
displayHelp(false, ev, help);
} else if (hasClass(search, "hidden") === false) {
Expand Down Expand Up @@ -395,6 +396,7 @@ function defocusSearchBar() {
case "s":
case "S":
displayHelp(false, ev);
hideModal();
ev.preventDefault();
focusSearchBar();
break;
Expand All @@ -407,6 +409,7 @@ function defocusSearchBar() {

case "?":
if (ev.shiftKey) {
hideModal();
displayHelp(true, ev);
}
break;
Expand Down Expand Up @@ -2621,6 +2624,16 @@ function defocusSearchBar() {
});
}());

function showImportantTraits(content) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this function is never used. Please remove it.

let list = content.classList
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is list used for?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing ; too. :)

}

onEachLazy(document.getElementsByClassName("important-traits"), function(e) {
e.onclick = function() {
e.getElementsByClassName('important-traits-tooltiptext')[0].classList.toggle("force-tooltip")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to use either this or event.target in here (event would be the first argument given to onclick).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't that just be e anyway?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too confident about the variable capture process in JS so I prefer to minimize it as much as possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also missing ;.

};
});

// In the search display, allows to switch between tabs.
function printTab(nb) {
if (nb === 0 || nb === 1 || nb === 2) {
Expand Down
Loading