From 07bb2f701e5bae0da724068bbce1920458df9cda Mon Sep 17 00:00:00 2001
From: Michael Howell <michael@notriddle.com>
Date: Thu, 27 Oct 2022 11:16:30 -0700
Subject: [PATCH 01/12] rustdoc: change `.src-line-numbers > span` to
 `.src-line-numbers > a`

This allows people to treat them like real links, such as right-click to
copy URL, and makes the line numbers in a scraped example work at all,
when before this commit was added, they had the clickable pointer cursor
but did not actually do anything when clicked.
---
 src/librustdoc/html/render/mod.rs             | 11 +++---
 src/librustdoc/html/sources.rs                | 21 +++++-----
 src/librustdoc/html/static/css/rustdoc.css    |  5 +--
 .../html/static/js/source-script.js           | 11 ++++--
 src/test/rustdoc-gui/source-code-page.goml    | 39 ++++++++++++++-----
 .../rustdoc/check-source-code-urls-to-def.rs  | 32 +++++++--------
 6 files changed, 72 insertions(+), 47 deletions(-)

diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 27dea8ec0b312..e09106077f7ed 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -2888,9 +2888,6 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
         })()
         .unwrap_or(rustc_span::DUMMY_SP);
 
-        // The root path is the inverse of Context::current
-        let root_path = vec!["../"; cx.current.len() - 1].join("");
-
         let mut decoration_info = FxHashMap::default();
         decoration_info.insert("highlight focus", vec![byte_ranges.remove(0)]);
         decoration_info.insert("highlight", byte_ranges);
@@ -2900,9 +2897,13 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
             contents_subset,
             file_span,
             cx,
-            &root_path,
+            &cx.root_path(),
             highlight::DecorationInfo(decoration_info),
-            sources::SourceContext::Embedded { offset: line_min, needs_expansion },
+            sources::SourceContext::Embedded {
+                url: &call_data.url,
+                offset: line_min,
+                needs_expansion,
+            },
         );
         write!(w, "</div></div>");
 
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
index 8a01c01049d6e..99b4678c4577f 100644
--- a/src/librustdoc/html/sources.rs
+++ b/src/librustdoc/html/sources.rs
@@ -256,9 +256,9 @@ where
     }
 }
 
