about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorManish Goregaokar <manishsmail@gmail.com>2020-07-06 12:53:44 -0700
committerManish Goregaokar <manishsmail@gmail.com>2020-07-16 09:58:17 -0700
commit98450757e5fa18ee0be9213d2830c9363b0f5fd3 (patch)
tree9ad6b743aa1994062558a71568569cce2664b033 /src
parent6ee1b62c811a6eb68d6db6dfb91f66a49956749b (diff)
downloadrust-98450757e5fa18ee0be9213d2830c9363b0f5fd3.tar.gz
rust-98450757e5fa18ee0be9213d2830c9363b0f5fd3.zip
Revert "Remove "important traits" feature"
This reverts commit 1244ced9580b942926afc06815e0691cf3f4a846.
Diffstat (limited to 'src')
-rw-r--r--src/doc/rustdoc/src/unstable-features.md21
-rw-r--r--src/doc/unstable-book/src/language-features/doc-spotlight.md30
-rw-r--r--src/librustc_feature/active.rs3
-rw-r--r--src/librustdoc/clean/inline.rs4
-rw-r--r--src/librustdoc/clean/mod.rs2
-rw-r--r--src/librustdoc/clean/types.rs1
-rw-r--r--src/librustdoc/html/format.rs12
-rw-r--r--src/librustdoc/html/render.rs87
-rw-r--r--src/librustdoc/html/static/main.js28
-rw-r--r--src/librustdoc/html/static/rustdoc.css96
-rw-r--r--src/librustdoc/html/static/themes/dark.css33
-rw-r--r--src/librustdoc/html/static/themes/light.css33
-rw-r--r--src/test/rustdoc/doc-spotlight.rs36
-rw-r--r--src/test/ui/feature-gates/feature-gate-doc_spotlight.rs4
-rw-r--r--src/test/ui/feature-gates/feature-gate-doc_spotlight.stderr12
15 files changed, 393 insertions, 9 deletions
diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index eea674f2b84..d16c2a9d034 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -150,6 +150,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
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/librustc_feature/active.rs b/src/librustc_feature/active.rs
index 0da3693af4f..d7c310a8b4c 100644
--- a/src/librustc_feature/active.rs
+++ b/src/librustc_feature/active.rs
@@ -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),
 
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index 78628b198a3..1387389981d 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -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};
@@ -194,6 +194,7 @@ 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,
@@ -201,6 +202,7 @@ pub fn build_external_trait(cx: &DocContext<'_>, did: DefId) -> clean::Trait {
         generics,
         items: trait_items,
         bounds: supertrait_bounds,
+        is_spotlight,
         is_auto,
     }
 }
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 03d6853494c..8a4ee91df40 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -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,
@@ -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),
             }),
         }
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index c9ae67ded0a..5f6d9ecc047 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -995,6 +995,7 @@ pub struct Trait {
     pub items: Vec<Item>,
     pub generics: Generics,
     pub bounds: Vec<GenericBound>,
+    pub is_spotlight: bool,
     pub is_auto: bool,
 }
 
diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs
index a453a8b3dcb..0d8284029af 100644
--- a/src/librustdoc/html/format.rs
+++ b/src/librustdoc/html/format.rs
@@ -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).
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index 9fa3f6cc396..940d7e87a18 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -2410,7 +2410,7 @@ fn item_function(w: &mut Buffer, cx: &Context, it: &clean::Item, f: &clean::Func
         f.generics.print()
     )
     .len();
-    write!(w, "<pre class='rust fn'>");
+    write!(w, "{}<pre class='rust fn'>", render_spotlight_traits(it));
     render_attributes(w, it, false);
     write!(
         w,
@@ -2612,7 +2612,12 @@ fn item_trait(w: &mut Buffer, cx: &Context, it: &clean::Item, t: &clean::Trait)
         let name = m.name.as_ref().unwrap();
         let item_type = m.type_();
         let id = cx.derive_id(format!("{}.{}", item_type, name));
-        write!(w, "<h3 id='{id}' class='method'><code>", id = id);
+        write!(
+            w,
+            "<h3 id='{id}' class='method'>{extra}<code>",
+            extra = render_spotlight_traits(m),
+            id = id
+        );
         render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl);
         write!(w, "</code>");
         render_stability_since(w, m, t);
@@ -3559,6 +3564,76 @@ fn should_render_item(item: &clean::Item, deref_mut_: bool) -> bool {
     }
 }
 
