about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-12-23 01:47:08 +0000
committerbors <bors@rust-lang.org>2021-12-23 01:47:08 +0000
commit5aa0239b169e9f45a9b5af1e989821b9b69bc9a2 (patch)
tree43b5df9ecf9a5f955a29f0f7cd3ba841b8ce3a39
parente98309298d927307c5184f4869604bd068d26183 (diff)
parent3afed8fc709cc174e9518269694b344b7487cb65 (diff)
downloadrust-5aa0239b169e9f45a9b5af1e989821b9b69bc9a2.tar.gz
rust-5aa0239b169e9f45a9b5af1e989821b9b69bc9a2.zip
Auto merge of #92216 - matthiaskrgr:rollup-luplvuc, r=matthiaskrgr
Rollup of 7 pull requests

Successful merges:

 - #88858 (Allow reverse iteration of lowercase'd/uppercase'd chars)
 - #91544 (Fix duplicate derive clone suggestion)
 - #92026 (Add some JSDoc comments to rustdoc JS)
 - #92117 (kmc-solid: Add `std::sys::solid::fs::File::read_buf`)
 - #92139 (Change Backtrace::enabled atomic from SeqCst to Relaxed)
 - #92146 (Don't emit shared files when scraping examples from dependencies in Rustdoc)
 - #92208 (Quote bat script command line)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--compiler/rustc_typeck/src/check/method/suggest.rs33
-rw-r--r--library/alloc/tests/str.rs31
-rw-r--r--library/core/src/char/mod.rs34
-rw-r--r--library/core/tests/char.rs6
-rw-r--r--library/std/src/backtrace.rs6
-rw-r--r--library/std/src/process/tests.rs19
-rw-r--r--library/std/src/sys/solid/fs.rs28
-rw-r--r--library/std/src/sys/windows/process.rs16
-rw-r--r--src/librustdoc/config.rs4
-rw-r--r--src/librustdoc/html/render/context.rs16
-rw-r--r--src/librustdoc/html/static/js/README.md15
-rw-r--r--src/librustdoc/html/static/js/externs.js32
-rw-r--r--src/librustdoc/html/static/js/main.js7
-rw-r--r--src/librustdoc/html/static/js/search.js130
-rw-r--r--src/librustdoc/html/static/js/storage.js16
-rw-r--r--src/librustdoc/scrape_examples.rs3
-rw-r--r--src/test/ui/derives/issue-91492.rs25
-rw-r--r--src/test/ui/derives/issue-91492.stderr54
18 files changed, 418 insertions, 57 deletions
diff --git a/compiler/rustc_typeck/src/check/method/suggest.rs b/compiler/rustc_typeck/src/check/method/suggest.rs
index d5a4de86d4d..f3a5fbbb444 100644
--- a/compiler/rustc_typeck/src/check/method/suggest.rs
+++ b/compiler/rustc_typeck/src/check/method/suggest.rs
@@ -1195,11 +1195,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     fn suggest_derive(
         &self,
         err: &mut DiagnosticBuilder<'_>,
-        unsatisfied_predicates: &Vec<(
+        unsatisfied_predicates: &[(
             ty::Predicate<'tcx>,
             Option<ty::Predicate<'tcx>>,
             Option<ObligationCause<'tcx>>,
-        )>,
+        )],
     ) {
         let mut derives = Vec::<(String, Span, String)>::new();
         let mut traits = Vec::<Span>::new();
@@ -1236,23 +1236,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 traits.push(self.tcx.def_span(trait_pred.def_id()));
             }
         }
-        derives.sort();
-        let derives_grouped = derives.into_iter().fold(
-            Vec::<(String, Span, String)>::new(),
-            |mut acc, (self_name, self_span, trait_name)| {
-                if let Some((acc_self_name, _, ref mut traits)) = acc.last_mut() {
-                    if acc_self_name == &self_name {
-                        traits.push_str(format!(", {}", trait_name).as_str());
-                        return acc;
-                    }
-                }
-                acc.push((self_name, self_span, trait_name));
-                acc
-            },
-        );
         traits.sort();
         traits.dedup();
 
