about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-09-05 20:34:38 +0000
committerbors <bors@rust-lang.org>2024-09-05 20:34:38 +0000
commit9c01301c52df5d2d7b6fe337707a74e011d68d6f (patch)
tree8043def18ff45012546ac3dc52ebec49da75e3b3 /src
parenteb33b43bab08223fa6b46abacc1e95e859fe375d (diff)
parentfee6c0a6835b1ade0a1dcff3e2d30dd6a018bc98 (diff)
Auto merge of #129994 - matthiaskrgr:rollup-zkj4ekl, r=matthiaskrgr
Rollup of 8 pull requests

Successful merges:

 - #128820 (fix: get llvm type of global val)
 - #129028 (`impl_trait_overcaptures`: Don't worry about uncaptured contravariant lifetimes if they outlive a captured lifetime)
 - #129471 ([rustdoc] Sort impl associated items by kinds and then by appearance)
 - #129706 (Rename dump of coroutine by-move-body to be more consistent, fix ICE in dump_mir)
 - #129720 (Simplify DestProp memory management)
 - #129796 (Unify scraped examples with other code examples)
 - #129938 (Elaborate on deriving vs implementing `Copy`)
 - #129973 (run_make_support: rename `Command::stdin` to `stdin_buf` and add `std{in,out,err}` config helpers)