-pub(crate) enum SourceContext {
+pub(crate) enum SourceContext<'a> {
     Standalone,
-    Embedded { offset: usize, needs_expansion: bool },
+    Embedded { url: &'a str, offset: usize, needs_expansion: bool },
 }
 
 /// Wrapper struct to render the source code of a file. This will do things like
@@ -270,31 +270,32 @@ pub(crate) fn print_src(
     context: &Context<'_>,
     root_path: &str,
     decoration_info: highlight::DecorationInfo,
-    source_context: SourceContext,
+    source_context: SourceContext<'_>,
 ) {
     let lines = s.lines().count();
     let mut line_numbers = Buffer::empty_from(buf);
     let extra;
     line_numbers.write_str("<pre class=\"src-line-numbers\">");
+    let current_href = &context
+        .href_from_span(clean::Span::new(file_span), false)
+        .expect("only local crates should have sources emitted");
     match source_context {
         SourceContext::Standalone => {
             extra = None;
             for line in 1..=lines {
-                writeln!(line_numbers, "<span id=\"{0}\">{0}</span>", line)
+                writeln!(line_numbers, "<a href=\"#{line}\" id=\"{line}\">{line}</a>")
             }
         }
-        SourceContext::Embedded { offset, needs_expansion } => {
+        SourceContext::Embedded { url, offset, needs_expansion } => {
             extra =
                 if needs_expansion { Some(r#"<span class="expand">&varr;</span>"#) } else { None };
-            for line in 1..=lines {
-                writeln!(line_numbers, "<span>{0}</span>", line + offset)
+            for line_number in 1..=lines {
+                let line = line_number + offset;
+                writeln!(line_numbers, "<a href=\"{root_path}{url}#{line}\">{line}</a>")
             }
         }
     }
     line_numbers.write_str("</pre>");
-    let current_href = &context
-        .href_from_span(clean::Span::new(file_span), false)
-        .expect("only local crates should have sources emitted");
     highlight::render_source_with_highlighting(
         s,
         buf,
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 894499e5c4fc9..df7b6e9b99a4e 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -575,8 +575,7 @@ ul.block, .block li {
 	border-color: var(--example-line-numbers-border-color);
 }
 
-.src-line-numbers span {
-	cursor: pointer;
+.src-line-numbers a {
 	color: var(--src-line-numbers-span-color);
 }
 .src-line-numbers .line-highlighted {
@@ -2060,7 +2059,7 @@ in storage.js
 	padding: 14px 0;
 }
 
-.scraped-example .code-wrapper .src-line-numbers span {
+.scraped-example .code-wrapper .src-line-numbers a {
 	padding: 0 14px;
 }
 
diff --git a/src/librustdoc/html/static/js/source-script.js b/src/librustdoc/html/static/js/source-script.js
index 0b9368dd89948..5db768c1c5753 100644
--- a/src/librustdoc/html/static/js/source-script.js
+++ b/src/librustdoc/html/static/js/source-script.js
@@ -157,7 +157,7 @@ function highlightSourceLines(match) {
         x.scrollIntoView();
     }
     onEachLazy(document.getElementsByClassName("src-line-numbers"), e => {
-        onEachLazy(e.getElementsByTagName("span"), i_e => {
+        onEachLazy(e.getElementsByTagName("a"), i_e => {
             removeClass(i_e, "line-highlighted");
         });
     });
@@ -188,8 +188,13 @@ const handleSourceHighlight = (function() {
 
     return ev => {
         let cur_line_id = parseInt(ev.target.id, 10);
-        // It can happen when clicking not on a line number span.
-        if (isNaN(cur_line_id)) {
+        // This event handler is attached to the entire line number column, but it should only
+        // be run if one of the anchors is clicked. It also shouldn't do anything if the anchor
+        // is clicked with a modifier key (to open a new browser tab).
+        if (isNaN(cur_line_id) ||
+            ev.ctrlKey ||
+            ev.altKey ||
+            ev.metaKey) {
             return;
         }
         ev.preventDefault();
diff --git a/src/test/rustdoc-gui/source-code-page.goml b/src/test/rustdoc-gui/source-code-page.goml
index a2dac2aa681d5..31d55cd7885d3 100644
--- a/src/test/rustdoc-gui/source-code-page.goml
+++ b/src/test/rustdoc-gui/source-code-page.goml
@@ -2,17 +2,17 @@
 goto: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
 show-text: true
 // Check that we can click on the line number.
-click: ".src-line-numbers > span:nth-child(4)" // This is the span for line 4.
+click: ".src-line-numbers > a:nth-child(4)" // This is the anchor for line 4.
 // Ensure that the page URL was updated.
 assert-document-property: ({"URL": "lib.rs.html#4"}, ENDS_WITH)
 assert-attribute: ("//*[@id='4']", {"class": "line-highlighted"})
-// We now check that the good spans are highlighted
+// We now check that the good anchors are highlighted
 goto: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html#4-6"
-assert-attribute-false: (".src-line-numbers > span:nth-child(3)", {"class": "line-highlighted"})
-assert-attribute: (".src-line-numbers > span:nth-child(4)", {"class": "line-highlighted"})
-assert-attribute: (".src-line-numbers > span:nth-child(5)", {"class": "line-highlighted"})
-assert-attribute: (".src-line-numbers > span:nth-child(6)", {"class": "line-highlighted"})
-assert-attribute-false: (".src-line-numbers > span:nth-child(7)", {"class": "line-highlighted"})
+assert-attribute-false: (".src-line-numbers > a:nth-child(3)", {"class": "line-highlighted"})
+assert-attribute: (".src-line-numbers > a:nth-child(4)", {"class": "line-highlighted"})
+assert-attribute: (".src-line-numbers > a:nth-child(5)", {"class": "line-highlighted"})
+assert-attribute: (".src-line-numbers > a:nth-child(6)", {"class": "line-highlighted"})
+assert-attribute-false: (".src-line-numbers > a:nth-child(7)", {"class": "line-highlighted"})
 
 define-function: (
     "check-colors",
@@ -21,12 +21,12 @@ define-function: (
         ("local-storage", {"rustdoc-theme": |theme|, "rustdoc-use-system-theme": "false"}),
         ("reload"),
         ("assert-css", (
-            ".src-line-numbers > span:not(.line-highlighted)",
+            ".src-line-numbers > a:not(.line-highlighted)",
             {"color": |color|, "background-color": |background_color|},
             ALL,
         )),
         ("assert-css", (
-            ".src-line-numbers > span.line-highlighted",
+            ".src-line-numbers > a.line-highlighted",
             {"color": |highlight_color|, "background-color": |highlight_background_color|},
             ALL,
         )),
@@ -57,6 +57,25 @@ call-function: ("check-colors", {
 
 // This is to ensure that the content is correctly align with the line numbers.
 compare-elements-position: ("//*[@id='1']", ".rust > code > span", ("y"))
+// Check the `href` property so that users can treat anchors as links.
+assert-property: (".src-line-numbers > a:nth-child(1)", {
+    "href": "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html#1"
+})
+assert-property: (".src-line-numbers > a:nth-child(2)", {
+    "href": "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html#2"
+})
+assert-property: (".src-line-numbers > a:nth-child(3)", {
+    "href": "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html#3"
+})
+assert-property: (".src-line-numbers > a:nth-child(4)", {
+    "href": "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html#4"
+})
+assert-property: (".src-line-numbers > a:nth-child(5)", {
+    "href": "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html#5"
+})
+assert-property: (".src-line-numbers > a:nth-child(6)", {
+    "href": "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html#6"
+})
 
 // Assert that the line numbers text is aligned to the right.
 assert-css: (".src-line-numbers", {"text-align": "right"})
@@ -66,7 +85,7 @@ assert-css: (".src-line-numbers", {"text-align": "right"})
 goto: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
 // We use this assert-position to know where we will click.
 assert-position: ("//*[@id='1']", {"x": 104, "y": 112})
-// We click on the left of the "1" span but still in the "src-line-number" `<pre>`.
+// We click on the left of the "1" anchor but still in the "src-line-number" `<pre>`.
 click: (103, 103)
 assert-document-property: ({"URL": "/lib.rs.html"}, ENDS_WITH)
 
diff --git a/src/test/rustdoc/check-source-code-urls-to-def.rs b/src/test/rustdoc/check-source-code-urls-to-def.rs
index d00a3e3551991..5959f9c7c5992 100644
--- a/src/test/rustdoc/check-source-code-urls-to-def.rs
+++ b/src/test/rustdoc/check-source-code-urls-to-def.rs
@@ -10,14 +10,14 @@ extern crate source_code;
 
 // @has 'src/foo/check-source-code-urls-to-def.rs.html'
 
-// @has - '//a[@href="auxiliary/source-code-bar.rs.html#1-17"]' 'bar'
+// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#1-17"]' 'bar'
 #[path = "auxiliary/source-code-bar.rs"]
 pub mod bar;
 
-// @count - '//a[@href="auxiliary/source-code-bar.rs.html#5"]' 4
+// @count - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#5"]' 4
 use bar::Bar;
-// @has - '//a[@href="auxiliary/source-code-bar.rs.html#13"]' 'self'
-// @has - '//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'Trait'
+// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#13"]' 'self'
+// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'Trait'
 use bar::sub::{self, Trait};
 
 pub struct Foo;
@@ -28,29 +28,29 @@ impl Foo {
 
 fn babar() {}
 
-// @has - '//a/@href' '/struct.String.html'
-// @has - '//a/@href' '/primitive.u32.html'
-// @has - '//a/@href' '/primitive.str.html'
-// @count - '//a[@href="#23"]' 5
-// @has - '//a[@href="../../source_code/struct.SourceCode.html"]' 'source_code::SourceCode'
+// @has - '//pre[@class="rust"]//a/@href' '/struct.String.html'
+// @has - '//pre[@class="rust"]//a/@href' '/primitive.u32.html'
+// @has - '//pre[@class="rust"]//a/@href' '/primitive.str.html'
+// @count - '//pre[@class="rust"]//a[@href="#23"]' 5
+// @has - '//pre[@class="rust"]//a[@href="../../source_code/struct.SourceCode.html"]' 'source_code::SourceCode'
 pub fn foo(a: u32, b: &str, c: String, d: Foo, e: bar::Bar, f: source_code::SourceCode) {
     let x = 12;
     let y: Foo = Foo;
     let z: Bar = bar::Bar { field: Foo };
     babar();
-    // @has - '//a[@href="#26"]' 'hello'
+    // @has - '//pre[@class="rust"]//a[@href="#26"]' 'hello'
     y.hello();
 }
 
-// @has - '//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'bar::sub::Trait'
-// @has - '//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'Trait'
+// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'bar::sub::Trait'
+// @has - '//pre[@class="rust"]//a[@href="auxiliary/source-code-bar.rs.html#14"]' 'Trait'
 pub fn foo2<T: bar::sub::Trait, V: Trait>(t: &T, v: &V, b: bool) {}
 
 pub trait AnotherTrait {}
 pub trait WhyNot {}
 
-// @has - '//a[@href="#49"]' 'AnotherTrait'
-// @has - '//a[@href="#50"]' 'WhyNot'
+// @has - '//pre[@class="rust"]//a[@href="#49"]' 'AnotherTrait'
+// @has - '//pre[@class="rust"]//a[@href="#50"]' 'WhyNot'
 pub fn foo3<T, V>(t: &T, v: &V)
 where
     T: AnotherTrait,
@@ -59,11 +59,11 @@ where
 
 pub trait AnotherTrait2 {}
 
-// @has - '//a[@href="#60"]' 'AnotherTrait2'
+// @has - '//pre[@class="rust"]//a[@href="#60"]' 'AnotherTrait2'
 pub fn foo4() {
     let x: Vec<AnotherTrait2> = Vec::new();
 }
 
-// @has - '//a[@href="../../foo/primitive.bool.html"]' 'bool'
+// @has - '//pre[@class="rust"]//a[@href="../../foo/primitive.bool.html"]' 'bool'
 #[doc(primitive = "bool")]
 mod whatever {}

From ba4ae13528428cf770701ea5d9203cbf64b5a1cd Mon Sep 17 00:00:00 2001
From: Michael Howell <michael@notriddle.com>
Date: Fri, 28 Oct 2022 09:51:57 -0700
Subject: [PATCH 02/12] rustdoc: remove left border from `.src-line-numbers >
 a`

---
 src/librustdoc/html/static/css/rustdoc.css | 8 +++++---
 src/test/rustdoc-gui/source-code-page.goml | 5 +++++
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index df7b6e9b99a4e..b64fe74e960e3 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -578,11 +578,13 @@ ul.block, .block li {
 .src-line-numbers a {
 	color: var(--src-line-numbers-span-color);
 }
-.src-line-numbers .line-highlighted {
-	background-color: var(--src-line-number-highlighted-background-color);
-}
 .src-line-numbers :target {
 	background-color: transparent;
+	border-right: none;
+	padding-right: 0;
+}
+.src-line-numbers .line-highlighted {
+	background-color: var(--src-line-number-highlighted-background-color);
 }
 
 .search-loading {
diff --git a/src/test/rustdoc-gui/source-code-page.goml b/src/test/rustdoc-gui/source-code-page.goml
index 31d55cd7885d3..c71a3d64d0c8a 100644
--- a/src/test/rustdoc-gui/source-code-page.goml
+++ b/src/test/rustdoc-gui/source-code-page.goml
@@ -6,6 +6,11 @@ click: ".src-line-numbers > a:nth-child(4)" // This is the anchor for line 4.
 // Ensure that the page URL was updated.
 assert-document-property: ({"URL": "lib.rs.html#4"}, ENDS_WITH)
 assert-attribute: ("//*[@id='4']", {"class": "line-highlighted"})
+// Ensure that the default style, with the right border, isn't used.
+assert-css: ("//*[@id='4']", {"border-right-width": "0px"})
+reload:
+assert-attribute: ("//*[@id='4']", {"class": "line-highlighted"})
+assert-css: ("//*[@id='4']", {"border-right-width": "0px"})
 // We now check that the good anchors are highlighted
 goto: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html#4-6"
 assert-attribute-false: (".src-line-numbers > a:nth-child(3)", {"class": "line-highlighted"})

From 9bcc083c873be61e62446d0240a72f99d71350e0 Mon Sep 17 00:00:00 2001
From: David Wood <david.wood@huawei.com>
Date: Mon, 7 Nov 2022 14:43:28 +0000
Subject: [PATCH 03/12] run-make-fulldeps: fix split debuginfo test

Add lots of comments to this test and enable parts of the test that were
added but never ran.

Signed-off-by: David Wood <david.wood@huawei.com>
---
 .../split-debuginfo/Makefile                  | 160 ++++++++++++++++--
 1 file changed, 145 insertions(+), 15 deletions(-)

diff --git a/src/test/run-make-fulldeps/split-debuginfo/Makefile b/src/test/run-make-fulldeps/split-debuginfo/Makefile
index 1032f3408f062..a511aff4f3af2 100644
--- a/src/test/run-make-fulldeps/split-debuginfo/Makefile
+++ b/src/test/run-make-fulldeps/split-debuginfo/Makefile
@@ -3,7 +3,7 @@ include ../tools.mk
 all: off packed unpacked
 
 ifeq ($(UNAME),Darwin)
-# If disabled, don't run dsymutil
+# If disabled, don't run `dsymutil`.
 off:
 	rm -rf $(TMPDIR)/*.dSYM
 	$(RUSTC) foo.rs -g -C split-debuginfo=off
@@ -29,98 +29,228 @@ unpacked:
 	[ ! -d $(TMPDIR)/foo.dSYM ]
 else
 ifdef IS_WINDOWS
-# Windows only supports =packed
+# Windows only supports packed debuginfo - nothing to test.
 off:
 packed:
 unpacked:
 else
+# Some non-Windows, non-Darwin platforms are not stable, and some are.
 ifeq ($(UNAME),Linux)
   UNSTABLEOPTS :=
 else
   UNSTABLEOPTS := -Zunstable-options
 endif
 
+# - Debuginfo in `.o` files
+# - `.o` deleted
+# - `.dwo` never created
+# - `.dwp` never created
 off:
 	$(RUSTC) foo.rs -g -C $(UNSTABLEOPTS) split-debuginfo=off
 	[ ! -f $(TMPDIR)/*.dwp ]
 	[ ! -f $(TMPDIR)/*.dwo ]
-
 	$(RUSTC) foo.rs -g
 	[ ! -f $(TMPDIR)/*.dwp ]
 	[ ! -f $(TMPDIR)/*.dwo ]
 
-packed: packed-split packed-single
+packed: packed-split packed-single packed-remapped packed-crosscrate
 
+# - Debuginfo in `.dwo` files
+# - `.o` deleted
+# - `.dwo` deleted
+# - `.dwp` present
 packed-split:
 	$(RUSTC) foo.rs -g $(UNSTABLEOPTS) -C split-debuginfo=packed -Zsplit-dwarf-kind=split
-	ls $(TMPDIR)/*.dwp
-	rm -rf $(TMPDIR)/*.dwp $(TMPDIR)/*.dwo
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
+	rm $(TMPDIR)/foo.dwp
+	rm $(TMPDIR)/$(call BIN,foo)
 
+# - Debuginfo in `.o` files
+# - `.o` deleted
+# - `.dwo` never created
+# - `.dwp` present
 packed-single:
 	$(RUSTC) foo.rs -g $(UNSTABLEOPTS) -C split-debuginfo=packed -Zsplit-dwarf-kind=single
-	ls $(TMPDIR)/*.dwp
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
 	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
-	rm -rf $(TMPDIR)/*.dwp
+	rm $(TMPDIR)/foo.dwp
+	rm $(TMPDIR)/$(call BIN,foo)
 
 packed-remapped: packed-remapped-split packed-remapped-single
 
+# - Debuginfo in `.dwo` files
+# - `.o` and binary refer to remapped `.dwo` paths which do not exist
+# - `.o` deleted
+# - `.dwo` deleted
+# - `.dwp` present
 packed-remapped-split:
 	$(RUSTC) $(UNSTABLEOPTS) -C split-debuginfo=packed -C debuginfo=2 \
 		-Z split-dwarf-kind=split --remap-path-prefix $(TMPDIR)=/a foo.rs -g
 	objdump -Wi $(TMPDIR)/foo | grep DW_AT_GNU_dwo_name | (! grep $(TMPDIR)) || exit 1
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
+	rm $(TMPDIR)/foo.dwp
+	rm $(TMPDIR)/$(call BIN,foo)
 
+# - Debuginfo in `.o` files
+# - `.o` and binary refer to remapped `.o` paths which do not exist
+# - `.o` deleted
+# - `.dwo` never created
+# - `.dwp` present
 packed-remapped-single:
 	$(RUSTC) $(UNSTABLEOPTS) -C split-debuginfo=packed -C debuginfo=2 \
 		-Z split-dwarf-kind=single --remap-path-prefix $(TMPDIR)=/a foo.rs -g
 	objdump -Wi $(TMPDIR)/foo | grep DW_AT_GNU_dwo_name | (! grep $(TMPDIR)) || exit 1
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
+	rm $(TMPDIR)/foo.dwp
+	rm $(TMPDIR)/$(call BIN,foo)
 
 packed-crosscrate: packed-crosscrate-split packed-crosscrate-single
 
+# - Debuginfo in `.dwo` files
+# - (bar) `.rlib` file created, contains `.dwo`
+# - (bar) `.o` deleted
+# - (bar) `.dwo` deleted
+# - (bar) `.dwp` never created
+# - (main) `.o` deleted
+# - (main) `.dwo` deleted
+# - (main) `.dwp` present
 packed-crosscrate-split:
 	$(RUSTC) --crate-type lib $(UNSTABLEOPTS) -C split-debuginfo=packed \
 		-Zsplit-dwarf-kind=split -C debuginfo=2 -g bar.rs
 	ls $(TMPDIR)/*.rlib
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
 	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
 	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
-	$(RUSTC) --extern bar=$(TMPDIR)/libbar.rlib -Z unstable-options $(UNSTABLEOPTS) \
+	$(RUSTC) --extern bar=$(TMPDIR)/libbar.rlib $(UNSTABLEOPTS) \
 		-C split-debuginfo=packed -Zsplit-dwarf-kind=split -C debuginfo=2 -g main.rs
-	rm $(TMPDIR)/*.dwo
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
 	rm $(TMPDIR)/main.dwp
 	rm $(TMPDIR)/$(call BIN,main)
 
+# - Debuginfo in `.o` files
+# - (bar) `.rlib` file created, contains `.o`
+# - (bar) `.o` deleted
+# - (bar) `.dwo` never created
+# - (bar) `.dwp` never created
+# - (main) `.o` deleted
+# - (main) `.dwo` never created
+# - (main) `.dwp` present
 packed-crosscrate-single:
 	$(RUSTC) --crate-type lib $(UNSTABLEOPTS) -C split-debuginfo=packed \
 		-Zsplit-dwarf-kind=single -C debuginfo=2 -g bar.rs
 	ls $(TMPDIR)/*.rlib
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
 	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
 	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
-	$(RUSTC) --extern bar=$(TMPDIR)/libbar.rlib -Z unstable-options $(UNSTABLEOPTS) \
+	$(RUSTC) --extern bar=$(TMPDIR)/libbar.rlib $(UNSTABLEOPTS) \
 		-C split-debuginfo=packed -Zsplit-dwarf-kind=single -C debuginfo=2 -g main.rs
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
 	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
 	rm $(TMPDIR)/main.dwp
 	rm $(TMPDIR)/$(call BIN,main)
 
-unpacked: unpacked-split unpacked-single unpacked-remapped-split unpacked-remapped-single
+unpacked: unpacked-split unpacked-single unpacked-remapped unpacked-crosscrate
 
+# - Debuginfo in `.dwo` files
+# - `.o` deleted
+# - `.dwo` present
+# - `.dwp` never created
 unpacked-split:
 	$(RUSTC) foo.rs -g $(UNSTABLEOPTS) -C split-debuginfo=unpacked -Zsplit-dwarf-kind=split
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	rm $(TMPDIR)/*.dwo
 	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
-	ls $(TMPDIR)/*.dwo
-	rm -rf $(TMPDIR)/*.dwp $(TMPDIR)/*.dwo
+	rm $(TMPDIR)/$(call BIN,foo)
 
+# - Debuginfo in `.o` files
+# - `.o` present
+# - `.dwo` never created
+# - `.dwp` never created
 unpacked-single:
 	$(RUSTC) foo.rs -g $(UNSTABLEOPTS) -C split-debuginfo=unpacked -Zsplit-dwarf-kind=single
-	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	ls $(TMPDIR)/*.o
 	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	rm $(TMPDIR)/$(call BIN,foo)
+
+unpacked-remapped: unpacked-remapped-split unpacked-remapped-single
 
+# - Debuginfo in `.dwo` files
+# - `.o` and binary refer to remapped `.dwo` paths which do not exist
+# - `.o` deleted
+# - `.dwo` present
+# - `.dwp` never created
 unpacked-remapped-split:
 	$(RUSTC) $(UNSTABLEOPTS) -C split-debuginfo=unpacked -C debuginfo=2 \
 		-Z split-dwarf-kind=split --remap-path-prefix $(TMPDIR)=/a foo.rs -g
 	objdump -Wi $(TMPDIR)/foo | grep DW_AT_GNU_dwo_name | (! grep $(TMPDIR)) || exit 1
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	rm $(TMPDIR)/*.dwo
+	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	rm $(TMPDIR)/$(call BIN,foo)
 
+# - Debuginfo in `.o` files
+# - `.o` and binary refer to remapped `.o` paths which do not exist
+# - `.o` present
+# - `.dwo` never created
+# - `.dwp` never created
 unpacked-remapped-single:
 	$(RUSTC) $(UNSTABLEOPTS) -C split-debuginfo=unpacked -C debuginfo=2 \
 		-Z split-dwarf-kind=single --remap-path-prefix $(TMPDIR)=/a foo.rs -g
 	objdump -Wi $(TMPDIR)/foo | grep DW_AT_GNU_dwo_name | (! grep $(TMPDIR)) || exit 1
+	ls $(TMPDIR)/*.o
+	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	rm $(TMPDIR)/$(call BIN,foo)
+
+unpacked-crosscrate: packed-crosscrate-split packed-crosscrate-single
+
+# - Debuginfo in `.dwo` files
+# - (bar) `.rlib` file created, contains `.dwo`
+# - (bar) `.o` deleted
+# - (bar) `.dwo` present
+# - (bar) `.dwp` never created
+# - (main) `.o` deleted
+# - (main) `.dwo` present
+# - (main) `.dwp` never created
+unpacked-crosscrate-split:
+	$(RUSTC) --crate-type lib $(UNSTABLEOPTS) -C split-debuginfo=unpacked \
+		-Zsplit-dwarf-kind=split -C debuginfo=2 -g bar.rs
+	ls $(TMPDIR)/*.rlib
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwo
+	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	$(RUSTC) --extern bar=$(TMPDIR)/libbar.rlib $(UNSTABLEOPTS) \
+		-C split-debuginfo=unpacked -Zsplit-dwarf-kind=split -C debuginfo=2 -g main.rs
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	rm $(TMPDIR)/*.dwo
+	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	rm $(TMPDIR)/$(call BIN,main)
+
+# - Debuginfo in `.o` files
+# - (bar) `.rlib` file created, contains `.o`
+# - (bar) `.o` present
+# - (bar) `.dwo` never created
+# - (bar) `.dwp` never created
+# - (main) `.o` present
+# - (main) `.dwo` never created
+# - (main) `.dwp` never created
+unpacked-crosscrate-single:
+	$(RUSTC) --crate-type lib $(UNSTABLEOPTS) -C split-debuginfo=unpacked \
+		-Zsplit-dwarf-kind=single -C debuginfo=2 -g bar.rs
+	ls $(TMPDIR)/*.rlib
+	ls $(TMPDIR)/*.o
+	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	$(RUSTC) --extern bar=$(TMPDIR)/libbar.rlib $(UNSTABLEOPTS) \
+		-C split-debuginfo=unpacked -Zsplit-dwarf-kind=single -C debuginfo=2 -g main.rs
+	ls $(TMPDIR)/*.o
+	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	rm $(TMPDIR)/$(call BIN,main)
 endif
 endif

From 29dc08307dde3051ab6bf17c53f2db99e32681ee Mon Sep 17 00:00:00 2001
From: David Wood <david.wood@huawei.com>
Date: Mon, 7 Nov 2022 15:15:24 +0000
Subject: [PATCH 04/12] llvm: dwo only emitted when object code emitted

`CompiledModule` should not think a DWARF object was emitted when a
bitcode-only compilation has happened, this can confuse archive file
creation (which expects to create an archive containing non-existent dwo
files).

Signed-off-by: David Wood <david.wood@huawei.com>
---
 compiler/rustc_codegen_llvm/src/back/write.rs | 16 +++++-
 .../split-debuginfo/Makefile                  | 56 ++++++++++++++++++-
 .../run-make-fulldeps/split-debuginfo/baz.rs  |  1 +
 3 files changed, 68 insertions(+), 5 deletions(-)
 create mode 100644 src/test/run-make-fulldeps/split-debuginfo/baz.rs

diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs
index 11053a8f6c452..97d0de47b3a6e 100644
--- a/compiler/rustc_codegen_llvm/src/back/write.rs
+++ b/compiler/rustc_codegen_llvm/src/back/write.rs
@@ -765,11 +765,21 @@ pub(crate) unsafe fn codegen(
         drop(handlers);
     }
 
+    // `.dwo` files are only emitted if:
+    //
+    // - Object files are being emitted (i.e. bitcode only or metadata only compilations will not
+    //   produce dwarf objects, even if otherwise enabled)
+    // - Target supports Split DWARF
+    // - Split debuginfo is enabled
+    // - Split DWARF kind is `split` (i.e. debuginfo is split into `.dwo` files, not different
+    //   sections in the `.o` files).
+    let dwarf_object_emitted = matches!(config.emit_obj, EmitObj::ObjectCode(_))
+        && cgcx.target_can_use_split_dwarf
+        && cgcx.split_debuginfo != SplitDebuginfo::Off
+        && cgcx.split_dwarf_kind == SplitDwarfKind::Split;
     Ok(module.into_compiled_module(
         config.emit_obj != EmitObj::None,
-        cgcx.target_can_use_split_dwarf
-            && cgcx.split_debuginfo != SplitDebuginfo::Off
-            && cgcx.split_dwarf_kind == SplitDwarfKind::Split,
+        dwarf_object_emitted,
         config.emit_bc,
         &cgcx.output_filenames,
     ))
diff --git a/src/test/run-make-fulldeps/split-debuginfo/Makefile b/src/test/run-make-fulldeps/split-debuginfo/Makefile
index a511aff4f3af2..1831ab38fab49 100644
--- a/src/test/run-make-fulldeps/split-debuginfo/Makefile
+++ b/src/test/run-make-fulldeps/split-debuginfo/Makefile
@@ -53,7 +53,7 @@ off:
 	[ ! -f $(TMPDIR)/*.dwp ]
 	[ ! -f $(TMPDIR)/*.dwo ]
 
-packed: packed-split packed-single packed-remapped packed-crosscrate
+packed: packed-split packed-single packed-lto packed-remapped packed-crosscrate
 
 # - Debuginfo in `.dwo` files
 # - `.o` deleted
@@ -77,6 +77,32 @@ packed-single:
 	rm $(TMPDIR)/foo.dwp
 	rm $(TMPDIR)/$(call BIN,foo)
 
+packed-lto: packed-lto-split packed-lto-single
+
+# - rmeta file added to rlib, no object files are generated and thus no debuginfo is generated
+# - `.o` never created
+# - `.dwo` never created
+# - `.dwp` never created
+packed-lto-split:
+	$(RUSTC) baz.rs -g $(UNSTABLEOPTS) -Csplit-debuginfo=packed -Zsplit-dwarf-kind=split \
+		--crate-type=rlib -Clinker-plugin-lto
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	rm $(TMPDIR)/libbaz.rlib
+
+# - rmeta file added to rlib, no object files are generated and thus no debuginfo is generated
+# - `.o` never created
+# - `.dwo` never created
+# - `.dwp` never created
+packed-lto-single:
+	$(RUSTC) baz.rs -g $(UNSTABLEOPTS) -Csplit-debuginfo=packed -Zsplit-dwarf-kind=single \
+		--crate-type=rlib -Clinker-plugin-lto
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	rm $(TMPDIR)/libbaz.rlib
+
 packed-remapped: packed-remapped-split packed-remapped-single
 
 # - Debuginfo in `.dwo` files
@@ -153,7 +179,7 @@ packed-crosscrate-single:
 	rm $(TMPDIR)/main.dwp
 	rm $(TMPDIR)/$(call BIN,main)
 
-unpacked: unpacked-split unpacked-single unpacked-remapped unpacked-crosscrate
+unpacked: unpacked-split unpacked-single unpacked-lto unpacked-remapped unpacked-crosscrate
 
 # - Debuginfo in `.dwo` files
 # - `.o` deleted
@@ -177,6 +203,32 @@ unpacked-single:
 	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
 	rm $(TMPDIR)/$(call BIN,foo)
 
+unpacked-lto: packed-lto-split packed-lto-single
+
+# - rmeta file added to rlib, no object files are generated and thus no debuginfo is generated
+# - `.o` never created
+# - `.dwo` never created
+# - `.dwp` never created
+unpacked-lto-split:
+	$(RUSTC) baz.rs -g $(UNSTABLEOPTS) -Csplit-debuginfo=unpacked -Zsplit-dwarf-kind=split \
+		--crate-type=rlib -Clinker-plugin-lto
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	rm $(TMPDIR)/libbaz.rlib
+
+# - rmeta file added to rlib, no object files are generated and thus no debuginfo is generated
+# - `.o` never created
+# - `.dwo` never created
+# - `.dwp` never created
+unpacked-lto-single:
+	$(RUSTC) baz.rs -g $(UNSTABLEOPTS) -Csplit-debuginfo=unpacked -Zsplit-dwarf-kind=single \
+		--crate-type=rlib -Clinker-plugin-lto
+	ls $(TMPDIR)/*.o && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwo && exit 1 || exit 0
+	ls $(TMPDIR)/*.dwp && exit 1 || exit 0
+	rm $(TMPDIR)/libbaz.rlib
+
 unpacked-remapped: unpacked-remapped-split unpacked-remapped-single
 
 # - Debuginfo in `.dwo` files
diff --git a/src/test/run-make-fulldeps/split-debuginfo/baz.rs b/src/test/run-make-fulldeps/split-debuginfo/baz.rs
new file mode 100644
index 0000000000000..8b1a393741c96
--- /dev/null
+++ b/src/test/run-make-fulldeps/split-debuginfo/baz.rs
@@ -0,0 +1 @@
+// empty

From 89ba71649f59e402a0019828223a878c06f625b1 Mon Sep 17 00:00:00 2001
From: Michael Howell <michael@notriddle.com>
Date: Tue, 8 Nov 2022 18:00:22 -0700
Subject: [PATCH 05/12] rustdoc: use consistent "popover" styling for notable
 traits

---
 src/librustdoc/html/render/mod.rs             |  6 +-
 src/librustdoc/html/static/css/rustdoc.css    | 73 +++++--------------
 src/librustdoc/html/static/css/themes/ayu.css |  4 -
 .../html/static/css/themes/dark.css           |  4 -
 .../html/static/css/themes/light.css          |  4 -
 src/librustdoc/html/static/js/main.js         | 52 ++++++++++---
 src/test/rustdoc-gui/notable-trait.goml       | 61 +++++++---------
 7 files changed, 89 insertions(+), 115 deletions(-)

diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 266ec2ac7ad73..75fdd6ade235f 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -1312,9 +1312,7 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> O
     if has_notable_trait {
         cx.types_with_notable_traits.insert(ty.clone());
         Some(format!(
-            "<span class=\"notable-traits\" data-ty=\"{ty}\">\
-                <span class=\"notable-traits-tooltip\">ⓘ</span>\
-            </span>",
+            " <a href=\"#\" class=\"notable-traits\" data-ty=\"{ty}\">ⓘ</a>",
             ty = Escape(&format!("{:#}", ty.print(cx))),
         ))
     } else {
@@ -1343,7 +1341,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
                 if out.is_empty() {
                     write!(
                         &mut out,
-                        "<h3 class=\"notable\">Notable traits for <code>{}</code></h3>\
+                        "<h3>Notable traits for <code>{}</code></h3>\
                      <pre class=\"content\"><code>",
                         impl_.for_.print(cx)
                     );
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 6a068a3d243d9..1209187dc4817 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -183,8 +183,6 @@ h4.code-header {
 	font-weight: 600;
 	margin: 0;
 	padding: 0;
-	/* position notable traits in mobile mode within the header */
-	position: relative;
 }
 
 #crate-search,
@@ -930,13 +928,14 @@ so that we can apply CSS-filters to change the arrow color in themes */
 	border-radius: 3px;
 	border: 1px solid var(--border-color);
 	font-size: 1rem;
+	--popover-arrow-offset: 11px;
 }
 
 /* This rule is to draw the little arrow connecting the settings menu to the gear icon. */
 .popover::before {
 	content: '';
 	position: absolute;
-	right: 11px;
+	right: var(--popover-arrow-offset);
 	border: solid var(--border-color);
 	border-width: 1px 1px 0 0;
 	display: inline-block;
@@ -953,10 +952,7 @@ so that we can apply CSS-filters to change the arrow color in themes */
 /* use larger max-width for help popover, but not for help.html */
 #help.popover {
 	max-width: 600px;
-}
-
-#help.popover::before {
-	right: 48px;
+	--popover-arrow-offset: 48px;
 }
 
 #help dt {
@@ -1275,54 +1271,34 @@ h3.variant {
 	border-right: 3px solid var(--target-border-color);
 }
 
-.notable-traits-tooltip {
-	display: inline-block;
-	cursor: pointer;
-}
-
-.notable-traits .notable-traits-tooltiptext {
-	display: inline-block;
-	visibility: hidden;
+.notable-traits {
+	color: inherit;
+	margin-right: 15px;
+	position: relative;
 }
 
-.notable-traits-tooltiptext {
-	padding: 5px 3px 3px 3px;
-	border-radius: 6px;
-	margin-left: 5px;
-	z-index: 10;
-	font-size: 1rem;
-	cursor: default;
+/* placeholder thunk so that the mouse can easily travel from "(i)" to popover
+	the resulting "hover tunnel" is a stepped triangle, approximating
+	https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown */
+.notable-traits:hover::after {
 	position: absolute;
-	border: 1px solid;
-}
-
-.notable-traits-tooltip::after {
-	/* The margin on the tooltip does not capture hover events,
-	   this extends the area of hover enough so that mouse hover is not
-	   lost when moving the mouse to the tooltip */
-	content: "\00a0\00a0\00a0";
-}
-
-.notable-traits-tooltiptext .docblock {
-	margin: 0;
+	top: calc(100% - 10px);
+	left: -15px;
+	right: -15px;
+	height: 20px;
+	content: "\00a0";
 }
 
-.notable-traits-tooltiptext .notable {
-	font-size: 1.1875rem;
-	font-weight: 600;
-	display: block;
+.notable .docblock {
+	margin: 0.25em 0.5em;
 }
 
-.notable-traits-tooltiptext pre, .notable-traits-tooltiptext code {
+.notable .docblock pre, .notable .docblock code {
 	background: transparent;
-}
-
-.notable-traits-tooltiptext .docblock pre.content {
 	margin: 0;
 	padding: 0;
 	font-size: 1.25rem;
 	white-space: pre-wrap;
-	overflow: hidden;
 }
 
 .search-failed {
@@ -1365,12 +1341,6 @@ h3.variant {
 	font-size: 1rem;
 }
 
-.notable-traits {
-	cursor: pointer;
-	z-index: 2;
-	margin-left: 5px;
-}
-
 #sidebar-toggle {
 	position: sticky;
 	top: 0;
@@ -1855,11 +1825,6 @@ in storage.js
 		border-bottom: 1px solid;
 	}
 
-	.notable-traits .notable-traits-tooltiptext {
-		left: 0;
-		top: 100%;
-	}
-
 	/* We don't display the help button on mobile devices. */
 	#help-button {
 		display: none;
diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css
index 2fa1fa39d63ab..eec3ce55e22f9 100644
--- a/src/librustdoc/html/static/css/themes/ayu.css
+++ b/src/librustdoc/html/static/css/themes/ayu.css
@@ -183,10 +183,6 @@ details.rustdoc-toggle > summary::before {
 	border-color: transparent #314559 transparent transparent;
 }
 
-.notable-traits-tooltiptext {
-	background-color: #314559;
-}
-
 #titles > button.selected {
 	background-color: #141920 !important;
 	border-bottom: 1px solid #ffb44c !important;
diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css
index 43f8dd42ab347..a93d9d6790a17 100644
--- a/src/librustdoc/html/static/css/themes/dark.css
+++ b/src/librustdoc/html/static/css/themes/dark.css
@@ -106,10 +106,6 @@ details.rustdoc-toggle > summary::before {
 	border-color: transparent black transparent transparent;
 }
 
-.notable-traits-tooltiptext {
-	background-color: #111;
-}
-
 #titles > button:not(.selected) {
 	background-color: #252525;
 	border-top-color: #252525;
diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css
index c8c5289ab540c..e69ae5abbc393 100644
--- a/src/librustdoc/html/static/css/themes/light.css
+++ b/src/librustdoc/html/static/css/themes/light.css
@@ -98,10 +98,6 @@ body.source .example-wrap pre.rust a {
 	border-color: transparent black transparent transparent;
 }
 
-.notable-traits-tooltiptext {
-	background-color: #eee;
-}
-
 #titles > button:not(.selected) {
 	background-color: #e6e6e6;
 	border-top-color: #e6e6e6;
diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index 0426774e80d46..b670ea3f0201d 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -850,18 +850,33 @@ function loadCss(cssUrl) {
         }
         hideNotable();
         const ty = e.getAttribute("data-ty");
-        const tooltip = e.getElementsByClassName("notable-traits-tooltip")[0];
         const wrapper = document.createElement("div");
         wrapper.innerHTML = "<div class=\"docblock\">" + window.NOTABLE_TRAITS[ty] + "</div>";
-        wrapper.className = "notable-traits-tooltiptext";
-        tooltip.appendChild(wrapper);
-        const pos = wrapper.getBoundingClientRect();
-        tooltip.removeChild(wrapper);
-        wrapper.style.top = (pos.top + window.scrollY) + "px";
-        wrapper.style.left = (pos.left + window.scrollX) + "px";
-        wrapper.style.width = pos.width + "px";
+        wrapper.className = "notable popover";
+        const focusCatcher = document.createElement("div");
+        focusCatcher.setAttribute("tabindex", "0");
+        focusCatcher.onfocus = hideNotable;
+        wrapper.appendChild(focusCatcher);
+        const pos = e.getBoundingClientRect();
+        // 5px overlap so that the mouse can easily travel from place to place
+        wrapper.style.top = (pos.top + window.scrollY + pos.height) + "px";
+        wrapper.style.left = 0;
+        wrapper.style.right = "auto";
+        wrapper.style.visibility = "hidden";
         const body = document.getElementsByTagName("body")[0];
         body.appendChild(wrapper);
+        const wrapperPos = wrapper.getBoundingClientRect();
+        // offset so that the arrow points at the center of the "(i)"
+        const finalPos = pos.left + window.scrollX - wrapperPos.width + 24;
+        if (finalPos > 0) {
+            wrapper.style.left = finalPos + "px";
+        } else {
+            wrapper.style.setProperty(
+                "--popover-arrow-offset",
+                (wrapperPos.right - pos.right + 4) + "px"
+            );
+        }
+        wrapper.style.visibility = "";
         window.CURRENT_NOTABLE_ELEMENT = wrapper;
         window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE = e;
         wrapper.onpointerleave = function(ev) {
@@ -875,9 +890,23 @@ function loadCss(cssUrl) {
         };
     }
 
+    function notableBlurHandler(event) {
+        if (window.CURRENT_NOTABLE_ELEMENT &&
+            !elemIsInParent(document.activeElement, window.CURRENT_NOTABLE_ELEMENT) &&
+            !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT) &&
+            !elemIsInParent(document.activeElement, window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE) &&
+            !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE)
+        ) {
+            hideNotable();
+        }
+    }
+
     function hideNotable() {
         if (window.CURRENT_NOTABLE_ELEMENT) {
-            window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE = false;
+            if (window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE) {
+                window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.focus();
+                window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE.NOTABLE_FORCE_VISIBLE = false;
+            }
             const body = document.getElementsByTagName("body")[0];
             body.removeChild(window.CURRENT_NOTABLE_ELEMENT);
             window.CURRENT_NOTABLE_ELEMENT = null;
@@ -891,7 +920,11 @@ function loadCss(cssUrl) {
                 hideNotable();
             } else {
                 showNotable(this);
+                window.CURRENT_NOTABLE_ELEMENT.setAttribute("tabindex", "0");
+                window.CURRENT_NOTABLE_ELEMENT.focus();
+                window.CURRENT_NOTABLE_ELEMENT.onblur = notableBlurHandler;
             }
+            return false;
         };
         e.onpointerenter = function(ev) {
             // If this is a synthetic touch event, ignore it. A click event will be along shortly.
@@ -1018,6 +1051,7 @@ function loadCss(cssUrl) {
         onEachLazy(document.querySelectorAll(".search-form .popover"), elem => {
             elem.style.display = "none";
         });
+        hideNotable();
     };
 
     /**
diff --git a/src/test/rustdoc-gui/notable-trait.goml b/src/test/rustdoc-gui/notable-trait.goml
index d8261d8dc902c..3bc1c5365e9e1 100644
--- a/src/test/rustdoc-gui/notable-trait.goml
+++ b/src/test/rustdoc-gui/notable-trait.goml
@@ -22,31 +22,26 @@ assert-position: (
 )
 assert-position: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    {"x": 951},
+    {"x": 955},
 )
-// The tooltip should be beside the `i`
+// The tooltip should be below the `i`
 // Also, clicking the tooltip should bring its text into the DOM
-assert-count: ("//*[@class='notable-traits-tooltiptext']", 0)
+assert-count: ("//*[@class='notable popover']", 0)
 click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"
-assert-count: ("//*[@class='notable-traits-tooltiptext']", 1)
+assert-count: ("//*[@class='notable popover']", 1)
 compare-elements-position-near: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    "//*[@class='notable-traits-tooltiptext']",
-    {"y": 2}
+    "//*[@class='notable popover']",
+    {"y": 30}
 )
 compare-elements-position-false: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    "//*[@class='notable-traits-tooltiptext']",
+    "//*[@class='notable popover']",
     ("x")
 )
-// The docblock should be flush with the border.
-assert-css: (
-    "//*[@class='notable-traits-tooltiptext']/*[@class='docblock']",
-    {"margin-left": "0px"}
-)
 click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"
 move-cursor-to: "//h1"
-assert-count: ("//*[@class='notable-traits-tooltiptext']", 0)
+assert-count: ("//*[@class='notable popover']", 0)
 
 // Now only the `i` should be on the next line.
 size: (1055, 600)
@@ -77,7 +72,7 @@ assert-position: (
 )
 assert-position: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    {"x": 519},
+    {"x": 523},
 )
 
 // Checking on mobile now.
@@ -101,34 +96,28 @@ assert-position: (
 )
 assert-position: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    {"x": 289},
+    {"x": 293},
 )
-// The tooltip should be below `i`
+// The tooltip should STILL be below `i`
 click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"
-assert-count: ("//*[@class='notable-traits-tooltiptext']", 1)
-compare-elements-position-near-false: (
+assert-count: ("//*[@class='notable popover']", 1)
+compare-elements-position-near: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    "//*[@class='notable-traits-tooltiptext']",
-    {"y": 2}
+    "//*[@class='notable popover']",
+    {"y": 30}
 )
 compare-elements-position-false: (
     "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    "//*[@class='notable-traits-tooltiptext']",
+    "//*[@class='notable popover']",
     ("x")
 )
-compare-elements-position-near: (
-    "//*[@id='method.create_an_iterator_from_read']",
-    "//*[@class='notable-traits-tooltiptext']",
-    {"x": 10}
-)
-// The docblock should be flush with the border.
-assert-css: (
-    "//*[@class='notable-traits-tooltiptext']/*[@class='docblock']",
-    {"margin-left": "0px"}
+assert-position: (
+    "//*[@class='notable popover']",
+    {"x": 0}
 )
 click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"
 move-cursor-to: "//h1"
-assert-count: ("//*[@class='notable-traits-tooltiptext']", 0)
+assert-count: ("//*[@class='notable popover']", 0)
 
 // Checking on very small mobile. The `i` should be on its own line.
 size: (365, 600)
@@ -153,25 +142,25 @@ define-function: (
         ("reload"),
 
         ("move-cursor-to", "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"),
-        ("assert-count", (".notable-traits-tooltiptext", 1)),
+        ("assert-count", (".notable.popover", 1)),
 
         ("assert-css", (
-             ".notable-traits-tooltiptext h3.notable",
+             ".notable.popover h3",
              {"color": |header_color|},
              ALL,
         )),
         ("assert-css", (
-             ".notable-traits-tooltiptext pre.content",
+             ".notable.popover pre",
              {"color": |content_color|},
              ALL,
         )),
         ("assert-css", (
-             ".notable-traits-tooltiptext pre.content a.struct",
+             ".notable.popover pre a.struct",
              {"color": |type_color|},
              ALL,
         )),
         ("assert-css", (
-             ".notable-traits-tooltiptext pre.content a.trait",
+             ".notable.popover pre a.trait",
              {"color": |trait_color|},
              ALL,
         )),

From 155750dc330d0a2148cff7efadd75d821854e947 Mon Sep 17 00:00:00 2001
From: Michael Howell <michael@notriddle.com>
Date: Tue, 8 Nov 2022 17:59:03 -0700
Subject: [PATCH 06/12] rustdoc: make notable traits popover behavior
 consistent with Help and Settings

---
 src/librustdoc/html/static/js/main.js   | 10 ++++++++-
 src/test/rustdoc-gui/notable-trait.goml | 28 +++++++++++++++++++++++++
 2 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js
index b670ea3f0201d..0538762e44d03 100644
--- a/src/librustdoc/html/static/js/main.js
+++ b/src/librustdoc/html/static/js/main.js
@@ -897,7 +897,15 @@ function loadCss(cssUrl) {
             !elemIsInParent(document.activeElement, window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE) &&
             !elemIsInParent(event.relatedTarget, window.CURRENT_NOTABLE_ELEMENT.NOTABLE_BASE)
         ) {
-            hideNotable();
+            // Work around a difference in the focus behaviour between Firefox, Chrome, and Safari.
+            // When I click the button on an already-opened notable trait popover, Safari
+            // hides the popover and then immediately shows it again, while everyone else hides it
+            // and it stays hidden.
+            //
+            // To work around this, make sure the click finishes being dispatched before
+            // hiding the popover. Since `hideNotable()` is idempotent, this makes Safari behave
+            // consistently with the other two.
+            setTimeout(hideNotable, 0);
         }
     }
 
diff --git a/src/test/rustdoc-gui/notable-trait.goml b/src/test/rustdoc-gui/notable-trait.goml
index 3bc1c5365e9e1..0b97cbae4991d 100644
--- a/src/test/rustdoc-gui/notable-trait.goml
+++ b/src/test/rustdoc-gui/notable-trait.goml
@@ -199,3 +199,31 @@ call-function: (
         "trait_color": "rgb(110, 79, 201)",
     },
 )
+
+reload:
+
+// Check that pressing escape works
+click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"
+move-cursor-to: "//*[@class='notable popover']"
+assert-count: ("//*[@class='notable popover']", 1)
+press-key: "Escape"
+assert-count: ("//*[@class='notable popover']", 0)
+
+// Check that clicking outside works.
+click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"
+assert-count: ("//*[@class='notable popover']", 1)
+click: ".search-input"
+assert-count: ("//*[@class='notable popover']", 0)
+
+// Check that pressing tab over and over works.
+click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']"
+move-cursor-to: "//*[@class='notable popover']"
+assert-count: ("//*[@class='notable popover']", 1)
+press-key: "Tab"
+press-key: "Tab"
+press-key: "Tab"
+press-key: "Tab"
+press-key: "Tab"
+press-key: "Tab"
+press-key: "Tab"
+assert-count: ("//*[@class='notable popover']", 0)

From 79b6112ab53a9f5cd2b267dfbdb8bcae7049b2f7 Mon Sep 17 00:00:00 2001
From: Michael Howell <michael@notriddle.com>
Date: Tue, 8 Nov 2022 17:49:29 -0700
Subject: [PATCH 07/12] rustdoc: update test cases

---
 src/test/rustdoc-gui/notable-trait.goml                   | 8 --------
 .../rustdoc/doc-notable_trait-slice.bare_fn_matches.html  | 2 +-
 src/test/rustdoc/doc-notable_trait.bare-fn.html           | 2 +-
 src/test/rustdoc/doc-notable_trait.rs                     | 6 +++---
 src/test/rustdoc/doc-notable_trait.some-struct-new.html   | 2 +-
 src/test/rustdoc/doc-notable_trait.wrap-me.html           | 2 +-
 src/test/rustdoc/spotlight-from-dependency.odd.html       | 2 +-
 src/test/rustdoc/spotlight-from-dependency.rs             | 2 +-
 8 files changed, 9 insertions(+), 17 deletions(-)

diff --git a/src/test/rustdoc-gui/notable-trait.goml b/src/test/rustdoc-gui/notable-trait.goml
index 0b97cbae4991d..4c3943d885830 100644
--- a/src/test/rustdoc-gui/notable-trait.goml
+++ b/src/test/rustdoc-gui/notable-trait.goml
@@ -119,14 +119,6 @@ click: "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits
 move-cursor-to: "//h1"
 assert-count: ("//*[@class='notable popover']", 0)
 
-// Checking on very small mobile. The `i` should be on its own line.
-size: (365, 600)
-compare-elements-position-false: (
-    "//*[@id='method.create_an_iterator_from_read']//a[text()='NotableStructWithLongName']",
-    "//*[@id='method.create_an_iterator_from_read']//*[@class='notable-traits']",
-    ("y", "x"),
-)
-
 // Now check the colors.
 define-function: (
     "check-colors",
diff --git a/src/test/rustdoc/doc-notable_trait-slice.bare_fn_matches.html b/src/test/rustdoc/doc-notable_trait-slice.bare_fn_matches.html
index 6b58be7e6853e..f2ec8320a0525 100644
--- a/src/test/rustdoc/doc-notable_trait-slice.bare_fn_matches.html
+++ b/src/test/rustdoc/doc-notable_trait-slice.bare_fn_matches.html
@@ -1 +1 @@
-<script type="text/json" id="notable-traits-data">{"&amp;'static [SomeStruct]":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&amp;amp;[&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait_slice::SomeStruct\"&gt;SomeStruct&lt;/a&gt;]&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait_slice::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &amp;amp;[&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait_slice::SomeStruct\"&gt;SomeStruct&lt;/a&gt;]&lt;/span&gt;"}</script>
\ No newline at end of file
+<script type="text/json" id="notable-traits-data">{"&amp;'static [SomeStruct]":"&lt;h3&gt;Notable traits for &lt;code&gt;&amp;amp;[&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait_slice::SomeStruct\"&gt;SomeStruct&lt;/a&gt;]&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait_slice::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &amp;amp;[&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait_slice::SomeStruct\"&gt;SomeStruct&lt;/a&gt;]&lt;/span&gt;"}</script>
\ No newline at end of file
diff --git a/src/test/rustdoc/doc-notable_trait.bare-fn.html b/src/test/rustdoc/doc-notable_trait.bare-fn.html
index 4e4a3f18f2498..b426a4d7a8b7b 100644
--- a/src/test/rustdoc/doc-notable_trait.bare-fn.html
+++ b/src/test/rustdoc/doc-notable_trait.bare-fn.html
@@ -1 +1 @@
-<script type="text/json" id="notable-traits-data">{"SomeStruct":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/span&gt;"}</script>
\ No newline at end of file
+<script type="text/json" id="notable-traits-data">{"SomeStruct":"&lt;h3&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/span&gt;"}</script>
\ No newline at end of file
diff --git a/src/test/rustdoc/doc-notable_trait.rs b/src/test/rustdoc/doc-notable_trait.rs
index 1f2cd7181c3d4..279faf5540140 100644
--- a/src/test/rustdoc/doc-notable_trait.rs
+++ b/src/test/rustdoc/doc-notable_trait.rs
@@ -9,7 +9,7 @@ impl<T: SomeTrait> SomeTrait for Wrapper<T> {}
 #[doc(notable_trait)]
 pub trait SomeTrait {
     // @has doc_notable_trait/trait.SomeTrait.html
-    // @has - '//span[@class="notable-traits"]/@data-ty' 'Wrapper<Self>'
+    // @has - '//a[@class="notable-traits"]/@data-ty' 'Wrapper<Self>'
     // @snapshot wrap-me - '//script[@id="notable-traits-data"]'
     fn wrap_me(self) -> Wrapper<Self> where Self: Sized {
         Wrapper {
@@ -23,7 +23,7 @@ impl SomeTrait for SomeStruct {}
 
 impl SomeStruct {
     // @has doc_notable_trait/struct.SomeStruct.html
-    // @has - '//span[@class="notable-traits"]/@data-ty' 'SomeStruct'
+    // @has - '//a[@class="notable-traits"]/@data-ty' 'SomeStruct'
     // @snapshot some-struct-new - '//script[@id="notable-traits-data"]'
     pub fn new() -> SomeStruct {
         SomeStruct
@@ -31,7 +31,7 @@ impl SomeStruct {
 }
 
 // @has doc_notable_trait/fn.bare_fn.html
-// @has - '//span[@class="notable-traits"]/@data-ty' 'SomeStruct'
+// @has - '//a[@class="notable-traits"]/@data-ty' 'SomeStruct'
 // @snapshot bare-fn - '//script[@id="notable-traits-data"]'
 pub fn bare_fn() -> SomeStruct {
     SomeStruct
diff --git a/src/test/rustdoc/doc-notable_trait.some-struct-new.html b/src/test/rustdoc/doc-notable_trait.some-struct-new.html
index c0fd9748fd371..4f8063807e67d 100644
--- a/src/test/rustdoc/doc-notable_trait.some-struct-new.html
+++ b/src/test/rustdoc/doc-notable_trait.some-struct-new.html
@@ -1 +1 @@
-<script type="text/json" id="notable-traits-data">{"SomeStruct":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/span&gt;","Wrapper&lt;Self&gt;":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl&amp;lt;T:&amp;nbsp;&lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt;&amp;gt; &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/span&gt;"}</script>
\ No newline at end of file
+<script type="text/json" id="notable-traits-data">{"SomeStruct":"&lt;h3&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.SomeStruct.html\" title=\"struct doc_notable_trait::SomeStruct\"&gt;SomeStruct&lt;/a&gt;&lt;/span&gt;","Wrapper&lt;Self&gt;":"&lt;h3&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl&amp;lt;T:&amp;nbsp;&lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt;&amp;gt; &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/span&gt;"}</script>
\ No newline at end of file
diff --git a/src/test/rustdoc/doc-notable_trait.wrap-me.html b/src/test/rustdoc/doc-notable_trait.wrap-me.html
index 9a59d5edd12a8..bed2a38b24a2b 100644
--- a/src/test/rustdoc/doc-notable_trait.wrap-me.html
+++ b/src/test/rustdoc/doc-notable_trait.wrap-me.html
@@ -1 +1 @@
-<script type="text/json" id="notable-traits-data">{"Wrapper&lt;Self&gt;":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl&amp;lt;T:&amp;nbsp;&lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt;&amp;gt; &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/span&gt;"}</script>
\ No newline at end of file
+<script type="text/json" id="notable-traits-data">{"Wrapper&lt;Self&gt;":"&lt;h3&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl&amp;lt;T:&amp;nbsp;&lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt;&amp;gt; &lt;a class=\"trait\" href=\"trait.SomeTrait.html\" title=\"trait doc_notable_trait::SomeTrait\"&gt;SomeTrait&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.Wrapper.html\" title=\"struct doc_notable_trait::Wrapper\"&gt;Wrapper&lt;/a&gt;&amp;lt;T&amp;gt;&lt;/span&gt;"}</script>
\ No newline at end of file
diff --git a/src/test/rustdoc/spotlight-from-dependency.odd.html b/src/test/rustdoc/spotlight-from-dependency.odd.html
index 987a949af44b1..1d02c13ebfb3c 100644
--- a/src/test/rustdoc/spotlight-from-dependency.odd.html
+++ b/src/test/rustdoc/spotlight-from-dependency.odd.html
@@ -1 +1 @@
-<script type="text/json" id="notable-traits-data">{"Odd":"&lt;h3 class=\"notable\"&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.Odd.html\" title=\"struct foo::Odd\"&gt;Odd&lt;/a&gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"{{channel}}/core/iter/traits/iterator/trait.Iterator.html\" title=\"trait core::iter::traits::iterator::Iterator\"&gt;Iterator&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.Odd.html\" title=\"struct foo::Odd\"&gt;Odd&lt;/a&gt;&lt;/span&gt;&lt;span class=\"where fmt-newline\"&gt;    type &lt;a href=\"{{channel}}/core/iter/traits/iterator/trait.Iterator.html#associatedtype.Item\" class=\"associatedtype\"&gt;Item&lt;/a&gt; = &lt;a class=\"primitive\" href=\"{{channel}}/std/primitive.usize.html\"&gt;usize&lt;/a&gt;;&lt;/span&gt;"}</script>
\ No newline at end of file
+<script type="text/json" id="notable-traits-data">{"Odd":"&lt;h3&gt;Notable traits for &lt;code&gt;&lt;a class=\"struct\" href=\"struct.Odd.html\" title=\"struct foo::Odd\"&gt;Odd&lt;/a&gt;&lt;/code&gt;&lt;/h3&gt;&lt;pre class=\"content\"&gt;&lt;code&gt;&lt;span class=\"where fmt-newline\"&gt;impl &lt;a class=\"trait\" href=\"{{channel}}/core/iter/traits/iterator/trait.Iterator.html\" title=\"trait core::iter::traits::iterator::Iterator\"&gt;Iterator&lt;/a&gt; for &lt;a class=\"struct\" href=\"struct.Odd.html\" title=\"struct foo::Odd\"&gt;Odd&lt;/a&gt;&lt;/span&gt;&lt;span class=\"where fmt-newline\"&gt;    type &lt;a href=\"{{channel}}/core/iter/traits/iterator/trait.Iterator.html#associatedtype.Item\" class=\"associatedtype\"&gt;Item&lt;/a&gt; = &lt;a class=\"primitive\" href=\"{{channel}}/std/primitive.usize.html\"&gt;usize&lt;/a&gt;;&lt;/span&gt;"}</script>
\ No newline at end of file
diff --git a/src/test/rustdoc/spotlight-from-dependency.rs b/src/test/rustdoc/spotlight-from-dependency.rs
index 156aedca62b4e..090ad187d9cc0 100644
--- a/src/test/rustdoc/spotlight-from-dependency.rs
+++ b/src/test/rustdoc/spotlight-from-dependency.rs
@@ -3,7 +3,7 @@
 use std::iter::Iterator;
 
 // @has foo/struct.Odd.html
-// @has - '//*[@id="method.new"]//span[@class="notable-traits"]/@data-ty' 'Odd'
+// @has - '//*[@id="method.new"]//a[@class="notable-traits"]/@data-ty' 'Odd'
 // @snapshot odd - '//script[@id="notable-traits-data"]'
 pub struct Odd {
     current: usize,

From fa2c08112fd9e7ce9d4109ed52a36f7378710526 Mon Sep 17 00:00:00 2001
From: Michael Howell <michael@notriddle.com>
Date: Sat, 12 Nov 2022 09:21:29 -0700
Subject: [PATCH 08/12] rustdoc: remove no-op CSS `.scrape-help { background:
 transparent }`

It's a link. This is the default CSS for it.
---
 src/librustdoc/html/static/css/rustdoc.css | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index 6a068a3d243d9..74d7518de6b66 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -1984,7 +1984,6 @@ in storage.js
 	font-size: 12px;
 	position: relative;
 	bottom: 1px;
-	background: transparent;
 	border-width: 1px;
 	border-style: solid;
 	border-radius: 50px;

From cb3a04b6ef4db1b16d5e8dac5fcc00d66183eed6 Mon Sep 17 00:00:00 2001
From: Michael Howell <michael@notriddle.com>
Date: Sat, 12 Nov 2022 08:53:12 -0700
Subject: [PATCH 09/12] rustdoc: avoid excessive HTML generated in example
 sources

---
 src/librustdoc/html/render/mod.rs          |  6 +-----
 src/librustdoc/html/sources.rs             | 10 +++++-----
 src/librustdoc/html/static/css/rustdoc.css |  5 +++--
 3 files changed, 9 insertions(+), 12 deletions(-)

diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index e09106077f7ed..1fe52353449ba 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -2899,11 +2899,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
             cx,
             &cx.root_path(),
             highlight::DecorationInfo(decoration_info),
-            sources::SourceContext::Embedded {
-                url: &call_data.url,
-                offset: line_min,
-                needs_expansion,
-            },
+            sources::SourceContext::Embedded { offset: line_min, needs_expansion },
         );
         write!(w, "</div></div>");
 
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
index 99b4678c4577f..50135d6019006 100644
--- a/src/librustdoc/html/sources.rs
+++ b/src/librustdoc/html/sources.rs
@@ -256,9 +256,9 @@ where
     }
 }
 
-pub(crate) enum SourceContext<'a> {
+pub(crate) enum SourceContext {
     Standalone,
-    Embedded { url: &'a str, offset: usize, needs_expansion: bool },
+    Embedded { offset: usize, needs_expansion: bool },
 }
 
 /// Wrapper struct to render the source code of a file. This will do things like
@@ -270,7 +270,7 @@ pub(crate) fn print_src(
     context: &Context<'_>,
     root_path: &str,
     decoration_info: highlight::DecorationInfo,
-    source_context: SourceContext<'_>,
+    source_context: SourceContext,
 ) {
     let lines = s.lines().count();
     let mut line_numbers = Buffer::empty_from(buf);
@@ -286,12 +286,12 @@ pub(crate) fn print_src(
                 writeln!(line_numbers, "<a href=\"#{line}\" id=\"{line}\">{line}</a>")
             }
         }
-        SourceContext::Embedded { url, offset, needs_expansion } => {
+        SourceContext::Embedded { offset, needs_expansion } => {
             extra =
                 if needs_expansion { Some(r#"<span class="expand">&varr;</span>"#) } else { None };
             for line_number in 1..=lines {
                 let line = line_number + offset;
-                writeln!(line_numbers, "<a href=\"{root_path}{url}#{line}\">{line}</a>")
+                writeln!(line_numbers, "<span>{line}</span>")
             }
         }
     }
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index b64fe74e960e3..bbcce72666852 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -575,7 +575,7 @@ ul.block, .block li {
 	border-color: var(--example-line-numbers-border-color);
 }
 
-.src-line-numbers a {
+.src-line-numbers a, .src-line-numbers span {
 	color: var(--src-line-numbers-span-color);
 }
 .src-line-numbers :target {
@@ -2061,7 +2061,8 @@ in storage.js
 	padding: 14px 0;
 }
 
-.scraped-example .code-wrapper .src-line-numbers a {
+.scraped-example .code-wrapper .src-line-numbers a,
+.scraped-example .code-wrapper .src-line-numbers span {
 	padding: 0 14px;
 }
 

From 34972c512b3384d2194eaa0a85afdf896c6a4d75 Mon Sep 17 00:00:00 2001
From: Joshua Nelson <jnelson@cloudflare.com>
Date: Sun, 6 Nov 2022 16:59:43 -0600
Subject: [PATCH 10/12] Distinguish `--dry-run` from the automatic dry run
 check

---
 src/bootstrap/builder.rs       | 12 ++++-----
 src/bootstrap/builder/tests.rs |  4 +--
 src/bootstrap/compile.rs       | 12 ++++-----
 src/bootstrap/config.rs        | 30 +++++++++++++++++-----
 src/bootstrap/dist.rs          | 12 ++++-----
 src/bootstrap/doc.rs           | 10 ++++----
 src/bootstrap/format.rs        |  2 +-
 src/bootstrap/lib.rs           | 46 +++++++++++++++++-----------------
 src/bootstrap/native.rs        | 20 +++++++--------
 src/bootstrap/sanity.rs        |  4 +--
 src/bootstrap/tarball.rs       |  2 +-
 src/bootstrap/test.rs          | 10 ++++----
 src/bootstrap/tool.rs          |  2 +-
 src/bootstrap/toolstate.rs     |  4 +--
 src/bootstrap/util.rs          |  4 +--
 15 files changed, 96 insertions(+), 78 deletions(-)

diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index 6fd363935079d..31158870f394e 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -1074,11 +1074,11 @@ impl<'a> Builder<'a> {
         let mut hasher = sha2::Sha256::new();
         // FIXME: this is ok for rustfmt (4.1 MB large at time of writing), but it seems memory-intensive for rustc and larger components.
         // Consider using streaming IO instead?
-        let contents = if self.config.dry_run { vec![] } else { t!(fs::read(path)) };
+        let contents = if self.config.dry_run() { vec![] } else { t!(fs::read(path)) };
         hasher.update(&contents);
         let found = hex::encode(hasher.finalize().as_slice());
         let verified = found == expected;
-        if !verified && !self.config.dry_run {
+        if !verified && !self.config.dry_run() {
             println!(
                 "invalid checksum: \n\
                 found:    {found}\n\
@@ -1292,7 +1292,7 @@ impl<'a> Builder<'a> {
     /// Note that this returns `None` if LLVM is disabled, or if we're in a
     /// check build or dry-run, where there's no need to build all of LLVM.
     fn llvm_config(&self, target: TargetSelection) -> Option<PathBuf> {
-        if self.config.llvm_enabled() && self.kind != Kind::Check && !self.config.dry_run {
+        if self.config.llvm_enabled() && self.kind != Kind::Check && !self.config.dry_run() {
             let llvm_config = self.ensure(native::Llvm { target });
             if llvm_config.is_file() {
                 return Some(llvm_config);
@@ -1644,7 +1644,7 @@ impl<'a> Builder<'a> {
         //
         // Only clear out the directory if we're compiling std; otherwise, we
         // should let Cargo take care of things for us (via depdep info)
-        if !self.config.dry_run && mode == Mode::Std && cmd == "build" {
+        if !self.config.dry_run() && mode == Mode::Std && cmd == "build" {
             self.clear_if_dirty(&out_dir, &self.rustc(compiler));
         }
 
@@ -2142,7 +2142,7 @@ impl<'a> Builder<'a> {
             (out, dur - deps)
         };
 
-        if self.config.print_step_timings && !self.config.dry_run {
+        if self.config.print_step_timings && !self.config.dry_run() {
             let step_string = format!("{:?}", step);
             let brace_index = step_string.find("{").unwrap_or(0);
             let type_string = type_name::<S>();
@@ -2216,7 +2216,7 @@ impl<'a> Builder<'a> {
     }
 
     pub(crate) fn open_in_browser(&self, path: impl AsRef<Path>) {
-        if self.config.dry_run || !self.config.cmd.open() {
+        if self.config.dry_run() || !self.config.cmd.open() {
             return;
         }
 
diff --git a/src/bootstrap/builder/tests.rs b/src/bootstrap/builder/tests.rs
index 88bbcc93d072c..5f21d2b0067dc 100644
--- a/src/bootstrap/builder/tests.rs
+++ b/src/bootstrap/builder/tests.rs
@@ -1,5 +1,5 @@
 use super::*;
-use crate::config::{Config, TargetSelection};
+use crate::config::{Config, DryRun, TargetSelection};
 use std::thread;
 
 fn configure(cmd: &str, host: &[&str], target: &[&str]) -> Config {
@@ -10,7 +10,7 @@ fn configure_with_args(cmd: &[String], host: &[&str], target: &[&str]) -> Config
     let mut config = Config::parse(cmd);
     // don't save toolstates
     config.save_toolstates = None;
-    config.dry_run = true;
+    config.dry_run = DryRun::SelfCheck;
 
     // Ignore most submodules, since we don't need them for a dry run.
     // But make sure to check out the `doc` and `rust-analyzer` submodules, since some steps need them
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index 18e780a108d5a..79e07be614c0b 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -447,7 +447,7 @@ fn copy_sanitizers(
 ) -> Vec<PathBuf> {
     let runtimes: Vec<native::SanitizerRuntime> = builder.ensure(native::Sanitizers { target });
 
-    if builder.config.dry_run {
+    if builder.config.dry_run() {
         return Vec::new();
     }
 
@@ -986,7 +986,7 @@ impl Step for CodegenBackend {
             compiler.stage, backend, &compiler.host, target
         ));
         let files = run_cargo(builder, cargo, vec![], &tmp_stamp, vec![], false);
-        if builder.config.dry_run {
+        if builder.config.dry_run() {
             return;
         }
         let mut files = files.into_iter().filter(|f| {
@@ -1034,7 +1034,7 @@ fn copy_codegen_backends_to_sysroot(
     let dst = builder.sysroot_codegen_backends(target_compiler);
     t!(fs::create_dir_all(&dst), dst);
 
-    if builder.config.dry_run {
+    if builder.config.dry_run() {
         return;
     }
 
@@ -1332,7 +1332,7 @@ impl Step for Assemble {
 
         if builder.config.rust_codegen_backends.contains(&INTERNER.intern_str("llvm")) {
             let llvm_config_bin = builder.ensure(native::Llvm { target: target_compiler.host });
-            if !builder.config.dry_run {
+            if !builder.config.dry_run() {
                 let llvm_bin_dir = output(Command::new(llvm_config_bin).arg("--bindir"));
                 let llvm_bin_dir = Path::new(llvm_bin_dir.trim());
 
@@ -1402,7 +1402,7 @@ pub fn run_cargo(
     additional_target_deps: Vec<(PathBuf, DependencyType)>,
     is_check: bool,
 ) -> Vec<PathBuf> {
-    if builder.config.dry_run {
+    if builder.config.dry_run() {
         return Vec::new();
     }
 
@@ -1542,7 +1542,7 @@ pub fn stream_cargo(
     cb: &mut dyn FnMut(CargoMessage<'_>),
 ) -> bool {
     let mut cargo = Command::from(cargo);
-    if builder.config.dry_run {
+    if builder.config.dry_run() {
         return true;
     }
     // Instruct Cargo to give us json messages on stdout, critically leaving
diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs
index 2afce4fac42f8..e843bd411c172 100644
--- a/src/bootstrap/config.rs
+++ b/src/bootstrap/config.rs
@@ -33,6 +33,17 @@ macro_rules! check_ci_llvm {
     };
 }
 
+#[derive(Clone, Default)]
+pub enum DryRun {
+    /// This isn't a dry run.
+    #[default]
+    Disabled,
+    /// This is a dry run enabled by bootstrap itself, so it can verify that no work is done.
+    SelfCheck,
+    /// This is a dry run enabled by the `--dry-run` flag.
+    UserSelected,
+}
+
 /// Global configuration for the entire build and/or bootstrap.
 ///
 /// This structure is derived from a combination of both `config.toml` and
@@ -84,7 +95,7 @@ pub struct Config {
     pub jobs: Option<u32>,
     pub cmd: Subcommand,
     pub incremental: bool,
-    pub dry_run: bool,
+    pub dry_run: DryRun,
     /// `None` if we shouldn't download CI compiler artifacts, or the commit to download if we should.
     #[cfg(not(test))]
     download_rustc_commit: Option<String>,
@@ -820,7 +831,7 @@ impl Config {
         config.jobs = flags.jobs.map(threads_from_config);
         config.cmd = flags.cmd;
         config.incremental = flags.incremental;
-        config.dry_run = flags.dry_run;
+        config.dry_run = if flags.dry_run { DryRun::UserSelected } else { DryRun::Disabled };
         config.keep_stage = flags.keep_stage;
         config.keep_stage_std = flags.keep_stage_std;
         config.color = flags.color;
@@ -965,7 +976,7 @@ impl Config {
             .unwrap_or_else(|| config.out.join(config.build.triple).join("stage0/bin/cargo"));
 
         // NOTE: it's important this comes *after* we set `initial_rustc` just above.
-        if config.dry_run {
+        if config.dry_run() {
             let dir = config.out.join("tmp-dry-run");
             t!(fs::create_dir_all(&dir));
             config.out = dir;
@@ -1372,6 +1383,13 @@ impl Config {
         config
     }
 
+    pub(crate) fn dry_run(&self) -> bool {
+        match self.dry_run {
+            DryRun::Disabled => false,
+            DryRun::SelfCheck | DryRun::UserSelected => true,
+        }
+    }
+
     /// A git invocation which runs inside the source directory.
     ///
     /// Use this rather than `Command::new("git")` in order to support out-of-tree builds.
@@ -1461,7 +1479,7 @@ impl Config {
     /// This is computed on demand since LLVM might have to first be downloaded from CI.
     pub(crate) fn llvm_link_shared(builder: &Builder<'_>) -> bool {
         let mut opt = builder.config.llvm_link_shared.get();
-        if opt.is_none() && builder.config.dry_run {
+        if opt.is_none() && builder.config.dry_run() {
             // just assume static for now - dynamic linking isn't supported on all platforms
             return false;
         }
@@ -1488,7 +1506,7 @@ impl Config {
     /// Return whether we will use a downloaded, pre-compiled version of rustc, or just build from source.
     pub(crate) fn download_rustc(builder: &Builder<'_>) -> bool {
         static DOWNLOAD_RUSTC: OnceCell<bool> = OnceCell::new();
-        if builder.config.dry_run && DOWNLOAD_RUSTC.get().is_none() {
+        if builder.config.dry_run() && DOWNLOAD_RUSTC.get().is_none() {
             // avoid trying to actually download the commit
             return false;
         }
@@ -1507,7 +1525,7 @@ impl Config {
             RustfmtState::SystemToolchain(p) | RustfmtState::Downloaded(p) => Some(p.clone()),
             RustfmtState::Unavailable => None,
             r @ RustfmtState::LazyEvaluated => {
-                if builder.config.dry_run {
+                if builder.config.dry_run() {
                     return Some(PathBuf::new());
                 }
                 let path = maybe_download_rustfmt(builder);
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index 110a3ee4918da..9fbe476534eb7 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -945,7 +945,7 @@ impl Step for PlainSourceTarball {
                 .arg(builder.src.join("./src/bootstrap/Cargo.toml"))
                 .current_dir(&plain_dst_src);
 
-            let config = if !builder.config.dry_run {
+            let config = if !builder.config.dry_run() {
                 t!(String::from_utf8(t!(cmd.output()).stdout))
             } else {
                 String::new()
@@ -1386,7 +1386,7 @@ impl Step for Extended {
         let etc = builder.src.join("src/etc/installer");
 
         // Avoid producing tarballs during a dry run.
-        if builder.config.dry_run {
+        if builder.config.dry_run() {
             return;
         }
 
@@ -1818,7 +1818,7 @@ impl Step for Extended {
             let _time = timeit(builder);
             builder.run(&mut cmd);
 
-            if !builder.config.dry_run {
+            if !builder.config.dry_run() {
                 t!(fs::rename(exe.join(&filename), distdir(builder).join(&filename)));
             }
         }
@@ -1882,12 +1882,12 @@ fn maybe_install_llvm(builder: &Builder<'_>, target: TargetSelection, dst_libdir
         if llvm_dylib_path.exists() {
             builder.install(&llvm_dylib_path, dst_libdir, 0o644);
         }
-        !builder.config.dry_run
+        !builder.config.dry_run()
     } else if let Ok(llvm_config) = crate::native::prebuilt_llvm_config(builder, target) {
         let mut cmd = Command::new(llvm_config);
         cmd.arg("--libfiles");
         builder.verbose(&format!("running {:?}", cmd));
-        let files = if builder.config.dry_run { "".into() } else { output(&mut cmd) };
+        let files = if builder.config.dry_run() { "".into() } else { output(&mut cmd) };
         let build_llvm_out = &builder.llvm_out(builder.config.build);
         let target_llvm_out = &builder.llvm_out(target);
         for file in files.trim_end().split(' ') {
@@ -1899,7 +1899,7 @@ fn maybe_install_llvm(builder: &Builder<'_>, target: TargetSelection, dst_libdir
             };
             builder.install(&file, dst_libdir, 0o644);
         }
-        !builder.config.dry_run
+        !builder.config.dry_run()
     } else {
         false
     }
diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index 280e232ca2dd0..c7d21bf3cdb3f 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -151,7 +151,7 @@ impl Step for RustbookSrc {
         let index = out.join("index.html");
         let rustbook = builder.tool_exe(Tool::Rustbook);
         let mut rustbook_cmd = builder.tool_cmd(Tool::Rustbook);
-        if builder.config.dry_run || up_to_date(&src, &index) && up_to_date(&rustbook, &index) {
+        if builder.config.dry_run() || up_to_date(&src, &index) && up_to_date(&rustbook, &index) {
             return;
         }
         builder.info(&format!("Rustbook ({}) - {}", target, name));
@@ -331,8 +331,8 @@ impl Step for Standalone {
                 && up_to_date(&footer, &html)
                 && up_to_date(&favicon, &html)
                 && up_to_date(&full_toc, &html)
-                && (builder.config.dry_run || up_to_date(&version_info, &html))
-                && (builder.config.dry_run || up_to_date(&rustdoc, &html))
+                && (builder.config.dry_run() || up_to_date(&version_info, &html))
+                && (builder.config.dry_run() || up_to_date(&rustdoc, &html))
             {
                 continue;
             }
@@ -402,7 +402,7 @@ impl Step for SharedAssets {
 
         let version_input = builder.src.join("src").join("doc").join("version_info.html.template");
         let version_info = out.join("version_info.html");
-        if !builder.config.dry_run && !up_to_date(&version_input, &version_info) {
+        if !builder.config.dry_run() && !up_to_date(&version_input, &version_info) {
             let info = t!(fs::read_to_string(&version_input))
                 .replace("VERSION", &builder.rust_release())
                 .replace("SHORT_HASH", builder.rust_info.sha_short().unwrap_or(""))
@@ -900,7 +900,7 @@ impl Step for UnstableBookGen {
 }
 
 fn symlink_dir_force(config: &Config, src: &Path, dst: &Path) -> io::Result<()> {
-    if config.dry_run {
+    if config.dry_run() {
         return Ok(());
     }
     if let Ok(m) = fs::symlink_metadata(dst) {
diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs
index 37322670e6564..5e7264fe765a9 100644
--- a/src/bootstrap/format.rs
+++ b/src/bootstrap/format.rs
@@ -43,7 +43,7 @@ struct RustfmtConfig {
 }
 
 pub fn format(build: &Builder<'_>, check: bool, paths: &[PathBuf]) {
-    if build.config.dry_run {
+    if build.config.dry_run() {
         return;
     }
     let mut builder = ignore::types::TypesBuilder::new();
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index f5def8ba8341f..7dfd75a53519f 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -112,7 +112,7 @@ use std::path::{Path, PathBuf};
 use std::process::Command;
 use std::str;
 
-use config::Target;
+use config::{DryRun, Target};
 use filetime::FileTime;
 use once_cell::sync::OnceCell;
 
@@ -430,7 +430,7 @@ impl Build {
         // we always try to use git for LLVM builds
         let in_tree_llvm_info = channel::GitInfo::new(false, &src.join("src/llvm-project"));
 
-        let initial_target_libdir_str = if config.dry_run {
+        let initial_target_libdir_str = if config.dry_run() {
             "/dummy/lib/path/to/lib/".to_string()
         } else {
             output(
@@ -444,7 +444,7 @@ impl Build {
         let initial_target_dir = Path::new(&initial_target_libdir_str).parent().unwrap();
         let initial_lld = initial_target_dir.join("bin").join("rust-lld");
 
-        let initial_sysroot = if config.dry_run {
+        let initial_sysroot = if config.dry_run() {
             "/dummy".to_string()
         } else {
             output(Command::new(&config.initial_rustc).arg("--print").arg("sysroot"))
@@ -689,13 +689,13 @@ impl Build {
             }
         }
 
-        if !self.config.dry_run {
+        if !self.config.dry_run() {
             {
-                self.config.dry_run = true;
+                self.config.dry_run = DryRun::SelfCheck;
                 let builder = builder::Builder::new(&self);
                 builder.execute_cli();
             }
-            self.config.dry_run = false;
+            self.config.dry_run = DryRun::Disabled;
             let builder = builder::Builder::new(&self);
             builder.execute_cli();
         } else {
@@ -947,7 +947,7 @@ impl Build {
 
     /// Runs a command, printing out nice contextual information if it fails.
     fn run(&self, cmd: &mut Command) {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return;
         }
         self.verbose(&format!("running: {:?}", cmd));
@@ -956,7 +956,7 @@ impl Build {
 
     /// Runs a command, printing out nice contextual information if it fails.
     fn run_quiet(&self, cmd: &mut Command) {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return;
         }
         self.verbose(&format!("running: {:?}", cmd));
@@ -967,7 +967,7 @@ impl Build {
     /// Exits if the command failed to execute at all, otherwise returns its
     /// `status.success()`.
     fn try_run(&self, cmd: &mut Command) -> bool {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return true;
         }
         self.verbose(&format!("running: {:?}", cmd));
@@ -978,7 +978,7 @@ impl Build {
     /// Exits if the command failed to execute at all, otherwise returns its
     /// `status.success()`.
     fn try_run_quiet(&self, cmd: &mut Command) -> bool {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return true;
         }
         self.verbose(&format!("running: {:?}", cmd));
@@ -989,7 +989,7 @@ impl Build {
     /// Returns false if do not execute at all, otherwise returns its
     /// `status.success()`.
     fn check_run(&self, cmd: &mut Command) -> bool {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return true;
         }
         self.verbose(&format!("running: {:?}", cmd));
@@ -1019,7 +1019,7 @@ impl Build {
     }
 
     fn info(&self, msg: &str) {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return;
         }
         println!("{}", msg);
@@ -1400,7 +1400,7 @@ impl Build {
     }
 
     fn read_stamp_file(&self, stamp: &Path) -> Vec<(PathBuf, DependencyType)> {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return Vec::new();
         }
 
@@ -1440,7 +1440,7 @@ impl Build {
     }
 
     fn copy_internal(&self, src: &Path, dst: &Path, dereference_symlinks: bool) {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return;
         }
         self.verbose_than(1, &format!("Copy {:?} to {:?}", src, dst));
@@ -1477,7 +1477,7 @@ impl Build {
     /// Copies the `src` directory recursively to `dst`. Both are assumed to exist
     /// when this function is called.
     pub fn cp_r(&self, src: &Path, dst: &Path) {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return;
         }
         for f in self.read_dir(src) {
@@ -1530,7 +1530,7 @@ impl Build {
     }
 
     fn install(&self, src: &Path, dstdir: &Path, perms: u32) {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return;
         }
         let dst = dstdir.join(src.file_name().unwrap());
@@ -1544,28 +1544,28 @@ impl Build {
     }
 
     fn create(&self, path: &Path, s: &str) {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return;
         }
         t!(fs::write(path, s));
     }
 
     fn read(&self, path: &Path) -> String {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return String::new();
         }
         t!(fs::read_to_string(path))
     }
 
     fn create_dir(&self, dir: &Path) {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return;
         }
         t!(fs::create_dir_all(dir))
     }
 
     fn remove_dir(&self, dir: &Path) {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return;
         }
         t!(fs::remove_dir_all(dir))
@@ -1574,7 +1574,7 @@ impl Build {
     fn read_dir(&self, dir: &Path) -> impl Iterator<Item = fs::DirEntry> {
         let iter = match fs::read_dir(dir) {
             Ok(v) => v,
-            Err(_) if self.config.dry_run => return vec![].into_iter(),
+            Err(_) if self.config.dry_run() => return vec![].into_iter(),
             Err(err) => panic!("could not read dir {:?}: {:?}", dir, err),
         };
         iter.map(|e| t!(e)).collect::<Vec<_>>().into_iter()
@@ -1585,11 +1585,11 @@ impl Build {
         use std::os::unix::fs::symlink as symlink_file;
         #[cfg(windows)]
         use std::os::windows::fs::symlink_file;
-        if !self.config.dry_run { symlink_file(src.as_ref(), link.as_ref()) } else { Ok(()) }
+        if !self.config.dry_run() { symlink_file(src.as_ref(), link.as_ref()) } else { Ok(()) }
     }
 
     fn remove(&self, f: &Path) {
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return;
         }
         fs::remove_file(f).unwrap_or_else(|_| panic!("failed to remove {:?}", f));
diff --git a/src/bootstrap/native.rs b/src/bootstrap/native.rs
index 94a61b727a32b..2407291ceea30 100644
--- a/src/bootstrap/native.rs
+++ b/src/bootstrap/native.rs
@@ -226,7 +226,7 @@ pub(crate) fn maybe_download_ci_llvm(builder: &Builder<'_>) {
     let llvm_stamp = llvm_root.join(".llvm-stamp");
     let llvm_sha = detect_llvm_sha(&config, builder.rust_info.is_managed_git_subrepository());
     let key = format!("{}{}", llvm_sha, config.llvm_assertions);
-    if program_out_of_date(&llvm_stamp, &key) && !config.dry_run {
+    if program_out_of_date(&llvm_stamp, &key) && !config.dry_run() {
         download_ci_llvm(builder, &llvm_sha);
         for entry in t!(fs::read_dir(llvm_root.join("bin"))) {
             builder.fix_bin_or_dylib(&t!(entry).path());
@@ -505,7 +505,7 @@ impl Step for Llvm {
         // https://llvm.org/docs/HowToCrossCompileLLVM.html
         if target != builder.config.build {
             let llvm_config = builder.ensure(Llvm { target: builder.config.build });
-            if !builder.config.dry_run {
+            if !builder.config.dry_run() {
                 let llvm_bindir = output(Command::new(&llvm_config).arg("--bindir"));
                 let host_bin = Path::new(llvm_bindir.trim());
                 cfg.define(
@@ -519,7 +519,7 @@ impl Step for Llvm {
             if builder.config.llvm_clang {
                 let build_bin = builder.llvm_out(builder.config.build).join("build").join("bin");
                 let clang_tblgen = build_bin.join("clang-tblgen").with_extension(EXE_EXTENSION);
-                if !builder.config.dry_run && !clang_tblgen.exists() {
+                if !builder.config.dry_run() && !clang_tblgen.exists() {
                     panic!("unable to find {}", clang_tblgen.display());
                 }
                 cfg.define("CLANG_TABLEGEN", clang_tblgen);
@@ -553,7 +553,7 @@ impl Step for Llvm {
         //        tools. Figure out how to filter them down and only build the right
         //        tools and libs on all platforms.
 
-        if builder.config.dry_run {
+        if builder.config.dry_run() {
             return build_llvm_config;
         }
 
@@ -611,7 +611,7 @@ fn check_llvm_version(builder: &Builder<'_>, llvm_config: &Path) {
         return;
     }
 
-    if builder.config.dry_run {
+    if builder.config.dry_run() {
         return;
     }
 
@@ -872,7 +872,7 @@ impl Step for Lld {
 
     /// Compile LLD for `target`.
     fn run(self, builder: &Builder<'_>) -> PathBuf {
-        if builder.config.dry_run {
+        if builder.config.dry_run() {
             return PathBuf::from("lld-out-dir-test-gen");
         }
         let target = self.target;
@@ -990,7 +990,7 @@ impl Step for TestHelpers {
     /// Compiles the `rust_test_helpers.c` library which we used in various
     /// `run-pass` tests for ABI testing.
     fn run(self, builder: &Builder<'_>) {
-        if builder.config.dry_run {
+        if builder.config.dry_run() {
             return;
         }
         // The x86_64-fortanix-unknown-sgx target doesn't have a working C
@@ -1066,7 +1066,7 @@ impl Step for Sanitizers {
         }
 
         let llvm_config = builder.ensure(Llvm { target: builder.config.build });
-        if builder.config.dry_run {
+        if builder.config.dry_run() {
             return runtimes;
         }
 
@@ -1240,7 +1240,7 @@ impl Step for CrtBeginEnd {
     fn run(self, builder: &Builder<'_>) -> Self::Output {
         let out_dir = builder.native_dir(self.target).join("crt");
 
-        if builder.config.dry_run {
+        if builder.config.dry_run() {
             return out_dir;
         }
 
@@ -1304,7 +1304,7 @@ impl Step for Libunwind {
 
     /// Build linunwind.a
     fn run(self, builder: &Builder<'_>) -> Self::Output {
-        if builder.config.dry_run {
+        if builder.config.dry_run() {
             return PathBuf::new();
         }
 
diff --git a/src/bootstrap/sanity.rs b/src/bootstrap/sanity.rs
index bdfd5fe5c421c..35c66cfd95f2f 100644
--- a/src/bootstrap/sanity.rs
+++ b/src/bootstrap/sanity.rs
@@ -163,7 +163,7 @@ than building it.
             continue;
         }
 
-        if !build.config.dry_run {
+        if !build.config.dry_run() {
             cmd_finder.must_have(build.cc(*target));
             if let Some(ar) = build.ar(*target) {
                 cmd_finder.must_have(ar);
@@ -172,7 +172,7 @@ than building it.
     }
 
     for host in &build.hosts {
-        if !build.config.dry_run {
+        if !build.config.dry_run() {
             cmd_finder.must_have(build.cxx(*host).unwrap());
         }
     }
diff --git a/src/bootstrap/tarball.rs b/src/bootstrap/tarball.rs
index d999b6c150333..82b063583c9f9 100644
--- a/src/bootstrap/tarball.rs
+++ b/src/bootstrap/tarball.rs
@@ -323,7 +323,7 @@ impl<'a> Tarball<'a> {
         // Ensure there are no symbolic links in the tarball. In particular,
         // rustup-toolchain-install-master and most versions of Windows can't handle symbolic links.
         let decompressed_output = self.temp_dir.join(&package_name);
-        if !self.builder.config.dry_run && !self.permit_symlinks {
+        if !self.builder.config.dry_run() && !self.permit_symlinks {
             for entry in walkdir::WalkDir::new(&decompressed_output) {
                 let entry = t!(entry);
                 if entry.path_is_symlink() {
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index 2b2257b72ae79..fd362b8367cc0 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -508,7 +508,7 @@ impl Miri {
         cargo.arg("--print-sysroot");
 
         // FIXME: Is there a way in which we can re-use the usual `run` helpers?
-        if builder.config.dry_run {
+        if builder.config.dry_run() {
             String::new()
         } else {
             builder.verbose(&format!("running: {:?}", cargo));
@@ -1537,7 +1537,7 @@ note: if you're sure you want to do this, please open an issue as to why. In the
         let mut copts_passed = false;
         if builder.config.llvm_enabled() {
             let llvm_config = builder.ensure(native::Llvm { target: builder.config.build });
-            if !builder.config.dry_run {
+            if !builder.config.dry_run() {
                 let llvm_version = output(Command::new(&llvm_config).arg("--version"));
                 let llvm_components = output(Command::new(&llvm_config).arg("--components"));
                 // Remove trailing newline from llvm-config output.
@@ -1555,14 +1555,14 @@ note: if you're sure you want to do this, please open an issue as to why. In the
             // requirement, but the `-L` library path is not propagated across
             // separate compilations. We can add LLVM's library path to the
             // platform-specific environment variable as a workaround.
-            if !builder.config.dry_run && suite.ends_with("fulldeps") {
+            if !builder.config.dry_run() && suite.ends_with("fulldeps") {
                 let llvm_libdir = output(Command::new(&llvm_config).arg("--libdir"));
                 add_link_lib_path(vec![llvm_libdir.trim().into()], &mut cmd);
             }
 
             // Only pass correct values for these flags for the `run-make` suite as it
             // requires that a C++ compiler was configured which isn't always the case.
-            if !builder.config.dry_run && matches!(suite, "run-make" | "run-make-fulldeps") {
+            if !builder.config.dry_run() && matches!(suite, "run-make" | "run-make-fulldeps") {
                 // The llvm/bin directory contains many useful cross-platform
                 // tools. Pass the path to run-make tests so they can use them.
                 let llvm_bin_path = llvm_config
@@ -1590,7 +1590,7 @@ note: if you're sure you want to do this, please open an issue as to why. In the
 
         // Only pass correct values for these flags for the `run-make` suite as it
         // requires that a C++ compiler was configured which isn't always the case.
-        if !builder.config.dry_run && matches!(suite, "run-make" | "run-make-fulldeps") {
+        if !builder.config.dry_run() && matches!(suite, "run-make" | "run-make-fulldeps") {
             cmd.arg("--cc")
                 .arg(builder.cc(target))
                 .arg("--cxx")
diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs
index d395220694705..ba329ea6c7592 100644
--- a/src/bootstrap/tool.rs
+++ b/src/bootstrap/tool.rs
@@ -522,7 +522,7 @@ impl Step for Rustdoc {
         builder.ensure(compile::Rustc::new(build_compiler, target_compiler.host));
         // NOTE: this implies that `download-rustc` is pretty useless when compiling with the stage0
         // compiler, since you do just as much work.
-        if !builder.config.dry_run && builder.download_rustc() && build_compiler.stage == 0 {
+        if !builder.config.dry_run() && builder.download_rustc() && build_compiler.stage == 0 {
             println!(
                 "warning: `download-rustc` does nothing when building stage1 tools; consider using `--stage 2` instead"
             );
diff --git a/src/bootstrap/toolstate.rs b/src/bootstrap/toolstate.rs
index 1a17744322753..1969e0b6f8722 100644
--- a/src/bootstrap/toolstate.rs
+++ b/src/bootstrap/toolstate.rs
@@ -158,7 +158,7 @@ impl Step for ToolStateCheck {
     ///   stable tool. That is, the status is not allowed to get worse
     ///   (test-pass to test-fail or build-fail).
     fn run(self, builder: &Builder<'_>) {
-        if builder.config.dry_run {
+        if builder.config.dry_run() {
             return;
         }
 
@@ -265,7 +265,7 @@ impl Builder<'_> {
         // If we're in a dry run setting we don't want to save toolstates as
         // that means if we e.g. panic down the line it'll look like we tested
         // everything (but we actually haven't).
-        if self.config.dry_run {
+        if self.config.dry_run() {
             return;
         }
         // Toolstate isn't tracked for clippy or rustfmt, but since most tools do, we avoid checking
diff --git a/src/bootstrap/util.rs b/src/bootstrap/util.rs
index 20c3801f0a502..8a2f46abbd23c 100644
--- a/src/bootstrap/util.rs
+++ b/src/bootstrap/util.rs
@@ -105,7 +105,7 @@ pub struct TimeIt(bool, Instant);
 
 /// Returns an RAII structure that prints out how long it took to drop.
 pub fn timeit(builder: &Builder<'_>) -> TimeIt {
-    TimeIt(builder.config.dry_run, Instant::now())
+    TimeIt(builder.config.dry_run(), Instant::now())
 }
 
 impl Drop for TimeIt {
@@ -128,7 +128,7 @@ pub(crate) fn program_out_of_date(stamp: &Path, key: &str) -> bool {
 /// Symlinks two directories, using junctions on Windows and normal symlinks on
 /// Unix.
 pub fn symlink_dir(config: &Config, src: &Path, dest: &Path) -> io::Result<()> {
-    if config.dry_run {
+    if config.dry_run() {
         return Ok(());
     }
     let _ = fs::remove_dir(dest);

From 24378885c8ca7e3cae25723da577445d200fe8e8 Mon Sep 17 00:00:00 2001
From: Joshua Nelson <jnelson@cloudflare.com>
Date: Sun, 6 Nov 2022 17:00:58 -0600
Subject: [PATCH 11/12] Print "Checking/Building ..." message even when
 --dry-run is passed

This makes it a lot easier to understand what commands will be run without
having to parse the `-vv` output, which isn't meant to be user facing.
---
 src/bootstrap/lib.rs | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index 7dfd75a53519f..bbb5a18ba07ba 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -1019,10 +1019,12 @@ impl Build {
     }
 
     fn info(&self, msg: &str) {
-        if self.config.dry_run() {
-            return;
+        match self.config.dry_run {
+            DryRun::SelfCheck => return,
+            DryRun::Disabled | DryRun::UserSelected => {
+                println!("{}", msg);
+            }
         }
-        println!("{}", msg);
     }
 
     /// Returns the number of parallel jobs that have been configured for this

From 01b9c8847a6b09988b70603878f10c9859bf2029 Mon Sep 17 00:00:00 2001
From: Joshua Nelson <jnelson@cloudflare.com>
Date: Thu, 27 Oct 2022 13:00:55 -0500
Subject: [PATCH 12/12] Don't set `is_preview` for clippy and rustfmt

These have been shipped on stable for many years now and it would be very disruptive to ever remove them.
Remove the `-preview` suffix from their dist components.
---
 src/bootstrap/dist.rs                    | 2 --
 src/tools/build-manifest/src/versions.rs | 4 ++--
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index 110a3ee4918da..e95170b8818ca 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -1153,7 +1153,6 @@ impl Step for Clippy {
 
         let mut tarball = Tarball::new(builder, "clippy", &target.triple);
         tarball.set_overlay(OverlayKind::Clippy);
-        tarball.is_preview(true);
         tarball.add_file(clippy, "bin", 0o755);
         tarball.add_file(cargoclippy, "bin", 0o755);
         tarball.add_legal_and_readme_to("share/doc/clippy");
@@ -1251,7 +1250,6 @@ impl Step for Rustfmt {
             .expect("cargo fmt expected to build - essential tool");
         let mut tarball = Tarball::new(builder, "rustfmt", &target.triple);
         tarball.set_overlay(OverlayKind::Rustfmt);
-        tarball.is_preview(true);
         tarball.add_file(rustfmt, "bin", 0o755);
         tarball.add_file(cargofmt, "bin", 0o755);
         tarball.add_legal_and_readme_to("share/doc/rustfmt");
diff --git a/src/tools/build-manifest/src/versions.rs b/src/tools/build-manifest/src/versions.rs
index dde9745afb785..66e6982d6b4d3 100644
--- a/src/tools/build-manifest/src/versions.rs
+++ b/src/tools/build-manifest/src/versions.rs
@@ -49,10 +49,10 @@ pkg_type! {
     Cargo = "cargo",
     HtmlDocs = "rust-docs",
     RustAnalysis = "rust-analysis",
+    Clippy = "clippy",
+    Rustfmt = "rustfmt",
     Rls = "rls"; preview = true,
     RustAnalyzer = "rust-analyzer"; preview = true,
-    Clippy = "clippy"; preview = true,
-    Rustfmt = "rustfmt"; preview = true,
     LlvmTools = "llvm-tools"; preview = true,
     Miri = "miri"; preview = true,
     JsonDocs = "rust-docs-json"; preview = true,