+        derives.sort();
+        derives.dedup();
+
+        let mut derives_grouped = Vec::<(String, Span, String)>::new();
+        for (self_name, self_span, trait_name) in derives.into_iter() {
+            if let Some((last_self_name, _, ref mut last_trait_names)) = derives_grouped.last_mut()
+            {
+                if last_self_name == &self_name {
+                    last_trait_names.push_str(format!(", {}", trait_name).as_str());
+                    continue;
+                }
+            }
+            derives_grouped.push((self_name, self_span, trait_name));
+        }
+
         let len = traits.len();
         if len > 0 {
             let span: MultiSpan = traits.into();
diff --git a/library/alloc/tests/str.rs b/library/alloc/tests/str.rs
index 7bd0abbad01..e92881b1049 100644
--- a/library/alloc/tests/str.rs
+++ b/library/alloc/tests/str.rs
@@ -1184,6 +1184,37 @@ fn test_rev_iterator() {
 }
 
 #[test]
+fn test_to_lowercase_rev_iterator() {
+    let s = "AÖßÜ💩ΣΤΙΓΜΑΣDžfiİ";
+    let v = ['\u{307}', 'i', 'fi', 'dž', 'σ', 'α', 'μ', 'γ', 'ι', 'τ', 'σ', '💩', 'ü', 'ß', 'ö', 'a'];
+
+    let mut pos = 0;
+    let it = s.chars().flat_map(|c| c.to_lowercase()).rev();
+
+    for c in it {
+        assert_eq!(c, v[pos]);
+        pos += 1;
+    }
+    assert_eq!(pos, v.len());
+}
+
+#[test]
+fn test_to_uppercase_rev_iterator() {
+    let s = "aößü💩στιγμαςDžfiᾀ";
+    let v =
+        ['Ι', 'Ἀ', 'I', 'F', 'DŽ', 'Σ', 'Α', 'Μ', 'Γ', 'Ι', 'Τ', 'Σ', '💩', 'Ü', 'S', 'S', 'Ö', 'A'];
+
+    let mut pos = 0;
+    let it = s.chars().flat_map(|c| c.to_uppercase()).rev();
+
+    for c in it {
+        assert_eq!(c, v[pos]);
+        pos += 1;
+    }
+    assert_eq!(pos, v.len());
+}
+
+#[test]
 #[cfg_attr(miri, ignore)] // Miri is too slow
 fn test_chars_decoding() {
     let mut bytes = [0; 4];
diff --git a/library/core/src/char/mod.rs b/library/core/src/char/mod.rs
index 0728523d0a4..5f30d5790a0 100644
--- a/library/core/src/char/mod.rs
+++ b/library/core/src/char/mod.rs
@@ -393,6 +393,13 @@ impl Iterator for ToLowercase {
     }
 }
 
+#[stable(feature = "case_mapping_double_ended", since = "1.59.0")]
+impl DoubleEndedIterator for ToLowercase {
+    fn next_back(&mut self) -> Option<char> {
+        self.0.next_back()
+    }
+}
+
 #[stable(feature = "fused", since = "1.26.0")]
 impl FusedIterator for ToLowercase {}
 
@@ -420,6 +427,13 @@ impl Iterator for ToUppercase {
     }
 }
 
+#[stable(feature = "case_mapping_double_ended", since = "1.59.0")]
+impl DoubleEndedIterator for ToUppercase {
+    fn next_back(&mut self) -> Option<char> {
+        self.0.next_back()
+    }
+}
+
 #[stable(feature = "fused", since = "1.26.0")]
 impl FusedIterator for ToUppercase {}
 
@@ -479,6 +493,26 @@ impl Iterator for CaseMappingIter {
     }
 }
 