r? `@ghost`
`@rustbot` modify labels: rollup
Diffstat (limited to 'src')
-rw-r--r--src/librustdoc/html/render/mod.rs86
-rw-r--r--src/librustdoc/html/render/print_item.rs32
-rw-r--r--src/librustdoc/html/render/sidebar.rs47
-rw-r--r--src/librustdoc/html/sources.rs57
-rw-r--r--src/librustdoc/html/static/css/noscript.css4
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css150
-rw-r--r--src/librustdoc/html/static/js/main.js9
-rw-r--r--src/librustdoc/html/static/js/scrape-examples.js7
-rw-r--r--src/librustdoc/html/templates/scraped_source.html33
-rw-r--r--src/librustdoc/html/templates/source.html14
-rw-r--r--src/tools/run-make-support/src/command.rs85
-rw-r--r--src/tools/run-make-support/src/external_deps/llvm.rs7
-rw-r--r--src/tools/run-make-support/src/external_deps/rustc.rs6
-rw-r--r--src/tools/run-make-support/src/external_deps/rustdoc.rs6
-rw-r--r--src/tools/run-make-support/src/lib.rs2
15 files changed, 354 insertions, 191 deletions
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index dc34c7844bf..f67d006116c 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -1794,13 +1794,64 @@ fn render_impl(
     let mut default_impl_items = Buffer::empty_from(w);
     let impl_ = i.inner_impl();
 
+    // Impl items are grouped by kinds:
+    //
+    // 1. Constants
+    // 2. Types
+    // 3. Functions
+    //
+    // This order is because you can have associated constants used in associated types (like array
+    // length), and both in associcated functions. So with this order, when reading from top to
+    // bottom, you should see items definitions before they're actually used most of the time.
+    let mut assoc_types = Vec::new();
+    let mut methods = Vec::new();
+
     if !impl_.is_negative_trait_impl() {
         for trait_item in &impl_.items {
+            match *trait_item.kind {
+                clean::MethodItem(..) | clean::TyMethodItem(_) => methods.push(trait_item),
+                clean::TyAssocTypeItem(..) | clean::AssocTypeItem(..) => {
+                    assoc_types.push(trait_item)
+                }
+                clean::TyAssocConstItem(..) | clean::AssocConstItem(_) => {
+                    // We render it directly since they're supposed to come first.
+                    doc_impl_item(
+                        &mut default_impl_items,
+                        &mut impl_items,
+                        cx,
+                        trait_item,
+                        if trait_.is_some() { &i.impl_item } else { parent },
+                        link,
+                        render_mode,
+                        false,
+                        trait_,
+                        rendering_params,
+                    );
+                }
+                _ => {}
+            }
+        }
+
+        for assoc_type in assoc_types {
             doc_impl_item(
                 &mut default_impl_items,
                 &mut impl_items,
                 cx,
-                trait_item,
+                assoc_type,
+                if trait_.is_some() { &i.impl_item } else { parent },
+                link,
+                render_mode,
+                false,
+                trait_,
+                rendering_params,
+            );
+        }
+        for method in methods {
+            doc_impl_item(
+                &mut default_impl_items,
+                &mut impl_items,
+                cx,
+                method,
                 if trait_.is_some() { &i.impl_item } else { parent },
                 link,
                 render_mode,
@@ -2455,28 +2506,6 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &mut Context<'_>, item: &c
         let needs_expansion = line_max - line_min > NUM_VISIBLE_LINES;
         let locations_encoded = serde_json::to_string(&line_ranges).unwrap();
 
-        write!(
-            &mut w,
-            "<div class=\"scraped-example {expanded_cls}\" data-locs=\"{locations}\">\
-                <div class=\"scraped-example-title\">\
-                   {name} (<a href=\"{url}\">{title}</a>)\
-                </div>\
-                <div class=\"code-wrapper\">",
-            expanded_cls = if needs_expansion { "" } else { "expanded" },
-            name = call_data.display_name,
-            url = init_url,
-            title = init_title,
-            // The locations are encoded as a data attribute, so they can be read
-            // later by the JS for interactions.
-            locations = Escape(&locations_encoded)
-        )
-        .unwrap();
-
-        if line_ranges.len() > 1 {
-            w.write_str(r#"<button class="prev">&pr;</button> <button class="next">&sc;</button>"#)
-                .unwrap();
-        }
-
         // Look for the example file in the source map if it exists, otherwise return a dummy span
         let file_span = (|| {
             let source_map = tcx.sess.source_map();
@@ -2507,9 +2536,16 @@ fn render_call_locations<W: fmt::Write>(mut w: W, cx: &mut Context<'_>, item: &c
             cx,
             &cx.root_path(),
             highlight::DecorationInfo(decoration_info),
-            sources::SourceContext::Embedded { offset: line_min, needs_expansion },
+            sources::SourceContext::Embedded(sources::ScrapedInfo {
+                needs_prev_next_buttons: line_ranges.len() > 1,
+                needs_expansion,
+                offset: line_min,
+                name: &call_data.display_name,
+                url: init_url,
+                title: init_title,
+                locations: locations_encoded,
+            }),
         );
-        w.write_str("</div></div>").unwrap();
 
         true
     };
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index a0a72d59d12..cda5409a460 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -843,55 +843,55 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
         }
     }
 
-    if !required_types.is_empty() {
+    if !required_consts.is_empty() {
         write_section_heading(
             w,
-            "Required Associated Types",
-            "required-associated-types",
+            "Required Associated Constants",
+            "required-associated-consts",
             None,
             "<div class=\"methods\">",
         );
-        for t in required_types {
+        for t in required_consts {
             trait_item(w, cx, t, it);
         }
         w.write_str("</div>");
     }
-    if !provided_types.is_empty() {
+    if !provided_consts.is_empty() {
         write_section_heading(
             w,
-            "Provided Associated Types",
-            "provided-associated-types",
+            "Provided Associated Constants",
+            "provided-associated-consts",
             None,
             "<div class=\"methods\">",
         );
-        for t in provided_types {
+        for t in provided_consts {
             trait_item(w, cx, t, it);
         }
         w.write_str("</div>");
     }
 
-    if !required_consts.is_empty() {
+    if !required_types.is_empty() {
         write_section_heading(
             w,
-            "Required Associated Constants",
-            "required-associated-consts",
+            "Required Associated Types",
+            "required-associated-types",
             None,
             "<div class=\"methods\">",
         );
-        for t in required_consts {
+        for t in required_types {
             trait_item(w, cx, t, it);
         }
         w.write_str("</div>");
     }
-    if !provided_consts.is_empty() {
+    if !provided_types.is_empty() {
         write_section_heading(
             w,
-            "Provided Associated Constants",
-            "provided-associated-consts",
+            "Provided Associated Types",
+            "provided-associated-types",
             None,
             "<div class=\"methods\">",
         );
-        for t in provided_consts {
+        for t in provided_types {
             trait_item(w, cx, t, it);
         }
         w.write_str("</div>");
diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs
index c75c28ef0a3..06cc57b2a55 100644
--- a/src/librustdoc/html/render/sidebar.rs
+++ b/src/librustdoc/html/render/sidebar.rs
@@ -302,10 +302,10 @@ fn sidebar_trait<'a>(
 
     blocks.extend(
         [
-            ("required-associated-types", "Required Associated Types", req_assoc),
-            ("provided-associated-types", "Provided Associated Types", prov_assoc),
             ("required-associated-consts", "Required Associated Constants", req_assoc_const),
             ("provided-associated-consts", "Provided Associated Constants", prov_assoc_const),
+            ("required-associated-types", "Required Associated Types", req_assoc),
+            ("provided-associated-types", "Provided Associated Types", prov_assoc),
             ("required-methods", "Required Methods", req_method),
             ("provided-methods", "Provided Methods", prov_method),
             ("foreign-impls", "Implementations on Foreign Types", foreign_impls),
@@ -394,6 +394,7 @@ fn sidebar_assoc_items<'a>(
     let cache = cx.cache();
 
     let mut assoc_consts = Vec::new();
+    let mut assoc_types = Vec::new();
     let mut methods = Vec::new();
     if let Some(v) = cache.impls.get(&did) {
         let mut used_links = FxHashSet::default();
@@ -401,22 +402,14 @@ fn sidebar_assoc_items<'a>(
 
         {
             let used_links_bor = &mut used_links;
-            assoc_consts.extend(
-                v.iter()
-                    .filter(|i| i.inner_impl().trait_.is_none())
-                    .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor)),
-            );
+            for impl_ in v.iter().map(|i| i.inner_impl()).filter(|i| i.trait_.is_none()) {
+                assoc_consts.extend(get_associated_constants(impl_, used_links_bor));
+                assoc_types.extend(get_associated_types(impl_, used_links_bor));
+                methods.extend(get_methods(impl_, false, used_links_bor, false, cx.tcx()));
+            }
             // We want links' order to be reproducible so we don't use unstable sort.
             assoc_consts.sort();
-
-            #[rustfmt::skip] // rustfmt makes the pipeline less readable
-            methods.extend(
-                v.iter()
-                    .filter(|i| i.inner_impl().trait_.is_none())
-                    .flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx())),
-            );
-
-            // We want links' order to be reproducible so we don't use unstable sort.
+            assoc_types.sort();
             methods.sort();
         }
 
@@ -426,6 +419,11 @@ fn sidebar_assoc_items<'a>(
                 "associatedconstant",
                 assoc_consts,
             ),
+            LinkBlock::new(
+                Link::new("implementations", "Associated Types"),
+                "associatedtype",
+                assoc_types,
+            ),
             LinkBlock::new(Link::new("implementations", "Methods"), "method", methods),
         ];
 
@@ -452,6 +450,7 @@ fn sidebar_assoc_items<'a>(
                 &mut blocks,
             );
         }
+
         links.append(&mut blocks);
     }
 }
@@ -715,3 +714,19 @@ fn get_associated_constants<'a>(
         })
         .collect::<Vec<_>>()
 }
+
+fn get_associated_types<'a>(
+    i: &'a clean::Impl,
+    used_links: &mut FxHashSet<String>,
+) -> Vec<Link<'a>> {
+    i.items
+        .iter()
+        .filter_map(|item| match item.name {
+            Some(ref name) if !name.is_empty() && item.is_associated_type() => Some(Link::new(
+                get_next_url(used_links, format!("{typ}.{name}", typ = ItemType::AssocType)),
+                name.as_str(),
+            )),
+            _ => None,
+        })
+        .collect::<Vec<_>>()
+}
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
index ae65dd76b5f..551bb56685c 100644
--- a/src/librustdoc/html/sources.rs
+++ b/src/librustdoc/html/sources.rs
@@ -290,9 +290,34 @@ where
     }
 }
 
