about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2017-11-21 03:03:28 +0000
committerbors <bors@rust-lang.org>2017-11-21 03:03:28 +0000
commit421a2113a840aa5801230e7226fc9628100072ef (patch)
tree17e45188fe0fcdd5ffcb838a33a200562528ab67
parent1e44fee88d48e9f7844c6d316ae8540db19dc9fa (diff)
parent6047a0365976cbf7ad2991e6d88e4adf2fc6d0f4 (diff)
downloadrust-421a2113a840aa5801230e7226fc9628100072ef.tar.gz
rust-421a2113a840aa5801230e7226fc9628100072ef.zip
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>
-rw-r--r--src/doc/unstable-book/src/language-features/doc-spotlight.md30
-rw-r--r--src/libcore/iter/iterator.rs1
-rw-r--r--src/libcore/lib.rs18
-rw-r--r--src/librustdoc/clean/inline.rs2
-rw-r--r--src/librustdoc/clean/mod.rs21
-rw-r--r--src/librustdoc/html/render.rs81
-rw-r--r--src/librustdoc/html/static/main.js28
-rw-r--r--src/librustdoc/html/static/rustdoc.css109
-rw-r--r--src/libstd/io/mod.rs2
-rw-r--r--src/libstd/lib.rs1
-rw-r--r--src/libsyntax/feature_gate.rs6
-rw-r--r--src/test/compile-fail/feature-gate-doc_spotlight.rs14
-rw-r--r--src/test/rustdoc/doc-spotlight.rs46
13 files changed, 345 insertions, 14 deletions
diff --git a/src/doc/unstable-book/src/language-features/doc-spotlight.md b/src/doc/unstable-book/src/language-features/doc-spotlight.md
new file mode 100644
index 00000000000..8117755fef1
--- /dev/null
+++ b/src/doc/unstable-book/src/language-features/doc-spotlight.md
@@ -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
diff --git a/src/libcore/iter/iterator.rs b/src/libcore/iter/iterator.rs
index 40298389c1a..7f6d627536d 100644
--- a/src/libcore/iter/iterator.rs
+++ b/src/libcore/iter/iterator.rs
@@ -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")]
diff --git a/src/libcore/lib.rs b/src/libcore/lib.rs
index 4a57417e86a..631b9f98589 100644
--- a/src/libcore/lib.rs
+++ b/src/libcore/lib.rs
@@ -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)]
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 4c518167e08..820c3e856db 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -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,
     }
 }
 
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 1d107c169b0..69226239c96 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -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,
             }),
         }
     }
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index e79a63fb8d8..8370f805828 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -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
diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js
index a3957ccdcb3..34f649aa2d3 100644
--- a/src/librustdoc/html/static/main.js
+++ b/src/librustdoc/html/static/main.js
@@ -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) {
diff --git a/src/librustdoc/html/static/rustdoc.css b/src/librustdoc/html/static/rustdoc.css
index 55acc575152..d7f4674908c 100644
--- a/src/librustdoc/html/static/rustdoc.css
+++ b/src/librustdoc/html/static/rustdoc.css
@@ -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
diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs
index 62313d7d3a6..ff952122d3d 100644
--- a/src/libstd/io/mod.rs
+++ b/src/libstd/io/mod.rs
@@ -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 @@ impl Initializer {
 /// # }
 /// ```
 #[stable(feature = "rust1", since = "1.0.0")]
+#[doc(spotlight)]
 pub trait Write {
     /// Write a buffer into this object, returning how many bytes were written.
     ///
diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs
index 37cc7a49b52..ccc89ccdcf4 100644
--- a/src/libstd/lib.rs
+++ b/src/libstd/lib.rs
@@ -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))]
 
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index ebe7853b8ab..5c7450baa2f 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -381,6 +381,8 @@ declare_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 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
                     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"
+                    );
                 }
             }
         }
diff --git a/src/test/compile-fail/feature-gate-doc_spotlight.rs b/src/test/compile-fail/feature-gate-doc_spotlight.rs
new file mode 100644
index 00000000000..6369358538d
--- /dev/null
+++ b/src/test/compile-fail/feature-gate-doc_spotlight.rs
@@ -0,0 +1,14 @@
+// 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() {}
diff --git a/src/test/rustdoc/doc-spotlight.rs b/src/test/rustdoc/doc-spotlight.rs
new file mode 100644
index 00000000000..a570aa2d398
--- /dev/null
+++ b/src/test/rustdoc/doc-spotlight.rs
@@ -0,0 +1,46 @@
+// 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
+}