+impl DoubleEndedIterator for CaseMappingIter {
+    fn next_back(&mut self) -> Option<char> {
+        match *self {
+            CaseMappingIter::Three(a, b, c) => {
+                *self = CaseMappingIter::Two(a, b);
+                Some(c)
+            }
+            CaseMappingIter::Two(b, c) => {
+                *self = CaseMappingIter::One(b);
+                Some(c)
+            }
+            CaseMappingIter::One(c) => {
+                *self = CaseMappingIter::Zero;
+                Some(c)
+            }
+            CaseMappingIter::Zero => None,
+        }
+    }
+}
+
 impl fmt::Display for CaseMappingIter {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match *self {
diff --git a/library/core/tests/char.rs b/library/core/tests/char.rs
index 6e434cf1a8d..2b857a65919 100644
--- a/library/core/tests/char.rs
+++ b/library/core/tests/char.rs
@@ -103,6 +103,9 @@ fn test_to_lowercase() {
         let iter: String = c.to_lowercase().collect();
         let disp: String = c.to_lowercase().to_string();
         assert_eq!(iter, disp);
+        let iter_rev: String = c.to_lowercase().rev().collect();
+        let disp_rev: String = disp.chars().rev().collect();
+        assert_eq!(iter_rev, disp_rev);
         iter
     }
     assert_eq!(lower('A'), "a");
@@ -130,6 +133,9 @@ fn test_to_uppercase() {
         let iter: String = c.to_uppercase().collect();
         let disp: String = c.to_uppercase().to_string();
         assert_eq!(iter, disp);
+        let iter_rev: String = c.to_uppercase().rev().collect();
+        let disp_rev: String = disp.chars().rev().collect();
+        assert_eq!(iter_rev, disp_rev);
         iter
     }
     assert_eq!(upper('a'), "A");
diff --git a/library/std/src/backtrace.rs b/library/std/src/backtrace.rs
index 0b86b4f30b9..94e6070c0f7 100644
--- a/library/std/src/backtrace.rs
+++ b/library/std/src/backtrace.rs
@@ -99,7 +99,7 @@ use crate::cell::UnsafeCell;
 use crate::env;
 use crate::ffi::c_void;
 use crate::fmt;
-use crate::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+use crate::sync::atomic::{AtomicUsize, Ordering::Relaxed};
 use crate::sync::Once;
 use crate::sys_common::backtrace::{lock, output_filename};
 use crate::vec::Vec;
@@ -256,7 +256,7 @@ impl Backtrace {
         // backtrace captures speedy, because otherwise reading environment
         // variables every time can be somewhat slow.
         static ENABLED: AtomicUsize = AtomicUsize::new(0);
-        match ENABLED.load(SeqCst) {
+        match ENABLED.load(Relaxed) {
             0 => {}
             1 => return false,
             _ => return true,
@@ -268,7 +268,7 @@ impl Backtrace {
                 Err(_) => false,
             },
         };
-        ENABLED.store(enabled as usize + 1, SeqCst);
+        ENABLED.store(enabled as usize + 1, Relaxed);
         enabled
     }
 
diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs
index 61ab0eb55e6..e5cdc473706 100644
--- a/library/std/src/process/tests.rs
+++ b/library/std/src/process/tests.rs
@@ -420,3 +420,22 @@ fn env_empty() {
     let p = Command::new("cmd").args(&["/C", "exit 0"]).env_clear().spawn();
     assert!(p.is_ok());
 }
+
+// See issue #91991
+#[test]
+#[cfg(windows)]
+fn run_bat_script() {
+    let tempdir = crate::sys_common::io::test::tmpdir();
+    let script_path = tempdir.join("hello.cmd");
+
+    crate::fs::write(&script_path, "@echo Hello, %~1!").unwrap();
+    let output = Command::new(&script_path)
+        .arg("fellow Rustaceans")
+        .stdout(crate::process::Stdio::piped())
+        .spawn()
+        .unwrap()
+        .wait_with_output()
+        .unwrap();
+    assert!(output.status.success());
+    assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "Hello, fellow Rustaceans!");
+}
diff --git a/library/std/src/sys/solid/fs.rs b/library/std/src/sys/solid/fs.rs
index abc60b56fbb..8a0eeff0c4d 100644
--- a/library/std/src/sys/solid/fs.rs
+++ b/library/std/src/sys/solid/fs.rs
@@ -2,7 +2,7 @@ use super::{abi, error};
 use crate::{
     ffi::{CStr, CString, OsStr, OsString},
     fmt,
-    io::{self, IoSlice, IoSliceMut, SeekFrom},
+    io::{self, IoSlice, IoSliceMut, ReadBuf, SeekFrom},
     mem::MaybeUninit,
     os::raw::{c_int, c_short},
     os::solid::ffi::OsStrExt,
@@ -339,6 +339,32 @@ impl File {
         }
     }
 
+    pub fn read_buf(&self, buf: &mut ReadBuf<'_>) -> io::Result<()> {
+        unsafe {
+            let len = buf.remaining();
+            let mut out_num_bytes = MaybeUninit::uninit();
+            error::SolidError::err_if_negative(abi::SOLID_FS_Read(
+                self.fd.raw(),
+                buf.unfilled_mut().as_mut_ptr() as *mut u8,
+                len,
+                out_num_bytes.as_mut_ptr(),
+            ))
+            .map_err(|e| e.as_io_error())?;
+
+            // Safety: `out_num_bytes` is filled by the successful call to
+            // `SOLID_FS_Read`
+            let num_bytes_read = out_num_bytes.assume_init();
+
+            // Safety: `num_bytes_read` bytes were written to the unfilled
+            // portion of the buffer
+            buf.assume_init(num_bytes_read);
+
+            buf.add_filled(num_bytes_read);
+
+            Ok(())
+        }
+    }
+
     pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
         crate::io::default_read_vectored(|buf| self.read(buf), bufs)
     }
diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs
index 66b210ce1bf..e84dfbce4a7 100644
--- a/library/std/src/sys/windows/process.rs
+++ b/library/std/src/sys/windows/process.rs
@@ -704,6 +704,19 @@ fn make_command_line(prog: &OsStr, args: &[Arg], force_quotes: bool) -> io::Resu
     // Encode the command and arguments in a command line string such
     // that the spawned process may recover them using CommandLineToArgvW.
     let mut cmd: Vec<u16> = Vec::new();
+
+    // CreateFileW has special handling for .bat and .cmd files, which means we
+    // need to add an extra pair of quotes surrounding the whole command line
+    // so they are properly passed on to the script.
+    // See issue #91991.
+    let is_batch_file = Path::new(prog)
+        .extension()
+        .map(|ext| ext.eq_ignore_ascii_case("cmd") || ext.eq_ignore_ascii_case("bat"))
+        .unwrap_or(false);
+    if is_batch_file {
+        cmd.push(b'"' as u16);
+    }
+
     // Always quote the program name so CreateProcess doesn't interpret args as
     // part of the name if the binary wasn't found first time.
     append_arg(&mut cmd, prog, Quote::Always)?;
@@ -715,6 +728,9 @@ fn make_command_line(prog: &OsStr, args: &[Arg], force_quotes: bool) -> io::Resu
         };
         append_arg(&mut cmd, arg, quote)?;
     }