-pub(crate) enum SourceContext {
+pub(crate) struct ScrapedInfo<'a> {
+    pub(crate) offset: usize,
+    pub(crate) needs_prev_next_buttons: bool,
+    pub(crate) name: &'a str,
+    pub(crate) url: &'a str,
+    pub(crate) title: &'a str,
+    pub(crate) locations: String,
+    pub(crate) needs_expansion: bool,
+}
+
+#[derive(Template)]
+#[template(path = "scraped_source.html")]
+struct ScrapedSource<'a, Code: std::fmt::Display> {
+    info: ScrapedInfo<'a>,
+    lines: RangeInclusive<usize>,
+    code_html: Code,
+}
+
+#[derive(Template)]
+#[template(path = "source.html")]
+struct Source<Code: std::fmt::Display> {
+    lines: RangeInclusive<usize>,
+    code_html: Code,
+}
+
+pub(crate) enum SourceContext<'a> {
     Standalone,
-    Embedded { offset: usize, needs_expansion: bool },
+    Embedded(ScrapedInfo<'a>),
 }
 
 /// Wrapper struct to render the source code of a file. This will do things like
@@ -304,23 +329,8 @@ pub(crate) fn print_src(
     context: &Context<'_>,
     root_path: &str,
     decoration_info: highlight::DecorationInfo,
-    source_context: SourceContext,
+    source_context: SourceContext<'_>,
 ) {
-    #[derive(Template)]
-    #[template(path = "source.html")]
-    struct Source<Code: std::fmt::Display> {
-        embedded: bool,
-        needs_expansion: bool,
-        lines: RangeInclusive<usize>,
-        code_html: Code,
-    }
-    let lines = s.lines().count();
-    let (embedded, needs_expansion, lines) = match source_context {
-        SourceContext::Standalone => (false, false, 1..=lines),
-        SourceContext::Embedded { offset, needs_expansion } => {
-            (true, needs_expansion, (1 + offset)..=(lines + offset))
-        }
-    };
     let current_href = context
         .href_from_span(clean::Span::new(file_span), false)
         .expect("only local crates should have sources emitted");
@@ -333,5 +343,14 @@ pub(crate) fn print_src(
         );
         Ok(())
     });
-    Source { embedded, needs_expansion, lines, code_html: code }.render_into(&mut writer).unwrap();
+    let lines = s.lines().count();
+    match source_context {
+        SourceContext::Standalone => {
+            Source { lines: (1..=lines), code_html: code }.render_into(&mut writer).unwrap()
+        }
+        SourceContext::Embedded(info) => {
+            let lines = (1 + info.offset)..=(lines + info.offset);
+            ScrapedSource { info, lines, code_html: code }.render_into(&mut writer).unwrap();
+        }
+    };
 }
diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css
index 86e8edad703..e62b16267f1 100644
--- a/src/librustdoc/html/static/css/noscript.css
+++ b/src/librustdoc/html/static/css/noscript.css
@@ -59,6 +59,8 @@ nav.sub {
 	--copy-path-button-color: #999;
 	--copy-path-img-filter: invert(50%);
 	--copy-path-img-hover-filter: invert(35%);
+	--code-example-button-color: #7f7f7f;
+	--code-example-button-hover-color: #595959;
 	--codeblock-error-hover-color: rgb(255, 0, 0);
 	--codeblock-error-color: rgba(255, 0, 0, .5);
 	--codeblock-ignore-hover-color: rgb(255, 142, 0);
@@ -162,6 +164,8 @@ nav.sub {
 		--copy-path-button-color: #999;
 		--copy-path-img-filter: invert(50%);
 		--copy-path-img-hover-filter: invert(65%);
+		--code-example-button-color: #7f7f7f;
+		--code-example-button-hover-color: #a5a5a5;
 		--codeblock-error-hover-color: rgb(255, 0, 0);
 		--codeblock-error-color: rgba(255, 0, 0, .5);
 		--codeblock-ignore-hover-color: rgb(255, 142, 0);
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 271de1b1ccf..38154dee3e2 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -378,7 +378,7 @@ pre.item-decl {
 .src .content pre {
 	padding: 20px;
 }
-.rustdoc.src .example-wrap pre.src-line-numbers  {
+.rustdoc.src .example-wrap .src-line-numbers  {
 	padding: 20px 0 20px 4px;
 }
 
@@ -757,10 +757,32 @@ ul.block, .block li, .block ul {
 	margin-bottom: 10px;
 }
 
-.rustdoc .example-wrap > pre {
+.rustdoc .example-wrap > pre,
+.rustdoc .scraped-example .src-line-numbers,
+.rustdoc .scraped-example .src-line-numbers > pre {
 	border-radius: 6px;
 }
 
+/*
+If the code example line numbers are displayed, there will be a weird radius in the middle from
+both the code example and the line numbers, so we need to remove the radius in this case.
+*/
+.rustdoc .example-wrap > .example-line-numbers,
+.rustdoc .scraped-example .src-line-numbers,
+.rustdoc .scraped-example .src-line-numbers > pre {
+	border-top-right-radius: 0;
+	border-bottom-right-radius: 0;
+}
+.rustdoc .example-wrap > .example-line-numbers + pre,
+.rustdoc .scraped-example .rust {
+	border-top-left-radius: 0;
+	border-bottom-left-radius: 0;
+}
+
+.rustdoc .scraped-example {
+	position: relative;
+}
+
 /* For the last child of a div, the margin will be taken care of
 	by the margin-top of the next item. */
 .rustdoc .example-wrap:last-child {
@@ -772,15 +794,36 @@ ul.block, .block li, .block ul {
 	flex-grow: 1;
 }
 
-.rustdoc:not(.src) .example-wrap pre {
+.scraped-example:not(.expanded) .example-wrap {
+	/* scrape-examples.js has a constant DEFAULT_MAX_LINES (call it N) for the number
+	 * of lines shown in the un-expanded example code viewer. This pre needs to have
+	 * a max-height equal to line-height * N. The line-height is currently 1.5em,
+	 * and we include additional 10px for padding. */
+	max-height: calc(1.5em * 5 + 10px);
+}
+
+.rustdoc:not(.src) .scraped-example:not(.expanded) .src-line-numbers,
+.rustdoc:not(.src) .scraped-example:not(.expanded) .src-line-numbers > pre,
+.rustdoc:not(.src) .scraped-example:not(.expanded) pre.rust {
+	padding-bottom: 0;
+	/* See above comment, should be the same max-height. */
 	overflow: auto hidden;
 }
+.rustdoc:not(.src) .scraped-example .src-line-numbers {
+	padding-top: 0;
+}
+.rustdoc:not(.src) .scraped-example.expanded .src-line-numbers {
+	padding-bottom: 0;
+}
+
+.rustdoc:not(.src) .example-wrap pre {
+	overflow: auto;
+}
 
 .rustdoc .example-wrap pre.example-line-numbers,
-.rustdoc .example-wrap pre.src-line-numbers {
-	flex-grow: 0;
+.rustdoc .example-wrap .src-line-numbers {
 	min-width: fit-content; /* prevent collapsing into nothing in truncated scraped examples */
-	overflow: initial;
+	flex-grow: 0;
 	text-align: right;
 	-webkit-user-select: none;
 	user-select: none;
@@ -788,7 +831,7 @@ ul.block, .block li, .block ul {
 	color: var(--src-line-numbers-span-color);
 }
 
-.rustdoc .example-wrap pre.src-line-numbers {
+.rustdoc .scraped-example .src-line-numbers {
 	padding: 14px 0;
 }
 .src-line-numbers a, .src-line-numbers span {
@@ -1500,17 +1543,23 @@ instead, we check that it's not a "finger" cursor.
 .example-wrap .button-holder.keep-visible {
 	visibility: visible;
 }
-.example-wrap .button-holder .copy-button, .example-wrap .test-arrow {
+.example-wrap .button-holder > * {
 	background: var(--main-background-color);
 	cursor: pointer;
 	border-radius: var(--button-border-radius);
 	height: var(--copy-path-height);
 	width: var(--copy-path-width);
+	border: 0;
+	color: var(--code-example-button-color);
 }
-.example-wrap .button-holder .copy-button {
+.example-wrap .button-holder > *:hover {
+	color: var(--code-example-button-hover-color);
+}
+.example-wrap .button-holder > *:not(:first-child) {
 	margin-left: var(--button-left-margin);
+}
+.example-wrap .button-holder .copy-button {
 	padding: 2px 0 0 4px;
-	border: 0;
 }
 .example-wrap .button-holder .copy-button::before,
 .example-wrap .test-arrow::before {
@@ -2254,6 +2303,7 @@ in src-script.js and main.js
 	}
 }
 
+
 /* Should have min-width: (N + 1)px where N is the mobile breakpoint above. */
 @media (min-width: 701px) {
 	/* Places file-link for a scraped example on top of the example to save space.
@@ -2356,99 +2406,41 @@ in src-script.js and main.js
 	color: var(--scrape-example-help-hover-color);
 }
 
-.scraped-example {
-	/* So .scraped-example-title can be positioned absolutely */
-	position: relative;
-}
-
-.scraped-example .code-wrapper {
-	position: relative;
-	display: flex;
-	flex-direction: row;
-	flex-wrap: wrap;
-	width: 100%;
-}
-
-.scraped-example:not(.expanded) .code-wrapper {
-	/* scrape-examples.js has a constant DEFAULT_MAX_LINES (call it N) for the number
-	 * of lines shown in the un-expanded example code viewer. This pre needs to have
-	 * a max-height equal to line-height * N. The line-height is currently 1.5em,
-	 * and we include additional 10px for padding. */
-	 max-height: calc(1.5em * 5 + 10px);
-}
-
-.scraped-example:not(.expanded) .code-wrapper pre {
-	overflow-y: hidden;
-	padding-bottom: 0;
-	/* See above comment, should be the same max-height. */
-	max-height: calc(1.5em * 5 + 10px);
-}
-
-.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper,
-.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper pre {
-	/* See above comment, except this height is based on HIDDEN_MAX_LINES. */
-	max-height: calc(1.5em * 10 + 10px);
-}
-
-.scraped-example .code-wrapper .next,
-.scraped-example .code-wrapper .prev,
-.scraped-example .code-wrapper .expand {
-	color: var(--main-color);
-	position: absolute;
-	top: 0.25em;
-	z-index: 1;
-	padding: 0;
-	background: none;
-	border: none;
-	/* iOS button gradient: https://stackoverflow.com/q/5438567 */
-	-webkit-appearance: none;
-	opacity: 1;
-}
-.scraped-example .code-wrapper .prev {
-	right: 2.25em;
-}
-.scraped-example .code-wrapper .next {
-	right: 1.25em;
-}
-.scraped-example .code-wrapper .expand {
-	right: 0.25em;
-}
-
-.scraped-example:not(.expanded) .code-wrapper::before,
-.scraped-example:not(.expanded) .code-wrapper::after {
+.scraped-example:not(.expanded) .example-wrap::before,
+.scraped-example:not(.expanded) .example-wrap::after {
 	content: " ";
 	width: 100%;
 	height: 5px;
 	position: absolute;
 	z-index: 1;
 }
-.scraped-example:not(.expanded) .code-wrapper::before {
+.scraped-example:not(.expanded) .example-wrap::before {
 	top: 0;
 	background: linear-gradient(to bottom,
 		var(--scrape-example-code-wrapper-background-start),
 		var(--scrape-example-code-wrapper-background-end));
 }
-.scraped-example:not(.expanded) .code-wrapper::after {
+.scraped-example:not(.expanded) .example-wrap::after {
 	bottom: 0;
 	background: linear-gradient(to top,
 		var(--scrape-example-code-wrapper-background-start),
 		var(--scrape-example-code-wrapper-background-end));
 }
 
-.scraped-example .code-wrapper .example-wrap {
+.scraped-example:not(.expanded) {
 	width: 100%;
 	overflow-y: hidden;
 	margin-bottom: 0;
 }
 
-.scraped-example:not(.expanded) .code-wrapper .example-wrap {
+.scraped-example:not(.expanded) {
 	overflow-x: hidden;
 }
 
-.scraped-example .example-wrap .rust span.highlight {
+.scraped-example .rust span.highlight {
 	background: var(--scrape-example-code-line-highlight);
 }
-.scraped-example .example-wrap .rust span.highlight.focus {
+.scraped-example .rust span.highlight.focus {
 	background: var(--scrape-example-code-line-highlight-focus);
 }
 
@@ -2542,6 +2534,8 @@ by default.
 	--copy-path-button-color: #999;
 	--copy-path-img-filter: invert(50%);
 	--copy-path-img-hover-filter: invert(35%);
+	--code-example-button-color: #7f7f7f;
+	--code-example-button-hover-color: #595959;
 	--codeblock-error-hover-color: rgb(255, 0, 0);
 	--codeblock-error-color: rgba(255, 0, 0, .5);
 	--codeblock-ignore-hover-color: rgb(255, 142, 0);
@@ -2644,6 +2638,8 @@ by default.
 	--copy-path-button-color: #999;
 	--copy-path-img-filter: invert(50%);
 	--copy-path-img-hover-filter: invert(65%);
+	--code-example-button-color: #7f7f7f;
+	--code-example-button-hover-color: #a5a5a5;
 	--codeblock-error-hover-color: rgb(255, 0, 0);
 	--codeblock-error-color: rgba(255, 0, 0, .5);
 	--codeblock-ignore-hover-color: rgb(255, 142, 0);
@@ -2753,6 +2749,8 @@ Original by Dempfi (https://github.com/dempfi/ayu)
 	--copy-path-button-color: #fff;
 	--copy-path-img-filter: invert(70%);
 	--copy-path-img-hover-filter: invert(100%);
+	--code-example-button-color: #b2b2b2;
+	--code-example-button-hover-color: #fff;
 	--codeblock-error-hover-color: rgb(255, 0, 0);
 	--codeblock-error-color: rgba(255, 0, 0, .5);
 	--codeblock-ignore-hover-color: rgb(255, 142, 0);
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index 0291b6ed6f5..858753a1917 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -1855,8 +1855,13 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.htm
         // Since the button will be added, no need to keep this listener around.
         elem.removeEventListener("mouseover", addCopyButton);
 
-        const parent = document.createElement("div");
-        parent.className = "button-holder";
+        // If this is a scrapped example, there will already be a "button-holder" element.
+        let parent = elem.querySelector(".button-holder");
+        if (!parent) {
+            parent = document.createElement("div");
+            parent.className = "button-holder";
+        }
+
         const runButton = elem.querySelector(".test-arrow");
         if (runButton !== null) {
             // If there is a run button, we move it into the same div.
diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js
index 7a3a9c5f340..06e42814d33 100644
--- a/src/librustdoc/html/static/js/scrape-examples.js
+++ b/src/librustdoc/html/static/js/scrape-examples.js
@@ -13,7 +13,7 @@
 
     // Scroll code block to the given code location
     function scrollToLoc(elt, loc, isHidden) {
-        const lines = elt.querySelector(".src-line-numbers");
+        const lines = elt.querySelector(".src-line-numbers > pre");
         let scrollOffset;
 
         // If the block is greater than the size of the viewer,
@@ -24,8 +24,7 @@
             const line = Math.max(0, loc[0] - 1);
             scrollOffset = lines.children[line].offsetTop;
         } else {
-            const wrapper = elt.querySelector(".code-wrapper");
-            const halfHeight = wrapper.offsetHeight / 2;
+            const halfHeight = elt.offsetHeight / 2;
             const offsetTop = lines.children[loc[0]].offsetTop;
             const lastLine = lines.children[loc[1]];
             const offsetBot = lastLine.offsetTop + lastLine.offsetHeight;
@@ -33,7 +32,7 @@
             scrollOffset = offsetMid - halfHeight;
         }
 
-        lines.scrollTo(0, scrollOffset);
+        lines.parentElement.scrollTo(0, scrollOffset);
         elt.querySelector(".rust").scrollTo(0, scrollOffset);
     }
 
diff --git a/src/librustdoc/html/templates/scraped_source.html b/src/librustdoc/html/templates/scraped_source.html
new file mode 100644
index 00000000000..e1fc2e69378
--- /dev/null
+++ b/src/librustdoc/html/templates/scraped_source.html
@@ -0,0 +1,33 @@
+<div class="scraped-example{% if !info.needs_expansion +%} expanded{% endif %}" data-locs="{{info.locations}}"> {# #}
+    <div class="scraped-example-title">
+       {{info.name +}} (<a href="{{info.url}}">{{info.title}}</a>) {# #}
+    </div>
+    <div class="example-wrap"> {# #}
+        {# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
+           Do not show "1 2 3 4 5 ..." in web search results. #}
+        <div class="src-line-numbers" data-nosnippet> {# #}
+            <pre>
+                {% for line in lines.clone() %}
+                    {# ~#}
+                    <span>{{line|safe}}</span>
+                {% endfor %}
+            </pre> {# #}
+        </div> {# #}
+        <pre class="rust"> {# #}
+            <code>
+                {{code_html|safe}}
+            </code> {# #}
+        </pre> {# #}
+        {% if info.needs_prev_next_buttons || info.needs_expansion %}
+            <div class="button-holder">
+                {% if info.needs_prev_next_buttons %}
+                    <button class="prev">&pr;</button> {# #}
+                    <button class="next">&sc;</button>
+                {% endif %}
+                {% if info.needs_expansion %}
+                    <button class="expand">&varr;</button>
+                {% endif %}
+            </div>
+        {% endif %}
+    </div> {# #}
+</div> {# #}
diff --git a/src/librustdoc/html/templates/source.html b/src/librustdoc/html/templates/source.html
index 42d01277db2..60a47f1b5de 100644
--- a/src/librustdoc/html/templates/source.html
+++ b/src/librustdoc/html/templates/source.html
@@ -1,21 +1,15 @@
-<div class="example-wrap"> {# #}
+<div class="example-wrap">
     {# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
        Do not show "1 2 3 4 5 ..." in web search results. #}
     <div data-nosnippet><pre class="src-line-numbers">
         {% for line in lines.clone() %}
-            {% if embedded %}
-                <span>{{line|safe}}</span>
-            {%~ else %}
-                <a href="#{{line|safe}}" id="{{line|safe}}">{{line|safe}}</a>
-            {%~ endif %}
+            {# ~#}
+            <a href="#{{line|safe}}" id="{{line|safe}}">{{line|safe}}</a>
         {% endfor %}
     </pre></div> {# #}
     <pre class="rust"> {# #}
         <code>
-            {% if needs_expansion %}
-                <button class="expand">&varr;</button>
-            {% endif %}
             {{code_html|safe}}
         </code> {# #}
     </pre> {# #}
-</div>
+</div> {# #}
diff --git a/src/tools/run-make-support/src/command.rs b/src/tools/run-make-support/src/command.rs
index a0b96e25a0c..6b58173b343 100644
--- a/src/tools/run-make-support/src/command.rs
+++ b/src/tools/run-make-support/src/command.rs
@@ -15,7 +15,7 @@ use crate::{
 /// This is a custom command wrapper that simplifies working with commands and makes it easier to
 /// ensure that we check the exit status of executed processes.
 ///
-/// # A [`Command`] must be executed
+/// # A [`Command`] must be executed exactly once
 ///
 /// A [`Command`] is armed by a [`DropBomb`] on construction to enforce that it will be executed. If
 /// a [`Command`] is constructed but never executed, the drop bomb will explode and cause the test
@@ -23,26 +23,73 @@ use crate::{
 /// containing constructed but never executed commands is dangerous because it can give a false
 /// sense of confidence.
 ///
+/// Each [`Command`] invocation can also only be executed once, because we want to enforce
+/// `std{in,out,err}` config via [`std::process::Stdio`] but [`std::process::Stdio`] is not
+/// cloneable.
+///
+/// In this sense, [`Command`] exhibits linear type semantics but enforced at run-time.
+///
 /// [`run`]: Self::run
 /// [`run_fail`]: Self::run_fail
 /// [`run_unchecked`]: Self::run_unchecked
 #[derive(Debug)]
 pub struct Command {
     cmd: StdCommand,
-    stdin: Option<Box<[u8]>>,
+    // Convience for providing a quick stdin buffer.
+    stdin_buf: Option<Box<[u8]>>,
+
+    // Configurations for child process's std{in,out,err} handles.
+    stdin: Option<Stdio>,
+    stdout: Option<Stdio>,
+    stderr: Option<Stdio>,
+
+    // Emulate linear type semantics.
     drop_bomb: DropBomb,
+    already_executed: bool,
 }
 
 impl Command {
     #[track_caller]
     pub fn new<P: AsRef<OsStr>>(program: P) -> Self {
         let program = program.as_ref();
-        Self { cmd: StdCommand::new(program), stdin: None, drop_bomb: DropBomb::arm(program) }
+        Self {
+            cmd: StdCommand::new(program),
+            stdin_buf: None,
+            drop_bomb: DropBomb::arm(program),
+            stdin: None,
+            stdout: None,
+            stderr: None,
+            already_executed: false,
+        }
     }
 
-    /// Specify a stdin input
-    pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
-        self.stdin = Some(input.as_ref().to_vec().into_boxed_slice());
+    /// Specify a stdin input buffer. This is a convenience helper,
+    pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
+        self.stdin_buf = Some(input.as_ref().to_vec().into_boxed_slice());
+        self
+    }
+
+    /// Configuration for the child process’s standard input (stdin) handle.
+    ///
+    /// See [`std::process::Command::stdin`].
+    pub fn stdin<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
+        self.stdin = Some(cfg.into());
+        self
+    }
+
+    /// Configuration for the child process’s standard output (stdout) handle.
+    ///
+    /// See [`std::process::Command::stdout`].
+    pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
+        self.stdout = Some(cfg.into());
+        self
+    }
+
+    /// Configuration for the child process’s standard error (stderr) handle.
+    ///
+    /// See [`std::process::Command::stderr`].
+    pub fn stderr<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Self {
+        self.stderr = Some(cfg.into());
         self
     }
 
@@ -105,6 +152,8 @@ impl Command {
     }
 
     /// Run the constructed command and assert that it is successfully run.
+    ///
+    /// By default, std{in,out,err} are [`Stdio::piped()`].
     #[track_caller]
     pub fn run(&mut self) -> CompletedProcess {
         let output = self.command_output();
@@ -115,6 +164,8 @@ impl Command {
     }
 
     /// Run the constructed command and assert that it does not successfully run.
+    ///
+    /// By default, std{in,out,err} are [`Stdio::piped()`].
     #[track_caller]
     pub fn run_fail(&mut self) -> CompletedProcess {
         let output = self.command_output();
@@ -124,10 +175,10 @@ impl Command {
         output
     }
 
-    /// Run the command but do not check its exit status.
-    /// Only use if you explicitly don't care about the exit status.
-    /// Prefer to use [`Self::run`] and [`Self::run_fail`]
-    /// whenever possible.
+    /// Run the command but do not check its exit status. Only use if you explicitly don't care
+    /// about the exit status.
+    ///
+    /// Prefer to use [`Self::run`] and [`Self::run_fail`] whenever possible.
     #[track_caller]
     pub fn run_unchecked(&mut self) -> CompletedProcess {
         self.command_output()
@@ -135,13 +186,19 @@ impl Command {
 
     #[track_caller]
     fn command_output(&mut self) -> CompletedProcess {
+        if self.already_executed {
+            panic!("command was already executed");
+        } else {
+            self.already_executed = true;
+        }
+
         self.drop_bomb.defuse();
         // let's make sure we piped all the input and outputs
-        self.cmd.stdin(Stdio::piped());
-        self.cmd.stdout(Stdio::piped());
-        self.cmd.stderr(Stdio::piped());
+        self.cmd.stdin(self.stdin.take().unwrap_or(Stdio::piped()));
+        self.cmd.stdout(self.stdout.take().unwrap_or(Stdio::piped()));
+        self.cmd.stderr(self.stderr.take().unwrap_or(Stdio::piped()));
 
-        let output = if let Some(input) = &self.stdin {
+        let output = if let Some(input) = &self.stdin_buf {
             let mut child = self.cmd.spawn().unwrap();
 
             {
diff --git a/src/tools/run-make-support/src/external_deps/llvm.rs b/src/tools/run-make-support/src/external_deps/llvm.rs
index e9315856cd7..2522c4aeb93 100644
--- a/src/tools/run-make-support/src/external_deps/llvm.rs
+++ b/src/tools/run-make-support/src/external_deps/llvm.rs
@@ -227,9 +227,10 @@ impl LlvmFilecheck {
         Self { cmd }
     }
 
-    /// Pipe a read file into standard input containing patterns that will be matched against the .patterns(path) call.
-    pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
-        self.cmd.stdin(input);
+    /// Provide a buffer representing standard input containing patterns that will be matched
+    /// against the `.patterns(path)` call.
+    pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
+        self.cmd.stdin_buf(input);
         self
     }
 
diff --git a/src/tools/run-make-support/src/external_deps/rustc.rs b/src/tools/run-make-support/src/external_deps/rustc.rs
index cece58d2956..f60ea972839 100644
--- a/src/tools/run-make-support/src/external_deps/rustc.rs
+++ b/src/tools/run-make-support/src/external_deps/rustc.rs
@@ -291,9 +291,9 @@ impl Rustc {
         self
     }
 
-    /// Specify a stdin input
-    pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
-        self.cmd.stdin(input);
+    /// Specify a stdin input buffer.
+    pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
+        self.cmd.stdin_buf(input);
         self
     }
 
diff --git a/src/tools/run-make-support/src/external_deps/rustdoc.rs b/src/tools/run-make-support/src/external_deps/rustdoc.rs
index 96b1c719e2e..df5e5d8a2e8 100644
--- a/src/tools/run-make-support/src/external_deps/rustdoc.rs
+++ b/src/tools/run-make-support/src/external_deps/rustdoc.rs
@@ -85,9 +85,9 @@ impl Rustdoc {
         self
     }
 
-    /// Specify a stdin input
-    pub fn stdin<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
-        self.cmd.stdin(input);
+    /// Specify a stdin input buffer.
+    pub fn stdin_buf<I: AsRef<[u8]>>(&mut self, input: I) -> &mut Self {
+        self.cmd.stdin_buf(input);
         self
     }
 
diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index 989d00d4c2f..fefafb95b33 100644
--- a/src/tools/run-make-support/src/lib.rs
+++ b/src/tools/run-make-support/src/lib.rs
@@ -34,6 +34,7 @@ pub mod rfs {
 }
 
 // Re-exports of third-party library crates.
+// tidy-alphabetical-start
 pub use bstr;
 pub use gimli;
 pub use libc;
@@ -41,6 +42,7 @@ pub use object;
 pub use regex;
 pub use serde_json;
 pub use wasmparser;
+// tidy-alphabetical-end
 
 // Re-exports of external dependencies.
 pub use external_deps::{c_build, cc, clang, htmldocck, llvm, python, rustc, rustdoc};