+fn render_spotlight_traits(item: &clean::Item) -> String {
+    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, .. }) => spotlight_decl(decl),
+        _ => String::new(),
+    }
+}
+
+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,
+            &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>");
+    }
+
+    out.into_inner()
+}
+
 fn render_impl(
     w: &mut Buffer,
     cx: &Context,
@@ -3665,12 +3740,14 @@ fn render_impl(
                 (true, " hidden")
             };
         match item.inner {
-            clean::MethodItem(clean::Method { .. })
-            | clean::TyMethodItem(clean::TyMethod { .. }) => {
+            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 = cx.derive_id(format!("{}.{}", item_type, name));
-                    write!(w, "<h4 id='{}' class=\"{}{}\"><code>", id, item_type, extra_class);
+                    write!(w, "<h4 id='{}' class=\"{}{}\">", id, item_type, extra_class);
+                    write!(w, "{}", spotlight_decl(decl));
+                    write!(w, "<code>");
                     render_assoc_item(w, item, link.anchor(&id), ItemType::Impl);
                     write!(w, "</code>");
                     render_stability_since_raw(w, item.stable_since(), outer_version);
diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js
index 336c691ac1c..524a841e098 100644
--- a/src/librustdoc/html/static/main.js
+++ b/src/librustdoc/html/static/main.js
@@ -365,6 +365,7 @@ function defocusSearchBar() {
     function handleEscape(ev) {
         var help = getHelpElement();
         var search = getSearchElement();
+        hideModal();
         if (hasClass(help, "hidden") === false) {
             displayHelp(false, ev, help);
         } else if (hasClass(search, "hidden") === false) {
@@ -397,6 +398,7 @@ function defocusSearchBar() {
             case "s":
             case "S":
                 displayHelp(false, ev);
+                hideModal();
                 ev.preventDefault();
                 focusSearchBar();
                 break;
@@ -409,6 +411,7 @@ function defocusSearchBar() {
 
             case "?":
                 if (ev.shiftKey) {
+                    hideModal();
                     displayHelp(true, ev);
                 }
                 break;
@@ -2636,6 +2639,31 @@ function defocusSearchBar() {
         });
     }());
 
+    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);
+        }
+    }
+
+    onEachLazy(document.getElementsByClassName("important-traits"), function(e) {
+        e.onclick = function() {
+            showModal(e.lastElementChild.innerHTML);
+        };
+    });
+
     // In the search display, allows to switch between tabs.
     function printTab(nb) {
         if (nb === 0 || nb === 1 || nb === 2) {
diff --git a/src/librustdoc/html/static/rustdoc.css b/src/librustdoc/html/static/rustdoc.css
index 15a0c76ceea..3b2a28a0f5e 100644
--- a/src/librustdoc/html/static/rustdoc.css
+++ b/src/librustdoc/html/static/rustdoc.css
@@ -146,9 +146,12 @@ code, pre, a.test-arrow {
 	border-radius: 3px;
 	padding: 0 0.1em;
 }
-.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;
 }
@@ -523,7 +526,7 @@ h4 > code, h3 > code, .invisible > code {
 	font-size: 0.8em;
 }
 
-.content .methods > div {
+.content .methods > div:not(.important-traits) {
 	margin-left: 40px;
 	margin-bottom: 15px;
 }
@@ -1098,7 +1101,7 @@ h3 > .collapse-toggle, h4 > .collapse-toggle {
 	font-size: 20px;
 }
 
-.tooltip .tooltiptext {
+.important-traits .tooltip .tooltiptext {
 	border: 1px solid;
 	font-weight: normal;
 }
@@ -1144,6 +1147,17 @@ pre.rust {
 	font-size: 16px;
 }
 
+.important-traits {
+	cursor: pointer;
+	z-index: 2;
+}
+
+h4 > .important-traits {
+	position: absolute;
+	left: -44px;
+	top: 2px;
+}
+
 #all-types {
 	text-align: center;
 	border: 1px solid;
@@ -1370,6 +1384,12 @@ pre.rust {
 		z-index: 1;
 	}
 
+	h4 > .important-traits {
+		position: absolute;
+		left: -22px;
+		top: 24px;
+	}
+
 	#titles > div > div.count {
 		float: left;
 		width: 100%;
@@ -1472,12 +1492,82 @@ pre.rust {
 	}
 }
 
+.modal {
+	position: fixed;
+	width: 100vw;
+	height: 100vh;
+	z-index: 10000;
+	top: 0;
+	left: 0;
+}
+
+.modal-content {
+	display: block;
+	max-width: 60%;
+	min-width: 200px;
+	padding: 8px;
+	top: 40%;
+	position: absolute;
+	left: 50%;
+	transform: translate(-50%, -40%);
+	border: 1px solid;
+	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;
+	width: 25px;
+	padding-right: 2px;
+	border-top-right-radius: 5px;
+	border-bottom-right-radius: 5px;
+	text-align: center;
+	border: 1px solid;
+	border-right: 0;
+	cursor: pointer;
+}
+
+.modal-content > .whiter {
+	height: 25px;
+	position: absolute;
+	width: 3px;
+	right: -2px;
+	top: 0px;
+}
+
+#main > div.important-traits {
+	position: absolute;
+	left: -24px;
+	margin-top: 16px;
+}
+
+.content > .methods > .method > div.important-traits {
+	position: absolute;
+	font-weight: 400;
+	left: -42px;
+	margin-top: 2px;
+}
+
 kbd {
 	display: inline-block;
 	padding: 3px 5px;
diff --git a/src/librustdoc/html/static/themes/dark.css b/src/librustdoc/html/static/themes/dark.css
index 41dcb5c2450..daa5ccf34bb 100644
--- a/src/librustdoc/html/static/themes/dark.css
+++ b/src/librustdoc/html/static/themes/dark.css
@@ -337,6 +337,12 @@ pre.ignore:hover, .information:hover + pre.ignore {
 	border-color: transparent black transparent transparent;
 }
 
+.important-traits .tooltip .tooltiptext {
+	background-color: white;
+	color: black;
+	border-color: black;
+}
+
 #titles > div:not(.selected) {
 	background-color: #252525;
 	border-top-color: #252525;
@@ -350,6 +356,33 @@ pre.ignore:hover, .information:hover + pre.ignore {
 	color: #888;
 }
 
+.modal {
+	background-color: rgba(0,0,0,0.3);
+}
+
+.modal-content {
+	background-color: #272727;
+	border-color: #999;
+}
+
+.modal-content > .close {
+	background-color: #272727;
+	border-color: #999;
+}
+
+.modal-content > .close:hover {
+	background-color: #ff1f1f;
+	color: white;
+}
+
+.modal-content > .whiter {
+	background-color: #272727;
+}
+
+.modal-content > .close:hover + .whiter {
+	background-color: #ff1f1f;
+}
+
 @media (max-width: 700px) {
 	.sidebar-menu {
 		background-color: #505050;
diff --git a/src/librustdoc/html/static/themes/light.css b/src/librustdoc/html/static/themes/light.css
index 386fe2398e6..aa7df01dc02 100644
--- a/src/librustdoc/html/static/themes/light.css
+++ b/src/librustdoc/html/static/themes/light.css
@@ -331,6 +331,12 @@ pre.ignore:hover, .information:hover + pre.ignore {
 	border-color: transparent black transparent transparent;
 }
 
+.important-traits .tooltip .tooltiptext {
+	background-color: white;
+	color: black;
+	border-color: black;
+}
+
 #titles > div:not(.selected) {
 	background-color: #e6e6e6;
 	border-top-color: #e6e6e6;
@@ -344,6 +350,33 @@ pre.ignore:hover, .information:hover + pre.ignore {
 	color: #888;
 }
 
+.modal {
+	background-color: rgba(0,0,0,0.3);
+}
+
+.modal-content {
+	background-color: #eee;
+	border-color: #999;
+}
+
+.modal-content > .close {
+	background-color: #eee;
+	border-color: #999;
+}
+
+.modal-content > .close:hover {
+	background-color: #ff1f1f;
+	color: white;
+}
+
+.modal-content > .whiter {
+	background-color: #eee;
+}
+
+.modal-content > .close:hover + .whiter {
+	background-color: #ff1f1f;
+}
+
 @media (max-width: 700px) {
 	.sidebar-menu {
 		background-color: #F1F1F1;
diff --git a/src/test/rustdoc/doc-spotlight.rs b/src/test/rustdoc/doc-spotlight.rs
new file mode 100644
index 00000000000..ddd46c3c215
--- /dev/null
+++ b/src/test/rustdoc/doc-spotlight.rs
@@ -0,0 +1,36 @@
+#![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
+}
diff --git a/src/test/ui/feature-gates/feature-gate-doc_spotlight.rs b/src/test/ui/feature-gates/feature-gate-doc_spotlight.rs
new file mode 100644
index 00000000000..452b45b3445
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-doc_spotlight.rs
@@ -0,0 +1,4 @@
+#[doc(spotlight)] //~ ERROR: `#[doc(spotlight)]` is experimental
+trait SomeTrait {}
+
+fn main() {}
diff --git a/src/test/ui/feature-gates/feature-gate-doc_spotlight.stderr b/src/test/ui/feature-gates/feature-gate-doc_spotlight.stderr
new file mode 100644
index 00000000000..010d74054a4
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-doc_spotlight.stderr
@@ -0,0 +1,12 @@
+error[E0658]: `#[doc(spotlight)]` is experimental
+  --> $DIR/feature-gate-doc_spotlight.rs:1:1
+   |
+LL | #[doc(spotlight)]
+   | ^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #45040 <https://github.com/rust-lang/rust/issues/45040> for more information
+   = help: add `#![feature(doc_spotlight)]` to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.