+    if is_batch_file {
+        cmd.push(b'"' as u16);
+    }
     return Ok(cmd);
 
     fn append_arg(cmd: &mut Vec<u16>, arg: &OsStr, quote: Quote) -> io::Result<()> {
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index 961a98a7205..d300afa3132 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -272,7 +272,10 @@ crate struct RenderOptions {
     crate emit: Vec<EmitType>,
     /// If `true`, HTML source pages will generate links for items to their definition.
     crate generate_link_to_definition: bool,
+    /// Set of function-call locations to include as examples
     crate call_locations: AllCallLocations,
+    /// If `true`, Context::init will not emit shared files.
+    crate no_emit_shared: bool,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -732,6 +735,7 @@ impl Options {
                 emit,
                 generate_link_to_definition,
                 call_locations,
+                no_emit_shared: false,
             },
             crate_name,
             output_format,
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 9c849b7789a..45a436c4487 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -397,6 +397,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             show_type_layout,
             generate_link_to_definition,
             call_locations,
+            no_emit_shared,
             ..
         } = options;
 
@@ -516,13 +517,16 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             sources::render(&mut cx, &krate)?;
         }
 
-        // Build our search index
-        let index = build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx);
+        if !no_emit_shared {
+            // Build our search index
+            let index = build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx);
+
+            // Write shared runs within a flock; disable thread dispatching of IO temporarily.
+            Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
+            write_shared(&cx, &krate, index, &md_opts)?;
+            Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
+        }
 
-        // Write shared runs within a flock; disable thread dispatching of IO temporarily.
-        Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(true);
-        write_shared(&cx, &krate, index, &md_opts)?;
-        Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
         Ok((cx, krate))
     }
 
diff --git a/src/librustdoc/html/static/js/README.md b/src/librustdoc/html/static/js/README.md
new file mode 100644
index 00000000000..1fd859ad7cf
--- /dev/null
+++ b/src/librustdoc/html/static/js/README.md
@@ -0,0 +1,15 @@
+# Rustdoc JS
+
+These JavaScript files are incorporated into the rustdoc binary at build time,
+and are minified and written to the filesystem as part of the doc build process.
+
+We use the [Closure Compiler](https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler)
+dialect of JSDoc to comment our code and annotate params and return types.
+To run a check:
+
+    ./x.py doc library/std
+    npm i -g google-closure-compiler
+    google-closure-compiler -W VERBOSE \
+      build/<YOUR PLATFORM>/doc/{search-index*.js,crates*.js} \
+      src/librustdoc/html/static/js/{search.js,main.js,storage.js} \
+      --externs src/librustdoc/html/static/js/externs.js >/dev/null
diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js
new file mode 100644
index 00000000000..629f90728d2
--- /dev/null
+++ b/src/librustdoc/html/static/js/externs.js
@@ -0,0 +1,32 @@
+// This file contains type definitions that are processed by the Closure Compiler but are
+// not put into the JavaScript we include as part of the documentation. It is used for
+// type checking. See README.md in this directory for more info.
+
+/* eslint-disable */
+var searchState;
+function initSearch(searchIndex){}
+
+/**
+ * @typedef {{
+ *   raw: string,
+ *   query: string,
+ *   type: string,
+ *   id: string,
+ * }}
+ */
+var ParsedQuery;
+
+/**
+ * @typedef {{
+ *    crate: string,
+ *    desc: string,
+ *    id: number,
+ *    name: string,
+ *    normalizedName: string,
+ *    parent: (Object|null|undefined),
+ *    path: string,
+ *    ty: (Number|null|number),
+ *    type: (Array<?>|null)
+ * }}
+ */
+var Row;
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index 411a94ef2d1..f81f6d5d61f 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -420,6 +420,13 @@ function hideThemeButtonState() {
         return document.getElementById("help");
     }
 
+    /**
+     * Show the help popup.
+     *
+     * @param {boolean} display    - Whether to show or hide the popup
+     * @param {Event}   ev         - The event that triggered this call
+     * @param {Element} [help]     - The help element if it already exists
+     */
     function displayHelp(display, ev, help) {
         if (display) {
             help = help ? help : getHelpElement(true);
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 971bd3930cf..cf320f7b495 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -113,7 +113,15 @@ window.initSearch = function(rawSearchIndex) {
     var INPUTS_DATA = 0;
     var OUTPUT_DATA = 1;
     var NO_TYPE_FILTER = -1;
-    var currentResults, index, searchIndex;
+    /**
+     *  @type {Array<Row>}
+     */
+    var searchIndex;
+    /**
+     *  @type {Array<string>}
+     */
+    var searchWords;
+    var currentResults;
     var ALIASES = {};
     var params = searchState.getQueryStringParams();
 
@@ -126,12 +134,15 @@ window.initSearch = function(rawSearchIndex) {
     }
 
     /**
-     * Executes the query and builds an index of results
-     * @param  {[Object]} query      [The user query]
-     * @param  {[type]} searchWords  [The list of search words to query
-     *                                against]
-     * @param  {[type]} filterCrates [Crate to search in if defined]
-     * @return {[type]}              [A search index of results]
+     * Executes the query and returns a list of results for each results tab.
+     * @param  {Object}        query          - The user query
+     * @param  {Array<string>} searchWords    - The list of search words to query against
+     * @param  {string}        [filterCrates] - Crate to search in
+     * @return {{
+     *   in_args: Array<?>,
+     *   returned: Array<?>,
+     *   others: Array<?>
+     * }}
      */
     function execQuery(query, searchWords, filterCrates) {
         function itemTypeFromName(typename) {
@@ -847,11 +858,11 @@ window.initSearch = function(rawSearchIndex) {
      * This could be written functionally, but I wanted to minimise
      * functions on stack.
      *
-     * @param  {[string]} name   [The name of the result]
-     * @param  {[string]} path   [The path of the result]
-     * @param  {[string]} keys   [The keys to be used (["file", "open"])]
-     * @param  {[object]} parent [The parent of the result]
-     * @return {boolean}       [Whether the result is valid or not]
+     * @param  {string} name   - The name of the result
+     * @param  {string} path   - The path of the result
+     * @param  {string} keys   - The keys to be used (["file", "open"])
+     * @param  {Object} parent - The parent of the result
+     * @return {boolean}       - Whether the result is valid or not
      */
     function validateResult(name, path, keys, parent) {
         for (var i = 0, len = keys.length; i < len; ++i) {
@@ -872,8 +883,14 @@ window.initSearch = function(rawSearchIndex) {
         return true;
     }
 
+    /**
+     * Parse a string into a query object.
+     *
+     * @param {string} raw - The text that the user typed.
+     * @returns {ParsedQuery}
+     */
     function getQuery(raw) {
-        var matches, type, query;
+        var matches, type = "", query;
         query = raw;
 
         matches = query.match(/^(fn|mod|struct|enum|trait|type|const|macro)\s*:\s*/i);
@@ -974,6 +991,12 @@ window.initSearch = function(rawSearchIndex) {
         return tmp;
     }
 
+    /**
+     * Render a set of search results for a single tab.
+     * @param {Array<?>}    array   - The search results for this tab
+     * @param {ParsedQuery} query
+     * @param {boolean}     display - True if this is the active tab
+     */
     function addTab(array, query, display) {
         var extraClass = "";
         if (display === true) {
@@ -1083,7 +1106,7 @@ window.initSearch = function(rawSearchIndex) {
 
         currentResults = query.id;
 
-        var ret_others = addTab(results.others, query);
+        var ret_others = addTab(results.others, query, true);
         var ret_in_args = addTab(results.in_args, query, false);
         var ret_returned = addTab(results.returned, query, false);
 
@@ -1253,6 +1276,12 @@ window.initSearch = function(rawSearchIndex) {
         return undefined;
     }
 
+    /**
+     * Perform a search based on the current state of the search input element
+     * and display the results.
+     * @param {Event}   [e]       - The event that triggered this search, if any
+     * @param {boolean} [forced]
+     */
     function search(e, forced) {
         var params = searchState.getQueryStringParams();
         var query = getQuery(searchState.input.value.trim());
@@ -1287,11 +1316,14 @@ window.initSearch = function(rawSearchIndex) {
         }
 
         var filterCrates = getFilterCrates();
-        showResults(execSearch(query, index, filterCrates), params.go_to_first);
+        showResults(execSearch(query, searchWords, filterCrates), params["go_to_first"]);
     }
 
     function buildIndex(rawSearchIndex) {
         searchIndex = [];
+        /**
+         * @type {Array<string>}
+         */
         var searchWords = [];
         var i, word;
         var currentIndex = 0;
@@ -1304,6 +1336,38 @@ window.initSearch = function(rawSearchIndex) {
 
             var crateSize = 0;
 
+            /**
+             * The raw search data for a given crate. `n`, `t`, `d`, and `q`, `i`, and `f`
+             * are arrays with the same length. n[i] contains the name of an item.
+             * t[i] contains the type of that item (as a small integer that represents an
+             * offset in `itemTypes`). d[i] contains the description of that item.
+             *
+             * q[i] contains the full path of the item, or an empty string indicating
+             * "same as q[i-1]".
+             *
+             * i[i], f[i] are a mystery.
+             *
+             * `a` defines aliases with an Array of pairs: [name, offset], where `offset`
+             * points into the n/t/d/q/i/f arrays.
+             *
+             * `doc` contains the description of the crate.
+             *
+             * `p` is a mystery and isn't the same length as n/t/d/q/i/f.
+             *
+             * @type {{
+             *   doc: string,
+             *   a: Object,
+             *   n: Array<string>,
+             *   t: Array<Number>,
+             *   d: Array<string>,
+             *   q: Array<string>,
+             *   i: Array<Number>,
+             *   f: Array<Array<?>>,
+             *   p: Array<Object>,
+             * }}
+             */
+            var crateCorpus = rawSearchIndex[crate];
+
             searchWords.push(crate);
             // This object should have exactly the same set of fields as the "row"
             // object defined below. Your JavaScript runtime will thank you.
@@ -1313,7 +1377,7 @@ window.initSearch = function(rawSearchIndex) {
                 ty: 1, // == ExternCrate
                 name: crate,
                 path: "",
-                desc: rawSearchIndex[crate].doc,
+                desc: crateCorpus.doc,
                 parent: undefined,
                 type: null,
                 id: id,
@@ -1324,23 +1388,23 @@ window.initSearch = function(rawSearchIndex) {
             currentIndex += 1;
 
             // an array of (Number) item types
-            var itemTypes = rawSearchIndex[crate].t;
+            var itemTypes = crateCorpus.t;
             // an array of (String) item names
-            var itemNames = rawSearchIndex[crate].n;
+            var itemNames = crateCorpus.n;
             // an array of (String) full paths (or empty string for previous path)
-            var itemPaths = rawSearchIndex[crate].q;
+            var itemPaths = crateCorpus.q;
             // an array of (String) descriptions
-            var itemDescs = rawSearchIndex[crate].d;
+            var itemDescs = crateCorpus.d;
             // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
-            var itemParentIdxs = rawSearchIndex[crate].i;
+            var itemParentIdxs = crateCorpus.i;
             // an array of (Object | null) the type of the function, if any
-            var itemFunctionSearchTypes = rawSearchIndex[crate].f;
+            var itemFunctionSearchTypes = crateCorpus.f;
             // an array of [(Number) item type,
             //              (String) name]
-            var paths = rawSearchIndex[crate].p;
+            var paths = crateCorpus.p;
             // an array of [(String) alias name
             //             [Number] index to items]
-            var aliases = rawSearchIndex[crate].a;
+            var aliases = crateCorpus.a;
 
             // convert `rawPaths` entries into object form
             var len = paths.length;
@@ -1406,6 +1470,16 @@ window.initSearch = function(rawSearchIndex) {
         return searchWords;
     }
 
+    /**
+     * Callback for when the search form is submitted.
+     * @param {Event} [e] - The event that triggered this call, if any
+     */
+    function onSearchSubmit(e) {
+        e.preventDefault();
+        searchState.clearInputTimeout();
+        search();
+    }
+
     function registerSearchEvents() {
         var searchAfter500ms = function() {
             searchState.clearInputTimeout();
@@ -1421,11 +1495,7 @@ window.initSearch = function(rawSearchIndex) {
         };
         searchState.input.onkeyup = searchAfter500ms;
         searchState.input.oninput = searchAfter500ms;
-        document.getElementsByClassName("search-form")[0].onsubmit = function(e) {
-            e.preventDefault();
-            searchState.clearInputTimeout();
-            search();
-        };
+        document.getElementsByClassName("search-form")[0].onsubmit = onSearchSubmit;
         searchState.input.onchange = function(e) {
             if (e.target !== document.activeElement) {
                 // To prevent doing anything when it's from a blur event.
@@ -1546,7 +1616,7 @@ window.initSearch = function(rawSearchIndex) {
         };
     }
 
-    index = buildIndex(rawSearchIndex);
+    searchWords = buildIndex(rawSearchIndex);
     registerSearchEvents();
     // If there's a search term in the URL, execute the search now.
     if (searchState.getQueryStringParams().search) {
diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js
index 606c237aea7..d8b3ba92dcb 100644
--- a/src/librustdoc/html/static/js/storage.js
+++ b/src/librustdoc/html/static/js/storage.js
@@ -55,6 +55,12 @@ function removeClass(elem, className) {
     elem.classList.remove(className);
 }
 
+/**
+ * Run a callback for every element of an Array.
+ * @param {Array<?>}    arr        - The array to iterate over
+ * @param {function(?)} func       - The callback
+ * @param {boolean}     [reversed] - Whether to iterate in reverse
+ */
 function onEach(arr, func, reversed) {
     if (arr && arr.length > 0 && func) {
         var length = arr.length;
@@ -76,6 +82,16 @@ function onEach(arr, func, reversed) {
     return false;
 }
 
+/**
+ * Turn an HTMLCollection or a NodeList into an Array, then run a callback
+ * for every element. This is useful because iterating over an HTMLCollection
+ * or a "live" NodeList while modifying it can be very slow.
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLCollection
+ * https://developer.mozilla.org/en-US/docs/Web/API/NodeList
+ * @param {NodeList<?>|HTMLCollection<?>} lazyArray  - An array to iterate over
+ * @param {function(?)}                   func       - The callback
+ * @param {boolean}                       [reversed] - Whether to iterate in reverse
+ */
 function onEachLazy(lazyArray, func, reversed) {
     return onEach(
         Array.prototype.slice.call(lazyArray),
diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs
index 10b6fdf87f4..6809551fcfd 100644
--- a/src/librustdoc/scrape_examples.rs
+++ b/src/librustdoc/scrape_examples.rs
@@ -223,13 +223,14 @@ where
 
 crate fn run(
     krate: clean::Crate,
-    renderopts: config::RenderOptions,
+    mut renderopts: config::RenderOptions,
     cache: formats::cache::Cache,
     tcx: TyCtxt<'_>,
     options: ScrapeExamplesOptions,
 ) -> interface::Result<()> {
     let inner = move || -> Result<(), String> {
         // Generates source files for examples
+        renderopts.no_emit_shared = true;
         let (cx, _) = Context::init(krate, renderopts, cache, tcx).map_err(|e| e.to_string())?;
 
         // Collect CrateIds corresponding to provided target crates
diff --git a/src/test/ui/derives/issue-91492.rs b/src/test/ui/derives/issue-91492.rs
new file mode 100644
index 00000000000..df792f118ab
--- /dev/null
+++ b/src/test/ui/derives/issue-91492.rs
@@ -0,0 +1,25 @@
+// Reproduce the issue with vec
+pub struct NoDerives;
+fn fun1(foo: &mut Vec<NoDerives>, bar: &[NoDerives]) {
+    foo.extend_from_slice(bar); //~ ERROR
+}
+
+// Reproduce the issue with vec
+// and demonstrate that other derives are ignored in the suggested output
+#[derive(Default, PartialEq)]
+pub struct SomeDerives;
+fn fun2(foo: &mut Vec<SomeDerives>, bar: &[SomeDerives]) {
+    foo.extend_from_slice(bar); //~ ERROR
+}
+
+// Try and fail to reproduce the issue without vec.
+// No idea why it doesnt reproduce the issue but its still a useful test case.
+struct Object<T, A>(T, A);
+impl<T: Clone, A: Default> Object<T, A> {
+    fn use_clone(&self) {}
+}
+fn fun3(foo: Object<NoDerives, SomeDerives>) {
+    foo.use_clone(); //~ ERROR
+}
+
+fn main() {}
diff --git a/src/test/ui/derives/issue-91492.stderr b/src/test/ui/derives/issue-91492.stderr
new file mode 100644
index 00000000000..73c91154a7b
--- /dev/null
+++ b/src/test/ui/derives/issue-91492.stderr
@@ -0,0 +1,54 @@
+error[E0599]: the method `extend_from_slice` exists for mutable reference `&mut Vec<NoDerives>`, but its trait bounds were not satisfied
+  --> $DIR/issue-91492.rs:4:9
+   |
+LL | pub struct NoDerives;
+   | --------------------- doesn't satisfy `NoDerives: Clone`
+LL | fn fun1(foo: &mut Vec<NoDerives>, bar: &[NoDerives]) {
+LL |     foo.extend_from_slice(bar);
+   |         ^^^^^^^^^^^^^^^^^
+   |
+   = note: the following trait bounds were not satisfied:
+           `NoDerives: Clone`
+help: consider annotating `NoDerives` with `#[derive(Clone)]`
+   |
+LL | #[derive(Clone)]
+   |
+
+error[E0599]: the method `extend_from_slice` exists for mutable reference `&mut Vec<SomeDerives>`, but its trait bounds were not satisfied
+  --> $DIR/issue-91492.rs:12:9
+   |
+LL | pub struct SomeDerives;
+   | ----------------------- doesn't satisfy `SomeDerives: Clone`
+LL | fn fun2(foo: &mut Vec<SomeDerives>, bar: &[SomeDerives]) {
+LL |     foo.extend_from_slice(bar);
+   |         ^^^^^^^^^^^^^^^^^
+   |
+   = note: the following trait bounds were not satisfied:
+           `SomeDerives: Clone`
+help: consider annotating `SomeDerives` with `#[derive(Clone)]`
+   |
+LL | #[derive(Clone)]
+   |
+
+error[E0599]: the method `use_clone` exists for struct `Object<NoDerives, SomeDerives>`, but its trait bounds were not satisfied
+  --> $DIR/issue-91492.rs:22:9
+   |
+LL | pub struct NoDerives;
+   | --------------------- doesn't satisfy `NoDerives: Clone`
+...
+LL | struct Object<T, A>(T, A);
+   | -------------------------- method `use_clone` not found for this
+...
+LL |     foo.use_clone();
+   |         ^^^^^^^^^ method cannot be called on `Object<NoDerives, SomeDerives>` due to unsatisfied trait bounds
+   |
+   = note: the following trait bounds were not satisfied:
+           `NoDerives: Clone`
+help: consider annotating `NoDerives` with `#[derive(Clone)]`
+   |
+LL | #[derive(Clone)]
+   |
+
+error: aborting due to 3 previous errors
+
+For more information about this error, try `rustc --explain E0599`.