about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock8
-rw-r--r--Cargo.toml3
-rw-r--r--RELEASES.md120
-rw-r--r--compiler/rustc_ast/src/mut_visit.rs360
-rw-r--r--compiler/rustc_ast/src/visit.rs298
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs2
-rw-r--r--compiler/rustc_ast_passes/src/ast_validation.rs2
-rw-r--r--compiler/rustc_ast_passes/src/feature_gate.rs2
-rw-r--r--compiler/rustc_ast_passes/src/node_count.rs2
-rw-r--r--compiler/rustc_builtin_macros/messages.ftl6
-rw-r--r--compiler/rustc_builtin_macros/src/alloc_error_handler.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/asm.rs4
-rw-r--r--compiler/rustc_builtin_macros/src/assert.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/cfg.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/cfg_eval.rs6
-rw-r--r--compiler/rustc_builtin_macros/src/compile_error.rs4
-rw-r--r--compiler/rustc_builtin_macros/src/concat.rs7
-rw-r--r--compiler/rustc_builtin_macros/src/concat_bytes.rs7
-rw-r--r--compiler/rustc_builtin_macros/src/concat_idents.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/derive.rs23
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/bounds.rs4
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/clone.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs4
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/debug.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/decodable.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/default.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/encodable.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/generic/mod.rs32
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/generic/ty.rs14
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/hash.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/mod.rs24
-rw-r--r--compiler/rustc_builtin_macros/src/edition_panic.rs6
-rw-r--r--compiler/rustc_builtin_macros/src/env.rs9
-rw-r--r--compiler/rustc_builtin_macros/src/errors.rs25
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs13
-rw-r--r--compiler/rustc_builtin_macros/src/global_allocator.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/lib.rs6
-rw-r--r--compiler/rustc_builtin_macros/src/log_syntax.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/pattern_type.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/source_util.rs24
-rw-r--r--compiler/rustc_builtin_macros/src/test.rs8
-rw-r--r--compiler/rustc_builtin_macros/src/trace_macros.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/util.rs192
-rw-r--r--compiler/rustc_errors/src/emitter.rs4
-rw-r--r--compiler/rustc_expand/messages.ftl9
-rw-r--r--compiler/rustc_expand/src/base.rs193
-rw-r--r--compiler/rustc_expand/src/errors.rs23
-rw-r--r--compiler/rustc_expand/src/expand.rs8
-rw-r--r--compiler/rustc_expand/src/placeholders.rs6
-rw-r--r--compiler/rustc_hir_analysis/src/collect.rs2
-rw-r--r--compiler/rustc_hir_typeck/src/closure.rs6
-rw-r--r--compiler/rustc_hir_typeck/src/expr.rs2
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs103
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs140
-rw-r--r--compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs1
-rw-r--r--compiler/rustc_lint/src/early.rs2
-rw-r--r--compiler/rustc_middle/src/mir/mod.rs9
-rw-r--r--compiler/rustc_middle/src/traits/solve/inspect.rs7
-rw-r--r--compiler/rustc_middle/src/traits/solve/inspect/format.rs3
-rw-r--r--compiler/rustc_passes/src/hir_stats.rs2
-rw-r--r--compiler/rustc_resolve/src/build_reduced_graph.rs8
-rw-r--r--compiler/rustc_resolve/src/def_collector.rs11
-rw-r--r--compiler/rustc_resolve/src/late.rs8
-rw-r--r--compiler/rustc_resolve/src/lib.rs4
-rw-r--r--compiler/rustc_resolve/src/macros.rs16
-rw-r--r--compiler/rustc_trait_selection/src/solve/assembly/mod.rs26
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs97
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs52
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs18
-rw-r--r--compiler/rustc_trait_selection/src/solve/inspect/analyse.rs202
-rw-r--r--compiler/rustc_trait_selection/src/solve/inspect/build.rs276
-rw-r--r--compiler/rustc_trait_selection/src/solve/mod.rs12
-rw-r--r--compiler/rustc_trait_selection/src/traits/coherence.rs10
-rw-r--r--library/alloc/src/lib.rs1
-rw-r--r--library/alloc/src/rc.rs11
-rw-r--r--library/alloc/src/str.rs2
-rw-r--r--library/alloc/src/string.rs4
-rw-r--r--library/core/src/intrinsics.rs2
-rw-r--r--library/core/src/lib.rs4
-rw-r--r--library/core/src/slice/iter/macros.rs41
-rw-r--r--library/core/src/str/lossy.rs74
-rw-r--r--library/core/src/str/mod.rs2
-rw-r--r--library/core/src/ub_checks.rs8
-rw-r--r--library/core/tests/lib.rs1
-rw-r--r--library/core/tests/str_lossy.rs6
-rw-r--r--library/std/src/ffi/os_str.rs6
-rw-r--r--library/std/src/lib.rs2
-rw-r--r--library/std/src/os/fd/owned.rs7
-rw-r--r--library/std/src/path.rs8
-rw-r--r--library/std/src/sys/os_str/bytes.rs12
-rw-r--r--library/std/src/sys/os_str/wtf8.rs6
-rw-r--r--library/std/src/sys/pal/unix/args.rs10
-rw-r--r--library/std/src/sys/pal/unix/fs.rs44
-rw-r--r--library/std/src/sys/thread_local/fast_local.rs7
-rw-r--r--library/std/src/sys/thread_local/mod.rs14
-rw-r--r--library/std/src/sys_common/wtf8.rs6
-rw-r--r--src/bootstrap/bootstrap.py8
-rw-r--r--src/bootstrap/mk/Makefile.in3
-rw-r--r--src/bootstrap/src/core/build_steps/clean.rs2
-rw-r--r--src/bootstrap/src/core/build_steps/dist.rs6
-rw-r--r--src/bootstrap/src/core/build_steps/mod.rs1
-rw-r--r--src/bootstrap/src/core/build_steps/run.rs5
-rw-r--r--src/bootstrap/src/core/build_steps/setup.rs13
-rw-r--r--src/bootstrap/src/core/build_steps/suggest.rs2
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs54
-rw-r--r--src/bootstrap/src/core/build_steps/toolstate.rs6
-rw-r--r--src/bootstrap/src/core/build_steps/vendor.rs61
-rw-r--r--src/bootstrap/src/core/builder.rs31
-rw-r--r--src/bootstrap/src/core/config/config.rs13
-rw-r--r--src/bootstrap/src/core/config/flags.rs104
-rw-r--r--src/bootstrap/src/core/sanity.rs29
-rw-r--r--src/etc/completions/x.py.fish37
-rw-r--r--src/etc/completions/x.py.ps144
-rw-r--r--src/etc/completions/x.py.sh119
-rw-r--r--src/etc/completions/x.py.zsh51
m---------src/tools/cargo0
-rw-r--r--src/tools/clippy/tests/ui/tabs_in_doc_comments.stderr48
-rw-r--r--src/tools/compiletest/Cargo.toml2
-rw-r--r--src/tools/compiletest/src/common.rs12
-rw-r--r--src/tools/compiletest/src/errors.rs9
-rw-r--r--src/tools/compiletest/src/header.rs9
-rw-r--r--src/tools/compiletest/src/lib.rs5
-rw-r--r--src/tools/compiletest/src/runtest.rs147
-rw-r--r--src/tools/miri/rust-version2
-rw-r--r--src/tools/miri/src/machine.rs11
-rw-r--r--src/tools/miri/src/shims/env.rs549
-rw-r--r--src/tools/miri/src/shims/extern_static.rs14
-rw-r--r--src/tools/miri/src/shims/unix/env.rs276
-rw-r--r--src/tools/miri/src/shims/unix/macos/foreign_items.rs11
-rw-r--r--src/tools/miri/src/shims/unix/mod.rs5
-rw-r--r--src/tools/miri/src/shims/windows/env.rs257
-rw-r--r--src/tools/miri/src/shims/windows/foreign_items.rs5
-rw-r--r--src/tools/miri/src/shims/windows/mod.rs8
-rw-r--r--src/tools/miri/tests/pass/path.rs60
-rw-r--r--src/tools/miri/tests/pass/shims/env/var.rs8
-rw-r--r--src/tools/miri/tests/pass/shims/path.rs37
-rw-r--r--src/tools/tidy/src/allowed_run_make_makefiles.txt1
-rw-r--r--src/tools/tidy/src/issues.txt1
-rw-r--r--src/version2
-rw-r--r--tests/crashes/123863.rs6
-rw-r--r--tests/crashes/124262.rs5
-rw-r--r--tests/crashes/124340.rs17
-rw-r--r--tests/crashes/124342.rs6
-rw-r--r--tests/crashes/124347.rs4
-rw-r--r--tests/crashes/124348.rs7
-rw-r--r--tests/crashes/124350.rs17
-rw-r--r--tests/crashes/124352.rs4
-rw-r--r--tests/crashes/124375.rs11
-rw-r--r--tests/crashes/92470.rs31
-rw-r--r--tests/incremental/slice-pattern-const-ice-83085.rs39
-rw-r--r--tests/run-make/print-native-static-libs/Makefile19
-rw-r--r--tests/run-make/print-native-static-libs/rmake.rs76
-rw-r--r--tests/ui/closures/deduce-signature/deduce-from-opaque-type-after-norm.rs11
-rw-r--r--tests/ui/closures/deduce-signature/deduce-from-opaque-type.rs9
-rw-r--r--tests/ui/closures/deduce-signature/infer-higher-ranked-signature.rs20
-rw-r--r--tests/ui/closures/deduce-signature/infer-signature-from-impl.rs (renamed from tests/ui/closures/infer-signature-from-impl.rs)3
-rw-r--r--tests/ui/closures/deduce-signature/obligation-with-leaking-placeholders.current.stderr15
-rw-r--r--tests/ui/closures/deduce-signature/obligation-with-leaking-placeholders.next.stderr (renamed from tests/ui/closures/infer-signature-from-impl.next.stderr)3
-rw-r--r--tests/ui/closures/deduce-signature/obligation-with-leaking-placeholders.rs23
-rw-r--r--tests/ui/closures/deduce-signature/supertrait-signature-inference-issue-23012.rs (renamed from tests/ui/closures/issue-23012-supertrait-signature-inference.rs)3
-rw-r--r--tests/ui/consts/const-eval/ice-unhandled-type-122191.rs18
-rw-r--r--tests/ui/consts/const-eval/ice-unhandled-type-122191.stderr58
-rw-r--r--tests/ui/consts/mono-reachable-invalid-const.rs23
-rw-r--r--tests/ui/consts/mono-reachable-invalid-const.stderr29
-rw-r--r--tests/ui/feature-gates/feature-gate-optimize_attribute.stderr20
-rw-r--r--tests/ui/feature-gates/issue-43106-gating-of-stable.stderr12
-rw-r--r--tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr12
-rw-r--r--tests/ui/parser/issues/issue-32505.stderr2
-rw-r--r--tests/ui/traits/next-solver/deduce-closure-signature-after-normalization.rs11
-rw-r--r--tests/ui/traits/next-solver/deduce-closure-signature-after-normalization.stderr14
-rw-r--r--tests/ui/typeck/ice-unexpected-region-123863.rs9
-rw-r--r--tests/ui/typeck/ice-unexpected-region-123863.stderr38
-rw-r--r--triagebot.toml4
176 files changed, 3463 insertions, 1971 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6b2593ebbb1..ac6e7974a12 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -760,11 +760,9 @@ dependencies = [
  "glob",
  "home",
  "indexmap",
- "lazycell",
  "libc",
  "miow",
  "miropt-test-tools",
- "once_cell",
  "regex",
  "rustfix 0.8.1",
  "serde",
@@ -2152,12 +2150,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
-name = "lazycell"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
-
-[[package]]
 name = "leb128"
 version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index bbf4ecfe61d..1379e4cd3bf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -112,6 +112,3 @@ strip = true
 rustc-std-workspace-core = { path = 'library/rustc-std-workspace-core' }
 rustc-std-workspace-alloc = { path = 'library/rustc-std-workspace-alloc' }
 rustc-std-workspace-std = { path = 'library/rustc-std-workspace-std' }
-
-[patch."https://github.com/rust-lang/rust-clippy"]
-clippy_lints = { path = "src/tools/clippy/clippy_lints" }
diff --git a/RELEASES.md b/RELEASES.md
index 3bd638ff64c..104ea497ba4 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,3 +1,123 @@
+Version 1.78.0 (2024-05-02)
+==========================
+
+<a id="1.78.0-Language"></a>
+
+Language
+--------
+- [Stabilize `#[cfg(target_abi = ...)]`](https://github.com/rust-lang/rust/pull/119590/)
+- [Stabilize the `#[diagnostic]` namespace and `#[diagnostic::on_unimplemented]` attribute](https://github.com/rust-lang/rust/pull/119888/)
+- [Make async-fn-in-trait implementable with concrete signatures](https://github.com/rust-lang/rust/pull/120103/)
+- [Make matching on NaN a hard error, and remove the rest of `illegal_floating_point_literal_pattern`](https://github.com/rust-lang/rust/pull/116284/)
+- [static mut: allow mutable reference to arbitrary types, not just slices and arrays](https://github.com/rust-lang/rust/pull/117614/)
+- [Extend `invalid_reference_casting` to include references casting to bigger memory layout](https://github.com/rust-lang/rust/pull/118983/)
+- [Add `non_contiguous_range_endpoints` lint for singleton gaps after exclusive ranges](https://github.com/rust-lang/rust/pull/118879/)
+- [Add `wasm_c_abi` lint for use of older wasm-bindgen versions](https://github.com/rust-lang/rust/pull/117918/)
+  This lint currently only works when using Cargo.
+- [Update `indirect_structural_match` and `pointer_structural_match` lints to match RFC](https://github.com/rust-lang/rust/pull/120423/)
+- [Make non-`PartialEq`-typed consts as patterns a hard error](https://github.com/rust-lang/rust/pull/120805/)
+- [Split `refining_impl_trait` lint into `_reachable`, `_internal` variants](https://github.com/rust-lang/rust/pull/121720/)
+- [Remove unnecessary type inference when using associated types inside of higher ranked `where`-bounds](https://github.com/rust-lang/rust/pull/119849)
+- [Weaken eager detection of cyclic types during type inference](https://github.com/rust-lang/rust/pull/119989)
+- [`trait Trait: Auto {}`: allow upcasting from `dyn Trait` to `dyn Auto`](https://github.com/rust-lang/rust/pull/119338)
+
+<a id="1.78.0-Compiler"></a>
+
+Compiler
+--------
+
+- [Made `INVALID_DOC_ATTRIBUTES` lint deny by default](https://github.com/rust-lang/rust/pull/111505/)
+- [Increase accuracy of redundant `use` checking](https://github.com/rust-lang/rust/pull/117772/)
+- [Suggest moving definition if non-found macro_rules! is defined later](https://github.com/rust-lang/rust/pull/121130/)
+- [Lower transmutes from int to pointer type as gep on null](https://github.com/rust-lang/rust/pull/121282/)
+
+Target changes:
+
+- [Windows tier 1 targets now require at least Windows 10](https://github.com/rust-lang/rust/pull/115141/)
+  - [Enable CMPXCHG16B, SSE3, SAHF/LAHF and 128-bit Atomics in tier 1 Windows](https://github.com/rust-lang/rust/pull/120820/)
+- [Add `wasm32-wasip1` tier 2 (without host tools) target](https://github.com/rust-lang/rust/pull/120468/)
+- [Add `wasm32-wasip2` tier 3 target](https://github.com/rust-lang/rust/pull/119616/)
+- [Rename `wasm32-wasi-preview1-threads` to `wasm32-wasip1-threads`](https://github.com/rust-lang/rust/pull/122170/)
+- [Add `arm64ec-pc-windows-msvc` tier 3 target](https://github.com/rust-lang/rust/pull/119199/)
+- [Add `armv8r-none-eabihf` tier 3 target for the Cortex-R52](https://github.com/rust-lang/rust/pull/110482/)
+- [Add `loongarch64-unknown-linux-musl` tier 3 target](https://github.com/rust-lang/rust/pull/121832/)
+
+Refer to Rust's [platform support page][platform-support-doc]
+for more information on Rust's tiered platform support.
+
+<a id="1.78.0-Libraries"></a>
+
+Libraries
+---------
+
+- [Bump Unicode to version 15.1.0, regenerate tables](https://github.com/rust-lang/rust/pull/120777/)
+- [Make align_offset, align_to well-behaved in all cases](https://github.com/rust-lang/rust/pull/121201/)
+- [PartialEq, PartialOrd: document expectations for transitive chains](https://github.com/rust-lang/rust/pull/115386/)
+- [Optimize away poison guards when std is built with panic=abort](https://github.com/rust-lang/rust/pull/100603/)
+- [Replace pthread `RwLock` with custom implementation](https://github.com/rust-lang/rust/pull/110211/)
+- [Implement unwind safety for Condvar on all platforms](https://github.com/rust-lang/rust/pull/121768/)
+- [Add ASCII fast-path for `char::is_grapheme_extended`](https://github.com/rust-lang/rust/pull/121138/)
+
+<a id="1.78.0-Stabilized-APIs"></a>
+
+Stabilized APIs
+---------------
+
+- [`impl Read for &Stdin`](https://doc.rust-lang.org/stable/std/io/struct.Stdin.html#impl-Read-for-%26Stdin)
+- [Accept non `'static` lifetimes for several `std::error::Error` related implementations](https://github.com/rust-lang/rust/pull/113833/)
+- [Make `impl<Fd: AsFd>` impl take `?Sized`](https://github.com/rust-lang/rust/pull/114655/)
+- [`impl From<TryReserveError> for io::Error`](https://doc.rust-lang.org/stable/std/io/struct.Error.html#impl-From%3CTryReserveError%3E-for-Error)
+
+These APIs are now stable in const contexts:
+
+- [`Barrier::new()`](https://doc.rust-lang.org/stable/std/sync/struct.Barrier.html#method.new)
+
+<a id="1.78.0-Cargo"></a>
+
+Cargo
+-----
+
+- [Stabilize lockfile v4](https://github.com/rust-lang/cargo/pull/12852/)
+- [Respect `rust-version` when generating lockfile](https://github.com/rust-lang/cargo/pull/12861/)
+- [Control `--charset` via auto-detecting config value](https://github.com/rust-lang/cargo/pull/13337/)
+- [Support `target.<triple>.rustdocflags` officially](https://github.com/rust-lang/cargo/pull/13197/)
+- [Stabilize global cache data tracking](https://github.com/rust-lang/cargo/pull/13492/)
+
+<a id="1.78.0-Misc"></a>
+
+Misc
+----
+
+- [rustdoc: add `--test-builder-wrapper` arg to support wrappers such as RUSTC_WRAPPER when building doctests](https://github.com/rust-lang/rust/pull/114651/)
+
+<a id="1.78.0-Compatibility-Notes"></a>
+
+Compatibility Notes
+-------------------
+
+- [Many unsafe precondition checks now run for user code with debug assertions enabled](https://github.com/rust-lang/rust/pull/120594/)
+  This change helps users catch undefined behavior in their code, though the details of how much is checked are generally not stable.
+- [riscv only supports split_debuginfo=off for now](https://github.com/rust-lang/rust/pull/120518/)
+- [Consistently check bounds on hidden types of `impl Trait`](https://github.com/rust-lang/rust/pull/121679)
+- [Change equality of higher ranked types to not rely on subtyping](https://github.com/rust-lang/rust/pull/118247)
+- [When called, additionally check bounds on normalized function return type](https://github.com/rust-lang/rust/pull/118882)
+- [Expand coverage for `arithmetic_overflow` lint](https://github.com/rust-lang/rust/pull/119432/)
+
+<a id="1.78.0-Internal-Changes"></a>
+
+Internal Changes
+----------------
+
+These changes do not affect any public interfaces of Rust, but they represent
+significant improvements to the performance or internals of rustc and related
+tools.
+
+- [Update to LLVM 18](https://github.com/rust-lang/rust/pull/120055/)
+- [Build `rustc` with 1CGU on `x86_64-pc-windows-msvc`](https://github.com/rust-lang/rust/pull/112267/)
+- [Build `rustc` with 1CGU on `x86_64-apple-darwin`](https://github.com/rust-lang/rust/pull/112268/)
+- [Introduce `run-make` V2 infrastructure, a `run_make_support` library and port over 2 tests as example](https://github.com/rust-lang/rust/pull/113026/)
+- [Windows: Implement condvar, mutex and rwlock using futex](https://github.com/rust-lang/rust/pull/121956/)
+
 Version 1.77.2 (2024-04-09)
 ===========================
 
diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs
index 1cceca33c17..fc445600e77 100644
--- a/compiler/rustc_ast/src/mut_visit.rs
+++ b/compiler/rustc_ast/src/mut_visit.rs
@@ -34,6 +34,10 @@ impl<A: Array> ExpectOne<A> for SmallVec<A> {
     }
 }
 
+pub trait NoopVisitItemKind {
+    fn noop_visit(&mut self, visitor: &mut impl MutVisitor);
+}
+
 pub trait MutVisitor: Sized {
     /// Mutable token visiting only exists for the `macro_rules` token marker and should not be
     /// used otherwise. Token visitor would be entirely separate from the regular visitor if
@@ -90,7 +94,7 @@ pub trait MutVisitor: Sized {
     }
 
     fn flat_map_foreign_item(&mut self, ni: P<ForeignItem>) -> SmallVec<[P<ForeignItem>; 1]> {
-        noop_flat_map_foreign_item(ni, self)
+        noop_flat_map_item(ni, self)
     }
 
     fn flat_map_item(&mut self, i: P<Item>) -> SmallVec<[P<Item>; 1]> {
@@ -105,16 +109,12 @@ pub trait MutVisitor: Sized {
         noop_flat_map_field_def(fd, self)
     }
 
-    fn visit_item_kind(&mut self, i: &mut ItemKind) {
-        noop_visit_item_kind(i, self);
-    }
-
     fn flat_map_trait_item(&mut self, i: P<AssocItem>) -> SmallVec<[P<AssocItem>; 1]> {
-        noop_flat_map_assoc_item(i, self)
+        noop_flat_map_item(i, self)
     }
 
     fn flat_map_impl_item(&mut self, i: P<AssocItem>) -> SmallVec<[P<AssocItem>; 1]> {
-        noop_flat_map_assoc_item(i, self)
+        noop_flat_map_item(i, self)
     }
 
     fn visit_fn_decl(&mut self, d: &mut P<FnDecl>) {
@@ -1068,149 +1068,151 @@ pub fn noop_visit_block<T: MutVisitor>(block: &mut P<Block>, vis: &mut T) {
     visit_lazy_tts(tokens, vis);
 }
 
-pub fn noop_visit_item_kind<T: MutVisitor>(kind: &mut ItemKind, vis: &mut T) {
-    match kind {
-        ItemKind::ExternCrate(_orig_name) => {}
-        ItemKind::Use(use_tree) => vis.visit_use_tree(use_tree),
-        ItemKind::Static(box StaticItem { ty, mutability: _, expr }) => {
-            vis.visit_ty(ty);
-            visit_opt(expr, |expr| vis.visit_expr(expr));
-        }
-        ItemKind::Const(item) => {
-            visit_const_item(item, vis);
-        }
-        ItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
-            visit_defaultness(defaultness, vis);
-            visit_fn_sig(sig, vis);
-            vis.visit_generics(generics);
-            visit_opt(body, |body| vis.visit_block(body));
-        }
-        ItemKind::Mod(unsafety, mod_kind) => {
-            visit_unsafety(unsafety, vis);
-            match mod_kind {
-                ModKind::Loaded(items, _inline, ModSpans { inner_span, inject_use_span }) => {
-                    vis.visit_span(inner_span);
-                    vis.visit_span(inject_use_span);
-                    items.flat_map_in_place(|item| vis.flat_map_item(item));
+pub fn noop_visit_item_kind(kind: &mut impl NoopVisitItemKind, vis: &mut impl MutVisitor) {
+    kind.noop_visit(vis)
+}
+
+impl NoopVisitItemKind for ItemKind {
+    fn noop_visit(&mut self, vis: &mut impl MutVisitor) {
+        match self {
+            ItemKind::ExternCrate(_orig_name) => {}
+            ItemKind::Use(use_tree) => vis.visit_use_tree(use_tree),
+            ItemKind::Static(box StaticItem { ty, mutability: _, expr }) => {
+                vis.visit_ty(ty);
+                visit_opt(expr, |expr| vis.visit_expr(expr));
+            }
+            ItemKind::Const(item) => {
+                visit_const_item(item, vis);
+            }
+            ItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
+                visit_defaultness(defaultness, vis);
+                visit_fn_sig(sig, vis);
+                vis.visit_generics(generics);
+                visit_opt(body, |body| vis.visit_block(body));
+            }
+            ItemKind::Mod(unsafety, mod_kind) => {
+                visit_unsafety(unsafety, vis);
+                match mod_kind {
+                    ModKind::Loaded(items, _inline, ModSpans { inner_span, inject_use_span }) => {
+                        vis.visit_span(inner_span);
+                        vis.visit_span(inject_use_span);
+                        items.flat_map_in_place(|item| vis.flat_map_item(item));
+                    }
+                    ModKind::Unloaded => {}
                 }
-                ModKind::Unloaded => {}
             }
-        }
-        ItemKind::ForeignMod(nm) => vis.visit_foreign_mod(nm),
-        ItemKind::GlobalAsm(asm) => vis.visit_inline_asm(asm),
-        ItemKind::TyAlias(box TyAlias {
-            defaultness, generics, where_clauses, bounds, ty, ..
-        }) => {
-            visit_defaultness(defaultness, vis);
-            vis.visit_generics(generics);
-            vis.visit_span(&mut where_clauses.before.span);
-            vis.visit_span(&mut where_clauses.after.span);
-            visit_bounds(bounds, vis);
-            visit_opt(ty, |ty| vis.visit_ty(ty));
-        }
-        ItemKind::Enum(EnumDef { variants }, generics) => {
-            variants.flat_map_in_place(|variant| vis.flat_map_variant(variant));
-            vis.visit_generics(generics);
-        }
-        ItemKind::Struct(variant_data, generics) | ItemKind::Union(variant_data, generics) => {
-            vis.visit_variant_data(variant_data);
-            vis.visit_generics(generics);
-        }
-        ItemKind::Impl(box Impl {
-            defaultness,
-            unsafety,
-            generics,
-            constness,
-            polarity,
-            of_trait,
-            self_ty,
-            items,
-        }) => {
-            visit_defaultness(defaultness, vis);
-            visit_unsafety(unsafety, vis);
-            vis.visit_generics(generics);
-            visit_constness(constness, vis);
-            visit_polarity(polarity, vis);
-            visit_opt(of_trait, |trait_ref| vis.visit_trait_ref(trait_ref));
-            vis.visit_ty(self_ty);
-            items.flat_map_in_place(|item| vis.flat_map_impl_item(item));
-        }
-        ItemKind::Trait(box Trait { unsafety, is_auto: _, generics, bounds, items }) => {
-            visit_unsafety(unsafety, vis);
-            vis.visit_generics(generics);
-            visit_bounds(bounds, vis);
-            items.flat_map_in_place(|item| vis.flat_map_trait_item(item));
-        }
-        ItemKind::TraitAlias(generics, bounds) => {
-            vis.visit_generics(generics);
-            visit_bounds(bounds, vis);
-        }
-        ItemKind::MacCall(m) => vis.visit_mac_call(m),
-        ItemKind::MacroDef(def) => vis.visit_macro_def(def),
-        ItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => {
-            vis.visit_id(id);
-            vis.visit_qself(qself);
-            vis.visit_path(path);
-            if let Some(rename) = rename {
-                vis.visit_ident(rename);
+            ItemKind::ForeignMod(nm) => vis.visit_foreign_mod(nm),
+            ItemKind::GlobalAsm(asm) => vis.visit_inline_asm(asm),
+            ItemKind::TyAlias(box TyAlias {
+                defaultness,
+                generics,
+                where_clauses,
+                bounds,
+                ty,
+                ..
+            }) => {
+                visit_defaultness(defaultness, vis);
+                vis.visit_generics(generics);
+                vis.visit_span(&mut where_clauses.before.span);
+                vis.visit_span(&mut where_clauses.after.span);
+                visit_bounds(bounds, vis);
+                visit_opt(ty, |ty| vis.visit_ty(ty));
+            }
+            ItemKind::Enum(EnumDef { variants }, generics) => {
+                variants.flat_map_in_place(|variant| vis.flat_map_variant(variant));
+                vis.visit_generics(generics);
+            }
+            ItemKind::Struct(variant_data, generics) | ItemKind::Union(variant_data, generics) => {
+                vis.visit_variant_data(variant_data);
+                vis.visit_generics(generics);
+            }
+            ItemKind::Impl(box Impl {
+                defaultness,
+                unsafety,
+                generics,
+                constness,
+                polarity,
+                of_trait,
+                self_ty,
+                items,
+            }) => {
+                visit_defaultness(defaultness, vis);
+                visit_unsafety(unsafety, vis);
+                vis.visit_generics(generics);
+                visit_constness(constness, vis);
+                visit_polarity(polarity, vis);
+                visit_opt(of_trait, |trait_ref| vis.visit_trait_ref(trait_ref));
+                vis.visit_ty(self_ty);
+                items.flat_map_in_place(|item| vis.flat_map_impl_item(item));
+            }
+            ItemKind::Trait(box Trait { unsafety, is_auto: _, generics, bounds, items }) => {
+                visit_unsafety(unsafety, vis);
+                vis.visit_generics(generics);
+                visit_bounds(bounds, vis);
+                items.flat_map_in_place(|item| vis.flat_map_trait_item(item));
+            }
+            ItemKind::TraitAlias(generics, bounds) => {
+                vis.visit_generics(generics);
+                visit_bounds(bounds, vis);
             }
-            if let Some(body) = body {
-                vis.visit_block(body);
+            ItemKind::MacCall(m) => vis.visit_mac_call(m),
+            ItemKind::MacroDef(def) => vis.visit_macro_def(def),
+            ItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => {
+                vis.visit_id(id);
+                vis.visit_qself(qself);
+                vis.visit_path(path);
+                if let Some(rename) = rename {
+                    vis.visit_ident(rename);
+                }
+                if let Some(body) = body {
+                    vis.visit_block(body);
+                }
             }
         }
     }
 }
 
-pub fn noop_flat_map_assoc_item<T: MutVisitor>(
-    mut item: P<AssocItem>,
-    visitor: &mut T,
-) -> SmallVec<[P<AssocItem>; 1]> {
-    let Item { id, ident, vis, attrs, kind, span, tokens } = item.deref_mut();
-    visitor.visit_id(id);
-    visitor.visit_ident(ident);
-    visitor.visit_vis(vis);
-    visit_attrs(attrs, visitor);
-    match kind {
-        AssocItemKind::Const(item) => {
-            visit_const_item(item, visitor);
-        }
-        AssocItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
-            visit_defaultness(defaultness, visitor);
-            visitor.visit_generics(generics);
-            visit_fn_sig(sig, visitor);
-            visit_opt(body, |body| visitor.visit_block(body));
-        }
-        AssocItemKind::Type(box TyAlias {
-            defaultness,
-            generics,
-            where_clauses,
-            bounds,
-            ty,
-            ..
-        }) => {
-            visit_defaultness(defaultness, visitor);
-            visitor.visit_generics(generics);
-            visitor.visit_span(&mut where_clauses.before.span);
-            visitor.visit_span(&mut where_clauses.after.span);
-            visit_bounds(bounds, visitor);
-            visit_opt(ty, |ty| visitor.visit_ty(ty));
-        }
-        AssocItemKind::MacCall(mac) => visitor.visit_mac_call(mac),
-        AssocItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => {
-            visitor.visit_id(id);
-            visitor.visit_qself(qself);
-            visitor.visit_path(path);
-            if let Some(rename) = rename {
-                visitor.visit_ident(rename);
+impl NoopVisitItemKind for AssocItemKind {
+    fn noop_visit(&mut self, visitor: &mut impl MutVisitor) {
+        match self {
+            AssocItemKind::Const(item) => {
+                visit_const_item(item, visitor);
             }
-            if let Some(body) = body {
-                visitor.visit_block(body);
+            AssocItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
+                visit_defaultness(defaultness, visitor);
+                visitor.visit_generics(generics);
+                visit_fn_sig(sig, visitor);
+                visit_opt(body, |body| visitor.visit_block(body));
+            }
+            AssocItemKind::Type(box TyAlias {
+                defaultness,
+                generics,
+                where_clauses,
+                bounds,
+                ty,
+                ..
+            }) => {
+                visit_defaultness(defaultness, visitor);
+                visitor.visit_generics(generics);
+                visitor.visit_span(&mut where_clauses.before.span);
+                visitor.visit_span(&mut where_clauses.after.span);
+                visit_bounds(bounds, visitor);
+                visit_opt(ty, |ty| visitor.visit_ty(ty));
+            }
+            AssocItemKind::MacCall(mac) => visitor.visit_mac_call(mac),
+            AssocItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => {
+                visitor.visit_id(id);
+                visitor.visit_qself(qself);
+                visitor.visit_path(path);
+                if let Some(rename) = rename {
+                    visitor.visit_ident(rename);
+                }
+                if let Some(body) = body {
+                    visitor.visit_block(body);
+                }
             }
         }
     }
-    visitor.visit_span(span);
-    visit_lazy_tts(tokens, visitor);
-    smallvec![item]
 }
 
 fn visit_const_item<T: MutVisitor>(
@@ -1241,62 +1243,52 @@ pub fn noop_visit_crate<T: MutVisitor>(krate: &mut Crate, vis: &mut T) {
 }
 
 // Mutates one item into possibly many items.
-pub fn noop_flat_map_item<T: MutVisitor>(
-    mut item: P<Item>,
-    visitor: &mut T,
-) -> SmallVec<[P<Item>; 1]> {
+pub fn noop_flat_map_item<K: NoopVisitItemKind>(
+    mut item: P<Item<K>>,
+    visitor: &mut impl MutVisitor,
+) -> SmallVec<[P<Item<K>>; 1]> {
     let Item { ident, attrs, id, kind, vis, span, tokens } = item.deref_mut();
-    visitor.visit_ident(ident);
-    visit_attrs(attrs, visitor);
     visitor.visit_id(id);
-    visitor.visit_item_kind(kind);
+    visit_attrs(attrs, visitor);
     visitor.visit_vis(vis);
+    visitor.visit_ident(ident);
+    kind.noop_visit(visitor);
     visitor.visit_span(span);
     visit_lazy_tts(tokens, visitor);
-
     smallvec![item]
 }
 
-pub fn noop_flat_map_foreign_item<T: MutVisitor>(
-    mut item: P<ForeignItem>,
-    visitor: &mut T,
-) -> SmallVec<[P<ForeignItem>; 1]> {
-    let Item { ident, attrs, id, kind, vis, span, tokens } = item.deref_mut();
-    visitor.visit_id(id);
-    visitor.visit_ident(ident);
-    visitor.visit_vis(vis);
-    visit_attrs(attrs, visitor);
-    match kind {
-        ForeignItemKind::Static(ty, _, expr) => {
-            visitor.visit_ty(ty);
-            visit_opt(expr, |expr| visitor.visit_expr(expr));
-        }
-        ForeignItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
-            visit_defaultness(defaultness, visitor);
-            visitor.visit_generics(generics);
-            visit_fn_sig(sig, visitor);
-            visit_opt(body, |body| visitor.visit_block(body));
-        }
-        ForeignItemKind::TyAlias(box TyAlias {
-            defaultness,
-            generics,
-            where_clauses,
-            bounds,
-            ty,
-            ..
-        }) => {
-            visit_defaultness(defaultness, visitor);
-            visitor.visit_generics(generics);
-            visitor.visit_span(&mut where_clauses.before.span);
-            visitor.visit_span(&mut where_clauses.after.span);
-            visit_bounds(bounds, visitor);
-            visit_opt(ty, |ty| visitor.visit_ty(ty));
+impl NoopVisitItemKind for ForeignItemKind {
+    fn noop_visit(&mut self, visitor: &mut impl MutVisitor) {
+        match self {
+            ForeignItemKind::Static(ty, _, expr) => {
+                visitor.visit_ty(ty);
+                visit_opt(expr, |expr| visitor.visit_expr(expr));
+            }
+            ForeignItemKind::Fn(box Fn { defaultness, generics, sig, body }) => {
+                visit_defaultness(defaultness, visitor);
+                visitor.visit_generics(generics);
+                visit_fn_sig(sig, visitor);
+                visit_opt(body, |body| visitor.visit_block(body));
+            }
+            ForeignItemKind::TyAlias(box TyAlias {
+                defaultness,
+                generics,
+                where_clauses,
+                bounds,
+                ty,
+                ..
+            }) => {
+                visit_defaultness(defaultness, visitor);
+                visitor.visit_generics(generics);
+                visitor.visit_span(&mut where_clauses.before.span);
+                visitor.visit_span(&mut where_clauses.after.span);
+                visit_bounds(bounds, visitor);
+                visit_opt(ty, |ty| visitor.visit_ty(ty));
+            }
+            ForeignItemKind::MacCall(mac) => visitor.visit_mac_call(mac),
         }
-        ForeignItemKind::MacCall(mac) => visitor.visit_mac_call(mac),
     }
-    visitor.visit_span(span);
-    visit_lazy_tts(tokens, visitor);
-    smallvec![item]
 }
 
 pub fn noop_visit_pat<T: MutVisitor>(pat: &mut P<Pat>, vis: &mut T) {
diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs
index d9740928f8d..a0ada9a7788 100644
--- a/compiler/rustc_ast/src/visit.rs
+++ b/compiler/rustc_ast/src/visit.rs
@@ -102,6 +102,15 @@ pub enum LifetimeCtxt {
     GenericArg,
 }
 
+pub trait WalkItemKind: Sized {
+    fn walk<'a, V: Visitor<'a>>(
+        &'a self,
+        item: &'a Item<Self>,
+        ctxt: AssocCtxt,
+        visitor: &mut V,
+    ) -> V::Result;
+}
+
 /// Each method of the `Visitor` trait is a hook to be potentially
 /// overridden. Each method's default implementation recursively visits
 /// the substructure of the input via the corresponding `walk` method;
@@ -120,7 +129,7 @@ pub trait Visitor<'ast>: Sized {
         Self::Result::output()
     }
     fn visit_foreign_item(&mut self, i: &'ast ForeignItem) -> Self::Result {
-        walk_foreign_item(self, i)
+        walk_item(self, i)
     }
     fn visit_item(&mut self, i: &'ast Item) -> Self::Result {
         walk_item(self, i)
@@ -312,87 +321,98 @@ pub fn walk_trait_ref<'a, V: Visitor<'a>>(visitor: &mut V, trait_ref: &'a TraitR
     visitor.visit_path(&trait_ref.path, trait_ref.ref_id)
 }
 
-pub fn walk_item<'a, V: Visitor<'a>>(visitor: &mut V, item: &'a Item) -> V::Result {
-    try_visit!(visitor.visit_vis(&item.vis));
-    try_visit!(visitor.visit_ident(item.ident));
-    match &item.kind {
-        ItemKind::ExternCrate(_) => {}
-        ItemKind::Use(use_tree) => try_visit!(visitor.visit_use_tree(use_tree, item.id, false)),
-        ItemKind::Static(box StaticItem { ty, mutability: _, expr }) => {
-            try_visit!(visitor.visit_ty(ty));
-            visit_opt!(visitor, visit_expr, expr);
-        }
-        ItemKind::Const(box ConstItem { defaultness: _, generics, ty, expr }) => {
-            try_visit!(visitor.visit_generics(generics));
-            try_visit!(visitor.visit_ty(ty));
-            visit_opt!(visitor, visit_expr, expr);
-        }
-        ItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
-            let kind =
-                FnKind::Fn(FnCtxt::Free, item.ident, sig, &item.vis, generics, body.as_deref());
-            try_visit!(visitor.visit_fn(kind, item.span, item.id));
-        }
-        ItemKind::Mod(_unsafety, mod_kind) => match mod_kind {
-            ModKind::Loaded(items, _inline, _inner_span) => {
-                walk_list!(visitor, visit_item, items);
+impl WalkItemKind for ItemKind {
+    fn walk<'a, V: Visitor<'a>>(
+        &'a self,
+        item: &'a Item<Self>,
+        _ctxt: AssocCtxt,
+        visitor: &mut V,
+    ) -> V::Result {
+        match self {
+            ItemKind::ExternCrate(_) => {}
+            ItemKind::Use(use_tree) => try_visit!(visitor.visit_use_tree(use_tree, item.id, false)),
+            ItemKind::Static(box StaticItem { ty, mutability: _, expr }) => {
+                try_visit!(visitor.visit_ty(ty));
+                visit_opt!(visitor, visit_expr, expr);
             }
-            ModKind::Unloaded => {}
-        },
-        ItemKind::ForeignMod(foreign_module) => {
-            walk_list!(visitor, visit_foreign_item, &foreign_module.items);
-        }
-        ItemKind::GlobalAsm(asm) => try_visit!(visitor.visit_inline_asm(asm)),
-        ItemKind::TyAlias(box TyAlias { generics, bounds, ty, .. }) => {
-            try_visit!(visitor.visit_generics(generics));
-            walk_list!(visitor, visit_param_bound, bounds, BoundKind::Bound);
-            visit_opt!(visitor, visit_ty, ty);
-        }
-        ItemKind::Enum(enum_definition, generics) => {
-            try_visit!(visitor.visit_generics(generics));
-            try_visit!(visitor.visit_enum_def(enum_definition));
-        }
-        ItemKind::Impl(box Impl {
-            defaultness: _,
-            unsafety: _,
-            generics,
-            constness: _,
-            polarity: _,
-            of_trait,
-            self_ty,
-            items,
-        }) => {
-            try_visit!(visitor.visit_generics(generics));
-            visit_opt!(visitor, visit_trait_ref, of_trait);
-            try_visit!(visitor.visit_ty(self_ty));
-            walk_list!(visitor, visit_assoc_item, items, AssocCtxt::Impl);
-        }
-        ItemKind::Struct(struct_definition, generics)
-        | ItemKind::Union(struct_definition, generics) => {
-            try_visit!(visitor.visit_generics(generics));
-            try_visit!(visitor.visit_variant_data(struct_definition));
-        }
-        ItemKind::Trait(box Trait { unsafety: _, is_auto: _, generics, bounds, items }) => {
-            try_visit!(visitor.visit_generics(generics));
-            walk_list!(visitor, visit_param_bound, bounds, BoundKind::SuperTraits);
-            walk_list!(visitor, visit_assoc_item, items, AssocCtxt::Trait);
-        }
-        ItemKind::TraitAlias(generics, bounds) => {
-            try_visit!(visitor.visit_generics(generics));
-            walk_list!(visitor, visit_param_bound, bounds, BoundKind::Bound);
-        }
-        ItemKind::MacCall(mac) => try_visit!(visitor.visit_mac_call(mac)),
-        ItemKind::MacroDef(ts) => try_visit!(visitor.visit_mac_def(ts, item.id)),
-        ItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => {
-            if let Some(qself) = qself {
-                try_visit!(visitor.visit_ty(&qself.ty));
+            ItemKind::Const(box ConstItem { defaultness: _, generics, ty, expr }) => {
+                try_visit!(visitor.visit_generics(generics));
+                try_visit!(visitor.visit_ty(ty));
+                visit_opt!(visitor, visit_expr, expr);
+            }
+            ItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
+                let kind =
+                    FnKind::Fn(FnCtxt::Free, item.ident, sig, &item.vis, generics, body.as_deref());
+                try_visit!(visitor.visit_fn(kind, item.span, item.id));
+            }
+            ItemKind::Mod(_unsafety, mod_kind) => match mod_kind {
+                ModKind::Loaded(items, _inline, _inner_span) => {
+                    walk_list!(visitor, visit_item, items);
+                }
+                ModKind::Unloaded => {}
+            },
+            ItemKind::ForeignMod(foreign_module) => {
+                walk_list!(visitor, visit_foreign_item, &foreign_module.items);
+            }
+            ItemKind::GlobalAsm(asm) => try_visit!(visitor.visit_inline_asm(asm)),
+            ItemKind::TyAlias(box TyAlias { generics, bounds, ty, .. }) => {
+                try_visit!(visitor.visit_generics(generics));
+                walk_list!(visitor, visit_param_bound, bounds, BoundKind::Bound);
+                visit_opt!(visitor, visit_ty, ty);
+            }
+            ItemKind::Enum(enum_definition, generics) => {
+                try_visit!(visitor.visit_generics(generics));
+                try_visit!(visitor.visit_enum_def(enum_definition));
+            }
+            ItemKind::Impl(box Impl {
+                defaultness: _,
+                unsafety: _,
+                generics,
+                constness: _,
+                polarity: _,
+                of_trait,
+                self_ty,
+                items,
+            }) => {
+                try_visit!(visitor.visit_generics(generics));
+                visit_opt!(visitor, visit_trait_ref, of_trait);
+                try_visit!(visitor.visit_ty(self_ty));
+                walk_list!(visitor, visit_assoc_item, items, AssocCtxt::Impl);
+            }
+            ItemKind::Struct(struct_definition, generics)
+            | ItemKind::Union(struct_definition, generics) => {
+                try_visit!(visitor.visit_generics(generics));
+                try_visit!(visitor.visit_variant_data(struct_definition));
+            }
+            ItemKind::Trait(box Trait { unsafety: _, is_auto: _, generics, bounds, items }) => {
+                try_visit!(visitor.visit_generics(generics));
+                walk_list!(visitor, visit_param_bound, bounds, BoundKind::SuperTraits);
+                walk_list!(visitor, visit_assoc_item, items, AssocCtxt::Trait);
+            }
+            ItemKind::TraitAlias(generics, bounds) => {
+                try_visit!(visitor.visit_generics(generics));
+                walk_list!(visitor, visit_param_bound, bounds, BoundKind::Bound);
+            }
+            ItemKind::MacCall(mac) => try_visit!(visitor.visit_mac_call(mac)),
+            ItemKind::MacroDef(ts) => try_visit!(visitor.visit_mac_def(ts, item.id)),
+            ItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => {
+                if let Some(qself) = qself {
+                    try_visit!(visitor.visit_ty(&qself.ty));
+                }
+                try_visit!(visitor.visit_path(path, *id));
+                visit_opt!(visitor, visit_ident, *rename);
+                visit_opt!(visitor, visit_block, body);
             }
-            try_visit!(visitor.visit_path(path, *id));
-            visit_opt!(visitor, visit_ident, *rename);
-            visit_opt!(visitor, visit_block, body);
         }
+        V::Result::output()
     }
-    walk_list!(visitor, visit_attribute, &item.attrs);
-    V::Result::output()
+}
+
+pub fn walk_item<'a, V: Visitor<'a>>(
+    visitor: &mut V,
+    item: &'a Item<impl WalkItemKind>,
+) -> V::Result {
+    walk_assoc_item(visitor, item, AssocCtxt::Trait /*ignored*/)
 }
 
 pub fn walk_enum_def<'a, V: Visitor<'a>>(
@@ -613,30 +633,34 @@ pub fn walk_pat<'a, V: Visitor<'a>>(visitor: &mut V, pattern: &'a Pat) -> V::Res
     V::Result::output()
 }
 
-pub fn walk_foreign_item<'a, V: Visitor<'a>>(visitor: &mut V, item: &'a ForeignItem) -> V::Result {
-    let &Item { id, span, ident, ref vis, ref attrs, ref kind, tokens: _ } = item;
-    try_visit!(visitor.visit_vis(vis));
-    try_visit!(visitor.visit_ident(ident));
-    walk_list!(visitor, visit_attribute, attrs);
-    match kind {
-        ForeignItemKind::Static(ty, _, expr) => {
-            try_visit!(visitor.visit_ty(ty));
-            visit_opt!(visitor, visit_expr, expr);
-        }
-        ForeignItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
-            let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, body.as_deref());
-            try_visit!(visitor.visit_fn(kind, span, id));
-        }
-        ForeignItemKind::TyAlias(box TyAlias { generics, bounds, ty, .. }) => {
-            try_visit!(visitor.visit_generics(generics));
-            walk_list!(visitor, visit_param_bound, bounds, BoundKind::Bound);
-            visit_opt!(visitor, visit_ty, ty);
-        }
-        ForeignItemKind::MacCall(mac) => {
-            try_visit!(visitor.visit_mac_call(mac));
+impl WalkItemKind for ForeignItemKind {
+    fn walk<'a, V: Visitor<'a>>(
+        &'a self,
+        item: &'a Item<Self>,
+        _ctxt: AssocCtxt,
+        visitor: &mut V,
+    ) -> V::Result {
+        let &Item { id, span, ident, ref vis, .. } = item;
+        match self {
+            ForeignItemKind::Static(ty, _, expr) => {
+                try_visit!(visitor.visit_ty(ty));
+                visit_opt!(visitor, visit_expr, expr);
+            }
+            ForeignItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
+                let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, body.as_deref());
+                try_visit!(visitor.visit_fn(kind, span, id));
+            }
+            ForeignItemKind::TyAlias(box TyAlias { generics, bounds, ty, .. }) => {
+                try_visit!(visitor.visit_generics(generics));
+                walk_list!(visitor, visit_param_bound, bounds, BoundKind::Bound);
+                visit_opt!(visitor, visit_ty, ty);
+            }
+            ForeignItemKind::MacCall(mac) => {
+                try_visit!(visitor.visit_mac_call(mac));
+            }
         }
+        V::Result::output()
     }
-    V::Result::output()
 }
 
 pub fn walk_param_bound<'a, V: Visitor<'a>>(visitor: &mut V, bound: &'a GenericBound) -> V::Result {
@@ -756,42 +780,56 @@ pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>) -> V::Resu
     V::Result::output()
 }
 
+impl WalkItemKind for AssocItemKind {
+    fn walk<'a, V: Visitor<'a>>(
+        &'a self,
+        item: &'a Item<Self>,
+        ctxt: AssocCtxt,
+        visitor: &mut V,
+    ) -> V::Result {
+        let &Item { id, span, ident, ref vis, .. } = item;
+        match self {
+            AssocItemKind::Const(box ConstItem { defaultness: _, generics, ty, expr }) => {
+                try_visit!(visitor.visit_generics(generics));
+                try_visit!(visitor.visit_ty(ty));
+                visit_opt!(visitor, visit_expr, expr);
+            }
+            AssocItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
+                let kind =
+                    FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, body.as_deref());
+                try_visit!(visitor.visit_fn(kind, span, id));
+            }
+            AssocItemKind::Type(box TyAlias { generics, bounds, ty, .. }) => {
+                try_visit!(visitor.visit_generics(generics));
+                walk_list!(visitor, visit_param_bound, bounds, BoundKind::Bound);
+                visit_opt!(visitor, visit_ty, ty);
+            }
+            AssocItemKind::MacCall(mac) => {
+                try_visit!(visitor.visit_mac_call(mac));
+            }
+            AssocItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => {
+                if let Some(qself) = qself {
+                    try_visit!(visitor.visit_ty(&qself.ty));
+                }
+                try_visit!(visitor.visit_path(path, *id));
+                visit_opt!(visitor, visit_ident, *rename);
+                visit_opt!(visitor, visit_block, body);
+            }
+        }
+        V::Result::output()
+    }
+}
+
 pub fn walk_assoc_item<'a, V: Visitor<'a>>(
     visitor: &mut V,
-    item: &'a AssocItem,
+    item: &'a Item<impl WalkItemKind>,
     ctxt: AssocCtxt,
 ) -> V::Result {
-    let &Item { id, span, ident, ref vis, ref attrs, ref kind, tokens: _ } = item;
+    let &Item { id: _, span: _, ident, ref vis, ref attrs, ref kind, tokens: _ } = item;
+    walk_list!(visitor, visit_attribute, attrs);
     try_visit!(visitor.visit_vis(vis));
     try_visit!(visitor.visit_ident(ident));
-    walk_list!(visitor, visit_attribute, attrs);
-    match kind {
-        AssocItemKind::Const(box ConstItem { defaultness: _, generics, ty, expr }) => {
-            try_visit!(visitor.visit_generics(generics));
-            try_visit!(visitor.visit_ty(ty));
-            visit_opt!(visitor, visit_expr, expr);
-        }
-        AssocItemKind::Fn(box Fn { defaultness: _, generics, sig, body }) => {
-            let kind = FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, body.as_deref());
-            try_visit!(visitor.visit_fn(kind, span, id));
-        }
-        AssocItemKind::Type(box TyAlias { generics, bounds, ty, .. }) => {
-            try_visit!(visitor.visit_generics(generics));
-            walk_list!(visitor, visit_param_bound, bounds, BoundKind::Bound);
-            visit_opt!(visitor, visit_ty, ty);
-        }
-        AssocItemKind::MacCall(mac) => {
-            try_visit!(visitor.visit_mac_call(mac));
-        }
-        AssocItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => {
-            if let Some(qself) = qself {
-                try_visit!(visitor.visit_ty(&qself.ty));
-            }
-            try_visit!(visitor.visit_path(path, *id));
-            visit_opt!(visitor, visit_ident, *rename);
-            visit_opt!(visitor, visit_block, body);
-        }
-    }
+    try_visit!(kind.walk(item, ctxt, visitor));
     V::Result::output()
 }
 
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index c5b5acf7f32..61bc7f268cf 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -394,7 +394,7 @@ fn index_crate<'a>(
             let def_id = self.node_id_to_def_id[&item.id];
             *self.index.ensure_contains_elem(def_id, || AstOwner::NonOwner) =
                 AstOwner::ForeignItem(item);
-            visit::walk_foreign_item(self, item);
+            visit::walk_item(self, item);
         }
     }
 }
diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs
index 4e3d560ce89..1fb410253d1 100644
--- a/compiler/rustc_ast_passes/src/ast_validation.rs
+++ b/compiler/rustc_ast_passes/src/ast_validation.rs
@@ -1192,7 +1192,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
             ForeignItemKind::MacCall(..) => {}
         }
 
-        visit::walk_foreign_item(self, fi)
+        visit::walk_item(self, fi)
     }
 
     // Mirrors `visit::walk_generic_args`, but tracks relevant state.
diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs
index d86196cbaa9..e0000e354ca 100644
--- a/compiler/rustc_ast_passes/src/feature_gate.rs
+++ b/compiler/rustc_ast_passes/src/feature_gate.rs
@@ -319,7 +319,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
             ast::ForeignItemKind::MacCall(..) => {}
         }
 
-        visit::walk_foreign_item(self, i)
+        visit::walk_item(self, i)
     }
 
     fn visit_ty(&mut self, ty: &'a ast::Ty) {
diff --git a/compiler/rustc_ast_passes/src/node_count.rs b/compiler/rustc_ast_passes/src/node_count.rs
index fa42f87786d..2128acba0ba 100644
--- a/compiler/rustc_ast_passes/src/node_count.rs
+++ b/compiler/rustc_ast_passes/src/node_count.rs
@@ -21,7 +21,7 @@ impl<'ast> Visitor<'ast> for NodeCounter {
     }
     fn visit_foreign_item(&mut self, i: &ForeignItem) {
         self.count += 1;
-        walk_foreign_item(self, i)
+        walk_item(self, i)
     }
     fn visit_item(&mut self, i: &Item) {
         self.count += 1;
diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl
index 2a5bc58af3b..0f158990319 100644
--- a/compiler/rustc_builtin_macros/messages.ftl
+++ b/compiler/rustc_builtin_macros/messages.ftl
@@ -118,6 +118,8 @@ builtin_macros_env_not_unicode = environment variable `{$var}` is not a valid Un
 
 builtin_macros_env_takes_args = `env!()` takes 1 or 2 arguments
 
+builtin_macros_expected_comma_in_list = expected token: `,`
+
 builtin_macros_expected_one_cfg_pattern = expected 1 cfg-pattern
 
 builtin_macros_expected_register_class_or_explicit_register = expected register class or explicit register
@@ -219,12 +221,16 @@ builtin_macros_non_exhaustive_default = default variant must be exhaustive
 builtin_macros_non_unit_default = the `#[default]` attribute may only be used on unit enum variants
     .help = consider a manual implementation of `Default`
 
+builtin_macros_only_one_argument = {$name} takes 1 argument
+
 builtin_macros_proc_macro = `proc-macro` crate types currently cannot export any items other than functions tagged with `#[proc_macro]`, `#[proc_macro_derive]`, or `#[proc_macro_attribute]`
 
 builtin_macros_requires_cfg_pattern =
     macro requires a cfg-pattern as an argument
     .label = cfg-pattern required
 
+builtin_macros_takes_no_arguments = {$name} takes no arguments
+
 builtin_macros_test_bad_fn = {$kind} functions cannot be used for tests
     .label = `{$kind}` because of this
 
diff --git a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs
index bc94e0b972b..064cf7d7f0f 100644
--- a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs
+++ b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs
@@ -9,7 +9,7 @@ use rustc_span::symbol::{kw, sym, Ident};
 use rustc_span::Span;
 use thin_vec::{thin_vec, ThinVec};
 
-pub fn expand(
+pub(crate) fn expand(
     ecx: &mut ExtCtxt<'_>,
     _span: Span,
     meta_item: &ast::MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs
index 137ac441579..49b1b8cf992 100644
--- a/compiler/rustc_builtin_macros/src/asm.rs
+++ b/compiler/rustc_builtin_macros/src/asm.rs
@@ -1,3 +1,5 @@
+use crate::errors;
+use crate::util::expr_to_spanned_string;
 use ast::token::IdentIsRaw;
 use rustc_ast as ast;
 use rustc_ast::ptr::P;
@@ -16,8 +18,6 @@ use rustc_span::{ErrorGuaranteed, InnerSpan, Span};
 use rustc_target::asm::InlineAsmArch;
 use smallvec::smallvec;
 
-use crate::errors;
-
 pub struct AsmArgs {
     pub templates: Vec<P<ast::Expr>>,
     pub operands: Vec<(ast::InlineAsmOperand, Span)>,
diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs
index d200179f3a0..c75050f2701 100644
--- a/compiler/rustc_builtin_macros/src/assert.rs
+++ b/compiler/rustc_builtin_macros/src/assert.rs
@@ -15,7 +15,7 @@ use rustc_span::symbol::{sym, Ident, Symbol};
 use rustc_span::{Span, DUMMY_SP};
 use thin_vec::thin_vec;
 
-pub fn expand_assert<'cx>(
+pub(crate) fn expand_assert<'cx>(
     cx: &'cx mut ExtCtxt<'_>,
     span: Span,
     tts: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs
index 5dc9bbacd06..827719d7944 100644
--- a/compiler/rustc_builtin_macros/src/cfg.rs
+++ b/compiler/rustc_builtin_macros/src/cfg.rs
@@ -11,7 +11,7 @@ use rustc_errors::PResult;
 use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
 use rustc_span::Span;
 
-pub fn expand_cfg(
+pub(crate) fn expand_cfg(
     cx: &mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs
index 93f7d09546b..c192b188aa6 100644
--- a/compiler/rustc_builtin_macros/src/cfg_eval.rs
+++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs
@@ -246,18 +246,18 @@ impl MutVisitor for CfgEval<'_, '_> {
     }
 
     fn flat_map_impl_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
-        mut_visit::noop_flat_map_assoc_item(configure!(self, item), self)
+        mut_visit::noop_flat_map_item(configure!(self, item), self)
     }
 
     fn flat_map_trait_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
-        mut_visit::noop_flat_map_assoc_item(configure!(self, item), self)
+        mut_visit::noop_flat_map_item(configure!(self, item), self)
     }
 
     fn flat_map_foreign_item(
         &mut self,
         foreign_item: P<ast::ForeignItem>,
     ) -> SmallVec<[P<ast::ForeignItem>; 1]> {
-        mut_visit::noop_flat_map_foreign_item(configure!(self, foreign_item), self)
+        mut_visit::noop_flat_map_item(configure!(self, foreign_item), self)
     }
 
     fn flat_map_arm(&mut self, arm: ast::Arm) -> SmallVec<[ast::Arm; 1]> {
diff --git a/compiler/rustc_builtin_macros/src/compile_error.rs b/compiler/rustc_builtin_macros/src/compile_error.rs
index 2f2a87fc9aa..a08e8b2819b 100644
--- a/compiler/rustc_builtin_macros/src/compile_error.rs
+++ b/compiler/rustc_builtin_macros/src/compile_error.rs
@@ -1,11 +1,11 @@
 // The compiler code necessary to support the compile_error! extension.
 
+use crate::util::get_single_str_from_tts;
 use rustc_ast::tokenstream::TokenStream;
-use rustc_expand::base::get_single_str_from_tts;
 use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
 use rustc_span::Span;
 
-pub fn expand_compile_error<'cx>(
+pub(crate) fn expand_compile_error<'cx>(
     cx: &'cx mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs
index 93a7ac05a9b..15af79ef67d 100644
--- a/compiler/rustc_builtin_macros/src/concat.rs
+++ b/compiler/rustc_builtin_macros/src/concat.rs
@@ -1,13 +1,12 @@
+use crate::errors;
+use crate::util::get_exprs_from_tts;
 use rustc_ast::tokenstream::TokenStream;
 use rustc_ast::{ExprKind, LitKind, UnOp};
-use rustc_expand::base::get_exprs_from_tts;
 use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
 use rustc_session::errors::report_lit_error;
 use rustc_span::symbol::Symbol;
 
-use crate::errors;
-
-pub fn expand_concat(
+pub(crate) fn expand_concat(
     cx: &mut ExtCtxt<'_>,
     sp: rustc_span::Span,
     tts: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs
index 45fec294578..3130870df41 100644
--- a/compiler/rustc_builtin_macros/src/concat_bytes.rs
+++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs
@@ -1,11 +1,10 @@
+use crate::errors;
+use crate::util::get_exprs_from_tts;
 use rustc_ast::{ptr::P, token, tokenstream::TokenStream, ExprKind, LitIntType, LitKind, UintTy};
-use rustc_expand::base::get_exprs_from_tts;
 use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
 use rustc_session::errors::report_lit_error;
 use rustc_span::{ErrorGuaranteed, Span};
 
-use crate::errors;
-
 /// Emits errors for literal expressions that are invalid inside and outside of an array.
 fn invalid_type_err(
     cx: &ExtCtxt<'_>,
@@ -108,7 +107,7 @@ fn handle_array_element(
     None
 }
 
-pub fn expand_concat_bytes(
+pub(crate) fn expand_concat_bytes(
     cx: &mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/concat_idents.rs b/compiler/rustc_builtin_macros/src/concat_idents.rs
index 3ddb0ae45b5..13729a9d250 100644
--- a/compiler/rustc_builtin_macros/src/concat_idents.rs
+++ b/compiler/rustc_builtin_macros/src/concat_idents.rs
@@ -8,7 +8,7 @@ use rustc_span::Span;
 
 use crate::errors;
 
-pub fn expand_concat_idents<'cx>(
+pub(crate) fn expand_concat_idents<'cx>(
     cx: &'cx mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs
index 4f412cf79d9..d14858e5c1d 100644
--- a/compiler/rustc_builtin_macros/src/derive.rs
+++ b/compiler/rustc_builtin_macros/src/derive.rs
@@ -3,14 +3,18 @@ use crate::errors;
 
 use rustc_ast as ast;
 use rustc_ast::{GenericParamKind, ItemKind, MetaItemKind, NestedMetaItem, StmtKind};
-use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier};
+use rustc_expand::base::{
+    Annotatable, DeriveResolution, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier,
+};
 use rustc_feature::AttributeTemplate;
 use rustc_parse::validate_attr;
 use rustc_session::Session;
 use rustc_span::symbol::{sym, Ident};
 use rustc_span::{ErrorGuaranteed, Span};
 
-pub(crate) struct Expander(pub bool);
+pub(crate) struct Expander {
+    pub is_const: bool,
+}
 
 impl MultiItemModifier for Expander {
     fn expand(
@@ -58,7 +62,12 @@ impl MultiItemModifier for Expander {
                                 report_path_args(sess, meta);
                                 meta.path.clone()
                             })
-                            .map(|path| (path, dummy_annotatable(), None, self.0))
+                            .map(|path| DeriveResolution {
+                                path,
+                                item: dummy_annotatable(),
+                                exts: None,
+                                is_const: self.is_const,
+                            })
                             .collect()
                     }
                     _ => vec![],
@@ -67,15 +76,15 @@ impl MultiItemModifier for Expander {
                 // Do not configure or clone items unless necessary.
                 match &mut resolutions[..] {
                     [] => {}
-                    [(_, first_item, ..), others @ ..] => {
-                        *first_item = cfg_eval(
+                    [first, others @ ..] => {
+                        first.item = cfg_eval(
                             sess,
                             features,
                             item.clone(),
                             ecx.current_expansion.lint_node_id,
                         );
-                        for (_, item, _, _) in others {
-                            *item = first_item.clone();
+                        for other in others {
+                            other.item = first.item.clone();
                         }
                     }
                 }
diff --git a/compiler/rustc_builtin_macros/src/deriving/bounds.rs b/compiler/rustc_builtin_macros/src/deriving/bounds.rs
index 26ef3da3a91..97e2344ff30 100644
--- a/compiler/rustc_builtin_macros/src/deriving/bounds.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/bounds.rs
@@ -5,7 +5,7 @@ use rustc_ast::MetaItem;
 use rustc_expand::base::{Annotatable, ExtCtxt};
 use rustc_span::Span;
 
-pub fn expand_deriving_copy(
+pub(crate) fn expand_deriving_copy(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &MetaItem,
@@ -28,7 +28,7 @@ pub fn expand_deriving_copy(
     trait_def.expand(cx, mitem, item, push);
 }
 
-pub fn expand_deriving_const_param_ty(
+pub(crate) fn expand_deriving_const_param_ty(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/deriving/clone.rs b/compiler/rustc_builtin_macros/src/deriving/clone.rs
index cb1c9ef90bd..abcb402a46f 100644
--- a/compiler/rustc_builtin_macros/src/deriving/clone.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/clone.rs
@@ -8,7 +8,7 @@ use rustc_span::symbol::{kw, sym, Ident};
 use rustc_span::Span;
 use thin_vec::{thin_vec, ThinVec};
 
-pub fn expand_deriving_clone(
+pub(crate) fn expand_deriving_clone(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
index 45c4467a109..53a15131605 100644
--- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
@@ -9,7 +9,7 @@ use rustc_span::symbol::sym;
 use rustc_span::Span;
 use thin_vec::{thin_vec, ThinVec};
 
-pub fn expand_deriving_eq(
+pub(crate) fn expand_deriving_eq(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs
index 1d7a69540ab..8470d466a23 100644
--- a/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs
@@ -7,7 +7,7 @@ use rustc_span::symbol::{sym, Ident};
 use rustc_span::Span;
 use thin_vec::thin_vec;
 
-pub fn expand_deriving_ord(
+pub(crate) fn expand_deriving_ord(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &MetaItem,
@@ -39,7 +39,7 @@ pub fn expand_deriving_ord(
     trait_def.expand(cx, mitem, item, push)
 }
 
-pub fn cs_cmp(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
+pub(crate) fn cs_cmp(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
     let test_id = Ident::new(sym::cmp, span);
     let equal_path = cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::Equal]));
     let cmp_path = cx.std_path(&[sym::cmp, sym::Ord, sym::cmp]);
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs
index 234918ae429..dc73caa4ad5 100644
--- a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs
@@ -8,7 +8,7 @@ use rustc_span::symbol::sym;
 use rustc_span::Span;
 use thin_vec::thin_vec;
 
-pub fn expand_deriving_partial_eq(
+pub(crate) fn expand_deriving_partial_eq(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs
index 63311c897ab..006e5a3d268 100644
--- a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs
@@ -7,7 +7,7 @@ use rustc_span::symbol::{sym, Ident};
 use rustc_span::Span;
 use thin_vec::thin_vec;
 
-pub fn expand_deriving_partial_ord(
+pub(crate) fn expand_deriving_partial_ord(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs
index 8b681db9670..57ec0435e3e 100644
--- a/compiler/rustc_builtin_macros/src/deriving/debug.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs
@@ -8,7 +8,7 @@ use rustc_span::symbol::{sym, Ident, Symbol};
 use rustc_span::Span;
 use thin_vec::{thin_vec, ThinVec};
 
-pub fn expand_deriving_debug(
+pub(crate) fn expand_deriving_debug(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/deriving/decodable.rs b/compiler/rustc_builtin_macros/src/deriving/decodable.rs
index 34798ab0a17..e9851c87aea 100644
--- a/compiler/rustc_builtin_macros/src/deriving/decodable.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/decodable.rs
@@ -10,7 +10,7 @@ use rustc_span::symbol::{sym, Ident, Symbol};
 use rustc_span::Span;
 use thin_vec::{thin_vec, ThinVec};
 
-pub fn expand_deriving_rustc_decodable(
+pub(crate) fn expand_deriving_rustc_decodable(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/deriving/default.rs b/compiler/rustc_builtin_macros/src/deriving/default.rs
index 328770ce10d..bf92ddb3370 100644
--- a/compiler/rustc_builtin_macros/src/deriving/default.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/default.rs
@@ -12,7 +12,7 @@ use rustc_span::{ErrorGuaranteed, Span};
 use smallvec::SmallVec;
 use thin_vec::{thin_vec, ThinVec};
 
-pub fn expand_deriving_default(
+pub(crate) fn expand_deriving_default(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &ast::MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/deriving/encodable.rs b/compiler/rustc_builtin_macros/src/deriving/encodable.rs
index 2e5f1173825..3bd74d8d019 100644
--- a/compiler/rustc_builtin_macros/src/deriving/encodable.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/encodable.rs
@@ -94,7 +94,7 @@ use rustc_span::symbol::{sym, Ident, Symbol};
 use rustc_span::Span;
 use thin_vec::{thin_vec, ThinVec};
 
-pub fn expand_deriving_rustc_encodable(
+pub(crate) fn expand_deriving_rustc_encodable(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
index 85d54e9257d..52c1ba1757b 100644
--- a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
@@ -174,8 +174,8 @@
 //! )
 //! ```
 
-pub use StaticFields::*;
-pub use SubstructureFields::*;
+pub(crate) use StaticFields::*;
+pub(crate) use SubstructureFields::*;
 
 use crate::{deriving, errors};
 use rustc_ast::ptr::P;
@@ -195,9 +195,9 @@ use std::vec;
 use thin_vec::{thin_vec, ThinVec};
 use ty::{Bounds, Path, Ref, Self_, Ty};
 
-pub mod ty;
+pub(crate) mod ty;
 
-pub struct TraitDef<'a> {
+pub(crate) struct TraitDef<'a> {
     /// The span for the current #[derive(Foo)] header.
     pub span: Span,
 
@@ -224,7 +224,7 @@ pub struct TraitDef<'a> {
     pub is_const: bool,
 }
 
-pub struct MethodDef<'a> {
+pub(crate) struct MethodDef<'a> {
     /// name of the method
     pub name: Symbol,
     /// List of generics, e.g., `R: rand::Rng`
@@ -248,7 +248,7 @@ pub struct MethodDef<'a> {
 
 /// How to handle fieldless enum variants.
 #[derive(PartialEq)]
-pub enum FieldlessVariantsStrategy {
+pub(crate) enum FieldlessVariantsStrategy {
     /// Combine fieldless variants into a single match arm.
     /// This assumes that relevant information has been handled
     /// by looking at the enum's discriminant.
@@ -263,7 +263,7 @@ pub enum FieldlessVariantsStrategy {
 }
 
 /// All the data about the data structure/method being derived upon.
-pub struct Substructure<'a> {
+pub(crate) struct Substructure<'a> {
     /// ident of self
     pub type_ident: Ident,
     /// Verbatim access to any non-selflike arguments, i.e. arguments that
@@ -273,7 +273,7 @@ pub struct Substructure<'a> {
 }
 
 /// Summary of the relevant parts of a struct/enum field.
-pub struct FieldInfo {
+pub(crate) struct FieldInfo {
     pub span: Span,
     /// None for tuple structs/normal enum variants, Some for normal
     /// structs/struct enum variants.
@@ -287,13 +287,13 @@ pub struct FieldInfo {
 }
 
 #[derive(Copy, Clone)]
-pub enum IsTuple {
+pub(crate) enum IsTuple {
     No,
     Yes,
 }
 
 /// Fields for a static method
-pub enum StaticFields {
+pub(crate) enum StaticFields {
     /// Tuple and unit structs/enum variants like this.
     Unnamed(Vec<Span>, IsTuple),
     /// Normal structs/struct variants.
@@ -301,7 +301,7 @@ pub enum StaticFields {
 }
 
 /// A summary of the possible sets of fields.
-pub enum SubstructureFields<'a> {
+pub(crate) enum SubstructureFields<'a> {
     /// A non-static method where `Self` is a struct.
     Struct(&'a ast::VariantData, Vec<FieldInfo>),
 
@@ -329,10 +329,10 @@ pub enum SubstructureFields<'a> {
 
 /// Combine the values of all the fields together. The last argument is
 /// all the fields of all the structures.
-pub type CombineSubstructureFunc<'a> =
+pub(crate) type CombineSubstructureFunc<'a> =
     Box<dyn FnMut(&ExtCtxt<'_>, Span, &Substructure<'_>) -> BlockOrExpr + 'a>;
 
-pub fn combine_substructure(
+pub(crate) fn combine_substructure(
     f: CombineSubstructureFunc<'_>,
 ) -> RefCell<CombineSubstructureFunc<'_>> {
     RefCell::new(f)
@@ -349,7 +349,7 @@ struct TypeParameter {
 /// avoiding the insertion of any unnecessary blocks.
 ///
 /// The statements come before the expression.
-pub struct BlockOrExpr(ThinVec<ast::Stmt>, Option<P<Expr>>);
+pub(crate) struct BlockOrExpr(ThinVec<ast::Stmt>, Option<P<Expr>>);
 
 impl BlockOrExpr {
     pub fn new_stmts(stmts: ThinVec<ast::Stmt>) -> BlockOrExpr {
@@ -1647,7 +1647,7 @@ impl<'a> TraitDef<'a> {
 /// The function passed to `cs_fold` is called repeatedly with a value of this
 /// type. It describes one part of the code generation. The result is always an
 /// expression.
-pub enum CsFold<'a> {
+pub(crate) enum CsFold<'a> {
     /// The basic case: a field expression for one or more selflike args. E.g.
     /// for `PartialEq::eq` this is something like `self.x == other.x`.
     Single(&'a FieldInfo),
@@ -1662,7 +1662,7 @@ pub enum CsFold<'a> {
 
 /// Folds over fields, combining the expressions for each field in a sequence.
 /// Statics may not be folded over.
-pub fn cs_fold<F>(
+pub(crate) fn cs_fold<F>(
     use_foldl: bool,
     cx: &ExtCtxt<'_>,
     trait_span: Span,
diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs
index 18883324683..f01d586033e 100644
--- a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs
@@ -1,7 +1,7 @@
 //! A mini version of ast::Ty, which is easier to use, and features an explicit `Self` type to use
 //! when specifying impls to be derived.
 
-pub use Ty::*;
+pub(crate) use Ty::*;
 
 use rustc_ast::ptr::P;
 use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind};
@@ -14,14 +14,14 @@ use thin_vec::ThinVec;
 /// A path, e.g., `::std::option::Option::<i32>` (global). Has support
 /// for type parameters.
 #[derive(Clone)]
-pub struct Path {
+pub(crate) struct Path {
     path: Vec<Symbol>,
     params: Vec<Box<Ty>>,
     kind: PathKind,
 }
 
 #[derive(Clone)]
-pub enum PathKind {
+pub(crate) enum PathKind {
     Local,
     Global,
     Std,
@@ -72,7 +72,7 @@ impl Path {
 
 /// A type. Supports pointers, Self, and literals.
 #[derive(Clone)]
-pub enum Ty {
+pub(crate) enum Ty {
     Self_,
     /// A reference.
     Ref(Box<Ty>, ast::Mutability),
@@ -83,7 +83,7 @@ pub enum Ty {
     Unit,
 }
 
-pub fn self_ref() -> Ty {
+pub(crate) fn self_ref() -> Ty {
     Ref(Box::new(Self_), ast::Mutability::Not)
 }
 
@@ -163,7 +163,7 @@ fn mk_ty_param(
 
 /// Bounds on type parameters.
 #[derive(Clone)]
-pub struct Bounds {
+pub(crate) struct Bounds {
     pub bounds: Vec<(Symbol, Vec<Path>)>,
 }
 
@@ -196,7 +196,7 @@ impl Bounds {
     }
 }
 
-pub fn get_explicit_self(cx: &ExtCtxt<'_>, span: Span) -> (P<Expr>, ast::ExplicitSelf) {
+pub(crate) fn get_explicit_self(cx: &ExtCtxt<'_>, span: Span) -> (P<Expr>, ast::ExplicitSelf) {
     // This constructs a fresh `self` path.
     let self_path = cx.expr_self(span);
     let self_ty = respan(span, SelfKind::Region(None, ast::Mutability::Not));
diff --git a/compiler/rustc_builtin_macros/src/deriving/hash.rs b/compiler/rustc_builtin_macros/src/deriving/hash.rs
index 41e27f65586..dcd92819865 100644
--- a/compiler/rustc_builtin_macros/src/deriving/hash.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/hash.rs
@@ -7,7 +7,7 @@ use rustc_span::symbol::sym;
 use rustc_span::Span;
 use thin_vec::thin_vec;
 
-pub fn expand_deriving_hash(
+pub(crate) fn expand_deriving_hash(
     cx: &ExtCtxt<'_>,
     span: Span,
     mitem: &MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/deriving/mod.rs b/compiler/rustc_builtin_macros/src/deriving/mod.rs
index 9f786d22c93..d6e2d1d4d07 100644
--- a/compiler/rustc_builtin_macros/src/deriving/mod.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs
@@ -20,24 +20,24 @@ macro path_std($($x:tt)*) {
     generic::ty::Path::new( pathvec_std!( $($x)* ) )
 }
 
-pub mod bounds;
-pub mod clone;
-pub mod debug;
-pub mod decodable;
-pub mod default;
-pub mod encodable;
-pub mod hash;
+pub(crate) mod bounds;
+pub(crate) mod clone;
+pub(crate) mod debug;
+pub(crate) mod decodable;
+pub(crate) mod default;
+pub(crate) mod encodable;
+pub(crate) mod hash;
 
 #[path = "cmp/eq.rs"]
-pub mod eq;
+pub(crate) mod eq;
 #[path = "cmp/ord.rs"]
-pub mod ord;
+pub(crate) mod ord;
 #[path = "cmp/partial_eq.rs"]
-pub mod partial_eq;
+pub(crate) mod partial_eq;
 #[path = "cmp/partial_ord.rs"]
-pub mod partial_ord;
+pub(crate) mod partial_ord;
 
-pub mod generic;
+pub(crate) mod generic;
 
 pub(crate) type BuiltinDeriveFn =
     fn(&ExtCtxt<'_>, Span, &MetaItem, &Annotatable, &mut dyn FnMut(Annotatable), bool);
diff --git a/compiler/rustc_builtin_macros/src/edition_panic.rs b/compiler/rustc_builtin_macros/src/edition_panic.rs
index bb3c83e8c0e..cc385bade47 100644
--- a/compiler/rustc_builtin_macros/src/edition_panic.rs
+++ b/compiler/rustc_builtin_macros/src/edition_panic.rs
@@ -16,7 +16,7 @@ use rustc_span::Span;
 ///
 /// `$crate` will refer to either the `std` or `core` crate depending on which
 /// one we're expanding from.
-pub fn expand_panic<'cx>(
+pub(crate) fn expand_panic<'cx>(
     cx: &'cx mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
@@ -29,7 +29,7 @@ pub fn expand_panic<'cx>(
 /// - `$crate::panic::unreachable_2015!(...)` or
 /// - `$crate::panic::unreachable_2021!(...)`
 /// depending on the edition.
-pub fn expand_unreachable<'cx>(
+pub(crate) fn expand_unreachable<'cx>(
     cx: &'cx mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
@@ -69,7 +69,7 @@ fn expand<'cx>(
     ))
 }
 
-pub fn use_panic_2021(mut span: Span) -> bool {
+pub(crate) fn use_panic_2021(mut span: Span) -> bool {
     // To determine the edition, we check the first span up the expansion
     // stack that does not have #[allow_internal_unstable(edition_panic)].
     // (To avoid using the edition of e.g. the assert!() or debug_assert!() definition.)
diff --git a/compiler/rustc_builtin_macros/src/env.rs b/compiler/rustc_builtin_macros/src/env.rs
index 93873045943..b03e14cf263 100644
--- a/compiler/rustc_builtin_macros/src/env.rs
+++ b/compiler/rustc_builtin_macros/src/env.rs
@@ -3,10 +3,11 @@
 // interface.
 //
 
+use crate::errors;
+use crate::util::{expr_to_string, get_exprs_from_tts, get_single_str_from_tts};
 use rustc_ast::token::{self, LitKind};
 use rustc_ast::tokenstream::TokenStream;
 use rustc_ast::{AstDeref, ExprKind, GenericArg, Mutability};
-use rustc_expand::base::{expr_to_string, get_exprs_from_tts, get_single_str_from_tts};
 use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_span::Span;
@@ -14,8 +15,6 @@ use std::env;
 use std::env::VarError;
 use thin_vec::thin_vec;
 
-use crate::errors;
-
 fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Result<Symbol, VarError> {
     let var = var.as_str();
     if let Some(value) = cx.sess.opts.logical_env.get(var) {
@@ -26,7 +25,7 @@ fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Result<Symbol, VarError
     Ok(Symbol::intern(&env::var(var)?))
 }
 
-pub fn expand_option_env<'cx>(
+pub(crate) fn expand_option_env<'cx>(
     cx: &'cx mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
@@ -66,7 +65,7 @@ pub fn expand_option_env<'cx>(
     ExpandResult::Ready(MacEager::expr(e))
 }
 
-pub fn expand_env<'cx>(
+pub(crate) fn expand_env<'cx>(
     cx: &'cx mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs
index 9078dc07a31..d157703723b 100644
--- a/compiler/rustc_builtin_macros/src/errors.rs
+++ b/compiler/rustc_builtin_macros/src/errors.rs
@@ -579,7 +579,7 @@ pub(crate) struct FormatUnknownTrait<'a> {
     style = "tool-only",
     applicability = "maybe-incorrect"
 )]
-pub struct FormatUnknownTraitSugg {
+pub(crate) struct FormatUnknownTraitSugg {
     #[primary_span]
     pub span: Span,
     pub fmt: &'static str,
@@ -842,3 +842,26 @@ pub(crate) struct ExpectedRegisterClassOrExplicitRegister {
     #[primary_span]
     pub(crate) span: Span,
 }
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_expected_comma_in_list)]
+pub(crate) struct ExpectedCommaInList {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_only_one_argument)]
+pub(crate) struct OnlyOneArgument<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub name: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_takes_no_arguments)]
+pub(crate) struct TakesNoArguments<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub name: &'a str,
+}
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index 51d6058a744..2c717661a1c 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -1,3 +1,5 @@
+use crate::errors;
+use crate::util::expr_to_spanned_string;
 use parse::Position::ArgumentNamed;
 use rustc_ast::ptr::P;
 use rustc_ast::tokenstream::TokenStream;
@@ -10,14 +12,13 @@ use rustc_ast::{
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::{Applicability, Diag, MultiSpan, PResult, SingleLabelManySpans};
 use rustc_expand::base::*;
+use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
+use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiag, LintId};
 use rustc_parse::parser::Recovered;
 use rustc_parse_format as parse;
 use rustc_span::symbol::{Ident, Symbol};
 use rustc_span::{BytePos, ErrorGuaranteed, InnerSpan, Span};
 
-use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
-use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiag, LintId};
-
 // The format_args!() macro is expanded in three steps:
 //  1. First, `parse_args` will parse the `(literal, arg, arg, name=arg, name=arg)` syntax,
 //     but doesn't parse the template (the literal) itself.
@@ -38,8 +39,6 @@ enum PositionUsedAs {
 }
 use PositionUsedAs::*;
 
-use crate::errors;
-
 #[derive(Debug)]
 struct MacroInput {
     fmtstr: P<Expr>,
@@ -1001,7 +1000,7 @@ fn expand_format_args_impl<'cx>(
     })
 }
 
-pub fn expand_format_args<'cx>(
+pub(crate) fn expand_format_args<'cx>(
     ecx: &'cx mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
@@ -1009,7 +1008,7 @@ pub fn expand_format_args<'cx>(
     expand_format_args_impl(ecx, sp, tts, false)
 }
 
-pub fn expand_format_args_nl<'cx>(
+pub(crate) fn expand_format_args_nl<'cx>(
     ecx: &'cx mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/global_allocator.rs b/compiler/rustc_builtin_macros/src/global_allocator.rs
index 099defd511b..a1630ad1379 100644
--- a/compiler/rustc_builtin_macros/src/global_allocator.rs
+++ b/compiler/rustc_builtin_macros/src/global_allocator.rs
@@ -12,7 +12,7 @@ use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_span::Span;
 use thin_vec::{thin_vec, ThinVec};
 
-pub fn expand(
+pub(crate) fn expand(
     ecx: &mut ExtCtxt<'_>,
     _span: Span,
     meta_item: &ast::MetaItem,
diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs
index 1b4c6041294..7c7b9c2d65f 100644
--- a/compiler/rustc_builtin_macros/src/lib.rs
+++ b/compiler/rustc_builtin_macros/src/lib.rs
@@ -50,13 +50,13 @@ mod pattern_type;
 mod source_util;
 mod test;
 mod trace_macros;
-mod util;
 
 pub mod asm;
 pub mod cmdline_attrs;
 pub mod proc_macro_harness;
 pub mod standard_library_imports;
 pub mod test_harness;
+pub mod util;
 
 rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
 
@@ -109,8 +109,8 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
         bench: test::expand_bench,
         cfg_accessible: cfg_accessible::Expander,
         cfg_eval: cfg_eval::expand,
-        derive: derive::Expander(false),
-        derive_const: derive::Expander(true),
+        derive: derive::Expander { is_const: false },
+        derive_const: derive::Expander { is_const: true },
         global_allocator: global_allocator::expand,
         test: test::expand_test,
         test_case: test::expand_test_case,
diff --git a/compiler/rustc_builtin_macros/src/log_syntax.rs b/compiler/rustc_builtin_macros/src/log_syntax.rs
index 288a475ac24..205f21ae7c9 100644
--- a/compiler/rustc_builtin_macros/src/log_syntax.rs
+++ b/compiler/rustc_builtin_macros/src/log_syntax.rs
@@ -2,7 +2,7 @@ use rustc_ast::tokenstream::TokenStream;
 use rustc_ast_pretty::pprust;
 use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
 
-pub fn expand_log_syntax<'cx>(
+pub(crate) fn expand_log_syntax<'cx>(
     _cx: &'cx mut ExtCtxt<'_>,
     sp: rustc_span::Span,
     tts: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/pattern_type.rs b/compiler/rustc_builtin_macros/src/pattern_type.rs
index 54039c2c538..31f5656df13 100644
--- a/compiler/rustc_builtin_macros/src/pattern_type.rs
+++ b/compiler/rustc_builtin_macros/src/pattern_type.rs
@@ -3,7 +3,7 @@ use rustc_errors::PResult;
 use rustc_expand::base::{self, DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
 use rustc_span::{sym, Span};
 
-pub fn expand<'cx>(
+pub(crate) fn expand<'cx>(
     cx: &'cx mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs
index c79ae716806..47b2ee975ca 100644
--- a/compiler/rustc_builtin_macros/src/source_util.rs
+++ b/compiler/rustc_builtin_macros/src/source_util.rs
@@ -1,3 +1,6 @@
+use crate::util::{
+    check_zero_tts, get_single_str_from_tts, get_single_str_spanned_from_tts, parse_expr,
+};
 use rustc_ast as ast;
 use rustc_ast::ptr::P;
 use rustc_ast::token;
@@ -5,11 +8,8 @@ use rustc_ast::tokenstream::TokenStream;
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::sync::Lrc;
 use rustc_expand::base::{
-    check_zero_tts, get_single_str_from_tts, get_single_str_spanned_from_tts, parse_expr,
-    resolve_path,
+    resolve_path, DummyResult, ExpandResult, ExtCtxt, MacEager, MacResult, MacroExpanderResult,
 };
-use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt};
-use rustc_expand::base::{MacEager, MacResult, MacroExpanderResult};
 use rustc_expand::module::DirOwnership;
 use rustc_parse::new_parser_from_file;
 use rustc_parse::parser::{ForceCollect, Parser};
@@ -26,7 +26,7 @@ use std::rc::Rc;
 // a given file into the current one.
 
 /// line!(): expands to the current line number
-pub fn expand_line(
+pub(crate) fn expand_line(
     cx: &mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
@@ -41,7 +41,7 @@ pub fn expand_line(
 }
 
 /* column!(): expands to the current column number */
-pub fn expand_column(
+pub(crate) fn expand_column(
     cx: &mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
@@ -58,7 +58,7 @@ pub fn expand_column(
 /// file!(): expands to the current filename */
 /// The source_file (`loc.file`) contains a bunch more information we could spit
 /// out if we wanted.
-pub fn expand_file(
+pub(crate) fn expand_file(
     cx: &mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
@@ -78,7 +78,7 @@ pub fn expand_file(
     )))
 }
 
-pub fn expand_stringify(
+pub(crate) fn expand_stringify(
     cx: &mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
@@ -88,7 +88,7 @@ pub fn expand_stringify(
     ExpandResult::Ready(MacEager::expr(cx.expr_str(sp, Symbol::intern(&s))))
 }
 
-pub fn expand_mod(
+pub(crate) fn expand_mod(
     cx: &mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
@@ -104,7 +104,7 @@ pub fn expand_mod(
 /// include! : parse the given file as an expr
 /// This is generally a bad idea because it's going to behave
 /// unhygienically.
-pub fn expand_include<'cx>(
+pub(crate) fn expand_include<'cx>(
     cx: &'cx mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
@@ -181,7 +181,7 @@ pub fn expand_include<'cx>(
 }
 
 /// `include_str!`: read the given file, insert it as a literal string expr
-pub fn expand_include_str(
+pub(crate) fn expand_include_str(
     cx: &mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
@@ -210,7 +210,7 @@ pub fn expand_include_str(
     })
 }
 
-pub fn expand_include_bytes(
+pub(crate) fn expand_include_bytes(
     cx: &mut ExtCtxt<'_>,
     sp: Span,
     tts: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs
index c7568f1461c..134d5451b9c 100644
--- a/compiler/rustc_builtin_macros/src/test.rs
+++ b/compiler/rustc_builtin_macros/src/test.rs
@@ -20,7 +20,7 @@ use thin_vec::{thin_vec, ThinVec};
 ///
 /// We mark item with an inert attribute "rustc_test_marker" which the test generation
 /// logic will pick up on.
-pub fn expand_test_case(
+pub(crate) fn expand_test_case(
     ecx: &mut ExtCtxt<'_>,
     attr_sp: Span,
     meta_item: &ast::MetaItem,
@@ -73,7 +73,7 @@ pub fn expand_test_case(
     vec![ret]
 }
 
-pub fn expand_test(
+pub(crate) fn expand_test(
     cx: &mut ExtCtxt<'_>,
     attr_sp: Span,
     meta_item: &ast::MetaItem,
@@ -84,7 +84,7 @@ pub fn expand_test(
     expand_test_or_bench(cx, attr_sp, item, false)
 }
 
-pub fn expand_bench(
+pub(crate) fn expand_bench(
     cx: &mut ExtCtxt<'_>,
     attr_sp: Span,
     meta_item: &ast::MetaItem,
@@ -95,7 +95,7 @@ pub fn expand_bench(
     expand_test_or_bench(cx, attr_sp, item, true)
 }
 
-pub fn expand_test_or_bench(
+pub(crate) fn expand_test_or_bench(
     cx: &ExtCtxt<'_>,
     attr_sp: Span,
     item: Annotatable,
diff --git a/compiler/rustc_builtin_macros/src/trace_macros.rs b/compiler/rustc_builtin_macros/src/trace_macros.rs
index 696d99004ba..4833ec32f76 100644
--- a/compiler/rustc_builtin_macros/src/trace_macros.rs
+++ b/compiler/rustc_builtin_macros/src/trace_macros.rs
@@ -4,7 +4,7 @@ use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult
 use rustc_span::symbol::kw;
 use rustc_span::Span;
 
-pub fn expand_trace_macros(
+pub(crate) fn expand_trace_macros(
     cx: &mut ExtCtxt<'_>,
     sp: Span,
     tt: TokenStream,
diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs
index ad6b09ba574..8dc7bc14ec3 100644
--- a/compiler/rustc_builtin_macros/src/util.rs
+++ b/compiler/rustc_builtin_macros/src/util.rs
@@ -1,11 +1,16 @@
-use rustc_ast::{attr, AttrStyle, Attribute, MetaItem};
-use rustc_expand::base::{Annotatable, ExtCtxt};
+use crate::errors;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast::{self as ast, attr, ptr::P, token, AttrStyle, Attribute, MetaItem};
+use rustc_errors::{Applicability, Diag, ErrorGuaranteed};
+use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt};
+use rustc_expand::expand::AstFragment;
 use rustc_feature::AttributeTemplate;
 use rustc_lint_defs::builtin::DUPLICATE_MACRO_ATTRIBUTES;
-use rustc_parse::validate_attr;
-use rustc_span::Symbol;
+use rustc_parse::{parser, validate_attr};
+use rustc_session::errors::report_lit_error;
+use rustc_span::{BytePos, Span, Symbol};
 
-pub fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, name: Symbol) {
+pub(crate) fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, name: Symbol) {
     // All the built-in macro attributes are "words" at the moment.
     let template = AttributeTemplate { word: true, ..Default::default() };
     validate_attr::check_builtin_meta_item(
@@ -19,7 +24,7 @@ pub fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, na
 
 /// Emit a warning if the item is annotated with the given attribute. This is used to diagnose when
 /// an attribute may have been mistakenly duplicated.
-pub fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name: Symbol) {
+pub(crate) fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name: Symbol) {
     let attrs: Option<&[Attribute]> = match item {
         Annotatable::Item(item) => Some(&item.attrs),
         Annotatable::TraitItem(item) => Some(&item.attrs),
@@ -46,3 +51,178 @@ pub fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name:
         }
     }
 }
+
+/// `Ok` represents successfully retrieving the string literal at the correct
+/// position, e.g., `println("abc")`.
+type ExprToSpannedStringResult<'a> = Result<(Symbol, ast::StrStyle, Span), UnexpectedExprKind<'a>>;
+
+/// - `Ok` is returned when the conversion to a string literal is unsuccessful,
+/// but another type of expression is obtained instead.
+/// - `Err` is returned when the conversion process fails.
+type UnexpectedExprKind<'a> = Result<(Diag<'a>, bool /* has_suggestions */), ErrorGuaranteed>;
+
+/// Extracts a string literal from the macro expanded version of `expr`,
+/// returning a diagnostic error of `err_msg` if `expr` is not a string literal.
+/// The returned bool indicates whether an applicable suggestion has already been
+/// added to the diagnostic to avoid emitting multiple suggestions. `Err(Err(ErrorGuaranteed))`
+/// indicates that an ast error was encountered.
+// FIXME(Nilstrieb) Make this function setup translatable
+#[allow(rustc::untranslatable_diagnostic)]
+pub(crate) fn expr_to_spanned_string<'a>(
+    cx: &'a mut ExtCtxt<'_>,
+    expr: P<ast::Expr>,
+    err_msg: &'static str,
+) -> ExpandResult<ExprToSpannedStringResult<'a>, ()> {
+    if !cx.force_mode
+        && let ast::ExprKind::MacCall(m) = &expr.kind
+        && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err()
+    {
+        return ExpandResult::Retry(());
+    }
+
+    // Perform eager expansion on the expression.
+    // We want to be able to handle e.g., `concat!("foo", "bar")`.
+    let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
+
+    ExpandResult::Ready(Err(match expr.kind {
+        ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
+            Ok(ast::LitKind::Str(s, style)) => {
+                return ExpandResult::Ready(Ok((s, style, expr.span)));
+            }
+            Ok(ast::LitKind::ByteStr(..)) => {
+                let mut err = cx.dcx().struct_span_err(expr.span, err_msg);
+                let span = expr.span.shrink_to_lo();
+                err.span_suggestion(
+                    span.with_hi(span.lo() + BytePos(1)),
+                    "consider removing the leading `b`",
+                    "",
+                    Applicability::MaybeIncorrect,
+                );
+                Ok((err, true))
+            }
+            Ok(ast::LitKind::Err(guar)) => Err(guar),
+            Err(err) => Err(report_lit_error(&cx.sess.psess, err, token_lit, expr.span)),
+            _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
+        },
+        ast::ExprKind::Err(guar) => Err(guar),
+        ast::ExprKind::Dummy => {
+            cx.dcx().span_bug(expr.span, "tried to get a string literal from `ExprKind::Dummy`")
+        }
+        _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
+    }))
+}
+
+/// Extracts a string literal from the macro expanded version of `expr`,
+/// emitting `err_msg` if `expr` is not a string literal. This does not stop
+/// compilation on error, merely emits a non-fatal error and returns `Err`.
+pub(crate) fn expr_to_string(
+    cx: &mut ExtCtxt<'_>,
+    expr: P<ast::Expr>,
+    err_msg: &'static str,
+) -> ExpandResult<Result<(Symbol, ast::StrStyle), ErrorGuaranteed>, ()> {
+    expr_to_spanned_string(cx, expr, err_msg).map(|res| {
+        res.map_err(|err| match err {
+            Ok((err, _)) => err.emit(),
+            Err(guar) => guar,
+        })
+        .map(|(symbol, style, _)| (symbol, style))
+    })
+}
+
+/// Non-fatally assert that `tts` is empty. Note that this function
+/// returns even when `tts` is non-empty, macros that *need* to stop
+/// compilation should call `cx.diagnostic().abort_if_errors()`
+/// (this should be done as rarely as possible).
+pub(crate) fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str) {
+    if !tts.is_empty() {
+        cx.dcx().emit_err(errors::TakesNoArguments { span, name });
+    }
+}
+
+/// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`.
+pub(crate) fn parse_expr(p: &mut parser::Parser<'_>) -> Result<P<ast::Expr>, ErrorGuaranteed> {
+    let guar = match p.parse_expr() {
+        Ok(expr) => return Ok(expr),
+        Err(err) => err.emit(),
+    };
+    while p.token != token::Eof {
+        p.bump();
+    }
+    Err(guar)
+}
+
+/// Interpreting `tts` as a comma-separated sequence of expressions,
+/// expect exactly one string literal, or emit an error and return `Err`.
+pub(crate) fn get_single_str_from_tts(
+    cx: &mut ExtCtxt<'_>,
+    span: Span,
+    tts: TokenStream,
+    name: &str,
+) -> ExpandResult<Result<Symbol, ErrorGuaranteed>, ()> {
+    get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s))
+}
+
+pub(crate) fn get_single_str_spanned_from_tts(
+    cx: &mut ExtCtxt<'_>,
+    span: Span,
+    tts: TokenStream,
+    name: &str,
+) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> {
+    let mut p = cx.new_parser_from_tts(tts);
+    if p.token == token::Eof {
+        let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
+        return ExpandResult::Ready(Err(guar));
+    }
+    let ret = match parse_expr(&mut p) {
+        Ok(ret) => ret,
+        Err(guar) => return ExpandResult::Ready(Err(guar)),
+    };
+    let _ = p.eat(&token::Comma);
+
+    if p.token != token::Eof {
+        cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
+    }
+    expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
+        res.map_err(|err| match err {
+            Ok((err, _)) => err.emit(),
+            Err(guar) => guar,
+        })
+        .map(|(symbol, _style, span)| (symbol, span))
+    })
+}
+
+/// Extracts comma-separated expressions from `tts`.
+/// On error, emit it, and return `Err`.
+pub(crate) fn get_exprs_from_tts(
+    cx: &mut ExtCtxt<'_>,
+    tts: TokenStream,
+) -> ExpandResult<Result<Vec<P<ast::Expr>>, ErrorGuaranteed>, ()> {
+    let mut p = cx.new_parser_from_tts(tts);
+    let mut es = Vec::new();
+    while p.token != token::Eof {
+        let expr = match parse_expr(&mut p) {
+            Ok(expr) => expr,
+            Err(guar) => return ExpandResult::Ready(Err(guar)),
+        };
+        if !cx.force_mode
+            && let ast::ExprKind::MacCall(m) = &expr.kind
+            && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err()
+        {
+            return ExpandResult::Retry(());
+        }
+
+        // Perform eager expansion on the expression.
+        // We want to be able to handle e.g., `concat!("foo", "bar")`.
+        let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
+
+        es.push(expr);
+        if p.eat(&token::Comma) {
+            continue;
+        }
+        if p.token != token::Eof {
+            let guar = cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span });
+            return ExpandResult::Ready(Err(guar));
+        }
+    }
+    ExpandResult::Ready(Ok(es))
+}
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index 6ce3fa3535d..6074a4a30bb 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -2019,7 +2019,7 @@ impl HumanEmitter {
                     let offset: isize = offsets
                         .iter()
                         .filter_map(
-                            |(start, v)| if span_start_pos <= *start { None } else { Some(v) },
+                            |(start, v)| if span_start_pos < *start { None } else { Some(v) },
                         )
                         .sum();
                     let underline_start = (span_start_pos + start) as isize + offset;
@@ -2028,7 +2028,7 @@ impl HumanEmitter {
                     let padding: usize = max_line_num_len + 3;
                     for p in underline_start..underline_end {
                         if let DisplaySuggestion::Underline = show_code_change {
-                            // If this is a replacement, underline with `^`, if this is an addition
+                            // If this is a replacement, underline with `~`, if this is an addition
                             // underline with `+`.
                             buffer.putc(
                                 row_num,
diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl
index fdd1a87cae8..b7aae2af9ef 100644
--- a/compiler/rustc_expand/messages.ftl
+++ b/compiler/rustc_expand/messages.ftl
@@ -30,9 +30,6 @@ expand_duplicate_matcher_binding = duplicate matcher binding
     .label = duplicate binding
     .label2 = previous binding
 
-expand_expected_comma_in_list =
-    expected token: `,`
-
 expand_expected_paren_or_brace =
     expected `(` or `{"{"}`, found `{$token}`
 
@@ -116,9 +113,6 @@ expand_must_repeat_once =
 expand_not_a_meta_item =
     not a meta item
 
-expand_only_one_argument =
-    {$name} takes 1 argument
-
 expand_only_one_word =
     must only be one word
 
@@ -146,9 +140,6 @@ expand_remove_node_not_supported =
 expand_resolve_relative_path =
     cannot resolve relative path in non-file source `{$path}`
 
-expand_takes_no_arguments =
-    {$name} takes no arguments
-
 expand_trace_macro = trace_macro
 
 expand_unsupported_key_value =
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 3c465709ec7..6fe74edbd70 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -5,27 +5,26 @@ use crate::module::DirOwnership;
 
 use rustc_ast::attr::MarkedAttrs;
 use rustc_ast::ptr::P;
-use rustc_ast::token::{self, Nonterminal};
+use rustc_ast::token::Nonterminal;
 use rustc_ast::tokenstream::TokenStream;
 use rustc_ast::visit::{AssocCtxt, Visitor};
 use rustc_ast::{self as ast, AttrVec, Attribute, HasAttrs, Item, NodeId, PatKind};
 use rustc_attr::{self as attr, Deprecation, Stability};
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::sync::{self, Lrc};
-use rustc_errors::{Applicability, Diag, DiagCtxt, ErrorGuaranteed, PResult};
+use rustc_errors::{DiagCtxt, ErrorGuaranteed, PResult};
 use rustc_feature::Features;
 use rustc_lint_defs::builtin::PROC_MACRO_BACK_COMPAT;
 use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiag, RegisteredTools};
 use rustc_parse::{parser, MACRO_ARGUMENTS};
 use rustc_session::config::CollapseMacroDebuginfo;
-use rustc_session::errors::report_lit_error;
 use rustc_session::{parse::ParseSess, Limit, Session};
 use rustc_span::def_id::{CrateNum, DefId, LocalDefId};
 use rustc_span::edition::Edition;
 use rustc_span::hygiene::{AstPass, ExpnData, ExpnKind, LocalExpnId};
 use rustc_span::source_map::SourceMap;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
-use rustc_span::{BytePos, FileName, Span, DUMMY_SP};
+use rustc_span::{FileName, Span, DUMMY_SP};
 use smallvec::{smallvec, SmallVec};
 use std::default::Default;
 use std::iter;
@@ -963,7 +962,12 @@ impl SyntaxExtension {
 /// Error type that denotes indeterminacy.
 pub struct Indeterminate;
 
-pub type DeriveResolutions = Vec<(ast::Path, Annotatable, Option<Lrc<SyntaxExtension>>, bool)>;
+pub struct DeriveResolution {
+    pub path: ast::Path,
+    pub item: Annotatable,
+    pub exts: Option<Lrc<SyntaxExtension>>,
+    pub is_const: bool,
+}
 
 pub trait ResolverExpand {
     fn next_node_id(&mut self) -> NodeId;
@@ -1006,11 +1010,11 @@ pub trait ResolverExpand {
         &mut self,
         expn_id: LocalExpnId,
         force: bool,
-        derive_paths: &dyn Fn() -> DeriveResolutions,
+        derive_paths: &dyn Fn() -> Vec<DeriveResolution>,
     ) -> Result<(), Indeterminate>;
     /// Take resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId`
     /// back from resolver.
-    fn take_derive_resolutions(&mut self, expn_id: LocalExpnId) -> Option<DeriveResolutions>;
+    fn take_derive_resolutions(&mut self, expn_id: LocalExpnId) -> Option<Vec<DeriveResolution>>;
     /// Path resolution logic for `#[cfg_accessible(path)]`.
     fn cfg_accessible(
         &mut self,
@@ -1264,181 +1268,6 @@ pub fn resolve_path(sess: &Session, path: impl Into<PathBuf>, span: Span) -> PRe
     }
 }
 
-/// `Ok` represents successfully retrieving the string literal at the correct
-/// position, e.g., `println("abc")`.
-type ExprToSpannedStringResult<'a> = Result<(Symbol, ast::StrStyle, Span), UnexpectedExprKind<'a>>;
-
-/// - `Ok` is returned when the conversion to a string literal is unsuccessful,
-/// but another type of expression is obtained instead.
-/// - `Err` is returned when the conversion process fails.
-type UnexpectedExprKind<'a> = Result<(Diag<'a>, bool /* has_suggestions */), ErrorGuaranteed>;
-
-/// Extracts a string literal from the macro expanded version of `expr`,
-/// returning a diagnostic error of `err_msg` if `expr` is not a string literal.
-/// The returned bool indicates whether an applicable suggestion has already been
-/// added to the diagnostic to avoid emitting multiple suggestions. `Err(Err(ErrorGuaranteed))`
-/// indicates that an ast error was encountered.
-// FIXME(Nilstrieb) Make this function setup translatable
-#[allow(rustc::untranslatable_diagnostic)]
-pub fn expr_to_spanned_string<'a>(
-    cx: &'a mut ExtCtxt<'_>,
-    expr: P<ast::Expr>,
-    err_msg: &'static str,
-) -> ExpandResult<ExprToSpannedStringResult<'a>, ()> {
-    if !cx.force_mode
-        && let ast::ExprKind::MacCall(m) = &expr.kind
-        && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err()
-    {
-        return ExpandResult::Retry(());
-    }
-
-    // Perform eager expansion on the expression.
-    // We want to be able to handle e.g., `concat!("foo", "bar")`.
-    let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
-
-    ExpandResult::Ready(Err(match expr.kind {
-        ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
-            Ok(ast::LitKind::Str(s, style)) => {
-                return ExpandResult::Ready(Ok((s, style, expr.span)));
-            }
-            Ok(ast::LitKind::ByteStr(..)) => {
-                let mut err = cx.dcx().struct_span_err(expr.span, err_msg);
-                let span = expr.span.shrink_to_lo();
-                err.span_suggestion(
-                    span.with_hi(span.lo() + BytePos(1)),
-                    "consider removing the leading `b`",
-                    "",
-                    Applicability::MaybeIncorrect,
-                );
-                Ok((err, true))
-            }
-            Ok(ast::LitKind::Err(guar)) => Err(guar),
-            Err(err) => Err(report_lit_error(&cx.sess.psess, err, token_lit, expr.span)),
-            _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
-        },
-        ast::ExprKind::Err(guar) => Err(guar),
-        ast::ExprKind::Dummy => {
-            cx.dcx().span_bug(expr.span, "tried to get a string literal from `ExprKind::Dummy`")
-        }
-        _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
-    }))
-}
-
-/// Extracts a string literal from the macro expanded version of `expr`,
-/// emitting `err_msg` if `expr` is not a string literal. This does not stop
-/// compilation on error, merely emits a non-fatal error and returns `Err`.
-pub fn expr_to_string(
-    cx: &mut ExtCtxt<'_>,
-    expr: P<ast::Expr>,
-    err_msg: &'static str,
-) -> ExpandResult<Result<(Symbol, ast::StrStyle), ErrorGuaranteed>, ()> {
-    expr_to_spanned_string(cx, expr, err_msg).map(|res| {
-        res.map_err(|err| match err {
-            Ok((err, _)) => err.emit(),
-            Err(guar) => guar,
-        })
-        .map(|(symbol, style, _)| (symbol, style))
-    })
-}
-
-/// Non-fatally assert that `tts` is empty. Note that this function
-/// returns even when `tts` is non-empty, macros that *need* to stop
-/// compilation should call `cx.diagnostic().abort_if_errors()`
-/// (this should be done as rarely as possible).
-pub fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str) {
-    if !tts.is_empty() {
-        cx.dcx().emit_err(errors::TakesNoArguments { span, name });
-    }
-}
-
-/// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`.
-pub fn parse_expr(p: &mut parser::Parser<'_>) -> Result<P<ast::Expr>, ErrorGuaranteed> {
-    let guar = match p.parse_expr() {
-        Ok(expr) => return Ok(expr),
-        Err(err) => err.emit(),
-    };
-    while p.token != token::Eof {
-        p.bump();
-    }
-    Err(guar)
-}
-
-/// Interpreting `tts` as a comma-separated sequence of expressions,
-/// expect exactly one string literal, or emit an error and return `Err`.
-pub fn get_single_str_from_tts(
-    cx: &mut ExtCtxt<'_>,
-    span: Span,
-    tts: TokenStream,
-    name: &str,
-) -> ExpandResult<Result<Symbol, ErrorGuaranteed>, ()> {
-    get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s))
-}
-
-pub fn get_single_str_spanned_from_tts(
-    cx: &mut ExtCtxt<'_>,
-    span: Span,
-    tts: TokenStream,
-    name: &str,
-) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> {
-    let mut p = cx.new_parser_from_tts(tts);
-    if p.token == token::Eof {
-        let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
-        return ExpandResult::Ready(Err(guar));
-    }
-    let ret = match parse_expr(&mut p) {
-        Ok(ret) => ret,
-        Err(guar) => return ExpandResult::Ready(Err(guar)),
-    };
-    let _ = p.eat(&token::Comma);
-
-    if p.token != token::Eof {
-        cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
-    }
-    expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
-        res.map_err(|err| match err {
-            Ok((err, _)) => err.emit(),
-            Err(guar) => guar,
-        })
-        .map(|(symbol, _style, span)| (symbol, span))
-    })
-}
-
-/// Extracts comma-separated expressions from `tts`.
-/// On error, emit it, and return `Err`.
-pub fn get_exprs_from_tts(
-    cx: &mut ExtCtxt<'_>,
-    tts: TokenStream,
-) -> ExpandResult<Result<Vec<P<ast::Expr>>, ErrorGuaranteed>, ()> {
-    let mut p = cx.new_parser_from_tts(tts);
-    let mut es = Vec::new();
-    while p.token != token::Eof {
-        let expr = match parse_expr(&mut p) {
-            Ok(expr) => expr,
-            Err(guar) => return ExpandResult::Ready(Err(guar)),
-        };
-        if !cx.force_mode
-            && let ast::ExprKind::MacCall(m) = &expr.kind
-            && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err()
-        {
-            return ExpandResult::Retry(());
-        }
-
-        // Perform eager expansion on the expression.
-        // We want to be able to handle e.g., `concat!("foo", "bar")`.
-        let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
-
-        es.push(expr);
-        if p.eat(&token::Comma) {
-            continue;
-        }
-        if p.token != token::Eof {
-            let guar = cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span });
-            return ExpandResult::Ready(Err(guar));
-        }
-    }
-    ExpandResult::Ready(Ok(es))
-}
-
 pub fn parse_macro_name_and_helper_attrs(
     dcx: &rustc_errors::DiagCtxt,
     attr: &Attribute,
diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs
index 21ce5e1d81e..db8e4ba07e8 100644
--- a/compiler/rustc_expand/src/errors.rs
+++ b/compiler/rustc_expand/src/errors.rs
@@ -153,29 +153,6 @@ pub(crate) struct HelperAttributeNameInvalid {
 }
 
 #[derive(Diagnostic)]
-#[diag(expand_expected_comma_in_list)]
-pub(crate) struct ExpectedCommaInList {
-    #[primary_span]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
-#[diag(expand_only_one_argument)]
-pub(crate) struct OnlyOneArgument<'a> {
-    #[primary_span]
-    pub span: Span,
-    pub name: &'a str,
-}
-
-#[derive(Diagnostic)]
-#[diag(expand_takes_no_arguments)]
-pub(crate) struct TakesNoArguments<'a> {
-    #[primary_span]
-    pub span: Span,
-    pub name: &'a str,
-}
-
-#[derive(Diagnostic)]
 #[diag(expand_feature_removed, code = E0557)]
 pub(crate) struct FeatureRemoved<'a> {
     #[primary_span]
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index 6029caa965c..503c9170cab 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -482,7 +482,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                             derive_invocations.reserve(derives.len());
                             derives
                                 .into_iter()
-                                .map(|(path, item, _exts, is_const)| {
+                                .map(|DeriveResolution { path, item, exts: _, is_const }| {
                                     // FIXME: Consider using the derive resolutions (`_exts`)
                                     // instead of enqueuing the derives to be resolved again later.
                                     let expn_id = LocalExpnId::fresh_empty();
@@ -1218,7 +1218,7 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, TraitItemTag>
         fragment.make_trait_items()
     }
     fn noop_flat_map<V: MutVisitor>(self, visitor: &mut V) -> Self::OutputTy {
-        noop_flat_map_assoc_item(self.wrapped, visitor)
+        noop_flat_map_item(self.wrapped, visitor)
     }
     fn is_mac_call(&self) -> bool {
         matches!(self.wrapped.kind, AssocItemKind::MacCall(..))
@@ -1243,7 +1243,7 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, ImplItemTag>
         fragment.make_impl_items()
     }
     fn noop_flat_map<V: MutVisitor>(self, visitor: &mut V) -> Self::OutputTy {
-        noop_flat_map_assoc_item(self.wrapped, visitor)
+        noop_flat_map_item(self.wrapped, visitor)
     }
     fn is_mac_call(&self) -> bool {
         matches!(self.wrapped.kind, AssocItemKind::MacCall(..))
@@ -1266,7 +1266,7 @@ impl InvocationCollectorNode for P<ast::ForeignItem> {
         fragment.make_foreign_items()
     }
     fn noop_flat_map<V: MutVisitor>(self, visitor: &mut V) -> Self::OutputTy {
-        noop_flat_map_foreign_item(self, visitor)
+        noop_flat_map_item(self, visitor)
     }
     fn is_mac_call(&self) -> bool {
         matches!(self.kind, ForeignItemKind::MacCall(..))
diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs
index 2c4187031ca..581d71875bd 100644
--- a/compiler/rustc_expand/src/placeholders.rs
+++ b/compiler/rustc_expand/src/placeholders.rs
@@ -271,14 +271,14 @@ impl MutVisitor for PlaceholderExpander {
     fn flat_map_trait_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
         match item.kind {
             ast::AssocItemKind::MacCall(_) => self.remove(item.id).make_trait_items(),
-            _ => noop_flat_map_assoc_item(item, self),
+            _ => noop_flat_map_item(item, self),
         }
     }
 
     fn flat_map_impl_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
         match item.kind {
             ast::AssocItemKind::MacCall(_) => self.remove(item.id).make_impl_items(),
-            _ => noop_flat_map_assoc_item(item, self),
+            _ => noop_flat_map_item(item, self),
         }
     }
 
@@ -288,7 +288,7 @@ impl MutVisitor for PlaceholderExpander {
     ) -> SmallVec<[P<ast::ForeignItem>; 1]> {
         match item.kind {
             ast::ForeignItemKind::MacCall(_) => self.remove(item.id).make_foreign_items(),
-            _ => noop_flat_map_foreign_item(item, self),
+            _ => noop_flat_map_item(item, self),
         }
     }
 
diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs
index 599f25147ce..0f0736f8756 100644
--- a/compiler/rustc_hir_analysis/src/collect.rs
+++ b/compiler/rustc_hir_analysis/src/collect.rs
@@ -386,6 +386,8 @@ impl<'tcx> HirTyLowerer<'tcx> for ItemCtxt<'tcx> {
 
     fn ct_infer(&self, ty: Ty<'tcx>, _: Option<&ty::GenericParamDef>, span: Span) -> Const<'tcx> {
         let ty = self.tcx.fold_regions(ty, |r, _| match *r {
+            rustc_type_ir::RegionKind::ReStatic => r,
+
             // This is never reached in practice. If it ever is reached,
             // `ReErased` should be changed to `ReStatic`, and any other region
             // left alone.
diff --git a/compiler/rustc_hir_typeck/src/closure.rs b/compiler/rustc_hir_typeck/src/closure.rs
index d6704d9e44f..4883c7aff8b 100644
--- a/compiler/rustc_hir_typeck/src/closure.rs
+++ b/compiler/rustc_hir_typeck/src/closure.rs
@@ -342,7 +342,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             ty::Infer(ty::TyVar(vid)) => self.deduce_closure_signature_from_predicates(
                 Ty::new_var(self.tcx, self.root_var(vid)),
                 closure_kind,
-                self.obligations_for_self_ty(vid).map(|obl| (obl.predicate, obl.cause.span)),
+                self.obligations_for_self_ty(vid)
+                    .into_iter()
+                    .map(|obl| (obl.predicate, obl.cause.span)),
             ),
             ty::FnPtr(sig) => match closure_kind {
                 hir::ClosureKind::Closure => {
@@ -889,7 +891,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
         let output_ty = match *ret_ty.kind() {
             ty::Infer(ty::TyVar(ret_vid)) => {
-                self.obligations_for_self_ty(ret_vid).find_map(|obligation| {
+                self.obligations_for_self_ty(ret_vid).into_iter().find_map(|obligation| {
                     get_future_output(obligation.predicate, obligation.cause.span)
                 })?
             }
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 48b9142b014..15792287a87 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -1,6 +1,6 @@
 //! Type checking expressions.
 //!
-//! See `mod.rs` for more context on type checking in general.
+//! See [`rustc_hir_analysis::check`] for more context on type checking in general.
 
 use crate::cast;
 use crate::coercion::CoerceMany;
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
index 0ba5d187864..2060e08aacf 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs
@@ -3,7 +3,6 @@ use crate::errors::CtorIsPrivate;
 use crate::method::{self, MethodCallee, SelfSource};
 use crate::rvalue_scopes;
 use crate::{BreakableCtxt, Diverges, Expectation, FnCtxt, LoweredTy};
-use rustc_data_structures::captures::Captures;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::{Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey};
 use rustc_hir as hir;
@@ -47,7 +46,7 @@ use std::slice;
 impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     /// Produces warning on the given node, if the current point in the
     /// function is unreachable, and there hasn't been another warning.
-    pub(in super::super) fn warn_if_unreachable(&self, id: HirId, span: Span, kind: &str) {
+    pub(crate) fn warn_if_unreachable(&self, id: HirId, span: Span, kind: &str) {
         // FIXME: Combine these two 'if' expressions into one once
         // let chains are implemented
         if let Diverges::Always { span: orig_span, custom_note } = self.diverges.get() {
@@ -87,7 +86,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     // FIXME(-Znext-solver): A lot of the calls to this method should
     // probably be `try_structurally_resolve_type` or `structurally_resolve_type` instead.
     #[instrument(skip(self), level = "debug", ret)]
-    pub(in super::super) fn resolve_vars_with_obligations(&self, mut ty: Ty<'tcx>) -> Ty<'tcx> {
+    pub(crate) fn resolve_vars_with_obligations(&self, mut ty: Ty<'tcx>) -> Ty<'tcx> {
         // No Infer()? Nothing needs doing.
         if !ty.has_non_region_infer() {
             debug!("no inference var, nothing needs doing");
@@ -109,7 +108,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         self.resolve_vars_if_possible(ty)
     }
 
-    pub(in super::super) fn record_deferred_call_resolution(
+    pub(crate) fn record_deferred_call_resolution(
         &self,
         closure_def_id: LocalDefId,
         r: DeferredCallResolution<'tcx>,
@@ -118,7 +117,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         deferred_call_resolutions.entry(closure_def_id).or_default().push(r);
     }
 
-    pub(in super::super) fn remove_deferred_call_resolutions(
+    pub(crate) fn remove_deferred_call_resolutions(
         &self,
         closure_def_id: LocalDefId,
     ) -> Vec<DeferredCallResolution<'tcx>> {
@@ -172,7 +171,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     }
 
     #[instrument(level = "debug", skip(self))]
-    pub(in super::super) fn write_resolution(
+    pub(crate) fn write_resolution(
         &self,
         hir_id: HirId,
         r: Result<(DefKind, DefId), ErrorGuaranteed>,
@@ -336,7 +335,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     }
 
     /// Instantiates and normalizes the bounds for a given item
-    pub(in super::super) fn instantiate_bounds(
+    pub(crate) fn instantiate_bounds(
         &self,
         span: Span,
         def_id: DefId,
@@ -349,7 +348,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         result
     }
 
-    pub(in super::super) fn normalize<T>(&self, span: Span, value: T) -> T
+    pub(crate) fn normalize<T>(&self, span: Span, value: T) -> T
     where
         T: TypeFoldable<TyCtxt<'tcx>>,
     {
@@ -537,7 +536,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         self.normalize(span, field.ty(self.tcx, args))
     }
 
-    pub(in super::super) fn resolve_rvalue_scopes(&self, def_id: DefId) {
+    pub(crate) fn resolve_rvalue_scopes(&self, def_id: DefId) {
         let scope_tree = self.tcx.region_scope_tree(def_id);
         let rvalue_scopes = { rvalue_scopes::resolve_rvalue_scopes(self, scope_tree, def_id) };
         let mut typeck_results = self.typeck_results.borrow_mut();
@@ -553,7 +552,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     /// We must not attempt to select obligations after this method has run, or risk query cycle
     /// ICE.
     #[instrument(level = "debug", skip(self))]
-    pub(in super::super) fn resolve_coroutine_interiors(&self) {
+    pub(crate) fn resolve_coroutine_interiors(&self) {
         // Try selecting all obligations that are not blocked on inference variables.
         // Once we start unifying coroutine witnesses, trying to select obligations on them will
         // trigger query cycle ICEs, as doing so requires MIR.
@@ -594,7 +593,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     }
 
     #[instrument(skip(self), level = "debug")]
-    pub(in super::super) fn report_ambiguity_errors(&self) {
+    pub(crate) fn report_ambiguity_errors(&self) {
         let mut errors = self.fulfillment_cx.borrow_mut().collect_remaining_errors(self);
 
         if !errors.is_empty() {
@@ -609,7 +608,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     }
 
     /// Select as many obligations as we can at present.
-    pub(in super::super) fn select_obligations_where_possible(
+    pub(crate) fn select_obligations_where_possible(
         &self,
         mutate_fulfillment_errors: impl Fn(&mut Vec<traits::FulfillmentError<'tcx>>),
     ) {
@@ -625,7 +624,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     /// returns a type of `&T`, but the actual type we assign to the
     /// *expression* is `T`. So this function just peels off the return
     /// type by one layer to yield `T`.
-    pub(in super::super) fn make_overloaded_place_return_type(
+    pub(crate) fn make_overloaded_place_return_type(
         &self,
         method: MethodCallee<'tcx>,
     ) -> ty::TypeAndMut<'tcx> {
@@ -636,67 +635,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         ret_ty.builtin_deref(true).unwrap()
     }
 
-    #[instrument(skip(self), level = "debug")]
-    fn self_type_matches_expected_vid(&self, self_ty: Ty<'tcx>, expected_vid: ty::TyVid) -> bool {
-        let self_ty = self.shallow_resolve(self_ty);
-        debug!(?self_ty);
-
-        match *self_ty.kind() {
-            ty::Infer(ty::TyVar(found_vid)) => {
-                let found_vid = self.root_var(found_vid);
-                debug!("self_type_matches_expected_vid - found_vid={:?}", found_vid);
-                expected_vid == found_vid
-            }
-            _ => false,
-        }
-    }
-
-    #[instrument(skip(self), level = "debug")]
-    pub(in super::super) fn obligations_for_self_ty<'b>(
-        &'b self,
-        self_ty: ty::TyVid,
-    ) -> impl DoubleEndedIterator<Item = traits::PredicateObligation<'tcx>> + Captures<'tcx> + 'b
-    {
-        let ty_var_root = self.root_var(self_ty);
-        trace!("pending_obligations = {:#?}", self.fulfillment_cx.borrow().pending_obligations());
-
-        self.fulfillment_cx.borrow().pending_obligations().into_iter().filter_map(
-            move |obligation| match &obligation.predicate.kind().skip_binder() {
-                ty::PredicateKind::Clause(ty::ClauseKind::Projection(data))
-                    if self.self_type_matches_expected_vid(
-                        data.projection_ty.self_ty(),
-                        ty_var_root,
-                    ) =>
-                {
-                    Some(obligation)
-                }
-                ty::PredicateKind::Clause(ty::ClauseKind::Trait(data))
-                    if self.self_type_matches_expected_vid(data.self_ty(), ty_var_root) =>
-                {
-                    Some(obligation)
-                }
-
-                ty::PredicateKind::Clause(ty::ClauseKind::Trait(..))
-                | ty::PredicateKind::Clause(ty::ClauseKind::Projection(..))
-                | ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..))
-                | ty::PredicateKind::Subtype(..)
-                | ty::PredicateKind::Coerce(..)
-                | ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(..))
-                | ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(..))
-                | ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(..))
-                | ty::PredicateKind::ObjectSafe(..)
-                | ty::PredicateKind::NormalizesTo(..)
-                | ty::PredicateKind::AliasRelate(..)
-                | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..))
-                | ty::PredicateKind::ConstEquate(..)
-                | ty::PredicateKind::Ambiguous => None,
-            },
-        )
-    }
-
-    pub(in super::super) fn type_var_is_sized(&self, self_ty: ty::TyVid) -> bool {
+    pub(crate) fn type_var_is_sized(&self, self_ty: ty::TyVid) -> bool {
         let sized_did = self.tcx.lang_items().sized_trait();
-        self.obligations_for_self_ty(self_ty).any(|obligation| {
+        self.obligations_for_self_ty(self_ty).into_iter().any(|obligation| {
             match obligation.predicate.kind().skip_binder() {
                 ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => {
                     Some(data.def_id()) == sized_did
@@ -706,7 +647,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         })
     }
 
-    pub(in super::super) fn err_args(&self, len: usize) -> Vec<Ty<'tcx>> {
+    pub(crate) fn err_args(&self, len: usize) -> Vec<Ty<'tcx>> {
         let ty_error = Ty::new_misc_error(self.tcx);
         vec![ty_error; len]
     }
@@ -714,7 +655,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     /// Unifies the output type with the expected type early, for more coercions
     /// and forward type information on the input expressions.
     #[instrument(skip(self, call_span), level = "debug")]
-    pub(in super::super) fn expected_inputs_for_expected_output(
+    pub(crate) fn expected_inputs_for_expected_output(
         &self,
         call_span: Span,
         expected_ret: Expectation<'tcx>,
@@ -747,7 +688,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         expect_args
     }
 
-    pub(in super::super) fn resolve_lang_item_path(
+    pub(crate) fn resolve_lang_item_path(
         &self,
         lang_item: hir::LangItem,
         span: Span,
@@ -926,7 +867,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     /// but we often want access to the parent function's signature.
     ///
     /// Otherwise, return false.
-    pub(in super::super) fn get_node_fn_decl(
+    pub(crate) fn get_node_fn_decl(
         &self,
         node: Node<'tcx>,
     ) -> Option<(LocalDefId, &'tcx hir::FnDecl<'tcx>, Ident, bool)> {
@@ -1004,7 +945,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         })
     }
 
-    pub(in super::super) fn note_internal_mutation_in_method(
+    pub(crate) fn note_internal_mutation_in_method(
         &self,
         err: &mut Diag<'_>,
         expr: &hir::Expr<'_>,
@@ -1549,7 +1490,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         }
     }
 
-    pub(in super::super) fn with_breakable_ctxt<F: FnOnce() -> R, R>(
+    pub(crate) fn with_breakable_ctxt<F: FnOnce() -> R, R>(
         &self,
         id: HirId,
         ctxt: BreakableCtxt<'tcx>,
@@ -1575,7 +1516,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
 
     /// Instantiate a QueryResponse in a probe context, without a
     /// good ObligationCause.
-    pub(in super::super) fn probe_instantiate_query_response(
+    pub(crate) fn probe_instantiate_query_response(
         &self,
         span: Span,
         original_values: &OriginalQueryValues<'tcx>,
@@ -1590,7 +1531,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
     }
 
     /// Returns `true` if an expression is contained inside the LHS of an assignment expression.
-    pub(in super::super) fn expr_in_place(&self, mut expr_id: HirId) -> bool {
+    pub(crate) fn expr_in_place(&self, mut expr_id: HirId) -> bool {
         let mut contained_in_place = false;
 
         while let hir::Node::Expr(parent_expr) = self.tcx.parent_hir_node(expr_id) {
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs
new file mode 100644
index 00000000000..c2068d9f66b
--- /dev/null
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs
@@ -0,0 +1,140 @@
+//! A utility module to inspect currently ambiguous obligations in the current context.
+use crate::rustc_middle::ty::TypeVisitableExt;
+use crate::FnCtxt;
+use rustc_infer::traits::solve::Goal;
+use rustc_infer::traits::{self, ObligationCause};
+use rustc_middle::ty::{self, Ty};
+use rustc_span::Span;
+use rustc_trait_selection::solve::inspect::ProofTreeInferCtxtExt;
+use rustc_trait_selection::solve::inspect::{InspectConfig, InspectGoal, ProofTreeVisitor};
+
+impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
+    /// Returns a list of all obligations whose self type has been unified
+    /// with the unconstrained type `self_ty`.
+    #[instrument(skip(self), level = "debug")]
+    pub(crate) fn obligations_for_self_ty(
+        &self,
+        self_ty: ty::TyVid,
+    ) -> Vec<traits::PredicateObligation<'tcx>> {
+        if self.next_trait_solver() {
+            self.obligations_for_self_ty_next(self_ty)
+        } else {
+            let ty_var_root = self.root_var(self_ty);
+            let mut obligations = self.fulfillment_cx.borrow().pending_obligations();
+            trace!("pending_obligations = {:#?}", obligations);
+            obligations
+                .retain(|obligation| self.predicate_has_self_ty(obligation.predicate, ty_var_root));
+            obligations
+        }
+    }
+
+    #[instrument(level = "debug", skip(self), ret)]
+    fn predicate_has_self_ty(
+        &self,
+        predicate: ty::Predicate<'tcx>,
+        expected_vid: ty::TyVid,
+    ) -> bool {
+        match predicate.kind().skip_binder() {
+            ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => {
+                self.type_matches_expected_vid(expected_vid, data.self_ty())
+            }
+            ty::PredicateKind::Clause(ty::ClauseKind::Projection(data)) => {
+                self.type_matches_expected_vid(expected_vid, data.projection_ty.self_ty())
+            }
+            ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..))
+            | ty::PredicateKind::Subtype(..)
+            | ty::PredicateKind::Coerce(..)
+            | ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(..))
+            | ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(..))
+            | ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(..))
+            | ty::PredicateKind::ObjectSafe(..)
+            | ty::PredicateKind::NormalizesTo(..)
+            | ty::PredicateKind::AliasRelate(..)
+            | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..))
+            | ty::PredicateKind::ConstEquate(..)
+            | ty::PredicateKind::Ambiguous => false,
+        }
+    }
+
+    #[instrument(level = "debug", skip(self), ret)]
+    fn type_matches_expected_vid(&self, expected_vid: ty::TyVid, ty: Ty<'tcx>) -> bool {
+        let ty = self.shallow_resolve(ty);
+        debug!(?ty);
+
+        match *ty.kind() {
+            ty::Infer(ty::TyVar(found_vid)) => {
+                self.root_var(expected_vid) == self.root_var(found_vid)
+            }
+            _ => false,
+        }
+    }
+
+    pub(crate) fn obligations_for_self_ty_next(
+        &self,
+        self_ty: ty::TyVid,
+    ) -> Vec<traits::PredicateObligation<'tcx>> {
+        let obligations = self.fulfillment_cx.borrow().pending_obligations();
+        debug!(?obligations);
+        let mut obligations_for_self_ty = vec![];
+        for obligation in obligations {
+            let mut visitor = NestedObligationsForSelfTy {
+                fcx: self,
+                self_ty,
+                obligations_for_self_ty: &mut obligations_for_self_ty,
+                root_cause: &obligation.cause,
+            };
+
+            let goal = Goal::new(self.tcx, obligation.param_env, obligation.predicate);
+            self.visit_proof_tree(goal, &mut visitor);
+        }
+
+        obligations_for_self_ty.retain_mut(|obligation| {
+            obligation.predicate = self.resolve_vars_if_possible(obligation.predicate);
+            !obligation.predicate.has_placeholders()
+        });
+        obligations_for_self_ty
+    }
+}
+
+struct NestedObligationsForSelfTy<'a, 'tcx> {
+    fcx: &'a FnCtxt<'a, 'tcx>,
+    self_ty: ty::TyVid,
+    root_cause: &'a ObligationCause<'tcx>,
+    obligations_for_self_ty: &'a mut Vec<traits::PredicateObligation<'tcx>>,
+}
+
+impl<'a, 'tcx> ProofTreeVisitor<'tcx> for NestedObligationsForSelfTy<'a, 'tcx> {
+    type Result = ();
+
+    fn span(&self) -> Span {
+        self.root_cause.span
+    }
+
+    fn config(&self) -> InspectConfig {
+        // Using an intentionally low depth to minimize the chance of future
+        // breaking changes in case we adapt the approach later on. This also
+        // avoids any hangs for exponentially growing proof trees.
+        InspectConfig { max_depth: 5 }
+    }
+
+    fn visit_goal(&mut self, inspect_goal: &InspectGoal<'_, 'tcx>) {
+        let tcx = self.fcx.tcx;
+        let goal = inspect_goal.goal();
+        if self.fcx.predicate_has_self_ty(goal.predicate, self.self_ty) {
+            self.obligations_for_self_ty.push(traits::Obligation::new(
+                tcx,
+                self.root_cause.clone(),
+                goal.param_env,
+                goal.predicate,
+            ));
+        }
+
+        // If there's a unique way to prove a given goal, recurse into
+        // that candidate. This means that for `impl<F: FnOnce(u32)> Trait<F> for () {}`
+        // and a `(): Trait<?0>` goal we recurse into the impl and look at
+        // the nested `?0: FnOnce(u32)` goal.
+        if let Some(candidate) = inspect_goal.unique_applicable_candidate() {
+            candidate.visit_nested_no_probe(self)
+        }
+    }
+}
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs
index 080571e1a70..2f96cf9e373 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/mod.rs
@@ -2,6 +2,7 @@ mod _impl;
 mod adjust_fulfillment_errors;
 mod arg_matrix;
 mod checks;
+mod inspect_obligations;
 mod suggestions;
 
 use crate::coercion::DynamicCoerceMany;
diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs
index 9fae32a49c7..3f627baf770 100644
--- a/compiler/rustc_lint/src/early.rs
+++ b/compiler/rustc_lint/src/early.rs
@@ -99,7 +99,7 @@ impl<'a, T: EarlyLintPass> ast_visit::Visitor<'a> for EarlyContextAndPass<'a, T>
 
     fn visit_foreign_item(&mut self, it: &'a ast::ForeignItem) {
         self.with_lint_attrs(it.id, &it.attrs, |cx| {
-            ast_visit::walk_foreign_item(cx, it);
+            ast_visit::walk_item(cx, it);
         })
     }
 
diff --git a/compiler/rustc_middle/src/mir/mod.rs b/compiler/rustc_middle/src/mir/mod.rs
index 43e4e8216e1..8555aa48c30 100644
--- a/compiler/rustc_middle/src/mir/mod.rs
+++ b/compiler/rustc_middle/src/mir/mod.rs
@@ -701,10 +701,7 @@ impl<'tcx> Body<'tcx> {
                 env,
                 crate::ty::EarlyBinder::bind(constant.const_),
             );
-            let Some(bits) = mono_literal.try_eval_bits(tcx, env) else {
-                bug!("Couldn't evaluate constant {:?} in mono {:?}", constant, instance);
-            };
-            bits
+            mono_literal.try_eval_bits(tcx, env)
         };
 
         let TerminatorKind::SwitchInt { discr, targets } = &block.terminator().kind else {
@@ -714,7 +711,7 @@ impl<'tcx> Body<'tcx> {
         // If this is a SwitchInt(const _), then we can just evaluate the constant and return.
         let discr = match discr {
             Operand::Constant(constant) => {
-                let bits = eval_mono_const(constant);
+                let bits = eval_mono_const(constant)?;
                 return Some((bits, targets));
             }
             Operand::Move(place) | Operand::Copy(place) => place,
@@ -748,7 +745,7 @@ impl<'tcx> Body<'tcx> {
         match rvalue {
             Rvalue::NullaryOp(NullOp::UbChecks, _) => Some((tcx.sess.ub_checks() as u128, targets)),
             Rvalue::Use(Operand::Constant(constant)) => {
-                let bits = eval_mono_const(constant);
+                let bits = eval_mono_const(constant)?;
                 Some((bits, targets))
             }
             _ => None,
diff --git a/compiler/rustc_middle/src/traits/solve/inspect.rs b/compiler/rustc_middle/src/traits/solve/inspect.rs
index 52cdbae1e56..1b2e2781bfe 100644
--- a/compiler/rustc_middle/src/traits/solve/inspect.rs
+++ b/compiler/rustc_middle/src/traits/solve/inspect.rs
@@ -102,6 +102,7 @@ pub struct Probe<'tcx> {
     /// What happened inside of this probe in chronological order.
     pub steps: Vec<ProbeStep<'tcx>>,
     pub kind: ProbeKind<'tcx>,
+    pub final_state: CanonicalState<'tcx, ()>,
 }
 
 impl Debug for Probe<'_> {
@@ -121,6 +122,12 @@ pub enum ProbeStep<'tcx> {
     /// used whenever there are multiple candidates to prove the
     /// current goalby .
     NestedProbe(Probe<'tcx>),
+    /// A call to `EvalCtxt::evaluate_added_goals_make_canonical_response` with
+    /// `Certainty` was made. This is the certainty passed in, so it's not unified
+    /// with the certainty of the `try_evaluate_added_goals` that is done within;
+    /// if it's `Certainty::Yes`, then we can trust that the candidate is "finished"
+    /// and we didn't force ambiguity for some reason.
+    MakeCanonicalResponse { shallow_certainty: Certainty },
 }
 
 /// What kind of probe we're in. In case the probe represents a candidate, or
diff --git a/compiler/rustc_middle/src/traits/solve/inspect/format.rs b/compiler/rustc_middle/src/traits/solve/inspect/format.rs
index 98f01fe8772..2e2d1df8d19 100644
--- a/compiler/rustc_middle/src/traits/solve/inspect/format.rs
+++ b/compiler/rustc_middle/src/traits/solve/inspect/format.rs
@@ -132,6 +132,9 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
                     }
                     ProbeStep::EvaluateGoals(eval) => this.format_added_goals_evaluation(eval)?,
                     ProbeStep::NestedProbe(probe) => this.format_probe(probe)?,
+                    ProbeStep::MakeCanonicalResponse { shallow_certainty } => {
+                        writeln!(this.f, "EVALUATE GOALS AND MAKE RESPONSE: {shallow_certainty:?}")?
+                    }
                 }
             }
             Ok(())
diff --git a/compiler/rustc_passes/src/hir_stats.rs b/compiler/rustc_passes/src/hir_stats.rs
index 49408c5618b..d7664d1c1ff 100644
--- a/compiler/rustc_passes/src/hir_stats.rs
+++ b/compiler/rustc_passes/src/hir_stats.rs
@@ -498,7 +498,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
             (self, i, i.kind, Id::None, ast, ForeignItem, ForeignItemKind),
             [Static, Fn, TyAlias, MacCall]
         );
-        ast_visit::walk_foreign_item(self, i)
+        ast_visit::walk_item(self, i)
     }
 
     fn visit_item(&mut self, i: &'v ast::Item) {
diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs
index 1b6387acf71..8068ea1a407 100644
--- a/compiler/rustc_resolve/src/build_reduced_graph.rs
+++ b/compiler/rustc_resolve/src/build_reduced_graph.rs
@@ -1304,11 +1304,7 @@ impl<'a, 'b, 'tcx> Visitor<'b> for BuildReducedGraphVisitor<'a, 'b, 'tcx> {
                 visit::walk_item(self, item);
                 macro_rules_scope
             }
-            ItemKind::MacCall(..) => {
-                let macro_rules_scope = self.visit_invoc_in_module(item.id);
-                visit::walk_item(self, item);
-                macro_rules_scope
-            }
+            ItemKind::MacCall(..) => self.visit_invoc_in_module(item.id),
             _ => {
                 let orig_macro_rules_scope = self.parent_scope.macro_rules;
                 self.build_reduced_graph_for_item(item);
@@ -1339,7 +1335,7 @@ impl<'a, 'b, 'tcx> Visitor<'b> for BuildReducedGraphVisitor<'a, 'b, 'tcx> {
         }
 
         self.build_reduced_graph_for_foreign_item(foreign_item);
-        visit::walk_foreign_item(self, foreign_item);
+        visit::walk_item(self, foreign_item);
     }
 
     fn visit_block(&mut self, block: &'b Block) {
diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs
index bef95aca0d1..d4d999f6f9c 100644
--- a/compiler/rustc_resolve/src/def_collector.rs
+++ b/compiler/rustc_resolve/src/def_collector.rs
@@ -136,14 +136,9 @@ impl<'a, 'b, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'b, 'tcx> {
                 opt_macro_data = Some(macro_data);
                 DefKind::Macro(macro_kind)
             }
-            ItemKind::MacCall(..) => {
-                visit::walk_item(self, i);
-                return self.visit_macro_invoc(i.id);
-            }
             ItemKind::GlobalAsm(..) => DefKind::GlobalAsm,
-            ItemKind::Use(..) => {
-                return visit::walk_item(self, i);
-            }
+            ItemKind::Use(..) => return visit::walk_item(self, i),
+            ItemKind::MacCall(..) => return self.visit_macro_invoc(i.id),
         };
         let def_id = self.create_def(i.id, i.ident.name, def_kind, i.span);
 
@@ -224,7 +219,7 @@ impl<'a, 'b, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'b, 'tcx> {
 
         let def = self.create_def(fi.id, fi.ident.name, def_kind, fi.span);
 
-        self.with_parent(def, |this| visit::walk_foreign_item(this, fi));
+        self.with_parent(def, |this| visit::walk_item(this, fi));
     }
 
     fn visit_variant(&mut self, v: &'a Variant) {
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index 292af43b602..6c870ddfd22 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -886,7 +886,7 @@ impl<'a: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast,
                         kind: LifetimeBinderKind::Item,
                         span: generics.span,
                     },
-                    |this| visit::walk_foreign_item(this, foreign_item),
+                    |this| visit::walk_item(this, foreign_item),
                 );
             }
             ForeignItemKind::Fn(box Fn { ref generics, .. }) => {
@@ -898,13 +898,11 @@ impl<'a: 'ast, 'ast, 'tcx> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast,
                         kind: LifetimeBinderKind::Function,
                         span: generics.span,
                     },
-                    |this| visit::walk_foreign_item(this, foreign_item),
+                    |this| visit::walk_item(this, foreign_item),
                 );
             }
             ForeignItemKind::Static(..) => {
-                self.with_static_rib(def_kind, |this| {
-                    visit::walk_foreign_item(this, foreign_item);
-                });
+                self.with_static_rib(def_kind, |this| visit::walk_item(this, foreign_item))
             }
             ForeignItemKind::MacCall(..) => {
                 panic!("unexpanded macro in resolve!")
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index e07c3247d07..af0b4792136 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -38,7 +38,7 @@ use rustc_data_structures::intern::Interned;
 use rustc_data_structures::steal::Steal;
 use rustc_data_structures::sync::{FreezeReadGuard, Lrc};
 use rustc_errors::{Applicability, Diag, ErrCode};
-use rustc_expand::base::{DeriveResolutions, SyntaxExtension, SyntaxExtensionKind};
+use rustc_expand::base::{DeriveResolution, SyntaxExtension, SyntaxExtensionKind};
 use rustc_feature::BUILTIN_ATTRIBUTES;
 use rustc_hir::def::Namespace::{self, *};
 use rustc_hir::def::NonMacroAttrKind;
@@ -959,7 +959,7 @@ enum BuiltinMacroState {
 }
 
 struct DeriveData {
-    resolutions: DeriveResolutions,
+    resolutions: Vec<DeriveResolution>,
     helper_attrs: Vec<(usize, Ident)>,
     has_derive_copy: bool,
 }
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index 2a23ed71753..82e41b6c314 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -15,7 +15,7 @@ use rustc_attr::StabilityLevel;
 use rustc_data_structures::intern::Interned;
 use rustc_data_structures::sync::Lrc;
 use rustc_errors::{Applicability, StashKey};
-use rustc_expand::base::{Annotatable, DeriveResolutions, Indeterminate, ResolverExpand};
+use rustc_expand::base::{Annotatable, DeriveResolution, Indeterminate, ResolverExpand};
 use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind};
 use rustc_expand::compile_declarative_macro;
 use rustc_expand::expand::{AstFragment, Invocation, InvocationKind, SupportsMacroExpansion};
@@ -344,7 +344,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
         &mut self,
         expn_id: LocalExpnId,
         force: bool,
-        derive_paths: &dyn Fn() -> DeriveResolutions,
+        derive_paths: &dyn Fn() -> Vec<DeriveResolution>,
     ) -> Result<(), Indeterminate> {
         // Block expansion of the container until we resolve all derives in it.
         // This is required for two reasons:
@@ -360,11 +360,11 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
             has_derive_copy: false,
         });
         let parent_scope = self.invocation_parent_scopes[&expn_id];
-        for (i, (path, _, opt_ext, _)) in entry.resolutions.iter_mut().enumerate() {
-            if opt_ext.is_none() {
-                *opt_ext = Some(
+        for (i, resolution) in entry.resolutions.iter_mut().enumerate() {
+            if resolution.exts.is_none() {
+                resolution.exts = Some(
                     match self.resolve_macro_path(
-                        path,
+                        &resolution.path,
                         Some(MacroKind::Derive),
                         &parent_scope,
                         true,
@@ -372,7 +372,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
                     ) {
                         Ok((Some(ext), _)) => {
                             if !ext.helper_attrs.is_empty() {
-                                let last_seg = path.segments.last().unwrap();
+                                let last_seg = resolution.path.segments.last().unwrap();
                                 let span = last_seg.ident.span.normalize_to_macros_2_0();
                                 entry.helper_attrs.extend(
                                     ext.helper_attrs
@@ -416,7 +416,7 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> {
         Ok(())
     }
 
-    fn take_derive_resolutions(&mut self, expn_id: LocalExpnId) -> Option<DeriveResolutions> {
+    fn take_derive_resolutions(&mut self, expn_id: LocalExpnId) -> Option<Vec<DeriveResolution>> {
         self.derive_data.remove(&expn_id).map(|data| data.resolutions)
     }
 
diff --git a/compiler/rustc_trait_selection/src/solve/assembly/mod.rs b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
index 68b0db21141..95221529093 100644
--- a/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
@@ -1,7 +1,7 @@
 //! Code shared by trait and projection goals for candidate assembly.
 
-use super::{EvalCtxt, SolverMode};
 use crate::solve::GoalSource;
+use crate::solve::{inspect, EvalCtxt, SolverMode};
 use crate::traits::coherence;
 use rustc_hir::def_id::DefId;
 use rustc_infer::traits::query::NoSolution;
@@ -16,6 +16,7 @@ use rustc_middle::ty::{fast_reject, TypeFoldable};
 use rustc_middle::ty::{ToPredicate, TypeVisitableExt};
 use rustc_span::{ErrorGuaranteed, DUMMY_SP};
 use std::fmt::Debug;
+use std::mem;
 
 pub(super) mod structural_traits;
 
@@ -315,20 +316,17 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
     }
 
     fn forced_ambiguity(&mut self, cause: MaybeCause) -> Vec<Candidate<'tcx>> {
-        let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc);
-        let certainty = Certainty::Maybe(cause);
         // This may fail if `try_evaluate_added_goals` overflows because it
         // fails to reach a fixpoint but ends up getting an error after
         // running for some additional step.
         //
-        // FIXME: Add a test for this. It seems to be necessary for typenum but
-        // is incredibly hard to minimize as it may rely on being inside of a
-        // trait solver cycle.
-        let result = self.evaluate_added_goals_and_make_canonical_response(certainty);
-        let mut dummy_probe = self.inspect.new_probe();
-        dummy_probe.probe_kind(ProbeKind::TraitCandidate { source, result });
-        self.inspect.finish_probe(dummy_probe);
-        if let Ok(result) = result { vec![Candidate { source, result }] } else { vec![] }
+        // cc trait-system-refactor-initiative#105
+        let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc);
+        let certainty = Certainty::Maybe(cause);
+        let result = self
+            .probe_trait_candidate(source)
+            .enter(|this| this.evaluate_added_goals_and_make_canonical_response(certainty));
+        if let Ok(cand) = result { vec![cand] } else { vec![] }
     }
 
     #[instrument(level = "debug", skip_all)]
@@ -813,6 +811,11 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         goal: Goal<'tcx, G>,
         candidates: &mut Vec<Candidate<'tcx>>,
     ) {
+        // HACK: We temporarily remove the `ProofTreeBuilder` to
+        // avoid adding `Trait` candidates to the candidates used
+        // to prove the current goal.
+        let inspect = mem::replace(&mut self.inspect, inspect::ProofTreeBuilder::new_noop());
+
         let tcx = self.tcx();
         let trait_goal: Goal<'tcx, ty::TraitPredicate<'tcx>> =
             goal.with(tcx, goal.predicate.trait_ref(tcx));
@@ -846,6 +849,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
                 }
             }
         }
+        self.inspect = inspect;
     }
 
     /// If there are multiple ways to prove a trait or projection goal, we have
diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs
index 0b37163d597..6722abd709c 100644
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/canonical.rs
@@ -19,9 +19,12 @@ use rustc_infer::infer::canonical::query_response::make_query_region_constraints
 use rustc_infer::infer::canonical::CanonicalVarValues;
 use rustc_infer::infer::canonical::{CanonicalExt, QueryRegionConstraints};
 use rustc_infer::infer::resolve::EagerResolver;
+use rustc_infer::infer::type_variable::TypeVariableOrigin;
+use rustc_infer::infer::RegionVariableOrigin;
 use rustc_infer::infer::{InferCtxt, InferOk};
 use rustc_infer::traits::solve::NestedNormalizationGoals;
 use rustc_middle::infer::canonical::Canonical;
+use rustc_middle::infer::unify_key::ConstVariableOrigin;
 use rustc_middle::traits::query::NoSolution;
 use rustc_middle::traits::solve::{
     ExternalConstraintsData, MaybeCause, PredefinedOpaquesData, QueryInput,
@@ -29,7 +32,7 @@ use rustc_middle::traits::solve::{
 use rustc_middle::traits::ObligationCause;
 use rustc_middle::ty::{self, BoundVar, GenericArgKind, Ty, TyCtxt, TypeFoldable};
 use rustc_next_trait_solver::canonicalizer::{CanonicalizeMode, Canonicalizer};
-use rustc_span::DUMMY_SP;
+use rustc_span::{Span, DUMMY_SP};
 use std::assert_matches::assert_matches;
 use std::iter;
 use std::ops::Deref;
@@ -95,6 +98,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
             previous call to `try_evaluate_added_goals!`"
         );
 
+        self.inspect.make_canonical_response(certainty);
+
         // When normalizing, we've replaced the expected term with an unconstrained
         // inference variable. This means that we dropped information which could
         // have been important. We handle this by instead returning the nested goals
@@ -374,36 +379,70 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
     }
 }
 
-impl<'tcx> inspect::ProofTreeBuilder<'tcx> {
-    pub fn make_canonical_state<T: TypeFoldable<TyCtxt<'tcx>>>(
-        ecx: &EvalCtxt<'_, 'tcx>,
-        data: T,
-    ) -> inspect::CanonicalState<'tcx, T> {
-        let state = inspect::State { var_values: ecx.var_values, data };
-        let state = state.fold_with(&mut EagerResolver::new(ecx.infcx));
-        Canonicalizer::canonicalize(
-            ecx.infcx,
-            CanonicalizeMode::Response { max_input_universe: ecx.max_input_universe },
-            &mut vec![],
-            state,
-        )
+/// Used by proof trees to be able to recompute intermediate actions while
+/// evaluating a goal. The `var_values` not only include the bound variables
+/// of the query input, but also contain all unconstrained inference vars
+/// created while evaluating this goal.
+pub(in crate::solve) fn make_canonical_state<'tcx, T: TypeFoldable<TyCtxt<'tcx>>>(
+    infcx: &InferCtxt<'tcx>,
+    var_values: &[ty::GenericArg<'tcx>],
+    max_input_universe: ty::UniverseIndex,
+    data: T,
+) -> inspect::CanonicalState<'tcx, T> {
+    let var_values = CanonicalVarValues { var_values: infcx.tcx.mk_args(var_values) };
+    let state = inspect::State { var_values, data };
+    let state = state.fold_with(&mut EagerResolver::new(infcx));
+    Canonicalizer::canonicalize(
+        infcx,
+        CanonicalizeMode::Response { max_input_universe },
+        &mut vec![],
+        state,
+    )
+}
+
+/// Instantiate a `CanonicalState`.
+///
+/// Unlike for query responses, `CanonicalState` also track fresh inference
+/// variables created while evaluating a goal. When creating two separate
+/// `CanonicalState` during a single evaluation both may reference this
+/// fresh inference variable. When instantiating them we now create separate
+/// inference variables for it and have to unify them somehow. We do this
+/// by extending the `var_values` while building the proof tree.
+///
+/// This currently assumes that unifying the var values trivially succeeds.
+/// Adding any inference constraints which weren't present when originally
+/// computing the canonical query can result in bugs.
+pub(in crate::solve) fn instantiate_canonical_state<'tcx, T: TypeFoldable<TyCtxt<'tcx>>>(
+    infcx: &InferCtxt<'tcx>,
+    span: Span,
+    param_env: ty::ParamEnv<'tcx>,
+    orig_values: &mut Vec<ty::GenericArg<'tcx>>,
+    state: inspect::CanonicalState<'tcx, T>,
+) -> T {
+    // In case any fresh inference variables have been created between `state`
+    // and the previous instantiation, extend `orig_values` for it.
+    assert!(orig_values.len() <= state.value.var_values.len());
+    for i in orig_values.len()..state.value.var_values.len() {
+        let unconstrained = match state.value.var_values.var_values[i].unpack() {
+            ty::GenericArgKind::Lifetime(_) => {
+                infcx.next_region_var(RegionVariableOrigin::MiscVariable(span)).into()
+            }
+            ty::GenericArgKind::Type(_) => {
+                infcx.next_ty_var(TypeVariableOrigin { param_def_id: None, span }).into()
+            }
+            ty::GenericArgKind::Const(ct) => infcx
+                .next_const_var(ct.ty(), ConstVariableOrigin { param_def_id: None, span })
+                .into(),
+        };
+
+        orig_values.push(unconstrained);
     }
 
-    /// Instantiate a `CanonicalState`. This assumes that unifying the var values
-    /// trivially succeeds. Adding any inference constraints which weren't present when
-    /// originally computing the canonical query can result in bugs.
-    pub fn instantiate_canonical_state<T: TypeFoldable<TyCtxt<'tcx>>>(
-        infcx: &InferCtxt<'tcx>,
-        param_env: ty::ParamEnv<'tcx>,
-        original_values: &[ty::GenericArg<'tcx>],
-        state: inspect::CanonicalState<'tcx, T>,
-    ) -> T {
-        let instantiation =
-            EvalCtxt::compute_query_response_instantiation_values(infcx, original_values, &state);
+    let instantiation =
+        EvalCtxt::compute_query_response_instantiation_values(infcx, orig_values, &state);
 
-        let inspect::State { var_values, data } = state.instantiate(infcx.tcx, &instantiation);
+    let inspect::State { var_values, data } = state.instantiate(infcx.tcx, &instantiation);
 
-        EvalCtxt::unify_query_var_values(infcx, param_env, original_values, var_values);
-        data
-    }
+    EvalCtxt::unify_query_var_values(infcx, param_env, orig_values, var_values);
+    data
 }
diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs
index f914e6b3f2e..21efce74879 100644
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs
@@ -34,7 +34,7 @@ use super::{search_graph::SearchGraph, Goal};
 use super::{GoalSource, SolverMode};
 pub use select::InferCtxtSelectExt;
 
-mod canonical;
+pub(super) mod canonical;
 mod probe;
 mod select;
 
@@ -84,7 +84,7 @@ pub struct EvalCtxt<'a, 'tcx> {
 
     pub(super) search_graph: &'a mut SearchGraph<'tcx>,
 
-    pub(super) nested_goals: NestedGoals<'tcx>,
+    nested_goals: NestedGoals<'tcx>,
 
     // Has this `EvalCtxt` errored out with `NoSolution` in `try_evaluate_added_goals`?
     //
@@ -161,7 +161,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
     /// Creates a root evaluation context and search graph. This should only be
     /// used from outside of any evaluation, and other methods should be preferred
     /// over using this manually (such as [`InferCtxtEvalExt::evaluate_root_goal`]).
-    fn enter_root<R>(
+    pub(super) fn enter_root<R>(
         infcx: &InferCtxt<'tcx>,
         generate_proof_tree: GenerateProofTree,
         f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> R,
@@ -242,7 +242,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
             search_graph,
             nested_goals: NestedGoals::new(),
             tainted: Ok(()),
-            inspect: canonical_goal_evaluation.new_goal_evaluation_step(input),
+            inspect: canonical_goal_evaluation.new_goal_evaluation_step(var_values, input),
         };
 
         for &(key, ty) in &input.predefined_opaques_in_body.opaque_types {
@@ -255,7 +255,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
         }
 
         let result = f(&mut ecx, input.goal);
-
+        ecx.inspect.probe_final_state(ecx.infcx, ecx.max_input_universe);
         canonical_goal_evaluation.goal_evaluation_step(ecx.inspect);
 
         // When creating a query response we clone the opaque type constraints
@@ -338,7 +338,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
     /// storage.
     // FIXME(-Znext-solver=coinduction): `_source` is currently unused but will
     // be necessary once we implement the new coinduction approach.
-    fn evaluate_goal_raw(
+    pub(super) fn evaluate_goal_raw(
         &mut self,
         goal_evaluation_kind: GoalEvaluationKind,
         _source: GoalSource,
@@ -458,13 +458,23 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
         }
     }
 
+    #[instrument(level = "debug", skip(self))]
+    pub(super) fn add_normalizes_to_goal(&mut self, goal: Goal<'tcx, ty::NormalizesTo<'tcx>>) {
+        self.inspect.add_normalizes_to_goal(self.infcx, self.max_input_universe, goal);
+        self.nested_goals.normalizes_to_goals.push(goal);
+    }
+
+    #[instrument(level = "debug", skip(self))]
+    pub(super) fn add_goal(&mut self, source: GoalSource, goal: Goal<'tcx, ty::Predicate<'tcx>>) {
+        self.inspect.add_goal(self.infcx, self.max_input_universe, source, goal);
+        self.nested_goals.goals.push((source, goal));
+    }
+
     // Recursively evaluates all the goals added to this `EvalCtxt` to completion, returning
     // the certainty of all the goals.
     #[instrument(level = "debug", skip(self))]
     pub(super) fn try_evaluate_added_goals(&mut self) -> Result<Certainty, NoSolution> {
-        let inspect = self.inspect.new_evaluate_added_goals();
-        let inspect = core::mem::replace(&mut self.inspect, inspect);
-
+        self.inspect.start_evaluate_added_goals();
         let mut response = Ok(Certainty::overflow(false));
         for _ in 0..FIXPOINT_STEP_LIMIT {
             // FIXME: This match is a bit ugly, it might be nice to change the inspect
@@ -482,15 +492,12 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
             }
         }
 
-        self.inspect.eval_added_goals_result(response);
+        self.inspect.evaluate_added_goals_result(response);
 
         if response.is_err() {
             self.tainted = Err(NoSolution);
         }
 
-        let goal_evaluations = std::mem::replace(&mut self.inspect, inspect);
-        self.inspect.added_goals_evaluation(goal_evaluations);
-
         response
     }
 
@@ -499,10 +506,9 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
     /// Goals for the next step get directly added to the nested goals of the `EvalCtxt`.
     fn evaluate_added_goals_step(&mut self) -> Result<Option<Certainty>, NoSolution> {
         let tcx = self.tcx();
+        self.inspect.start_evaluate_added_goals_step();
         let mut goals = core::mem::take(&mut self.nested_goals);
 
-        self.inspect.evaluate_added_goals_loop_start();
-
         // If this loop did not result in any progress, what's our final certainty.
         let mut unchanged_certainty = Some(Certainty::Yes);
         for goal in goals.normalizes_to_goals {
@@ -586,17 +592,23 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
         self.infcx.tcx
     }
 
-    pub(super) fn next_ty_infer(&self) -> Ty<'tcx> {
-        self.infcx.next_ty_var(TypeVariableOrigin { param_def_id: None, span: DUMMY_SP })
+    pub(super) fn next_ty_infer(&mut self) -> Ty<'tcx> {
+        let ty = self.infcx.next_ty_var(TypeVariableOrigin { param_def_id: None, span: DUMMY_SP });
+        self.inspect.add_var_value(ty);
+        ty
     }
 
-    pub(super) fn next_const_infer(&self, ty: Ty<'tcx>) -> ty::Const<'tcx> {
-        self.infcx.next_const_var(ty, ConstVariableOrigin { param_def_id: None, span: DUMMY_SP })
+    pub(super) fn next_const_infer(&mut self, ty: Ty<'tcx>) -> ty::Const<'tcx> {
+        let ct = self
+            .infcx
+            .next_const_var(ty, ConstVariableOrigin { param_def_id: None, span: DUMMY_SP });
+        self.inspect.add_var_value(ct);
+        ct
     }
 
     /// Returns a ty infer or a const infer depending on whether `kind` is a `Ty` or `Const`.
     /// If `kind` is an integer inference variable this will still return a ty infer var.
-    pub(super) fn next_term_infer_of_kind(&self, kind: ty::Term<'tcx>) -> ty::Term<'tcx> {
+    pub(super) fn next_term_infer_of_kind(&mut self, kind: ty::Term<'tcx>) -> ty::Term<'tcx> {
         match kind.unpack() {
             ty::TermKind::Ty(_) => self.next_ty_infer().into(),
             ty::TermKind::Const(ct) => self.next_const_infer(ct.ty()).into(),
diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs
index 5b1124e8b9f..9d59723e441 100644
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/probe.rs
@@ -20,23 +20,29 @@ where
     pub(in crate::solve) fn enter(self, f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> T) -> T {
         let ProbeCtxt { ecx: outer_ecx, probe_kind, _result } = self;
 
+        let infcx = outer_ecx.infcx;
+        let max_input_universe = outer_ecx.max_input_universe;
         let mut nested_ecx = EvalCtxt {
-            infcx: outer_ecx.infcx,
+            infcx,
             variables: outer_ecx.variables,
             var_values: outer_ecx.var_values,
             is_normalizes_to_goal: outer_ecx.is_normalizes_to_goal,
             predefined_opaques_in_body: outer_ecx.predefined_opaques_in_body,
-            max_input_universe: outer_ecx.max_input_universe,
+            max_input_universe,
             search_graph: outer_ecx.search_graph,
             nested_goals: outer_ecx.nested_goals.clone(),
             tainted: outer_ecx.tainted,
-            inspect: outer_ecx.inspect.new_probe(),
+            inspect: outer_ecx.inspect.take_and_enter_probe(),
         };
-        let r = nested_ecx.infcx.probe(|_| f(&mut nested_ecx));
-        if !outer_ecx.inspect.is_noop() {
+        let r = nested_ecx.infcx.probe(|_| {
+            let r = f(&mut nested_ecx);
+            nested_ecx.inspect.probe_final_state(infcx, max_input_universe);
+            r
+        });
+        if !nested_ecx.inspect.is_noop() {
             let probe_kind = probe_kind(&r);
             nested_ecx.inspect.probe_kind(probe_kind);
-            outer_ecx.inspect.finish_probe(nested_ecx.inspect);
+            outer_ecx.inspect = nested_ecx.inspect.finish_probe();
         }
         r
     }
diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
index 56c32d3d539..e918f20577c 100644
--- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
+++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
@@ -11,15 +11,26 @@
 
 use rustc_ast_ir::try_visit;
 use rustc_ast_ir::visit::VisitorResult;
-use rustc_infer::infer::InferCtxt;
+use rustc_infer::infer::resolve::EagerResolver;
+use rustc_infer::infer::type_variable::TypeVariableOrigin;
+use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk};
+use rustc_middle::infer::unify_key::ConstVariableOrigin;
 use rustc_middle::traits::query::NoSolution;
 use rustc_middle::traits::solve::{inspect, QueryResult};
 use rustc_middle::traits::solve::{Certainty, Goal};
+use rustc_middle::traits::ObligationCause;
 use rustc_middle::ty;
+use rustc_middle::ty::TypeFoldable;
+use rustc_span::Span;
 
-use crate::solve::inspect::ProofTreeBuilder;
+use crate::solve::eval_ctxt::canonical;
+use crate::solve::{EvalCtxt, GoalEvaluationKind, GoalSource};
 use crate::solve::{GenerateProofTree, InferCtxtEvalExt};
 
+pub struct InspectConfig {
+    pub max_depth: usize,
+}
+
 pub struct InspectGoal<'a, 'tcx> {
     infcx: &'a InferCtxt<'tcx>,
     depth: usize,
@@ -32,14 +43,12 @@ pub struct InspectCandidate<'a, 'tcx> {
     goal: &'a InspectGoal<'a, 'tcx>,
     kind: inspect::ProbeKind<'tcx>,
     nested_goals: Vec<inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>>,
+    final_state: inspect::CanonicalState<'tcx, ()>,
     result: QueryResult<'tcx>,
+    candidate_certainty: Option<Certainty>,
 }
 
 impl<'a, 'tcx> InspectCandidate<'a, 'tcx> {
-    pub fn infcx(&self) -> &'a InferCtxt<'tcx> {
-        self.goal.infcx
-    }
-
     pub fn kind(&self) -> inspect::ProbeKind<'tcx> {
         self.kind
     }
@@ -48,55 +57,101 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> {
         self.result.map(|c| c.value.certainty)
     }
 
-    /// Visit the nested goals of this candidate.
+    /// Certainty passed into `evaluate_added_goals_and_make_canonical_response`.
+    ///
+    /// If this certainty is `Some(Yes)`, then we must be confident that the candidate
+    /// must hold iff it's nested goals hold. This is not true if the certainty is
+    /// `Some(Maybe)`, which suggests we forced ambiguity instead, or if it is `None`,
+    /// which suggests we may have not assembled any candidates at all.
     ///
-    /// FIXME(@lcnr): we have to slightly adapt this API
-    /// to also use it to compute the most relevant goal
-    /// for fulfillment errors. Will do that once we actually
-    /// need it.
-    pub fn visit_nested<V: ProofTreeVisitor<'tcx>>(&self, visitor: &mut V) -> V::Result {
-        // HACK: An arbitrary cutoff to avoid dealing with overflow and cycles.
-        if self.goal.depth <= 10 {
+    /// This is *not* the certainty of the candidate's nested evaluation, which can be
+    /// accessed with [`Self::result`] instead.
+    pub fn candidate_certainty(&self) -> Option<Certainty> {
+        self.candidate_certainty
+    }
+
+    /// Visit all nested goals of this candidate without rolling
+    /// back their inference constraints. This function modifies
+    /// the state of the `infcx`.
+    pub fn visit_nested_no_probe<V: ProofTreeVisitor<'tcx>>(&self, visitor: &mut V) -> V::Result {
+        if self.goal.depth < visitor.config().max_depth {
             let infcx = self.goal.infcx;
-            infcx.probe(|_| {
-                let mut instantiated_goals = vec![];
-                for goal in &self.nested_goals {
-                    let goal = ProofTreeBuilder::instantiate_canonical_state(
-                        infcx,
-                        self.goal.goal.param_env,
-                        self.goal.orig_values,
-                        *goal,
-                    );
-                    instantiated_goals.push(goal);
-                }
+            let param_env = self.goal.goal.param_env;
+            let mut orig_values = self.goal.orig_values.to_vec();
+            let mut instantiated_goals = vec![];
+            for goal in &self.nested_goals {
+                let goal = canonical::instantiate_canonical_state(
+                    infcx,
+                    visitor.span(),
+                    param_env,
+                    &mut orig_values,
+                    *goal,
+                );
+                instantiated_goals.push(goal);
+            }
 
-                for goal in instantiated_goals.iter().copied() {
-                    // We need to be careful with `NormalizesTo` goals as the
-                    // expected term has to be replaced with an unconstrained
-                    // inference variable.
-                    if let Some(kind) = goal.predicate.kind().no_bound_vars()
-                        && let ty::PredicateKind::NormalizesTo(predicate) = kind
-                        && !predicate.alias.is_opaque(infcx.tcx)
-                    {
-                        // FIXME: We currently skip these goals as
-                        // `fn evaluate_root_goal` ICEs if there are any
-                        // `NestedNormalizationGoals`.
-                        continue;
-                    };
-                    let (_, proof_tree) = infcx.evaluate_root_goal(goal, GenerateProofTree::Yes);
-                    let proof_tree = proof_tree.unwrap();
-                    try_visit!(visitor.visit_goal(&InspectGoal::new(
-                        infcx,
-                        self.goal.depth + 1,
-                        &proof_tree,
-                    )));
-                }
+            let () = canonical::instantiate_canonical_state(
+                infcx,
+                visitor.span(),
+                param_env,
+                &mut orig_values,
+                self.final_state,
+            );
 
-                V::Result::output()
-            })
-        } else {
-            V::Result::output()
+            for &goal in &instantiated_goals {
+                let proof_tree = match goal.predicate.kind().no_bound_vars() {
+                    Some(ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term })) => {
+                        let unconstrained_term = match term.unpack() {
+                            ty::TermKind::Ty(_) => infcx
+                                .next_ty_var(TypeVariableOrigin {
+                                    param_def_id: None,
+                                    span: visitor.span(),
+                                })
+                                .into(),
+                            ty::TermKind::Const(ct) => infcx
+                                .next_const_var(
+                                    ct.ty(),
+                                    ConstVariableOrigin {
+                                        param_def_id: None,
+                                        span: visitor.span(),
+                                    },
+                                )
+                                .into(),
+                        };
+                        let goal = goal
+                            .with(infcx.tcx, ty::NormalizesTo { alias, term: unconstrained_term });
+                        let proof_tree =
+                            EvalCtxt::enter_root(infcx, GenerateProofTree::Yes, |ecx| {
+                                ecx.evaluate_goal_raw(
+                                    GoalEvaluationKind::Root,
+                                    GoalSource::Misc,
+                                    goal,
+                                )
+                            })
+                            .1;
+                        let InferOk { value: (), obligations: _ } = infcx
+                            .at(&ObligationCause::dummy(), param_env)
+                            .eq(DefineOpaqueTypes::Yes, term, unconstrained_term)
+                            .unwrap();
+                        proof_tree
+                    }
+                    _ => infcx.evaluate_root_goal(goal, GenerateProofTree::Yes).1,
+                };
+                try_visit!(visitor.visit_goal(&InspectGoal::new(
+                    infcx,
+                    self.goal.depth + 1,
+                    &proof_tree.unwrap(),
+                )));
+            }
         }
+
+        V::Result::output()
+    }
+
+    /// Visit all nested goals of this candidate, rolling back
+    /// all inference constraints.
+    pub fn visit_nested_in_probe<V: ProofTreeVisitor<'tcx>>(&self, visitor: &mut V) -> V::Result {
+        self.goal.infcx.probe(|_| self.visit_nested_no_probe(visitor))
     }
 }
 
@@ -119,6 +174,9 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
         nested_goals: &mut Vec<inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>>,
         probe: &inspect::Probe<'tcx>,
     ) {
+        let mut candidate_certainty = None;
+        let num_candidates = candidates.len();
+
         for step in &probe.steps {
             match step {
                 &inspect::ProbeStep::AddGoal(_source, goal) => nested_goals.push(goal),
@@ -130,6 +188,9 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
                     self.candidates_recur(candidates, nested_goals, probe);
                     nested_goals.truncate(num_goals);
                 }
+                inspect::ProbeStep::MakeCanonicalResponse { shallow_certainty } => {
+                    assert_eq!(candidate_certainty.replace(*shallow_certainty), None);
+                }
                 inspect::ProbeStep::EvaluateGoals(_) => (),
             }
         }
@@ -144,24 +205,28 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
             // FIXME: This is currently wrong if we don't even try any
             // candidates, e.g. for a trait goal, as in this case `candidates` is
             // actually supposed to be empty.
-            inspect::ProbeKind::Root { result } => {
-                if candidates.is_empty() {
+            inspect::ProbeKind::Root { result }
+            | inspect::ProbeKind::TryNormalizeNonRigid { result } => {
+                if candidates.len() == num_candidates {
                     candidates.push(InspectCandidate {
                         goal: self,
                         kind: probe.kind,
                         nested_goals: nested_goals.clone(),
+                        final_state: probe.final_state,
                         result,
-                    });
+                        candidate_certainty,
+                    })
                 }
             }
-            inspect::ProbeKind::TryNormalizeNonRigid { result }
-            | inspect::ProbeKind::MiscCandidate { name: _, result }
+            inspect::ProbeKind::MiscCandidate { name: _, result }
             | inspect::ProbeKind::TraitCandidate { source: _, result } => {
                 candidates.push(InspectCandidate {
                     goal: self,
                     kind: probe.kind,
                     nested_goals: nested_goals.clone(),
+                    final_state: probe.final_state,
                     result,
+                    candidate_certainty,
                 });
             }
         }
@@ -191,6 +256,17 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
         candidates
     }
 
+    /// Returns the single candidate applicable for the current goal, if it exists.
+    ///
+    /// Returns `None` if there are either no or multiple applicable candidates.
+    pub fn unique_applicable_candidate(&'a self) -> Option<InspectCandidate<'a, 'tcx>> {
+        // FIXME(-Znext-solver): This does not handle impl candidates
+        // hidden by env candidates.
+        let mut candidates = self.candidates();
+        candidates.retain(|c| c.result().is_ok());
+        candidates.pop().filter(|_| candidates.is_empty())
+    }
+
     fn new(
         infcx: &'a InferCtxt<'tcx>,
         depth: usize,
@@ -201,7 +277,7 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
                 infcx,
                 depth,
                 orig_values,
-                goal: infcx.resolve_vars_if_possible(root.uncanonicalized_goal),
+                goal: root.uncanonicalized_goal.fold_with(&mut EagerResolver::new(infcx)),
                 evaluation: root,
             },
             inspect::GoalEvaluationKind::Nested { .. } => unreachable!(),
@@ -213,6 +289,12 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
 pub trait ProofTreeVisitor<'tcx> {
     type Result: VisitorResult = ();
 
+    fn span(&self) -> Span;
+
+    fn config(&self) -> InspectConfig {
+        InspectConfig { max_depth: 10 }
+    }
+
     fn visit_goal(&mut self, goal: &InspectGoal<'_, 'tcx>) -> Self::Result;
 }
 
@@ -223,10 +305,8 @@ impl<'tcx> InferCtxt<'tcx> {
         goal: Goal<'tcx, ty::Predicate<'tcx>>,
         visitor: &mut V,
     ) -> V::Result {
-        self.probe(|_| {
-            let (_, proof_tree) = self.evaluate_root_goal(goal, GenerateProofTree::Yes);
-            let proof_tree = proof_tree.unwrap();
-            visitor.visit_goal(&InspectGoal::new(self, 0, &proof_tree))
-        })
+        let (_, proof_tree) = self.evaluate_root_goal(goal, GenerateProofTree::Yes);
+        let proof_tree = proof_tree.unwrap();
+        visitor.visit_goal(&InspectGoal::new(self, 0, &proof_tree))
     }
 }
diff --git a/compiler/rustc_trait_selection/src/solve/inspect/build.rs b/compiler/rustc_trait_selection/src/solve/inspect/build.rs
index 43c76cc5f4a..466d0d80060 100644
--- a/compiler/rustc_trait_selection/src/solve/inspect/build.rs
+++ b/compiler/rustc_trait_selection/src/solve/inspect/build.rs
@@ -5,6 +5,8 @@
 //! see the comment on [ProofTreeBuilder].
 use std::mem;
 
+use rustc_infer::infer::InferCtxt;
+use rustc_middle::infer::canonical::CanonicalVarValues;
 use rustc_middle::traits::query::NoSolution;
 use rustc_middle::traits::solve::{
     CanonicalInput, Certainty, Goal, GoalSource, QueryInput, QueryResult,
@@ -12,7 +14,8 @@ use rustc_middle::traits::solve::{
 use rustc_middle::ty::{self, TyCtxt};
 use rustc_session::config::DumpSolverProofTree;
 
-use crate::solve::{self, inspect, EvalCtxt, GenerateProofTree};
+use crate::solve::eval_ctxt::canonical;
+use crate::solve::{self, inspect, GenerateProofTree};
 
 /// The core data structure when building proof trees.
 ///
@@ -47,9 +50,7 @@ enum DebugSolver<'tcx> {
     Root,
     GoalEvaluation(WipGoalEvaluation<'tcx>),
     CanonicalGoalEvaluation(WipCanonicalGoalEvaluation<'tcx>),
-    AddedGoalsEvaluation(WipAddedGoalsEvaluation<'tcx>),
     GoalEvaluationStep(WipGoalEvaluationStep<'tcx>),
-    Probe(WipProbe<'tcx>),
 }
 
 impl<'tcx> From<WipGoalEvaluation<'tcx>> for DebugSolver<'tcx> {
@@ -64,24 +65,12 @@ impl<'tcx> From<WipCanonicalGoalEvaluation<'tcx>> for DebugSolver<'tcx> {
     }
 }
 
-impl<'tcx> From<WipAddedGoalsEvaluation<'tcx>> for DebugSolver<'tcx> {
-    fn from(g: WipAddedGoalsEvaluation<'tcx>) -> DebugSolver<'tcx> {
-        DebugSolver::AddedGoalsEvaluation(g)
-    }
-}
-
 impl<'tcx> From<WipGoalEvaluationStep<'tcx>> for DebugSolver<'tcx> {
     fn from(g: WipGoalEvaluationStep<'tcx>) -> DebugSolver<'tcx> {
         DebugSolver::GoalEvaluationStep(g)
     }
 }
 
-impl<'tcx> From<WipProbe<'tcx>> for DebugSolver<'tcx> {
-    fn from(p: WipProbe<'tcx>) -> DebugSolver<'tcx> {
-        DebugSolver::Probe(p)
-    }
-}
-
 #[derive(Eq, PartialEq, Debug)]
 struct WipGoalEvaluation<'tcx> {
     pub uncanonicalized_goal: Goal<'tcx, ty::Predicate<'tcx>>,
@@ -184,12 +173,41 @@ impl<'tcx> WipAddedGoalsEvaluation<'tcx> {
 
 #[derive(Eq, PartialEq, Debug)]
 struct WipGoalEvaluationStep<'tcx> {
+    /// Unlike `EvalCtxt::var_values`, we append a new
+    /// generic arg here whenever we create a new inference
+    /// variable.
+    ///
+    /// This is necessary as we otherwise don't unify these
+    /// vars when instantiating multiple `CanonicalState`.
+    var_values: Vec<ty::GenericArg<'tcx>>,
     instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>,
-
+    probe_depth: usize,
     evaluation: WipProbe<'tcx>,
 }
 
 impl<'tcx> WipGoalEvaluationStep<'tcx> {
+    fn current_evaluation_scope(&mut self) -> &mut WipProbe<'tcx> {
+        let mut current = &mut self.evaluation;
+        for _ in 0..self.probe_depth {
+            match current.steps.last_mut() {
+                Some(WipProbeStep::NestedProbe(p)) => current = p,
+                _ => bug!(),
+            }
+        }
+        current
+    }
+
+    fn added_goals_evaluation(&mut self) -> &mut WipAddedGoalsEvaluation<'tcx> {
+        let mut current = &mut self.evaluation;
+        loop {
+            match current.steps.last_mut() {
+                Some(WipProbeStep::NestedProbe(p)) => current = p,
+                Some(WipProbeStep::EvaluateGoals(evaluation)) => return evaluation,
+                _ => bug!(),
+            }
+        }
+    }
+
     fn finalize(self) -> inspect::GoalEvaluationStep<'tcx> {
         let evaluation = self.evaluation.finalize();
         match evaluation.kind {
@@ -202,8 +220,10 @@ impl<'tcx> WipGoalEvaluationStep<'tcx> {
 
 #[derive(Eq, PartialEq, Debug)]
 struct WipProbe<'tcx> {
-    pub steps: Vec<WipProbeStep<'tcx>>,
-    pub kind: Option<inspect::ProbeKind<'tcx>>,
+    initial_num_var_values: usize,
+    steps: Vec<WipProbeStep<'tcx>>,
+    kind: Option<inspect::ProbeKind<'tcx>>,
+    final_state: Option<inspect::CanonicalState<'tcx, ()>>,
 }
 
 impl<'tcx> WipProbe<'tcx> {
@@ -211,6 +231,7 @@ impl<'tcx> WipProbe<'tcx> {
         inspect::Probe {
             steps: self.steps.into_iter().map(WipProbeStep::finalize).collect(),
             kind: self.kind.unwrap(),
+            final_state: self.final_state.unwrap(),
         }
     }
 }
@@ -220,6 +241,7 @@ enum WipProbeStep<'tcx> {
     AddGoal(GoalSource, inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>),
     EvaluateGoals(WipAddedGoalsEvaluation<'tcx>),
     NestedProbe(WipProbe<'tcx>),
+    MakeCanonicalResponse { shallow_certainty: Certainty },
 }
 
 impl<'tcx> WipProbeStep<'tcx> {
@@ -228,6 +250,9 @@ impl<'tcx> WipProbeStep<'tcx> {
             WipProbeStep::AddGoal(source, goal) => inspect::ProbeStep::AddGoal(source, goal),
             WipProbeStep::EvaluateGoals(eval) => inspect::ProbeStep::EvaluateGoals(eval.finalize()),
             WipProbeStep::NestedProbe(probe) => inspect::ProbeStep::NestedProbe(probe.finalize()),
+            WipProbeStep::MakeCanonicalResponse { shallow_certainty } => {
+                inspect::ProbeStep::MakeCanonicalResponse { shallow_certainty }
+            }
         }
     }
 }
@@ -245,6 +270,12 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
         self.state.as_deref_mut()
     }
 
+    pub fn take_and_enter_probe(&mut self) -> ProofTreeBuilder<'tcx> {
+        let mut nested = ProofTreeBuilder { state: self.state.take() };
+        nested.enter_probe();
+        nested
+    }
+
     pub fn finalize(self) -> Option<inspect::GoalEvaluation<'tcx>> {
         match *self.state? {
             DebugSolver::GoalEvaluation(wip_goal_evaluation) => {
@@ -362,11 +393,14 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
         if let Some(this) = self.as_mut() {
             match (this, *goal_evaluation.state.unwrap()) {
                 (
-                    DebugSolver::AddedGoalsEvaluation(WipAddedGoalsEvaluation {
-                        evaluations, ..
-                    }),
+                    DebugSolver::GoalEvaluationStep(state),
                     DebugSolver::GoalEvaluation(goal_evaluation),
-                ) => evaluations.last_mut().unwrap().push(goal_evaluation),
+                ) => state
+                    .added_goals_evaluation()
+                    .evaluations
+                    .last_mut()
+                    .unwrap()
+                    .push(goal_evaluation),
                 (this @ DebugSolver::Root, goal_evaluation) => *this = goal_evaluation,
                 _ => unreachable!(),
             }
@@ -375,13 +409,22 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
 
     pub fn new_goal_evaluation_step(
         &mut self,
+        var_values: CanonicalVarValues<'tcx>,
         instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>,
     ) -> ProofTreeBuilder<'tcx> {
         self.nested(|| WipGoalEvaluationStep {
+            var_values: var_values.var_values.to_vec(),
             instantiated_goal,
-            evaluation: WipProbe { steps: vec![], kind: None },
+            evaluation: WipProbe {
+                initial_num_var_values: var_values.len(),
+                steps: vec![],
+                kind: None,
+                final_state: None,
+            },
+            probe_depth: 0,
         })
     }
+
     pub fn goal_evaluation_step(&mut self, goal_evaluation_step: ProofTreeBuilder<'tcx>) {
         if let Some(this) = self.as_mut() {
             match (this, *goal_evaluation_step.state.unwrap()) {
@@ -396,112 +439,159 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
         }
     }
 
-    pub fn new_probe(&mut self) -> ProofTreeBuilder<'tcx> {
-        self.nested(|| WipProbe { steps: vec![], kind: None })
+    pub fn add_var_value<T: Into<ty::GenericArg<'tcx>>>(&mut self, arg: T) {
+        match self.as_mut() {
+            None => {}
+            Some(DebugSolver::GoalEvaluationStep(state)) => {
+                state.var_values.push(arg.into());
+            }
+            Some(s) => bug!("tried to add var values to {s:?}"),
+        }
+    }
+
+    pub fn enter_probe(&mut self) {
+        match self.as_mut() {
+            None => {}
+            Some(DebugSolver::GoalEvaluationStep(state)) => {
+                let initial_num_var_values = state.var_values.len();
+                state.current_evaluation_scope().steps.push(WipProbeStep::NestedProbe(WipProbe {
+                    initial_num_var_values,
+                    steps: vec![],
+                    kind: None,
+                    final_state: None,
+                }));
+                state.probe_depth += 1;
+            }
+            Some(s) => bug!("tried to start probe to {s:?}"),
+        }
     }
 
     pub fn probe_kind(&mut self, probe_kind: inspect::ProbeKind<'tcx>) {
-        if let Some(this) = self.as_mut() {
-            match this {
-                DebugSolver::Probe(this) => {
-                    assert_eq!(this.kind.replace(probe_kind), None)
-                }
-                _ => unreachable!(),
+        match self.as_mut() {
+            None => {}
+            Some(DebugSolver::GoalEvaluationStep(state)) => {
+                let prev = state.current_evaluation_scope().kind.replace(probe_kind);
+                assert_eq!(prev, None);
             }
+            _ => bug!(),
         }
     }
 
-    pub fn add_normalizes_to_goal(
-        ecx: &mut EvalCtxt<'_, 'tcx>,
-        goal: Goal<'tcx, ty::NormalizesTo<'tcx>>,
+    pub fn probe_final_state(
+        &mut self,
+        infcx: &InferCtxt<'tcx>,
+        max_input_universe: ty::UniverseIndex,
     ) {
-        if ecx.inspect.is_noop() {
-            return;
+        match self.as_mut() {
+            None => {}
+            Some(DebugSolver::GoalEvaluationStep(state)) => {
+                let final_state = canonical::make_canonical_state(
+                    infcx,
+                    &state.var_values,
+                    max_input_universe,
+                    (),
+                );
+                let prev = state.current_evaluation_scope().final_state.replace(final_state);
+                assert_eq!(prev, None);
+            }
+            _ => bug!(),
         }
+    }
 
-        Self::add_goal(ecx, GoalSource::Misc, goal.with(ecx.tcx(), goal.predicate));
+    pub fn add_normalizes_to_goal(
+        &mut self,
+        infcx: &InferCtxt<'tcx>,
+        max_input_universe: ty::UniverseIndex,
+        goal: Goal<'tcx, ty::NormalizesTo<'tcx>>,
+    ) {
+        self.add_goal(
+            infcx,
+            max_input_universe,
+            GoalSource::Misc,
+            goal.with(infcx.tcx, goal.predicate),
+        );
     }
 
     pub fn add_goal(
-        ecx: &mut EvalCtxt<'_, 'tcx>,
+        &mut self,
+        infcx: &InferCtxt<'tcx>,
+        max_input_universe: ty::UniverseIndex,
         source: GoalSource,
         goal: Goal<'tcx, ty::Predicate<'tcx>>,
     ) {
-        // Can't use `if let Some(this) = ecx.inspect.as_mut()` here because
-        // we have to immutably use the `EvalCtxt` for `make_canonical_state`.
-        if ecx.inspect.is_noop() {
-            return;
+        match self.as_mut() {
+            None => {}
+            Some(DebugSolver::GoalEvaluationStep(state)) => {
+                let goal = canonical::make_canonical_state(
+                    infcx,
+                    &state.var_values,
+                    max_input_universe,
+                    goal,
+                );
+                state.current_evaluation_scope().steps.push(WipProbeStep::AddGoal(source, goal))
+            }
+            _ => bug!(),
         }
+    }
 
-        let goal = Self::make_canonical_state(ecx, goal);
-
-        match ecx.inspect.as_mut().unwrap() {
-            DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
-                evaluation: WipProbe { steps, .. },
-                ..
-            })
-            | DebugSolver::Probe(WipProbe { steps, .. }) => {
-                steps.push(WipProbeStep::AddGoal(source, goal))
+    pub fn make_canonical_response(&mut self, shallow_certainty: Certainty) {
+        match self.as_mut() {
+            Some(DebugSolver::GoalEvaluationStep(state)) => {
+                state
+                    .current_evaluation_scope()
+                    .steps
+                    .push(WipProbeStep::MakeCanonicalResponse { shallow_certainty });
             }
-            s => unreachable!("tried to add {goal:?} to {s:?}"),
+            None => {}
+            _ => {}
         }
     }
 
-    pub fn finish_probe(&mut self, probe: ProofTreeBuilder<'tcx>) {
-        if let Some(this) = self.as_mut() {
-            match (this, *probe.state.unwrap()) {
-                (
-                    DebugSolver::Probe(WipProbe { steps, .. })
-                    | DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
-                        evaluation: WipProbe { steps, .. },
-                        ..
-                    }),
-                    DebugSolver::Probe(probe),
-                ) => steps.push(WipProbeStep::NestedProbe(probe)),
-                _ => unreachable!(),
+    pub fn finish_probe(mut self) -> ProofTreeBuilder<'tcx> {
+        match self.as_mut() {
+            None => {}
+            Some(DebugSolver::GoalEvaluationStep(state)) => {
+                assert_ne!(state.probe_depth, 0);
+                let num_var_values = state.current_evaluation_scope().initial_num_var_values;
+                state.var_values.truncate(num_var_values);
+                state.probe_depth -= 1;
             }
+            _ => bug!(),
         }
-    }
 
-    pub fn new_evaluate_added_goals(&mut self) -> ProofTreeBuilder<'tcx> {
-        self.nested(|| WipAddedGoalsEvaluation { evaluations: vec![], result: None })
+        self
     }
 
-    pub fn evaluate_added_goals_loop_start(&mut self) {
-        if let Some(this) = self.as_mut() {
-            match this {
-                DebugSolver::AddedGoalsEvaluation(this) => {
-                    this.evaluations.push(vec![]);
-                }
-                _ => unreachable!(),
+    pub fn start_evaluate_added_goals(&mut self) {
+        match self.as_mut() {
+            None => {}
+            Some(DebugSolver::GoalEvaluationStep(state)) => {
+                state.current_evaluation_scope().steps.push(WipProbeStep::EvaluateGoals(
+                    WipAddedGoalsEvaluation { evaluations: vec![], result: None },
+                ));
             }
+            _ => bug!(),
         }
     }
 
-    pub fn eval_added_goals_result(&mut self, result: Result<Certainty, NoSolution>) {
-        if let Some(this) = self.as_mut() {
-            match this {
-                DebugSolver::AddedGoalsEvaluation(this) => {
-                    assert_eq!(this.result.replace(result), None);
-                }
-                _ => unreachable!(),
+    pub fn start_evaluate_added_goals_step(&mut self) {
+        match self.as_mut() {
+            None => {}
+            Some(DebugSolver::GoalEvaluationStep(state)) => {
+                state.added_goals_evaluation().evaluations.push(vec![]);
             }
+            _ => bug!(),
         }
     }
 
-    pub fn added_goals_evaluation(&mut self, added_goals_evaluation: ProofTreeBuilder<'tcx>) {
-        if let Some(this) = self.as_mut() {
-            match (this, *added_goals_evaluation.state.unwrap()) {
-                (
-                    DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
-                        evaluation: WipProbe { steps, .. },
-                        ..
-                    })
-                    | DebugSolver::Probe(WipProbe { steps, .. }),
-                    DebugSolver::AddedGoalsEvaluation(added_goals_evaluation),
-                ) => steps.push(WipProbeStep::EvaluateGoals(added_goals_evaluation)),
-                _ => unreachable!(),
+    pub fn evaluate_added_goals_result(&mut self, result: Result<Certainty, NoSolution>) {
+        match self.as_mut() {
+            None => {}
+            Some(DebugSolver::GoalEvaluationStep(state)) => {
+                let prev = state.added_goals_evaluation().result.replace(result);
+                assert_eq!(prev, None);
             }
+            _ => bug!(),
         }
     }
 
diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs
index da5cd15a10d..e58babe3208 100644
--- a/compiler/rustc_trait_selection/src/solve/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/mod.rs
@@ -200,18 +200,6 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
 }
 
 impl<'tcx> EvalCtxt<'_, 'tcx> {
-    #[instrument(level = "debug", skip(self))]
-    fn add_normalizes_to_goal(&mut self, goal: Goal<'tcx, ty::NormalizesTo<'tcx>>) {
-        inspect::ProofTreeBuilder::add_normalizes_to_goal(self, goal);
-        self.nested_goals.normalizes_to_goals.push(goal);
-    }
-
-    #[instrument(level = "debug", skip(self))]
-    fn add_goal(&mut self, source: GoalSource, goal: Goal<'tcx, ty::Predicate<'tcx>>) {
-        inspect::ProofTreeBuilder::add_goal(self, source, goal);
-        self.nested_goals.goals.push((source, goal));
-    }
-
     #[instrument(level = "debug", skip(self, goals))]
     fn add_goals(
         &mut self,
diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs
index 36028b51659..a4208cdc3c6 100644
--- a/compiler/rustc_trait_selection/src/traits/coherence.rs
+++ b/compiler/rustc_trait_selection/src/traits/coherence.rs
@@ -30,7 +30,7 @@ use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
 use rustc_middle::ty::visit::{TypeVisitable, TypeVisitableExt};
 use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor};
 use rustc_span::symbol::sym;
-use rustc_span::DUMMY_SP;
+use rustc_span::{Span, DUMMY_SP};
 use std::fmt::Debug;
 use std::ops::ControlFlow;
 
@@ -1022,10 +1022,14 @@ struct AmbiguityCausesVisitor<'a, 'tcx> {
 }
 
 impl<'a, 'tcx> ProofTreeVisitor<'tcx> for AmbiguityCausesVisitor<'a, 'tcx> {
+    fn span(&self) -> Span {
+        DUMMY_SP
+    }
+
     fn visit_goal(&mut self, goal: &InspectGoal<'_, 'tcx>) {
         let infcx = goal.infcx();
         for cand in goal.candidates() {
-            cand.visit_nested(self);
+            cand.visit_nested_in_probe(self);
         }
         // When searching for intercrate ambiguity causes, we only need to look
         // at ambiguous goals, as for others the coherence unknowable candidate
@@ -1157,5 +1161,5 @@ fn search_ambiguity_causes<'tcx>(
     goal: Goal<'tcx, ty::Predicate<'tcx>>,
     causes: &mut FxIndexSet<IntercrateAmbiguityCause<'tcx>>,
 ) {
-    infcx.visit_proof_tree(goal, &mut AmbiguityCausesVisitor { causes });
+    infcx.probe(|_| infcx.visit_proof_tree(goal, &mut AmbiguityCausesVisitor { causes }));
 }
diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs
index b417513aaa2..2bd8fca7e01 100644
--- a/library/alloc/src/lib.rs
+++ b/library/alloc/src/lib.rs
@@ -161,7 +161,6 @@
 #![feature(tuple_trait)]
 #![feature(unicode_internals)]
 #![feature(unsize)]
-#![feature(utf8_chunks)]
 #![feature(vec_pop_if)]
 // tidy-alphabetical-end
 //
diff --git a/library/alloc/src/rc.rs b/library/alloc/src/rc.rs
index a320a244abd..c245b42c3e8 100644
--- a/library/alloc/src/rc.rs
+++ b/library/alloc/src/rc.rs
@@ -3029,13 +3029,10 @@ impl<T: ?Sized, A: Allocator> Weak<T, A> {
     /// [`as_ptr`]: Weak::as_ptr
     #[inline]
     #[unstable(feature = "allocator_api", issue = "32838")]
-    pub fn into_raw_and_alloc(self) -> (*const T, A)
-    where
-        A: Clone,
-    {
-        let result = self.as_ptr();
-        let alloc = self.alloc.clone();
-        mem::forget(self);
+    pub fn into_raw_and_alloc(self) -> (*const T, A) {
+        let rc = mem::ManuallyDrop::new(self);
+        let result = rc.as_ptr();
+        let alloc = unsafe { ptr::read(&rc.alloc) };
         (result, alloc)
     }
 
diff --git a/library/alloc/src/str.rs b/library/alloc/src/str.rs
index ade114678b7..d88639c4092 100644
--- a/library/alloc/src/str.rs
+++ b/library/alloc/src/str.rs
@@ -53,7 +53,7 @@ pub use core::str::{RSplit, Split};
 pub use core::str::{RSplitN, SplitN};
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use core::str::{RSplitTerminator, SplitTerminator};
-#[unstable(feature = "utf8_chunks", issue = "99543")]
+#[stable(feature = "utf8_chunks", since = "CURRENT_RUSTC_VERSION")]
 pub use core::str::{Utf8Chunk, Utf8Chunks};
 
 /// Note: `str` in `Concat<str>` is not meaningful here.
diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs
index 082af1447ec..7c9f13e30ff 100644
--- a/library/alloc/src/string.rs
+++ b/library/alloc/src/string.rs
@@ -58,8 +58,6 @@ use core::ops::{self, Range, RangeBounds};
 use core::ptr;
 use core::slice;
 use core::str::pattern::Pattern;
-#[cfg(not(no_global_oom_handling))]
-use core::str::Utf8Chunks;
 
 #[cfg(not(no_global_oom_handling))]
 use crate::borrow::{Cow, ToOwned};
@@ -633,7 +631,7 @@ impl String {
     #[cfg(not(no_global_oom_handling))]
     #[stable(feature = "rust1", since = "1.0.0")]
     pub fn from_utf8_lossy(v: &[u8]) -> Cow<'_, str> {
-        let mut iter = Utf8Chunks::new(v);
+        let mut iter = v.utf8_chunks();
 
         let first_valid = if let Some(chunk) = iter.next() {
             let valid = chunk.valid();
diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs
index 7ace874fa90..4b00bbe4dc2 100644
--- a/library/core/src/intrinsics.rs
+++ b/library/core/src/intrinsics.rs
@@ -2767,7 +2767,7 @@ pub const unsafe fn typed_swap<T>(x: *mut T, y: *mut T) {
 #[unstable(feature = "core_intrinsics", issue = "none")]
 #[inline(always)]
 #[cfg_attr(not(bootstrap), rustc_intrinsic)] // just make it a regular fn in bootstrap
-pub(crate) const fn ub_checks() -> bool {
+pub const fn ub_checks() -> bool {
     cfg!(debug_assertions)
 }
 
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 6925a7d1da1..19e0ce7099d 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -193,6 +193,7 @@
 #![feature(str_split_inclusive_remainder)]
 #![feature(str_split_remainder)]
 #![feature(strict_provenance)]
+#![feature(ub_checks)]
 #![feature(unchecked_shifts)]
 #![feature(utf16_extra)]
 #![feature(utf16_extra_const)]
@@ -370,7 +371,8 @@ pub mod hint;
 pub mod intrinsics;
 pub mod mem;
 pub mod ptr;
-mod ub_checks;
+#[unstable(feature = "ub_checks", issue = "none")]
+pub mod ub_checks;
 
 /* Core language traits */
 
diff --git a/library/core/src/slice/iter/macros.rs b/library/core/src/slice/iter/macros.rs
index 7910981d0f5..0b8ff5cc012 100644
--- a/library/core/src/slice/iter/macros.rs
+++ b/library/core/src/slice/iter/macros.rs
@@ -70,21 +70,19 @@ macro_rules! iterator {
         $into_ref:ident,
         {$($extra:tt)*}
     ) => {
-        // Returns the first element and moves the start of the iterator forwards by 1.
-        // Greatly improves performance compared to an inlined function. The iterator
-        // must not be empty.
-        macro_rules! next_unchecked {
-            ($self: ident) => { $self.post_inc_start(1).$into_ref() }
-        }
-
-        // Returns the last element and moves the end of the iterator backwards by 1.
-        // Greatly improves performance compared to an inlined function. The iterator
-        // must not be empty.
-        macro_rules! next_back_unchecked {
-            ($self: ident) => { $self.pre_dec_end(1).$into_ref() }
-        }
-
         impl<'a, T> $name<'a, T> {
+            /// Returns the last element and moves the end of the iterator backwards by 1.
+            ///
+            /// # Safety
+            ///
+            /// The iterator must not be empty
+            #[inline]
+            unsafe fn next_back_unchecked(&mut self) -> $elem {
+                // SAFETY: the caller promised it's not empty, so
+                // the offsetting is in-bounds and there's an element to return.
+                unsafe { self.pre_dec_end(1).$into_ref() }
+            }
+
             // Helper function for creating a slice from the iterator.
             #[inline(always)]
             fn make_slice(&self) -> &'a [T] {
@@ -156,13 +154,13 @@ macro_rules! iterator {
             fn next(&mut self) -> Option<$elem> {
                 // could be implemented with slices, but this avoids bounds checks
 
-                // SAFETY: The call to `next_unchecked!` is
+                // SAFETY: The call to `next_unchecked` is
                 // safe since we check if the iterator is empty first.
                 unsafe {
                     if is_empty!(self) {
                         None
                     } else {
-                        Some(next_unchecked!(self))
+                        Some(self.next_unchecked())
                     }
                 }
             }
@@ -191,7 +189,7 @@ macro_rules! iterator {
                 // SAFETY: We are in bounds. `post_inc_start` does the right thing even for ZSTs.
                 unsafe {
                     self.post_inc_start(n);
-                    Some(next_unchecked!(self))
+                    Some(self.next_unchecked())
                 }
             }
 
@@ -392,13 +390,13 @@ macro_rules! iterator {
             fn next_back(&mut self) -> Option<$elem> {
                 // could be implemented with slices, but this avoids bounds checks
 
-                // SAFETY: The call to `next_back_unchecked!`
+                // SAFETY: The call to `next_back_unchecked`
                 // is safe since we check if the iterator is empty first.
                 unsafe {
                     if is_empty!(self) {
                         None
                     } else {
-                        Some(next_back_unchecked!(self))
+                        Some(self.next_back_unchecked())
                     }
                 }
             }
@@ -416,7 +414,7 @@ macro_rules! iterator {
                 // SAFETY: We are in bounds. `pre_dec_end` does the right thing even for ZSTs.
                 unsafe {
                     self.pre_dec_end(n);
-                    Some(next_back_unchecked!(self))
+                    Some(self.next_back_unchecked())
                 }
             }
 
@@ -436,10 +434,11 @@ macro_rules! iterator {
         unsafe impl<T> TrustedLen for $name<'_, T> {}
 
         impl<'a, T> UncheckedIterator for $name<'a, T> {
+            #[inline]
             unsafe fn next_unchecked(&mut self) -> $elem {
                 // SAFETY: The caller promised there's at least one more item.
                 unsafe {
-                    next_unchecked!(self)
+                    self.post_inc_start(1).$into_ref()
                 }
             }
         }
diff --git a/library/core/src/str/lossy.rs b/library/core/src/str/lossy.rs
index 59f873d1268..f8ecf1f3a7c 100644
--- a/library/core/src/str/lossy.rs
+++ b/library/core/src/str/lossy.rs
@@ -6,6 +6,46 @@ use crate::iter::FusedIterator;
 use super::from_utf8_unchecked;
 use super::validations::utf8_char_width;
 
+impl [u8] {
+    /// Creates an iterator over the contiguous valid UTF-8 ranges of this
+    /// slice, and the non-UTF-8 fragments in between.
+    ///
+    /// # Examples
+    ///
+    /// This function formats arbitrary but mostly-UTF-8 bytes into Rust source
+    /// code in the form of a C-string literal (`c"..."`).
+    ///
+    /// ```
+    /// use std::fmt::Write as _;
+    ///
+    /// pub fn cstr_literal(bytes: &[u8]) -> String {
+    ///     let mut repr = String::new();
+    ///     repr.push_str("c\"");
+    ///     for chunk in bytes.utf8_chunks() {
+    ///         for ch in chunk.valid().chars() {
+    ///             // Escapes \0, \t, \r, \n, \\, \', \", and uses \u{...} for non-printable characters.
+    ///             write!(repr, "{}", ch.escape_debug()).unwrap();
+    ///         }
+    ///         for byte in chunk.invalid() {
+    ///             write!(repr, "\\x{:02X}", byte).unwrap();
+    ///         }
+    ///     }
+    ///     repr.push('"');
+    ///     repr
+    /// }
+    ///
+    /// fn main() {
+    ///     let lit = cstr_literal(b"\xferris the \xf0\x9f\xa6\x80\x07");
+    ///     let expected = stringify!(c"\xFErris the 🦀\u{7}");
+    ///     assert_eq!(lit, expected);
+    /// }
+    /// ```
+    #[stable(feature = "utf8_chunks", since = "CURRENT_RUSTC_VERSION")]
+    pub fn utf8_chunks(&self) -> Utf8Chunks<'_> {
+        Utf8Chunks { source: self }
+    }
+}
+
 /// An item returned by the [`Utf8Chunks`] iterator.
 ///
 /// A `Utf8Chunk` stores a sequence of [`u8`] up to the first broken character
@@ -14,15 +54,11 @@ use super::validations::utf8_char_width;
 /// # Examples
 ///
 /// ```
-/// #![feature(utf8_chunks)]
-///
-/// use std::str::Utf8Chunks;
-///
 /// // An invalid UTF-8 string
 /// let bytes = b"foo\xF1\x80bar";
 ///
 /// // Decode the first `Utf8Chunk`
-/// let chunk = Utf8Chunks::new(bytes).next().unwrap();
+/// let chunk = bytes.utf8_chunks().next().unwrap();
 ///
 /// // The first three characters are valid UTF-8
 /// assert_eq!("foo", chunk.valid());
@@ -30,7 +66,7 @@ use super::validations::utf8_char_width;
 /// // The fourth character is broken
 /// assert_eq!(b"\xF1\x80", chunk.invalid());
 /// ```
-#[unstable(feature = "utf8_chunks", issue = "99543")]
+#[stable(feature = "utf8_chunks", since = "CURRENT_RUSTC_VERSION")]
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct Utf8Chunk<'a> {
     valid: &'a str,
@@ -43,7 +79,7 @@ impl<'a> Utf8Chunk<'a> {
     /// This substring can be empty at the start of the string or between
     /// broken UTF-8 characters.
     #[must_use]
-    #[unstable(feature = "utf8_chunks", issue = "99543")]
+    #[stable(feature = "utf8_chunks", since = "CURRENT_RUSTC_VERSION")]
     pub fn valid(&self) -> &'a str {
         self.valid
     }
@@ -63,7 +99,7 @@ impl<'a> Utf8Chunk<'a> {
     /// [`valid`]: Self::valid
     /// [`U+FFFD REPLACEMENT CHARACTER`]: crate::char::REPLACEMENT_CHARACTER
     #[must_use]
-    #[unstable(feature = "utf8_chunks", issue = "99543")]
+    #[stable(feature = "utf8_chunks", since = "CURRENT_RUSTC_VERSION")]
     pub fn invalid(&self) -> &'a [u8] {
         self.invalid
     }
@@ -78,7 +114,7 @@ impl fmt::Debug for Debug<'_> {
     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
         f.write_char('"')?;
 
-        for chunk in Utf8Chunks::new(self.0) {
+        for chunk in self.0.utf8_chunks() {
             // Valid part.
             // Here we partially parse UTF-8 again which is suboptimal.
             {
@@ -123,12 +159,8 @@ impl fmt::Debug for Debug<'_> {
 /// [`String::from_utf8_lossy`] without allocating heap memory:
 ///
 /// ```
-/// #![feature(utf8_chunks)]
-///
-/// use std::str::Utf8Chunks;
-///
 /// fn from_utf8_lossy<F>(input: &[u8], mut push: F) where F: FnMut(&str) {
-///     for chunk in Utf8Chunks::new(input) {
+///     for chunk in input.utf8_chunks() {
 ///         push(chunk.valid());
 ///
 ///         if !chunk.invalid().is_empty() {
@@ -140,19 +172,13 @@ impl fmt::Debug for Debug<'_> {
 ///
 /// [`String::from_utf8_lossy`]: ../../std/string/struct.String.html#method.from_utf8_lossy
 #[must_use = "iterators are lazy and do nothing unless consumed"]
-#[unstable(feature = "utf8_chunks", issue = "99543")]
+#[stable(feature = "utf8_chunks", since = "CURRENT_RUSTC_VERSION")]
 #[derive(Clone)]
 pub struct Utf8Chunks<'a> {
     source: &'a [u8],
 }
 
 impl<'a> Utf8Chunks<'a> {
-    /// Creates a new iterator to decode the bytes.
-    #[unstable(feature = "utf8_chunks", issue = "99543")]
-    pub fn new(bytes: &'a [u8]) -> Self {
-        Self { source: bytes }
-    }
-
     #[doc(hidden)]
     #[unstable(feature = "str_internals", issue = "none")]
     pub fn debug(&self) -> Debug<'_> {
@@ -160,7 +186,7 @@ impl<'a> Utf8Chunks<'a> {
     }
 }
 
-#[unstable(feature = "utf8_chunks", issue = "99543")]
+#[stable(feature = "utf8_chunks", since = "CURRENT_RUSTC_VERSION")]
 impl<'a> Iterator for Utf8Chunks<'a> {
     type Item = Utf8Chunk<'a>;
 
@@ -259,10 +285,10 @@ impl<'a> Iterator for Utf8Chunks<'a> {
     }
 }
 
-#[unstable(feature = "utf8_chunks", issue = "99543")]
+#[stable(feature = "utf8_chunks", since = "CURRENT_RUSTC_VERSION")]
 impl FusedIterator for Utf8Chunks<'_> {}
 
-#[unstable(feature = "utf8_chunks", issue = "99543")]
+#[stable(feature = "utf8_chunks", since = "CURRENT_RUSTC_VERSION")]
 impl fmt::Debug for Utf8Chunks<'_> {
     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
         f.debug_struct("Utf8Chunks").field("source", &self.debug()).finish()
diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs
index 61a60456145..3313da9dce7 100644
--- a/library/core/src/str/mod.rs
+++ b/library/core/src/str/mod.rs
@@ -24,7 +24,7 @@ use crate::slice::{self, SliceIndex};
 pub mod pattern;
 
 mod lossy;
-#[unstable(feature = "utf8_chunks", issue = "99543")]
+#[stable(feature = "utf8_chunks", since = "CURRENT_RUSTC_VERSION")]
 pub use lossy::{Utf8Chunk, Utf8Chunks};
 
 #[stable(feature = "rust1", since = "1.0.0")]
diff --git a/library/core/src/ub_checks.rs b/library/core/src/ub_checks.rs
index ff6b2d30539..1aa6a288e70 100644
--- a/library/core/src/ub_checks.rs
+++ b/library/core/src/ub_checks.rs
@@ -46,6 +46,8 @@ use crate::intrinsics::{self, const_eval_select};
 /// variables cannot be optimized out in MIR, an innocent-looking `let` can produce enough
 /// debuginfo to have a measurable compile-time impact on debug builds.
 #[allow_internal_unstable(const_ub_checks)] // permit this to be called in stably-const fn
+#[macro_export]
+#[unstable(feature = "ub_checks", issue = "none")]
 macro_rules! assert_unsafe_precondition {
     ($kind:ident, $message:expr, ($($name:ident:$ty:ty = $arg:expr),*$(,)?) => $e:expr $(,)?) => {
         {
@@ -75,11 +77,13 @@ macro_rules! assert_unsafe_precondition {
         }
     };
 }
-pub(crate) use assert_unsafe_precondition;
+#[unstable(feature = "ub_checks", issue = "none")]
+pub use assert_unsafe_precondition;
 
 /// Checking library UB is always enabled when UB-checking is done
 /// (and we use a reexport so that there is no unnecessary wrapper function).
-pub(crate) use intrinsics::ub_checks as check_library_ub;
+#[unstable(feature = "ub_checks", issue = "none")]
+pub use intrinsics::ub_checks as check_library_ub;
 
 /// Determines whether we should check for language UB.
 ///
diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs
index d6e705a37a7..8a35fdd1857 100644
--- a/library/core/tests/lib.rs
+++ b/library/core/tests/lib.rs
@@ -117,7 +117,6 @@
 #![feature(error_generic_member_access)]
 #![feature(error_in_core)]
 #![feature(trait_upcasting)]
-#![feature(utf8_chunks)]
 #![feature(is_ascii_octdigit)]
 #![feature(get_many_mut)]
 #![feature(iter_map_windows)]
diff --git a/library/core/tests/str_lossy.rs b/library/core/tests/str_lossy.rs
index 9d3f0b65fdb..6e70ea3e285 100644
--- a/library/core/tests/str_lossy.rs
+++ b/library/core/tests/str_lossy.rs
@@ -1,10 +1,8 @@
-use core::str::Utf8Chunks;
-
 #[test]
 fn chunks() {
     macro_rules! assert_chunks {
         ( $string:expr, $(($valid:expr, $invalid:expr)),* $(,)? ) => {{
-            let mut iter = Utf8Chunks::new($string);
+            let mut iter = $string.utf8_chunks();
             $(
                 let chunk = iter.next().expect("missing chunk");
                 assert_eq!($valid, chunk.valid());
@@ -79,7 +77,7 @@ fn debug() {
         "\"Hello\\xC0\\x80 There\\xE6\\x83 Goodbye\\u{10d4ea}\"",
         &format!(
             "{:?}",
-            Utf8Chunks::new(b"Hello\xC0\x80 There\xE6\x83 Goodbye\xf4\x8d\x93\xaa").debug(),
+            b"Hello\xC0\x80 There\xE6\x83 Goodbye\xf4\x8d\x93\xaa".utf8_chunks().debug(),
         ),
     );
 }
diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs
index 20ebe1c4f8a..9dd3d7d3fa1 100644
--- a/library/std/src/ffi/os_str.rs
+++ b/library/std/src/ffi/os_str.rs
@@ -532,6 +532,12 @@ impl OsString {
         let rw = Box::into_raw(self.inner.into_box()) as *mut OsStr;
         unsafe { Box::from_raw(rw) }
     }
+
+    /// Part of a hack to make PathBuf::push/pop more efficient.
+    #[inline]
+    pub(crate) fn as_mut_vec_for_path_buf(&mut self) -> &mut Vec<u8> {
+        self.inner.as_mut_vec_for_path_buf()
+    }
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index aa908f0499f..fb63445c22a 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -314,7 +314,6 @@
 #![feature(thread_local)]
 #![feature(try_blocks)]
 #![feature(type_alias_impl_trait)]
-#![feature(utf8_chunks)]
 // tidy-alphabetical-end
 //
 // Library features (core):
@@ -357,6 +356,7 @@
 #![feature(str_internals)]
 #![feature(strict_provenance)]
 #![feature(strict_provenance_atomic_ptr)]
+#![feature(ub_checks)]
 // tidy-alphabetical-end
 //
 // Library features (alloc):
diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs
index 010ce4e5076..8c7fc4cb2e4 100644
--- a/library/std/src/os/fd/owned.rs
+++ b/library/std/src/os/fd/owned.rs
@@ -176,7 +176,12 @@ impl Drop for OwnedFd {
             // something like EINTR), we might close another valid file descriptor
             // opened after we closed ours.
             #[cfg(not(target_os = "hermit"))]
-            let _ = libc::close(self.fd);
+            {
+                #[cfg(unix)]
+                crate::sys::fs::debug_assert_fd_is_open(self.fd);
+
+                let _ = libc::close(self.fd);
+            }
             #[cfg(target_os = "hermit")]
             let _ = hermit_abi::close(self.fd);
         }
diff --git a/library/std/src/path.rs b/library/std/src/path.rs
index 1c58671a0cf..85355435100 100644
--- a/library/std/src/path.rs
+++ b/library/std/src/path.rs
@@ -1158,12 +1158,6 @@ impl FusedIterator for Ancestors<'_> {}
 /// Which method works best depends on what kind of situation you're in.
 #[cfg_attr(not(test), rustc_diagnostic_item = "PathBuf")]
 #[stable(feature = "rust1", since = "1.0.0")]
-// `PathBuf::as_mut_vec` current implementation relies
-// on `PathBuf` being layout-compatible with `Vec<u8>`.
-// However, `PathBuf` layout is considered an implementation detail and must not be relied upon. We
-// want `repr(transparent)` but we don't want it to show up in rustdoc, so we hide it under
-// `cfg(doc)`. This is an ad-hoc implementation of attribute privacy.
-#[cfg_attr(not(doc), repr(transparent))]
 pub struct PathBuf {
     inner: OsString,
 }
@@ -1171,7 +1165,7 @@ pub struct PathBuf {
 impl PathBuf {
     #[inline]
     fn as_mut_vec(&mut self) -> &mut Vec<u8> {
-        unsafe { &mut *(self as *mut PathBuf as *mut Vec<u8>) }
+        self.inner.as_mut_vec_for_path_buf()
     }
 
     /// Allocates an empty `PathBuf`.
diff --git a/library/std/src/sys/os_str/bytes.rs b/library/std/src/sys/os_str/bytes.rs
index 4ca3f1cd185..18b969bca85 100644
--- a/library/std/src/sys/os_str/bytes.rs
+++ b/library/std/src/sys/os_str/bytes.rs
@@ -11,8 +11,6 @@ use crate::str;
 use crate::sync::Arc;
 use crate::sys_common::{AsInner, IntoInner};
 
-use core::str::Utf8Chunks;
-
 #[cfg(test)]
 mod tests;
 
@@ -29,7 +27,7 @@ pub struct Slice {
 
 impl fmt::Debug for Slice {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        fmt::Debug::fmt(&Utf8Chunks::new(&self.inner).debug(), f)
+        fmt::Debug::fmt(&self.inner.utf8_chunks().debug(), f)
     }
 }
 
@@ -41,7 +39,7 @@ impl fmt::Display for Slice {
             return "".fmt(f);
         }
 
-        for chunk in Utf8Chunks::new(&self.inner) {
+        for chunk in self.inner.utf8_chunks() {
             let valid = chunk.valid();
             // If we successfully decoded the whole chunk as a valid string then
             // we can return a direct formatting of the string which will also
@@ -198,6 +196,12 @@ impl Buf {
     pub fn into_rc(&self) -> Rc<Slice> {
         self.as_slice().into_rc()
     }
+
+    /// Part of a hack to make PathBuf::push/pop more efficient.
+    #[inline]
+    pub(crate) fn as_mut_vec_for_path_buf(&mut self) -> &mut Vec<u8> {
+        &mut self.inner
+    }
 }
 
 impl Slice {
diff --git a/library/std/src/sys/os_str/wtf8.rs b/library/std/src/sys/os_str/wtf8.rs
index 352bd735903..b3ceb55802d 100644
--- a/library/std/src/sys/os_str/wtf8.rs
+++ b/library/std/src/sys/os_str/wtf8.rs
@@ -158,6 +158,12 @@ impl Buf {
     pub fn into_rc(&self) -> Rc<Slice> {
         self.as_slice().into_rc()
     }
+
+    /// Part of a hack to make PathBuf::push/pop more efficient.
+    #[inline]
+    pub(crate) fn as_mut_vec_for_path_buf(&mut self) -> &mut Vec<u8> {
+        self.inner.as_mut_vec_for_path_buf()
+    }
 }
 
 impl Slice {
diff --git a/library/std/src/sys/pal/unix/args.rs b/library/std/src/sys/pal/unix/args.rs
index acf8100d47f..afe703bcb2f 100644
--- a/library/std/src/sys/pal/unix/args.rs
+++ b/library/std/src/sys/pal/unix/args.rs
@@ -98,12 +98,10 @@ mod imp {
     }
 
     #[inline(always)]
-    pub unsafe fn init(_argc: isize, _argv: *const *const u8) {
-        // On Linux-GNU, we rely on `ARGV_INIT_ARRAY` below to initialize
-        // `ARGC` and `ARGV`. But in Miri that does not actually happen so we
-        // still initialize here.
-        #[cfg(any(miri, not(all(target_os = "linux", target_env = "gnu"))))]
-        really_init(_argc, _argv);
+    pub unsafe fn init(argc: isize, argv: *const *const u8) {
+        // on GNU/Linux if we are main then we will init argv and argc twice, it "duplicates work"
+        // BUT edge-cases are real: only using .init_array can break most emulators, dlopen, etc.
+        really_init(argc, argv);
     }
 
     /// glibc passes argc, argv, and envp to functions in .init_array, as a non-standard extension.
diff --git a/library/std/src/sys/pal/unix/fs.rs b/library/std/src/sys/pal/unix/fs.rs
index 3456155509e..abd32b819e0 100644
--- a/library/std/src/sys/pal/unix/fs.rs
+++ b/library/std/src/sys/pal/unix/fs.rs
@@ -198,20 +198,16 @@ cfg_has_statx! {{
                 return Some(Err(err));
             }
 
-            // `ENOSYS` might come from a faulty FUSE driver.
-            //
-            // Other errors are not a good enough indicator either -- it is
-            // known that `EPERM` can be returned as a result of using seccomp to
-            // block the syscall.
+            // We're not yet entirely sure whether `statx` is usable on this kernel
+            // or not. Syscalls can return errors from things other than the kernel
+            // per se, e.g. `EPERM` can be returned if seccomp is used to block the
+            // syscall, or `ENOSYS` might be returned from a faulty FUSE driver.
             //
             // Availability is checked by performing a call which expects `EFAULT`
             // if the syscall is usable.
             //
             // See: https://github.com/rust-lang/rust/issues/65662
             //
-            // FIXME this can probably just do the call if `EPERM` was received, but
-            // previous iteration of the code checked it for all errors and for now
-            // this is retained.
             // FIXME what about transient conditions like `ENOMEM`?
             let err2 = cvt(statx(0, ptr::null(), 0, libc::STATX_ALL, ptr::null_mut()))
                 .err()
@@ -868,8 +864,40 @@ impl Iterator for ReadDir {
     }
 }
 
+/// Aborts the process if a file desceriptor is not open, if debug asserts are enabled
+///
+/// Many IO syscalls can't be fully trusted about EBADF error codes because those
+/// might get bubbled up from a remote FUSE server rather than the file descriptor
+/// in the current process being invalid.
+///
+/// So we check file flags instead which live on the file descriptor and not the underlying file.
+/// The downside is that it costs an extra syscall, so we only do it for debug.
+#[inline]
+pub(crate) fn debug_assert_fd_is_open(fd: RawFd) {
+    use crate::sys::os::errno;
+
+    // this is similar to assert_unsafe_precondition!() but it doesn't require const
+    if core::ub_checks::check_library_ub() {
+        if unsafe { libc::fcntl(fd, libc::F_GETFD) } == -1 && errno() == libc::EBADF {
+            rtabort!("IO Safety violation: owned file descriptor already closed");
+        }
+    }
+}
+
 impl Drop for Dir {
     fn drop(&mut self) {
+        // dirfd isn't supported everywhere
+        #[cfg(not(any(
+            miri,
+            target_os = "redox",
+            target_os = "nto",
+            target_os = "vita",
+            target_os = "hurd",
+        )))]
+        {
+            let fd = unsafe { libc::dirfd(self.0) };
+            debug_assert_fd_is_open(fd);
+        }
         let r = unsafe { libc::closedir(self.0) };
         assert!(
             r == 0 || crate::io::Error::last_os_error().is_interrupted(),
diff --git a/library/std/src/sys/thread_local/fast_local.rs b/library/std/src/sys/thread_local/fast_local.rs
index 69ee70de30c..49b51a729e4 100644
--- a/library/std/src/sys/thread_local/fast_local.rs
+++ b/library/std/src/sys/thread_local/fast_local.rs
@@ -1,7 +1,7 @@
 use super::lazy::LazyKeyInner;
 use crate::cell::Cell;
 use crate::sys::thread_local_dtor::register_dtor;
-use crate::{fmt, mem, panic};
+use crate::{fmt, mem, panic, ptr};
 
 #[doc(hidden)]
 #[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)]
@@ -237,8 +237,9 @@ unsafe extern "C" fn destroy_value<T>(ptr: *mut u8) {
     // Wrap the call in a catch to ensure unwinding is caught in the event
     // a panic takes place in a destructor.
     if let Err(_) = panic::catch_unwind(panic::AssertUnwindSafe(|| unsafe {
-        let value = (*ptr).inner.take();
-        (*ptr).dtor_state.set(DtorState::RunningOrHasRun);
+        let Key { inner, dtor_state } = &*ptr;
+        let value = inner.take();
+        dtor_state.set(DtorState::RunningOrHasRun);
         drop(value);
     })) {
         rtabort!("thread local panicked on drop");
diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs
index 8b2c839f837..7500c95d8b4 100644
--- a/library/std/src/sys/thread_local/mod.rs
+++ b/library/std/src/sys/thread_local/mod.rs
@@ -91,13 +91,15 @@ mod lazy {
             }
         }
 
-        /// The other methods hand out references while taking &self.
-        /// As such, callers of this method must ensure no `&` and `&mut` are
-        /// available and used at the same time.
+        /// Watch out: unsynchronized internal mutability!
+        ///
+        /// # Safety
+        /// Causes UB if any reference to the value is used after this.
         #[allow(unused)]
-        pub unsafe fn take(&mut self) -> Option<T> {
-            // SAFETY: See doc comment for this method.
-            unsafe { (*self.inner.get()).take() }
+        pub(crate) unsafe fn take(&self) -> Option<T> {
+            let mutable: *mut _ = UnsafeCell::get(&self.inner);
+            // SAFETY: That's the caller's problem.
+            unsafe { mutable.replace(None) }
         }
     }
 }
diff --git a/library/std/src/sys_common/wtf8.rs b/library/std/src/sys_common/wtf8.rs
index 2dbd19d7171..38e15f9f549 100644
--- a/library/std/src/sys_common/wtf8.rs
+++ b/library/std/src/sys_common/wtf8.rs
@@ -468,6 +468,12 @@ impl Wtf8Buf {
         let bytes: Box<[u8]> = unsafe { mem::transmute(boxed) };
         Wtf8Buf { bytes: bytes.into_vec(), is_known_utf8: false }
     }
+
+    /// Part of a hack to make PathBuf::push/pop more efficient.
+    #[inline]
+    pub(crate) fn as_mut_vec_for_path_buf(&mut self) -> &mut Vec<u8> {
+        &mut self.bytes
+    }
 }
 
 /// Creates a new WTF-8 string from an iterator of code points.
diff --git a/src/bootstrap/bootstrap.py b/src/bootstrap/bootstrap.py
index 39add60e705..e464e444fea 100644
--- a/src/bootstrap/bootstrap.py
+++ b/src/bootstrap/bootstrap.py
@@ -1035,14 +1035,8 @@ class RustBuild(object):
         if self.use_vendored_sources:
             vendor_dir = os.path.join(self.rust_root, 'vendor')
             if not os.path.exists(vendor_dir):
-                sync_dirs = "--sync ./src/tools/cargo/Cargo.toml " \
-                            "--sync ./src/tools/rust-analyzer/Cargo.toml " \
-                            "--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \
-                            "--sync ./compiler/rustc_codegen_gcc/Cargo.toml " \
-                            "--sync ./src/bootstrap/Cargo.toml "
                 eprint('ERROR: vendoring required, but vendor directory does not exist.')
-                eprint('       Run `cargo vendor {}` to initialize the '
-                      'vendor directory.'.format(sync_dirs))
+                eprint('       Run `x.py vendor` to initialize the vendor directory.')
                 eprint('       Alternatively, use the pre-vendored `rustc-src` dist component.')
                 eprint('       To get a stable/beta/nightly version, download it from: ')
                 eprint('       '
diff --git a/src/bootstrap/mk/Makefile.in b/src/bootstrap/mk/Makefile.in
index 3cfd0240794..fc433bc5843 100644
--- a/src/bootstrap/mk/Makefile.in
+++ b/src/bootstrap/mk/Makefile.in
@@ -53,15 +53,12 @@ check-aux:
 		src/tools/cargotest \
 		$(BOOTSTRAP_ARGS)
 	# Run standard library tests in Miri.
-	# We use a 64bit little-endian and a 32bit big-endian target for max coverage.
 	$(Q)BOOTSTRAP_SKIP_TARGET_SANITY=1 \
 		$(BOOTSTRAP) miri --stage 2 \
-		--target x86_64-unknown-linux-gnu,mips-unknown-linux-gnu \
 		library/core \
 		library/alloc \
 		--no-doc
 	# Some doctests have intentional memory leaks.
-	# Also, they work only on the host.
 	$(Q)MIRIFLAGS="-Zmiri-ignore-leaks -Zmiri-disable-isolation" \
 		$(BOOTSTRAP) miri --stage 2 \
 		library/core \
diff --git a/src/bootstrap/src/core/build_steps/clean.rs b/src/bootstrap/src/core/build_steps/clean.rs
index 5bcaeed7faa..a81d6403013 100644
--- a/src/bootstrap/src/core/build_steps/clean.rs
+++ b/src/bootstrap/src/core/build_steps/clean.rs
@@ -1,4 +1,4 @@
-//! Implementation of `make clean` in rustbuild.
+//! `./x.py clean`
 //!
 //! Responsible for cleaning out a build directory of all old and stale
 //! artifacts to prepare for a fresh build. Currently doesn't remove the
diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs
index 0eca20901b7..770a5cdb232 100644
--- a/src/bootstrap/src/core/build_steps/dist.rs
+++ b/src/bootstrap/src/core/build_steps/dist.rs
@@ -830,6 +830,12 @@ fn copy_src_dirs(
             return false;
         }
 
+        // Cargo tests use some files like `.gitignore` that we would otherwise exclude.
+        const CARGO_TESTS: &[&str] = &["tools/cargo/tests", "tools\\cargo\\tests"];
+        if CARGO_TESTS.iter().any(|path| spath.contains(path)) {
+            return true;
+        }
+
         let full_path = Path::new(dir).join(path);
         if exclude_dirs.iter().any(|excl| full_path == Path::new(excl)) {
             return false;
diff --git a/src/bootstrap/src/core/build_steps/mod.rs b/src/bootstrap/src/core/build_steps/mod.rs
index 9b7378165de..381ee7ef53b 100644
--- a/src/bootstrap/src/core/build_steps/mod.rs
+++ b/src/bootstrap/src/core/build_steps/mod.rs
@@ -14,3 +14,4 @@ pub(crate) mod synthetic_targets;
 pub(crate) mod test;
 pub(crate) mod tool;
 pub(crate) mod toolstate;
+pub(crate) mod vendor;
diff --git a/src/bootstrap/src/core/build_steps/run.rs b/src/bootstrap/src/core/build_steps/run.rs
index 7028bffea54..0a428ec5ca0 100644
--- a/src/bootstrap/src/core/build_steps/run.rs
+++ b/src/bootstrap/src/core/build_steps/run.rs
@@ -1,3 +1,8 @@
+//! Build-and-run steps for in-repo tools
+//!
+//! A bit of a hodge-podge as e.g. if a tool's a test fixture it should be in `build_steps::test`.
+//! If it can be reached from `./x.py run` it can go here.
+
 use std::path::PathBuf;
 use std::process::Command;
 
diff --git a/src/bootstrap/src/core/build_steps/setup.rs b/src/bootstrap/src/core/build_steps/setup.rs
index c0683cdda1e..df38d6166eb 100644
--- a/src/bootstrap/src/core/build_steps/setup.rs
+++ b/src/bootstrap/src/core/build_steps/setup.rs
@@ -1,3 +1,10 @@
+//! First time setup of a dev environment
+//!
+//! These are build-and-run steps for `./x.py setup`, which allows quickly setting up the directory
+//! for modifying, building, and running the compiler and library. Running arbitrary configuration
+//! allows setting up things that cannot be simply captured inside the config.toml, in addition to
+//! leading people away from manually editing most of the config.toml values.
+
 use crate::core::builder::{Builder, RunConfig, ShouldRun, Step};
 use crate::t;
 use crate::utils::change_tracker::CONFIG_CHANGE_HISTORY;
@@ -25,6 +32,8 @@ pub enum Profile {
     None,
 }
 
+static PROFILE_DIR: &str = "src/bootstrap/defaults";
+
 /// A list of historical hashes of `src/etc/rust_analyzer_settings.json`.
 /// New entries should be appended whenever this is updated so we can detect
 /// outdated vs. user-modified settings files.
@@ -41,7 +50,7 @@ static RUST_ANALYZER_SETTINGS: &str = include_str!("../../../../etc/rust_analyze
 
 impl Profile {
     fn include_path(&self, src_path: &Path) -> PathBuf {
-        PathBuf::from(format!("{}/src/bootstrap/defaults/config.{}.toml", src_path.display(), self))
+        PathBuf::from(format!("{}/{PROFILE_DIR}/config.{}.toml", src_path.display(), self))
     }
 
     pub fn all() -> impl Iterator<Item = Self> {
@@ -220,7 +229,7 @@ fn setup_config_toml(path: &PathBuf, profile: Profile, config: &Config) {
 
     let latest_change_id = CONFIG_CHANGE_HISTORY.last().unwrap().change_id;
     let settings = format!(
-        "# Includes one of the default files in src/bootstrap/defaults\n\
+        "# Includes one of the default files in {PROFILE_DIR}\n\
     profile = \"{profile}\"\n\
     change-id = {latest_change_id}\n"
     );
diff --git a/src/bootstrap/src/core/build_steps/suggest.rs b/src/bootstrap/src/core/build_steps/suggest.rs
index c057fa9a566..754d1e61da8 100644
--- a/src/bootstrap/src/core/build_steps/suggest.rs
+++ b/src/bootstrap/src/core/build_steps/suggest.rs
@@ -1,3 +1,5 @@
+//! Attempt to magically identify good tests to run
+
 #![cfg_attr(feature = "build-metrics", allow(unused))]
 
 use clap::Parser;
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index ca10a128b98..411ed99532f 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -1,7 +1,7 @@
-//! Implementation of the test-related targets of the build system.
+//! Build-and-run steps for `./x.py test` test fixtures
 //!
-//! This file implements the various regression test suites that we execute on
-//! our CI.
+//! `./x.py test` (aka [`Kind::Test`]) is currently allowed to reach build steps in other modules.
+//! However, this contains ~all test parts we expect people to be able to build and run locally.
 
 use std::env;
 use std::ffi::OsStr;
@@ -1371,6 +1371,7 @@ impl Step for RunMakeSupport {
         run.builder.ensure(RunMakeSupport { compiler, target: run.build_triple() });
     }
 
+    /// Builds run-make-support and returns the path to the resulting rlib.
     fn run(self, builder: &Builder<'_>) -> PathBuf {
         builder.ensure(compile::Std::new(self.compiler, self.target));
 
@@ -1397,6 +1398,53 @@ impl Step for RunMakeSupport {
     }
 }
 
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct CrateRunMakeSupport {
+    host: TargetSelection,
+}
+
+impl Step for CrateRunMakeSupport {
+    type Output = ();
+    const ONLY_HOSTS: bool = true;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        run.path("src/tools/run-make-support")
+    }
+
+    fn make_run(run: RunConfig<'_>) {
+        run.builder.ensure(CrateRunMakeSupport { host: run.target });
+    }
+
+    /// Runs `cargo test` for run-make-support.
+    fn run(self, builder: &Builder<'_>) {
+        let host = self.host;
+        let compiler = builder.compiler(builder.top_stage, host);
+
+        builder.ensure(compile::Std::new(compiler, host));
+        let mut cargo = tool::prepare_tool_cargo(
+            builder,
+            compiler,
+            Mode::ToolStd,
+            host,
+            "test",
+            "src/tools/run-make-support",
+            SourceType::InTree,
+            &[],
+        );
+        cargo.allow_features("test");
+        run_cargo_test(
+            cargo,
+            &[],
+            &[],
+            "run-make-support",
+            "run-make-support self test",
+            compiler,
+            host,
+            builder,
+        );
+    }
+}
+
 default_test!(Ui { path: "tests/ui", mode: "ui", suite: "ui" });
 
 default_test!(Crashes { path: "tests/crashes", mode: "crashes", suite: "crashes" });
diff --git a/src/bootstrap/src/core/build_steps/toolstate.rs b/src/bootstrap/src/core/build_steps/toolstate.rs
index f88c1b3ee82..ca3756df4d7 100644
--- a/src/bootstrap/src/core/build_steps/toolstate.rs
+++ b/src/bootstrap/src/core/build_steps/toolstate.rs
@@ -1,3 +1,9 @@
+//! [Toolstate] checks to keep tools building
+//!
+//! Reachable via `./x.py test` but mostly relevant for CI, since it isn't run locally by default.
+//!
+//! [Toolstate]: https://forge.rust-lang.org/infra/toolstate.html
+
 use crate::core::builder::{Builder, RunConfig, ShouldRun, Step};
 use crate::utils::helpers::t;
 use serde_derive::{Deserialize, Serialize};
diff --git a/src/bootstrap/src/core/build_steps/vendor.rs b/src/bootstrap/src/core/build_steps/vendor.rs
new file mode 100644
index 00000000000..68f1b1bef3f
--- /dev/null
+++ b/src/bootstrap/src/core/build_steps/vendor.rs
@@ -0,0 +1,61 @@
+use crate::core::builder::{Builder, RunConfig, ShouldRun, Step};
+use std::path::PathBuf;
+use std::process::Command;
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub(crate) struct Vendor {
+    sync_args: Vec<PathBuf>,
+    versioned_dirs: bool,
+    root_dir: PathBuf,
+}
+
+impl Step for Vendor {
+    type Output = ();
+    const DEFAULT: bool = true;
+    const ONLY_HOSTS: bool = true;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        run.alias("placeholder").default_condition(true)
+    }
+
+    fn make_run(run: RunConfig<'_>) {
+        run.builder.ensure(Vendor {
+            sync_args: run.builder.config.cmd.vendor_sync_args(),
+            versioned_dirs: run.builder.config.cmd.vendor_versioned_dirs(),
+            root_dir: run.builder.src.clone(),
+        });
+    }
+
+    fn run(self, builder: &Builder<'_>) -> Self::Output {
+        let mut cmd = Command::new(&builder.initial_cargo);
+        cmd.arg("vendor");
+
+        if self.versioned_dirs {
+            cmd.arg("--versioned-dirs");
+        }
+
+        // Sync these paths by default.
+        for p in [
+            "src/tools/cargo/Cargo.toml",
+            "src/tools/rust-analyzer/Cargo.toml",
+            "compiler/rustc_codegen_cranelift/Cargo.toml",
+            "compiler/rustc_codegen_gcc/Cargo.toml",
+            "src/bootstrap/Cargo.toml",
+        ] {
+            cmd.arg("--sync").arg(builder.src.join(p));
+        }
+
+        // Also sync explicitly requested paths.
+        for sync_arg in self.sync_args {
+            cmd.arg("--sync").arg(sync_arg);
+        }
+
+        // Will read the libstd Cargo.toml
+        // which uses the unstable `public-dependency` feature.
+        cmd.env("RUSTC_BOOTSTRAP", "1");
+
+        cmd.current_dir(self.root_dir);
+
+        builder.run(&mut cmd);
+    }
+}
diff --git a/src/bootstrap/src/core/builder.rs b/src/bootstrap/src/core/builder.rs
index adef9ebd0e3..ae5440de572 100644
--- a/src/bootstrap/src/core/builder.rs
+++ b/src/bootstrap/src/core/builder.rs
@@ -14,8 +14,9 @@ use std::sync::OnceLock;
 use std::time::{Duration, Instant};
 
 use crate::core::build_steps::tool::{self, SourceType};
-use crate::core::build_steps::{check, clean, compile, dist, doc, install, run, setup, test};
-use crate::core::build_steps::{clippy, llvm};
+use crate::core::build_steps::{
+    check, clean, clippy, compile, dist, doc, install, llvm, run, setup, test, vendor,
+};
 use crate::core::config::flags::{Color, Subcommand};
 use crate::core::config::{DryRun, SplitDebuginfo, TargetSelection};
 use crate::prepare_behaviour_dump_dir;
@@ -34,13 +35,34 @@ use once_cell::sync::Lazy;
 #[cfg(test)]
 mod tests;
 
+/// Builds and performs different [`Self::kind`]s of stuff and actions, taking
+/// into account build configuration from e.g. config.toml.
 pub struct Builder<'a> {
+    /// Build configuration from e.g. config.toml.
     pub build: &'a Build,
+
+    /// The stage to use. Either implicitly determined based on subcommand, or
+    /// explicitly specified with `--stage N`. Normally this is the stage we
+    /// use, but sometimes we want to run steps with a lower stage than this.
     pub top_stage: u32,
+
+    /// What to build or what action to perform.
     pub kind: Kind,
+
+    /// A cache of outputs of [`Step`]s so we can avoid running steps we already
+    /// ran.
     cache: Cache,
+
+    /// A stack of [`Step`]s to run before we can run this builder. The output
+    /// of steps is cached in [`Self::cache`].
     stack: RefCell<Vec<Box<dyn Any>>>,
+
+    /// The total amount of time we spent running [`Step`]s in [`Self::stack`].
     time_spent_on_dependencies: Cell<Duration>,
+
+    /// The paths passed on the command line. Used by steps to figure out what
+    /// to do. For example: with `./x check foo bar` we get `paths=["foo",
+    /// "bar"]`.
     pub paths: Vec<PathBuf>,
 }
 
@@ -638,6 +660,7 @@ pub enum Kind {
     Run,
     Setup,
     Suggest,
+    Vendor,
 }
 
 impl Kind {
@@ -658,6 +681,7 @@ impl Kind {
             Kind::Run => "run",
             Kind::Setup => "setup",
             Kind::Suggest => "suggest",
+            Kind::Vendor => "vendor",
         }
     }
 
@@ -817,6 +841,7 @@ impl<'a> Builder<'a> {
                 test::Clippy,
                 test::RustDemangler,
                 test::CompiletestTest,
+                test::CrateRunMakeSupport,
                 test::RustdocJSStd,
                 test::RustdocJSNotStd,
                 test::RustdocGUI,
@@ -920,6 +945,7 @@ impl<'a> Builder<'a> {
             ),
             Kind::Setup => describe!(setup::Profile, setup::Hook, setup::Link, setup::Vscode),
             Kind::Clean => describe!(clean::CleanAll, clean::Rustc, clean::Std),
+            Kind::Vendor => describe!(vendor::Vendor),
             // special-cased in Build::build()
             Kind::Format | Kind::Suggest => vec![],
         }
@@ -992,6 +1018,7 @@ impl<'a> Builder<'a> {
                 Kind::Setup,
                 path.as_ref().map_or([].as_slice(), |path| std::slice::from_ref(path)),
             ),
+            Subcommand::Vendor { .. } => (Kind::Vendor, &paths[..]),
         };
 
         Self::new_internal(build, kind, paths.to_owned())
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index 2acce627359..ed45bc30362 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -345,6 +345,8 @@ pub struct Config {
     #[cfg(test)]
     pub initial_rustfmt: RefCell<RustfmtState>,
 
+    /// The paths to work with. For example: with `./x check foo bar` we get
+    /// `paths=["foo", "bar"]`.
     pub paths: Vec<PathBuf>,
 }
 
@@ -2027,7 +2029,8 @@ impl Config {
             | Subcommand::Run { .. }
             | Subcommand::Setup { .. }
             | Subcommand::Format { .. }
-            | Subcommand::Suggest { .. } => flags.stage.unwrap_or(0),
+            | Subcommand::Suggest { .. }
+            | Subcommand::Vendor { .. } => flags.stage.unwrap_or(0),
         };
 
         // CI should always run stage 2 builds, unless it specifically states otherwise
@@ -2054,7 +2057,8 @@ impl Config {
                 | Subcommand::Run { .. }
                 | Subcommand::Setup { .. }
                 | Subcommand::Format { .. }
-                | Subcommand::Suggest { .. } => {}
+                | Subcommand::Suggest { .. }
+                | Subcommand::Vendor { .. } => {}
             }
         }
 
@@ -2488,11 +2492,6 @@ impl Config {
 
                 b
             }
-            // FIXME: "if-available" is deprecated. Remove this block later (around mid 2024)
-            // to not break builds between the recent-to-old checkouts.
-            Some(StringOrBool::String(s)) if s == "if-available" => {
-                llvm::is_ci_llvm_available(self, asserts)
-            }
             Some(StringOrBool::String(s)) if s == "if-unchanged" => if_unchanged(),
             Some(StringOrBool::String(other)) => {
                 panic!("unrecognized option for download-ci-llvm: {:?}", other)
diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs
index aaa2a2c47e0..a5af87ad5c7 100644
--- a/src/bootstrap/src/core/config/flags.rs
+++ b/src/bootstrap/src/core/config/flags.rs
@@ -41,72 +41,72 @@ pub struct Flags {
     #[command(subcommand)]
     pub cmd: Subcommand,
 
-    #[arg(global(true), short, long, action = clap::ArgAction::Count)]
+    #[arg(global = true, short, long, action = clap::ArgAction::Count)]
     /// use verbose output (-vv for very verbose)
     pub verbose: u8, // each extra -v after the first is passed to Cargo
-    #[arg(global(true), short, long)]
+    #[arg(global = true, short, long)]
     /// use incremental compilation
     pub incremental: bool,
-    #[arg(global(true), long, value_hint = clap::ValueHint::FilePath, value_name = "FILE")]
+    #[arg(global = true, long, value_hint = clap::ValueHint::FilePath, value_name = "FILE")]
     /// TOML configuration file for build
     pub config: Option<PathBuf>,
-    #[arg(global(true), long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
+    #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
     /// Build directory, overrides `build.build-dir` in `config.toml`
     pub build_dir: Option<PathBuf>,
 
-    #[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "BUILD")]
+    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "BUILD")]
     /// build target of the stage0 compiler
     pub build: Option<String>,
 
-    #[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "HOST", value_parser = target_selection_list)]
+    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "HOST", value_parser = target_selection_list)]
     /// host targets to build
     pub host: Option<TargetSelectionList>,
 
-    #[arg(global(true), long, value_hint = clap::ValueHint::Other, value_name = "TARGET", value_parser = target_selection_list)]
+    #[arg(global = true, long, value_hint = clap::ValueHint::Other, value_name = "TARGET", value_parser = target_selection_list)]
     /// target targets to build
     pub target: Option<TargetSelectionList>,
 
-    #[arg(global(true), long, value_name = "PATH")]
+    #[arg(global = true, long, value_name = "PATH")]
     /// build paths to exclude
     pub exclude: Vec<PathBuf>, // keeping for client backward compatibility
-    #[arg(global(true), long, value_name = "PATH")]
+    #[arg(global = true, long, value_name = "PATH")]
     /// build paths to skip
     pub skip: Vec<PathBuf>,
-    #[arg(global(true), long)]
+    #[arg(global = true, long)]
     /// include default paths in addition to the provided ones
     pub include_default_paths: bool,
 
-    #[arg(global(true), value_hint = clap::ValueHint::Other, long)]
+    #[arg(global = true, value_hint = clap::ValueHint::Other, long)]
     pub rustc_error_format: Option<String>,
 
-    #[arg(global(true), long, value_hint = clap::ValueHint::CommandString, value_name = "CMD")]
+    #[arg(global = true, long, value_hint = clap::ValueHint::CommandString, value_name = "CMD")]
     /// command to run on failure
     pub on_fail: Option<String>,
-    #[arg(global(true), long)]
+    #[arg(global = true, long)]
     /// dry run; don't build anything
     pub dry_run: bool,
     /// Indicates whether to dump the work done from bootstrap shims
-    #[arg(global(true), long)]
+    #[arg(global = true, long)]
     pub dump_bootstrap_shims: bool,
-    #[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")]
+    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
     /// stage to build (indicates compiler to use/test, e.g., stage 0 uses the
     /// bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)
     pub stage: Option<u32>,
 
-    #[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")]
+    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
     /// stage(s) to keep without recompiling
     /// (pass multiple times to keep e.g., both stages 0 and 1)
     pub keep_stage: Vec<u32>,
-    #[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "N")]
+    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "N")]
     /// stage(s) of the standard library to keep without recompiling
     /// (pass multiple times to keep e.g., both stages 0 and 1)
     pub keep_stage_std: Vec<u32>,
-    #[arg(global(true), long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
+    #[arg(global = true, long, value_hint = clap::ValueHint::DirPath, value_name = "DIR")]
     /// path to the root of the rust checkout
     pub src: Option<PathBuf>,
 
     #[arg(
-        global(true),
+        global = true,
         short,
         long,
         value_hint = clap::ValueHint::Other,
@@ -117,26 +117,26 @@ pub struct Flags {
     pub jobs: usize,
     // This overrides the deny-warnings configuration option,
     // which passes -Dwarnings to the compiler invocations.
-    #[arg(global(true), long)]
+    #[arg(global = true, long)]
     #[arg(value_enum, default_value_t=Warnings::Default, value_name = "deny|warn")]
     /// if value is deny, will deny warnings
     /// if value is warn, will emit warnings
     /// otherwise, use the default configured behaviour
     pub warnings: Warnings,
 
-    #[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "FORMAT")]
+    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "FORMAT")]
     /// rustc error format
     pub error_format: Option<String>,
-    #[arg(global(true), long)]
+    #[arg(global = true, long)]
     /// use message-format=json
     pub json_output: bool,
 
-    #[arg(global(true), long, value_name = "STYLE")]
+    #[arg(global = true, long, value_name = "STYLE")]
     #[arg(value_enum, default_value_t = Color::Auto)]
     /// whether to use color in cargo and rustc output
     pub color: Color,
 
-    #[arg(global(true), long)]
+    #[arg(global = true, long)]
     /// Bootstrap uses this value to decide whether it should bypass locking the build process.
     /// This is rarely needed (e.g., compiling the std library for different targets in parallel).
     ///
@@ -144,41 +144,41 @@ pub struct Flags {
     pub bypass_bootstrap_lock: bool,
 
     /// whether rebuilding llvm should be skipped, overriding `skip-rebuld` in config.toml
-    #[arg(global(true), long, value_name = "VALUE")]
+    #[arg(global = true, long, value_name = "VALUE")]
     pub llvm_skip_rebuild: Option<bool>,
     /// generate PGO profile with rustc build
-    #[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
+    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
     pub rust_profile_generate: Option<String>,
     /// use PGO profile for rustc build
-    #[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
+    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
     pub rust_profile_use: Option<String>,
     /// use PGO profile for LLVM build
-    #[arg(global(true), value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
+    #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")]
     pub llvm_profile_use: Option<String>,
     // LLVM doesn't support a custom location for generating profile
     // information.
     //
     // llvm_out/build/profiles/ is the location this writes to.
     /// generate PGO profile with llvm built for rustc
-    #[arg(global(true), long)]
+    #[arg(global = true, long)]
     pub llvm_profile_generate: bool,
     /// Enable BOLT link flags
-    #[arg(global(true), long)]
+    #[arg(global = true, long)]
     pub enable_bolt_settings: bool,
     /// Skip stage0 compiler validation
-    #[arg(global(true), long)]
+    #[arg(global = true, long)]
     pub skip_stage0_validation: bool,
     /// Additional reproducible artifacts that should be added to the reproducible artifacts archive.
-    #[arg(global(true), long)]
+    #[arg(global = true, long)]
     pub reproducible_artifact: Vec<String>,
-    #[arg(global(true))]
+    #[arg(global = true)]
     /// paths for the subcommand
     pub paths: Vec<PathBuf>,
     /// override options in config.toml
-    #[arg(global(true), value_hint = clap::ValueHint::Other, long, value_name = "section.option=value")]
+    #[arg(global = true, value_hint = clap::ValueHint::Other, long, value_name = "section.option=value")]
     pub set: Vec<String>,
     /// arguments passed to subcommands
-    #[arg(global(true), last(true), value_name = "ARGS")]
+    #[arg(global = true, last(true), value_name = "ARGS")]
     pub free_args: Vec<String>,
 }
 
@@ -192,7 +192,7 @@ impl Flags {
         struct HelpVerboseOnly {
             #[arg(short, long)]
             help: bool,
-            #[arg(global(true), short, long, action = clap::ArgAction::Count)]
+            #[arg(global = true, short, long, action = clap::ArgAction::Count)]
             pub verbose: u8,
             #[arg(value_enum)]
             cmd: Kind,
@@ -260,16 +260,16 @@ pub enum Subcommand {
         #[arg(long, requires = "fix")]
         allow_staged: bool,
         /// clippy lints to allow
-        #[arg(global(true), short = 'A', action = clap::ArgAction::Append, value_name = "LINT")]
+        #[arg(global = true, short = 'A', action = clap::ArgAction::Append, value_name = "LINT")]
         allow: Vec<String>,
         /// clippy lints to deny
-        #[arg(global(true), short = 'D', action = clap::ArgAction::Append, value_name = "LINT")]
+        #[arg(global = true, short = 'D', action = clap::ArgAction::Append, value_name = "LINT")]
         deny: Vec<String>,
         /// clippy lints to warn on
-        #[arg(global(true), short = 'W', action = clap::ArgAction::Append, value_name = "LINT")]
+        #[arg(global = true, short = 'W', action = clap::ArgAction::Append, value_name = "LINT")]
         warn: Vec<String>,
         /// clippy lints to forbid
-        #[arg(global(true), short = 'F', action = clap::ArgAction::Append, value_name = "LINT")]
+        #[arg(global = true, short = 'F', action = clap::ArgAction::Append, value_name = "LINT")]
         forbid: Vec<String>,
     },
     /// Run cargo fix
@@ -456,6 +456,15 @@ Arguments:
         #[arg(long)]
         run: bool,
     },
+    /// Vendor dependencies
+    Vendor {
+        /// Additional `Cargo.toml` to sync and vendor
+        #[arg(long)]
+        sync: Vec<PathBuf>,
+        /// Always include version in subdir name
+        #[arg(long)]
+        versioned_dirs: bool,
+    },
 }
 
 impl Subcommand {
@@ -476,6 +485,7 @@ impl Subcommand {
             Subcommand::Run { .. } => Kind::Run,
             Subcommand::Setup { .. } => Kind::Setup,
             Subcommand::Suggest { .. } => Kind::Suggest,
+            Subcommand::Vendor { .. } => Kind::Vendor,
         }
     }
 
@@ -581,6 +591,20 @@ impl Subcommand {
             _ => false,
         }
     }
+
+    pub fn vendor_versioned_dirs(&self) -> bool {
+        match *self {
+            Subcommand::Vendor { versioned_dirs, .. } => versioned_dirs,
+            _ => false,
+        }
+    }
+
+    pub fn vendor_sync_args(&self) -> Vec<PathBuf> {
+        match self {
+            Subcommand::Vendor { sync, .. } => sync.clone(),
+            _ => vec![],
+        }
+    }
 }
 
 /// Returns the shell completion for a given shell, if the result differs from the current
diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs
index e03b1e17908..6ccf29fb6cb 100644
--- a/src/bootstrap/src/core/sanity.rs
+++ b/src/bootstrap/src/core/sanity.rs
@@ -14,6 +14,7 @@ use std::ffi::{OsStr, OsString};
 use std::fs;
 use std::path::PathBuf;
 use std::process::Command;
+use walkdir::WalkDir;
 
 use crate::builder::Kind;
 use crate::core::config::Target;
@@ -177,6 +178,34 @@ than building it.
             continue;
         }
 
+        // Check if there exists a built-in target in the list of supported targets.
+        let mut has_target = false;
+        let target_str = target.to_string();
+
+        let supported_target_list =
+            output(Command::new(&build.config.initial_rustc).args(["--print", "target-list"]));
+
+        has_target |= supported_target_list.contains(&target_str);
+
+        // If not, check for a valid file location that may have been specified
+        // by the user for the custom target.
+        if let Some(custom_target_path) = env::var_os("RUST_TARGET_PATH") {
+            let mut target_os_str = OsString::from(&target_str);
+            target_os_str.push(".json");
+            // Recursively traverse through nested directories.
+            let walker = WalkDir::new(custom_target_path).into_iter();
+            for entry in walker.filter_map(|e| e.ok()) {
+                has_target |= entry.file_name() == target_os_str;
+            }
+        }
+
+        if !has_target && !["A", "B", "C"].contains(&target_str.as_str()) {
+            panic!(
+                "No such target exists in the target list,
+                specify a correct location of the JSON specification file for custom targets!"
+            );
+        }
+
         if !build.config.dry_run() {
             cmd_finder.must_have(build.cc(*target));
             if let Some(ar) = build.ar(*target) {
diff --git a/src/etc/completions/x.py.fish b/src/etc/completions/x.py.fish
index 5ae27bc8c91..40a25f13fcb 100644
--- a/src/etc/completions/x.py.fish
+++ b/src/etc/completions/x.py.fish
@@ -47,6 +47,7 @@ complete -c x.py -n "__fish_use_subcommand" -f -a "install" -d 'Install distribu
 complete -c x.py -n "__fish_use_subcommand" -f -a "run" -d 'Run tools contained in this repository'
 complete -c x.py -n "__fish_use_subcommand" -f -a "setup" -d 'Set up the environment for development'
 complete -c x.py -n "__fish_use_subcommand" -f -a "suggest" -d 'Suggest a subset of tests to run, based on modified files'
+complete -c x.py -n "__fish_use_subcommand" -f -a "vendor" -d 'Vendor dependencies'
 complete -c x.py -n "__fish_seen_subcommand_from build" -l config -d 'TOML configuration file for build' -r -F
 complete -c x.py -n "__fish_seen_subcommand_from build" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)"
 complete -c x.py -n "__fish_seen_subcommand_from build" -l build -d 'build target of the stage0 compiler' -r -f
@@ -590,3 +591,39 @@ complete -c x.py -n "__fish_seen_subcommand_from suggest" -l llvm-profile-genera
 complete -c x.py -n "__fish_seen_subcommand_from suggest" -l enable-bolt-settings -d 'Enable BOLT link flags'
 complete -c x.py -n "__fish_seen_subcommand_from suggest" -l skip-stage0-validation -d 'Skip stage0 compiler validation'
 complete -c x.py -n "__fish_seen_subcommand_from suggest" -s h -l help -d 'Print help (see more with \'--help\')'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l sync -d 'Additional `Cargo.toml` to sync and vendor' -r -F
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l config -d 'TOML configuration file for build' -r -F
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l build-dir -d 'Build directory, overrides `build.build-dir` in `config.toml`' -r -f -a "(__fish_complete_directories)"
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l build -d 'build target of the stage0 compiler' -r -f
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l host -d 'host targets to build' -r -f
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l target -d 'target targets to build' -r -f
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l exclude -d 'build paths to exclude' -r -F
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l skip -d 'build paths to skip' -r -F
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l rustc-error-format -r -f
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l on-fail -d 'command to run on failure' -r -f -a "(__fish_complete_command)"
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l stage -d 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)' -r -f
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l keep-stage -d 'stage(s) to keep without recompiling (pass multiple times to keep e.g., both stages 0 and 1)' -r -f
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l keep-stage-std -d 'stage(s) of the standard library to keep without recompiling (pass multiple times to keep e.g., both stages 0 and 1)' -r -f
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l src -d 'path to the root of the rust checkout' -r -f -a "(__fish_complete_directories)"
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -s j -l jobs -d 'number of jobs to run in parallel' -r -f
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l warnings -d 'if value is deny, will deny warnings if value is warn, will emit warnings otherwise, use the default configured behaviour' -r -f -a "{deny	'',warn	'',default	''}"
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l error-format -d 'rustc error format' -r -f
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l color -d 'whether to use color in cargo and rustc output' -r -f -a "{always	'',never	'',auto	''}"
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l llvm-skip-rebuild -d 'whether rebuilding llvm should be skipped, overriding `skip-rebuld` in config.toml' -r -f -a "{true	'',false	''}"
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l rust-profile-generate -d 'generate PGO profile with rustc build' -r -F
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l rust-profile-use -d 'use PGO profile for rustc build' -r -F
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l llvm-profile-use -d 'use PGO profile for LLVM build' -r -F
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l reproducible-artifact -d 'Additional reproducible artifacts that should be added to the reproducible artifacts archive' -r
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l set -d 'override options in config.toml' -r -f
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l versioned-dirs -d 'Always include version in subdir name'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -s v -l verbose -d 'use verbose output (-vv for very verbose)'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -s i -l incremental -d 'use incremental compilation'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l include-default-paths -d 'include default paths in addition to the provided ones'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l dry-run -d 'dry run; don\'t build anything'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l dump-bootstrap-shims -d 'Indicates whether to dump the work done from bootstrap shims'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l json-output -d 'use message-format=json'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l bypass-bootstrap-lock -d 'Bootstrap uses this value to decide whether it should bypass locking the build process. This is rarely needed (e.g., compiling the std library for different targets in parallel)'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l llvm-profile-generate -d 'generate PGO profile with llvm built for rustc'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l enable-bolt-settings -d 'Enable BOLT link flags'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -l skip-stage0-validation -d 'Skip stage0 compiler validation'
+complete -c x.py -n "__fish_seen_subcommand_from vendor" -s h -l help -d 'Print help (see more with \'--help\')'
diff --git a/src/etc/completions/x.py.ps1 b/src/etc/completions/x.py.ps1
index d39639a1f91..f3d1d372c73 100644
--- a/src/etc/completions/x.py.ps1
+++ b/src/etc/completions/x.py.ps1
@@ -74,6 +74,7 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock {
             [CompletionResult]::new('run', 'run', [CompletionResultType]::ParameterValue, 'Run tools contained in this repository')
             [CompletionResult]::new('setup', 'setup', [CompletionResultType]::ParameterValue, 'Set up the environment for development')
             [CompletionResult]::new('suggest', 'suggest', [CompletionResultType]::ParameterValue, 'Suggest a subset of tests to run, based on modified files')
+            [CompletionResult]::new('vendor', 'vendor', [CompletionResultType]::ParameterValue, 'Vendor dependencies')
             break
         }
         'x.py;build' {
@@ -724,6 +725,49 @@ Register-ArgumentCompleter -Native -CommandName 'x.py' -ScriptBlock {
             [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
             break
         }
+        'x.py;vendor' {
+            [CompletionResult]::new('--sync', 'sync', [CompletionResultType]::ParameterName, 'Additional `Cargo.toml` to sync and vendor')
+            [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'TOML configuration file for build')
+            [CompletionResult]::new('--build-dir', 'build-dir', [CompletionResultType]::ParameterName, 'Build directory, overrides `build.build-dir` in `config.toml`')
+            [CompletionResult]::new('--build', 'build', [CompletionResultType]::ParameterName, 'build target of the stage0 compiler')
+            [CompletionResult]::new('--host', 'host', [CompletionResultType]::ParameterName, 'host targets to build')
+            [CompletionResult]::new('--target', 'target', [CompletionResultType]::ParameterName, 'target targets to build')
+            [CompletionResult]::new('--exclude', 'exclude', [CompletionResultType]::ParameterName, 'build paths to exclude')
+            [CompletionResult]::new('--skip', 'skip', [CompletionResultType]::ParameterName, 'build paths to skip')
+            [CompletionResult]::new('--rustc-error-format', 'rustc-error-format', [CompletionResultType]::ParameterName, 'rustc-error-format')
+            [CompletionResult]::new('--on-fail', 'on-fail', [CompletionResultType]::ParameterName, 'command to run on failure')
+            [CompletionResult]::new('--stage', 'stage', [CompletionResultType]::ParameterName, 'stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)')
+            [CompletionResult]::new('--keep-stage', 'keep-stage', [CompletionResultType]::ParameterName, 'stage(s) to keep without recompiling (pass multiple times to keep e.g., both stages 0 and 1)')
+            [CompletionResult]::new('--keep-stage-std', 'keep-stage-std', [CompletionResultType]::ParameterName, 'stage(s) of the standard library to keep without recompiling (pass multiple times to keep e.g., both stages 0 and 1)')
+            [CompletionResult]::new('--src', 'src', [CompletionResultType]::ParameterName, 'path to the root of the rust checkout')
+            [CompletionResult]::new('-j', 'j', [CompletionResultType]::ParameterName, 'number of jobs to run in parallel')
+            [CompletionResult]::new('--jobs', 'jobs', [CompletionResultType]::ParameterName, 'number of jobs to run in parallel')
+            [CompletionResult]::new('--warnings', 'warnings', [CompletionResultType]::ParameterName, 'if value is deny, will deny warnings if value is warn, will emit warnings otherwise, use the default configured behaviour')
+            [CompletionResult]::new('--error-format', 'error-format', [CompletionResultType]::ParameterName, 'rustc error format')
+            [CompletionResult]::new('--color', 'color', [CompletionResultType]::ParameterName, 'whether to use color in cargo and rustc output')
+            [CompletionResult]::new('--llvm-skip-rebuild', 'llvm-skip-rebuild', [CompletionResultType]::ParameterName, 'whether rebuilding llvm should be skipped, overriding `skip-rebuld` in config.toml')
+            [CompletionResult]::new('--rust-profile-generate', 'rust-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with rustc build')
+            [CompletionResult]::new('--rust-profile-use', 'rust-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for rustc build')
+            [CompletionResult]::new('--llvm-profile-use', 'llvm-profile-use', [CompletionResultType]::ParameterName, 'use PGO profile for LLVM build')
+            [CompletionResult]::new('--reproducible-artifact', 'reproducible-artifact', [CompletionResultType]::ParameterName, 'Additional reproducible artifacts that should be added to the reproducible artifacts archive')
+            [CompletionResult]::new('--set', 'set', [CompletionResultType]::ParameterName, 'override options in config.toml')
+            [CompletionResult]::new('--versioned-dirs', 'versioned-dirs', [CompletionResultType]::ParameterName, 'Always include version in subdir name')
+            [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)')
+            [CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'use verbose output (-vv for very verbose)')
+            [CompletionResult]::new('-i', 'i', [CompletionResultType]::ParameterName, 'use incremental compilation')
+            [CompletionResult]::new('--incremental', 'incremental', [CompletionResultType]::ParameterName, 'use incremental compilation')
+            [CompletionResult]::new('--include-default-paths', 'include-default-paths', [CompletionResultType]::ParameterName, 'include default paths in addition to the provided ones')
+            [CompletionResult]::new('--dry-run', 'dry-run', [CompletionResultType]::ParameterName, 'dry run; don''t build anything')
+            [CompletionResult]::new('--dump-bootstrap-shims', 'dump-bootstrap-shims', [CompletionResultType]::ParameterName, 'Indicates whether to dump the work done from bootstrap shims')
+            [CompletionResult]::new('--json-output', 'json-output', [CompletionResultType]::ParameterName, 'use message-format=json')
+            [CompletionResult]::new('--bypass-bootstrap-lock', 'bypass-bootstrap-lock', [CompletionResultType]::ParameterName, 'Bootstrap uses this value to decide whether it should bypass locking the build process. This is rarely needed (e.g., compiling the std library for different targets in parallel)')
+            [CompletionResult]::new('--llvm-profile-generate', 'llvm-profile-generate', [CompletionResultType]::ParameterName, 'generate PGO profile with llvm built for rustc')
+            [CompletionResult]::new('--enable-bolt-settings', 'enable-bolt-settings', [CompletionResultType]::ParameterName, 'Enable BOLT link flags')
+            [CompletionResult]::new('--skip-stage0-validation', 'skip-stage0-validation', [CompletionResultType]::ParameterName, 'Skip stage0 compiler validation')
+            [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
+            [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
+            break
+        }
     })
 
     $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
diff --git a/src/etc/completions/x.py.sh b/src/etc/completions/x.py.sh
index 1f1a9a70767..82cacb52ffe 100644
--- a/src/etc/completions/x.py.sh
+++ b/src/etc/completions/x.py.sh
@@ -57,6 +57,9 @@ _x.py() {
             bootstrap,test)
                 cmd="bootstrap__test"
                 ;;
+            bootstrap,vendor)
+                cmd="bootstrap__vendor"
+                ;;
             *)
                 ;;
         esac
@@ -64,7 +67,7 @@ _x.py() {
 
     case "${cmd}" in
         x.py)
-            opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [PATHS]... [ARGS]... build check clippy fix fmt doc test miri bench clean dist install run setup suggest"
+            opts="-v -i -j -h --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [PATHS]... [ARGS]... build check clippy fix fmt doc test miri bench clean dist install run setup suggest vendor"
             if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
                 COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                 return 0
@@ -1879,6 +1882,120 @@ _x.py() {
             COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
             return 0
             ;;
+        x.py__vendor)
+            opts="-v -i -j -h --sync --versioned-dirs --verbose --incremental --config --build-dir --build --host --target --exclude --skip --include-default-paths --rustc-error-format --on-fail --dry-run --dump-bootstrap-shims --stage --keep-stage --keep-stage-std --src --jobs --warnings --error-format --json-output --color --bypass-bootstrap-lock --llvm-skip-rebuild --rust-profile-generate --rust-profile-use --llvm-profile-use --llvm-profile-generate --enable-bolt-settings --skip-stage0-validation --reproducible-artifact --set --help [PATHS]... [ARGS]..."
+            if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
+                COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+                return 0
+            fi
+            case "${prev}" in
+                --sync)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
+                --config)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
+                --build-dir)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
+                --build)
+                    COMPREPLY=("${cur}")
+                    return 0
+                    ;;
+                --host)
+                    COMPREPLY=("${cur}")
+                    return 0
+                    ;;
+                --target)
+                    COMPREPLY=("${cur}")
+                    return 0
+                    ;;
+                --exclude)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
+                --skip)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
+                --rustc-error-format)
+                    COMPREPLY=("${cur}")
+                    return 0
+                    ;;
+                --on-fail)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
+                --stage)
+                    COMPREPLY=("${cur}")
+                    return 0
+                    ;;
+                --keep-stage)
+                    COMPREPLY=("${cur}")
+                    return 0
+                    ;;
+                --keep-stage-std)
+                    COMPREPLY=("${cur}")
+                    return 0
+                    ;;
+                --src)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
+                --jobs)
+                    COMPREPLY=("${cur}")
+                    return 0
+                    ;;
+                -j)
+                    COMPREPLY=("${cur}")
+                    return 0
+                    ;;
+                --warnings)
+                    COMPREPLY=($(compgen -W "deny warn default" -- "${cur}"))
+                    return 0
+                    ;;
+                --error-format)
+                    COMPREPLY=("${cur}")
+                    return 0
+                    ;;
+                --color)
+                    COMPREPLY=($(compgen -W "always never auto" -- "${cur}"))
+                    return 0
+                    ;;
+                --llvm-skip-rebuild)
+                    COMPREPLY=($(compgen -W "true false" -- "${cur}"))
+                    return 0
+                    ;;
+                --rust-profile-generate)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
+                --rust-profile-use)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
+                --llvm-profile-use)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
+                --reproducible-artifact)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
+                --set)
+                    COMPREPLY=("${cur}")
+                    return 0
+                    ;;
+                *)
+                    COMPREPLY=()
+                    ;;
+            esac
+            COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
+            return 0
+            ;;
     esac
 }
 
diff --git a/src/etc/completions/x.py.zsh b/src/etc/completions/x.py.zsh
index b920309ac2c..12e96dbd40a 100644
--- a/src/etc/completions/x.py.zsh
+++ b/src/etc/completions/x.py.zsh
@@ -743,6 +743,51 @@ _arguments "${_arguments_options[@]}" \
 '*::paths -- paths for the subcommand:_files' \
 && ret=0
 ;;
+(vendor)
+_arguments "${_arguments_options[@]}" \
+'*--sync=[Additional \`Cargo.toml\` to sync and vendor]:SYNC:_files' \
+'--config=[TOML configuration file for build]:FILE:_files' \
+'--build-dir=[Build directory, overrides \`build.build-dir\` in \`config.toml\`]:DIR:_files -/' \
+'--build=[build target of the stage0 compiler]:BUILD:( )' \
+'--host=[host targets to build]:HOST:( )' \
+'--target=[target targets to build]:TARGET:( )' \
+'*--exclude=[build paths to exclude]:PATH:_files' \
+'*--skip=[build paths to skip]:PATH:_files' \
+'--rustc-error-format=[]:RUSTC_ERROR_FORMAT:( )' \
+'--on-fail=[command to run on failure]:CMD:_cmdstring' \
+'--stage=[stage to build (indicates compiler to use/test, e.g., stage 0 uses the bootstrap compiler, stage 1 the stage 0 rustc artifacts, etc.)]:N:( )' \
+'*--keep-stage=[stage(s) to keep without recompiling (pass multiple times to keep e.g., both stages 0 and 1)]:N:( )' \
+'*--keep-stage-std=[stage(s) of the standard library to keep without recompiling (pass multiple times to keep e.g., both stages 0 and 1)]:N:( )' \
+'--src=[path to the root of the rust checkout]:DIR:_files -/' \
+'-j+[number of jobs to run in parallel]:JOBS:( )' \
+'--jobs=[number of jobs to run in parallel]:JOBS:( )' \
+'--warnings=[if value is deny, will deny warnings if value is warn, will emit warnings otherwise, use the default configured behaviour]:deny|warn:(deny warn default)' \
+'--error-format=[rustc error format]:FORMAT:( )' \
+'--color=[whether to use color in cargo and rustc output]:STYLE:(always never auto)' \
+'--llvm-skip-rebuild=[whether rebuilding llvm should be skipped, overriding \`skip-rebuld\` in config.toml]:VALUE:(true false)' \
+'--rust-profile-generate=[generate PGO profile with rustc build]:PROFILE:_files' \
+'--rust-profile-use=[use PGO profile for rustc build]:PROFILE:_files' \
+'--llvm-profile-use=[use PGO profile for LLVM build]:PROFILE:_files' \
+'*--reproducible-artifact=[Additional reproducible artifacts that should be added to the reproducible artifacts archive]:REPRODUCIBLE_ARTIFACT: ' \
+'*--set=[override options in config.toml]:section.option=value:( )' \
+'--versioned-dirs[Always include version in subdir name]' \
+'*-v[use verbose output (-vv for very verbose)]' \
+'*--verbose[use verbose output (-vv for very verbose)]' \
+'-i[use incremental compilation]' \
+'--incremental[use incremental compilation]' \
+'--include-default-paths[include default paths in addition to the provided ones]' \
+'--dry-run[dry run; don'\''t build anything]' \
+'--dump-bootstrap-shims[Indicates whether to dump the work done from bootstrap shims]' \
+'--json-output[use message-format=json]' \
+'--bypass-bootstrap-lock[Bootstrap uses this value to decide whether it should bypass locking the build process. This is rarely needed (e.g., compiling the std library for different targets in parallel)]' \
+'--llvm-profile-generate[generate PGO profile with llvm built for rustc]' \
+'--enable-bolt-settings[Enable BOLT link flags]' \
+'--skip-stage0-validation[Skip stage0 compiler validation]' \
+'-h[Print help (see more with '\''--help'\'')]' \
+'--help[Print help (see more with '\''--help'\'')]' \
+'*::paths -- paths for the subcommand:_files' \
+&& ret=0
+;;
         esac
     ;;
 esac
@@ -766,6 +811,7 @@ _x.py_commands() {
 'run:Run tools contained in this repository' \
 'setup:Set up the environment for development' \
 'suggest:Suggest a subset of tests to run, based on modified files' \
+'vendor:Vendor dependencies' \
     )
     _describe -t commands 'x.py commands' commands "$@"
 }
@@ -844,6 +890,11 @@ _x.py__test_commands() {
     local commands; commands=()
     _describe -t commands 'x.py test commands' commands "$@"
 }
+(( $+functions[_x.py__vendor_commands] )) ||
+_x.py__vendor_commands() {
+    local commands; commands=()
+    _describe -t commands 'x.py vendor commands' commands "$@"
+}
 
 if [ "$funcstack[1]" = "_x.py" ]; then
     _x.py "$@"
diff --git a/src/tools/cargo b/src/tools/cargo
-Subproject c9392675917adc2edab269eea27c222b5359c63
+Subproject b60a1555155111e962018007a6d0ef85207db46
diff --git a/src/tools/clippy/tests/ui/tabs_in_doc_comments.stderr b/src/tools/clippy/tests/ui/tabs_in_doc_comments.stderr
index 23d5dcd3a8d..aef6c391452 100644
--- a/src/tools/clippy/tests/ui/tabs_in_doc_comments.stderr
+++ b/src/tools/clippy/tests/ui/tabs_in_doc_comments.stderr
@@ -1,53 +1,53 @@
 error: using tabs in doc comments is not recommended
-  --> tests/ui/tabs_in_doc_comments.rs:10:9
+  --> tests/ui/tabs_in_doc_comments.rs:6:5
    |
-LL |     ///     - First String:
-   |         ^^^^ help: consider using four spaces per tab
+LL | ///     - first        one
+   |     ^^^^ help: consider using four spaces per tab
    |
    = note: `-D clippy::tabs-in-doc-comments` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::tabs_in_doc_comments)]`
 
 error: using tabs in doc comments is not recommended
-  --> tests/ui/tabs_in_doc_comments.rs:11:9
+  --> tests/ui/tabs_in_doc_comments.rs:6:13
    |
-LL |     ///         - needs to be inside here
-   |         ^^^^^^^^ help: consider using four spaces per tab
+LL | ///     - first        one
+   |                ^^^^^^^^ help: consider using four spaces per tab
 
 error: using tabs in doc comments is not recommended
-  --> tests/ui/tabs_in_doc_comments.rs:14:9
+  --> tests/ui/tabs_in_doc_comments.rs:7:5
    |
-LL |     ///     - Second String:
-   |         ^^^^ help: consider using four spaces per tab
+LL | ///     - second    one
+   |     ^^^^ help: consider using four spaces per tab
 
 error: using tabs in doc comments is not recommended
-  --> tests/ui/tabs_in_doc_comments.rs:15:9
+  --> tests/ui/tabs_in_doc_comments.rs:7:14
    |
-LL |     ///         - needs to be inside here
-   |         ^^^^^^^^ help: consider using four spaces per tab
+LL | ///     - second    one
+   |                 ^^^^ help: consider using four spaces per tab
 
 error: using tabs in doc comments is not recommended
-  --> tests/ui/tabs_in_doc_comments.rs:6:5
+  --> tests/ui/tabs_in_doc_comments.rs:10:9
    |
-LL | ///     - first        one
-   |     ^^^^ help: consider using four spaces per tab
+LL |     ///     - First String:
+   |         ^^^^ help: consider using four spaces per tab
 
 error: using tabs in doc comments is not recommended
-  --> tests/ui/tabs_in_doc_comments.rs:6:13
+  --> tests/ui/tabs_in_doc_comments.rs:11:9
    |
-LL | ///     - first        one
-   |                ^^^^^^^^ help: consider using four spaces per tab
+LL |     ///         - needs to be inside here
+   |         ^^^^^^^^ help: consider using four spaces per tab
 
 error: using tabs in doc comments is not recommended
-  --> tests/ui/tabs_in_doc_comments.rs:7:5
+  --> tests/ui/tabs_in_doc_comments.rs:14:9
    |
-LL | ///     - second    one
-   |     ^^^^ help: consider using four spaces per tab
+LL |     ///     - Second String:
+   |         ^^^^ help: consider using four spaces per tab
 
 error: using tabs in doc comments is not recommended
-  --> tests/ui/tabs_in_doc_comments.rs:7:14
+  --> tests/ui/tabs_in_doc_comments.rs:15:9
    |
-LL | ///     - second    one
-   |                 ^^^^ help: consider using four spaces per tab
+LL |     ///         - needs to be inside here
+   |         ^^^^^^^^ help: consider using four spaces per tab
 
 error: aborting due to 8 previous errors
 
diff --git a/src/tools/compiletest/Cargo.toml b/src/tools/compiletest/Cargo.toml
index 38c29b91928..52beb4c8b3d 100644
--- a/src/tools/compiletest/Cargo.toml
+++ b/src/tools/compiletest/Cargo.toml
@@ -21,10 +21,8 @@ regex = "1.0"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 rustfix = "0.8.1"
-once_cell = "1.16.0"
 walkdir = "2"
 glob = "0.3.0"
-lazycell = "1.3.0"
 anyhow = "1"
 home = "0.5.5"
 
diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs
index aa69791b3b4..afbcc3e92bc 100644
--- a/src/tools/compiletest/src/common.rs
+++ b/src/tools/compiletest/src/common.rs
@@ -6,10 +6,10 @@ use std::iter;
 use std::path::{Path, PathBuf};
 use std::process::Command;
 use std::str::FromStr;
+use std::sync::OnceLock;
 
 use crate::util::{add_dylib_path, PathBufExt};
 use build_helper::git::GitConfig;
-use lazycell::AtomicLazyCell;
 use serde::de::{Deserialize, Deserializer, Error as _};
 use std::collections::{HashMap, HashSet};
 use test::{ColorConfig, OutputFormat};
@@ -384,7 +384,7 @@ pub struct Config {
     /// Only rerun the tests that result has been modified accoring to Git status
     pub only_modified: bool,
 
-    pub target_cfgs: AtomicLazyCell<TargetCfgs>,
+    pub target_cfgs: OnceLock<TargetCfgs>,
 
     pub nocapture: bool,
 
@@ -406,13 +406,7 @@ impl Config {
     }
 
     pub fn target_cfgs(&self) -> &TargetCfgs {
-        match self.target_cfgs.borrow() {
-            Some(cfgs) => cfgs,
-            None => {
-                let _ = self.target_cfgs.fill(TargetCfgs::new(self));
-                self.target_cfgs.borrow().unwrap()
-            }
-        }
+        self.target_cfgs.get_or_init(|| TargetCfgs::new(self))
     }
 
     pub fn target_cfg(&self) -> &TargetCfg {
diff --git a/src/tools/compiletest/src/errors.rs b/src/tools/compiletest/src/errors.rs
index 3231f9fd3a4..c11d3da13a8 100644
--- a/src/tools/compiletest/src/errors.rs
+++ b/src/tools/compiletest/src/errors.rs
@@ -6,8 +6,8 @@ use std::io::prelude::*;
 use std::io::BufReader;
 use std::path::Path;
 use std::str::FromStr;
+use std::sync::OnceLock;
 
-use once_cell::sync::Lazy;
 use regex::Regex;
 use tracing::*;
 
@@ -117,10 +117,11 @@ fn parse_expected(
     //     //~^^^^^
     //     //[rev1]~
     //     //[rev1,rev2]~^^
-    static RE: Lazy<Regex> =
-        Lazy::new(|| Regex::new(r"//(?:\[(?P<revs>[\w\-,]+)])?~(?P<adjust>\||\^*)").unwrap());
+    static RE: OnceLock<Regex> = OnceLock::new();
 
-    let captures = RE.captures(line)?;
+    let captures = RE
+        .get_or_init(|| Regex::new(r"//(?:\[(?P<revs>[\w\-,]+)])?~(?P<adjust>\||\^*)").unwrap())
+        .captures(line)?;
 
     match (test_revision, captures.name("revs")) {
         // Only error messages that contain our revision between the square brackets apply to us.
diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs
index 8aafbb3e399..34e65c7d61f 100644
--- a/src/tools/compiletest/src/header.rs
+++ b/src/tools/compiletest/src/header.rs
@@ -5,8 +5,8 @@ use std::io::prelude::*;
 use std::io::BufReader;
 use std::path::{Path, PathBuf};
 use std::process::Command;
+use std::sync::OnceLock;
 
-use once_cell::sync::Lazy;
 use regex::Regex;
 use tracing::*;
 
@@ -1021,8 +1021,9 @@ fn iter_header(
     let mut line_number = 0;
 
     // Match on error annotations like `//~ERROR`.
-    static REVISION_MAGIC_COMMENT_RE: Lazy<Regex> =
-        Lazy::new(|| Regex::new("//(\\[.*\\])?~.*").unwrap());
+    static REVISION_MAGIC_COMMENT_RE: OnceLock<Regex> = OnceLock::new();
+    let revision_magic_comment_re =
+        REVISION_MAGIC_COMMENT_RE.get_or_init(|| Regex::new("//(\\[.*\\])?~.*").unwrap());
 
     loop {
         line_number += 1;
@@ -1087,7 +1088,7 @@ fn iter_header(
             });
         // Then we try to check for legacy-style candidates, which are not the magic ~ERROR family
         // error annotations.
-        } else if !REVISION_MAGIC_COMMENT_RE.is_match(ln) {
+        } else if !revision_magic_comment_re.is_match(ln) {
             let Some((_, rest)) = line_directive("//", ln) else {
                 continue;
             };
diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs
index c8a8b79921e..2e45caec46c 100644
--- a/src/tools/compiletest/src/lib.rs
+++ b/src/tools/compiletest/src/lib.rs
@@ -24,13 +24,13 @@ use crate::util::logv;
 use build_helper::git::{get_git_modified_files, get_git_untracked_files};
 use core::panic;
 use getopts::Options;
-use lazycell::AtomicLazyCell;
 use std::collections::HashSet;
 use std::ffi::OsString;
 use std::fs;
 use std::io::{self, ErrorKind};
 use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
+use std::sync::{Arc, OnceLock};
 use std::time::SystemTime;
 use std::{env, vec};
 use test::ColorConfig;
@@ -39,7 +39,6 @@ use walkdir::WalkDir;
 
 use self::header::{make_test_description, EarlyProps};
 use crate::header::HeadersCache;
-use std::sync::Arc;
 
 pub fn parse_config(args: Vec<String>) -> Config {
     let mut opts = Options::new();
@@ -320,7 +319,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
 
         force_rerun: matches.opt_present("force-rerun"),
 
-        target_cfgs: AtomicLazyCell::new(),
+        target_cfgs: OnceLock::new(),
 
         nocapture: matches.opt_present("nocapture"),
 
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 1dd639a8918..b0f84b1162f 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -36,7 +36,6 @@ use std::sync::Arc;
 
 use anyhow::Context;
 use glob::glob;
-use once_cell::sync::Lazy;
 use tracing::*;
 
 use crate::extract_gdb_version;
@@ -48,6 +47,13 @@ use debugger::DebuggerCommands;
 #[cfg(test)]
 mod tests;
 
+macro_rules! static_regex {
+    ($re:literal) => {{
+        static RE: ::std::sync::OnceLock<::regex::Regex> = ::std::sync::OnceLock::new();
+        RE.get_or_init(|| ::regex::Regex::new($re).unwrap())
+    }};
+}
+
 const FAKE_SRC_BASE: &str = "fake-test-src-base";
 
 #[cfg(windows)]
@@ -765,28 +771,23 @@ impl<'test> TestCx<'test> {
         // `  100|` => `   LL|`
         // `  | 1000|`    => `  |   LL|`
         // `  |  | 1000|` => `  |  |   LL|`
-        static LINE_NUMBER_RE: Lazy<Regex> =
-            Lazy::new(|| Regex::new(r"(?m:^)(?<prefix>(?:  \|)*) *[0-9]+\|").unwrap());
-        let coverage = LINE_NUMBER_RE.replace_all(&coverage, "${prefix}   LL|");
+        let coverage = static_regex!(r"(?m:^)(?<prefix>(?:  \|)*) *[0-9]+\|")
+            .replace_all(&coverage, "${prefix}   LL|");
 
         // `  |  Branch (1:`     => `  |  Branch (LL:`
         // `  |  |  Branch (10:` => `  |  |  Branch (LL:`
-        static BRANCH_LINE_NUMBER_RE: Lazy<Regex> =
-            Lazy::new(|| Regex::new(r"(?m:^)(?<prefix>(?:  \|)+  Branch \()[0-9]+:").unwrap());
-        let coverage = BRANCH_LINE_NUMBER_RE.replace_all(&coverage, "${prefix}LL:");
+        let coverage = static_regex!(r"(?m:^)(?<prefix>(?:  \|)+  Branch \()[0-9]+:")
+            .replace_all(&coverage, "${prefix}LL:");
 
         // `  |---> MC/DC Decision Region (1:30) to (2:`     => `  |---> MC/DC Decision Region (LL:30) to (LL:`
-        static MCDC_DECISION_LINE_NUMBER_RE: Lazy<Regex> = Lazy::new(|| {
-            Regex::new(r"(?m:^)(?<prefix>(?:  \|)+---> MC/DC Decision Region \()[0-9]+:(?<middle>[0-9]+\) to \()[0-9]+:").unwrap()
-        });
         let coverage =
-            MCDC_DECISION_LINE_NUMBER_RE.replace_all(&coverage, "${prefix}LL:${middle}LL:");
+            static_regex!(r"(?m:^)(?<prefix>(?:  \|)+---> MC/DC Decision Region \()[0-9]+:(?<middle>[0-9]+\) to \()[0-9]+:")
+            .replace_all(&coverage, "${prefix}LL:${middle}LL:");
 
         // `  |     Condition C1 --> (1:`     => `  |     Condition C1 --> (LL:`
-        static MCDC_CONDITION_LINE_NUMBER_RE: Lazy<Regex> = Lazy::new(|| {
-            Regex::new(r"(?m:^)(?<prefix>(?:  \|)+     Condition C[0-9]+ --> \()[0-9]+:").unwrap()
-        });
-        let coverage = MCDC_CONDITION_LINE_NUMBER_RE.replace_all(&coverage, "${prefix}LL:");
+        let coverage =
+            static_regex!(r"(?m:^)(?<prefix>(?:  \|)+     Condition C[0-9]+ --> \()[0-9]+:")
+                .replace_all(&coverage, "${prefix}LL:");
 
         coverage.into_owned()
     }
@@ -3471,13 +3472,12 @@ impl<'test> TestCx<'test> {
         // the form <crate-name1>.<crate-disambiguator1>-in-<crate-name2>.<crate-disambiguator2>,
         // remove all crate-disambiguators.
         fn remove_crate_disambiguator_from_cgu(cgu: &str) -> String {
-            static RE: Lazy<Regex> = Lazy::new(|| {
-                Regex::new(r"^[^\.]+(?P<d1>\.[[:alnum:]]+)(-in-[^\.]+(?P<d2>\.[[:alnum:]]+))?")
-                    .unwrap()
-            });
-
-            let captures =
-                RE.captures(cgu).unwrap_or_else(|| panic!("invalid cgu name encountered: {}", cgu));
+            let Some(captures) =
+                static_regex!(r"^[^\.]+(?P<d1>\.[[:alnum:]]+)(-in-[^\.]+(?P<d2>\.[[:alnum:]]+))?")
+                    .captures(cgu)
+            else {
+                panic!("invalid cgu name encountered: {cgu}");
+            };
 
             let mut new_name = cgu.to_owned();
 
@@ -4073,18 +4073,16 @@ impl<'test> TestCx<'test> {
                 // 'uploaded "$TEST_BUILD_DIR/<test_executable>, waiting for result"'
                 // is printed to stdout by the client and then captured in the ProcRes,
                 // so it needs to be removed when comparing the run-pass test execution output.
-                static REMOTE_TEST_RE: Lazy<Regex> = Lazy::new(|| {
-                    Regex::new(
-                        "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-.]+)+\", waiting for result\n"
-                    )
-                    .unwrap()
-                });
-                normalized_stdout = REMOTE_TEST_RE.replace(&normalized_stdout, "").to_string();
+                normalized_stdout = static_regex!(
+                    "^uploaded \"\\$TEST_BUILD_DIR(/[[:alnum:]_\\-.]+)+\", waiting for result\n"
+                )
+                .replace(&normalized_stdout, "")
+                .to_string();
                 // When there is a panic, the remote-test-client also prints "died due to signal";
                 // that needs to be removed as well.
-                static SIGNAL_DIED_RE: Lazy<Regex> =
-                    Lazy::new(|| Regex::new("^died due to signal [0-9]+\n").unwrap());
-                normalized_stdout = SIGNAL_DIED_RE.replace(&normalized_stdout, "").to_string();
+                normalized_stdout = static_regex!("^died due to signal [0-9]+\n")
+                    .replace(&normalized_stdout, "")
+                    .to_string();
                 // FIXME: it would be much nicer if we could just tell the remote-test-client to not
                 // print these things.
             }
@@ -4556,10 +4554,9 @@ impl<'test> TestCx<'test> {
         // with placeholders as we do not want tests needing updated when compiler source code
         // changes.
         // eg. $SRC_DIR/libcore/mem.rs:323:14 becomes $SRC_DIR/libcore/mem.rs:LL:COL
-        static SRC_DIR_RE: Lazy<Regex> =
-            Lazy::new(|| Regex::new("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?").unwrap());
-
-        normalized = SRC_DIR_RE.replace_all(&normalized, "SRC_DIR$1:LL:COL").into_owned();
+        normalized = static_regex!("SRC_DIR(.+):\\d+:\\d+(: \\d+:\\d+)?")
+            .replace_all(&normalized, "SRC_DIR$1:LL:COL")
+            .into_owned();
 
         normalized = Self::normalize_platform_differences(&normalized);
         normalized = normalized.replace("\t", "\\t"); // makes tabs visible
@@ -4568,34 +4565,29 @@ impl<'test> TestCx<'test> {
         // since they duplicate actual errors and make the output hard to read.
         // This mirrors the regex in src/tools/tidy/src/style.rs, please update
         // both if either are changed.
-        static ANNOTATION_RE: Lazy<Regex> =
-            Lazy::new(|| Regex::new("\\s*//(\\[.*\\])?~.*").unwrap());
-
-        normalized = ANNOTATION_RE.replace_all(&normalized, "").into_owned();
+        normalized =
+            static_regex!("\\s*//(\\[.*\\])?~.*").replace_all(&normalized, "").into_owned();
 
         // This code normalizes various hashes in v0 symbol mangling that is
         // emitted in the ui and mir-opt tests.
-        static V0_CRATE_HASH_PREFIX_RE: Lazy<Regex> =
-            Lazy::new(|| Regex::new(r"_R.*?Cs[0-9a-zA-Z]+_").unwrap());
-        static V0_CRATE_HASH_RE: Lazy<Regex> =
-            Lazy::new(|| Regex::new(r"Cs[0-9a-zA-Z]+_").unwrap());
+        let v0_crate_hash_prefix_re = static_regex!(r"_R.*?Cs[0-9a-zA-Z]+_");
+        let v0_crate_hash_re = static_regex!(r"Cs[0-9a-zA-Z]+_");
 
         const V0_CRATE_HASH_PLACEHOLDER: &str = r"CsCRATE_HASH_";
-        if V0_CRATE_HASH_PREFIX_RE.is_match(&normalized) {
+        if v0_crate_hash_prefix_re.is_match(&normalized) {
             // Normalize crate hash
             normalized =
-                V0_CRATE_HASH_RE.replace_all(&normalized, V0_CRATE_HASH_PLACEHOLDER).into_owned();
+                v0_crate_hash_re.replace_all(&normalized, V0_CRATE_HASH_PLACEHOLDER).into_owned();
         }
 
-        static V0_BACK_REF_PREFIX_RE: Lazy<Regex> =
-            Lazy::new(|| Regex::new(r"\(_R.*?B[0-9a-zA-Z]_").unwrap());
-        static V0_BACK_REF_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"B[0-9a-zA-Z]_").unwrap());
+        let v0_back_ref_prefix_re = static_regex!(r"\(_R.*?B[0-9a-zA-Z]_");
+        let v0_back_ref_re = static_regex!(r"B[0-9a-zA-Z]_");
 
         const V0_BACK_REF_PLACEHOLDER: &str = r"B<REF>_";
-        if V0_BACK_REF_PREFIX_RE.is_match(&normalized) {
+        if v0_back_ref_prefix_re.is_match(&normalized) {
             // Normalize back references (see RFC 2603)
             normalized =
-                V0_BACK_REF_RE.replace_all(&normalized, V0_BACK_REF_PLACEHOLDER).into_owned();
+                v0_back_ref_re.replace_all(&normalized, V0_BACK_REF_PLACEHOLDER).into_owned();
         }
 
         // AllocId are numbered globally in a compilation session. This can lead to changes
@@ -4608,26 +4600,22 @@ impl<'test> TestCx<'test> {
             let mut seen_allocs = indexmap::IndexSet::new();
 
             // The alloc-id appears in pretty-printed allocations.
-            static ALLOC_ID_PP_RE: Lazy<Regex> = Lazy::new(|| {
-                Regex::new(r"╾─*a(lloc)?([0-9]+)(\+0x[0-9]+)?(<imm>)?( \([0-9]+ ptr bytes\))?─*╼")
-                    .unwrap()
-            });
-            normalized = ALLOC_ID_PP_RE
-                .replace_all(&normalized, |caps: &Captures<'_>| {
-                    // Renumber the captured index.
-                    let index = caps.get(2).unwrap().as_str().to_string();
-                    let (index, _) = seen_allocs.insert_full(index);
-                    let offset = caps.get(3).map_or("", |c| c.as_str());
-                    let imm = caps.get(4).map_or("", |c| c.as_str());
-                    // Do not bother keeping it pretty, just make it deterministic.
-                    format!("╾ALLOC{index}{offset}{imm}╼")
-                })
-                .into_owned();
+            normalized = static_regex!(
+                r"╾─*a(lloc)?([0-9]+)(\+0x[0-9]+)?(<imm>)?( \([0-9]+ ptr bytes\))?─*╼"
+            )
+            .replace_all(&normalized, |caps: &Captures<'_>| {
+                // Renumber the captured index.
+                let index = caps.get(2).unwrap().as_str().to_string();
+                let (index, _) = seen_allocs.insert_full(index);
+                let offset = caps.get(3).map_or("", |c| c.as_str());
+                let imm = caps.get(4).map_or("", |c| c.as_str());
+                // Do not bother keeping it pretty, just make it deterministic.
+                format!("╾ALLOC{index}{offset}{imm}╼")
+            })
+            .into_owned();
 
             // The alloc-id appears in a sentence.
-            static ALLOC_ID_RE: Lazy<Regex> =
-                Lazy::new(|| Regex::new(r"\balloc([0-9]+)\b").unwrap());
-            normalized = ALLOC_ID_RE
+            normalized = static_regex!(r"\balloc([0-9]+)\b")
                 .replace_all(&normalized, |caps: &Captures<'_>| {
                     let index = caps.get(1).unwrap().as_str().to_string();
                     let (index, _) = seen_allocs.insert_full(index);
@@ -4650,12 +4638,13 @@ impl<'test> TestCx<'test> {
     /// Replaces backslashes in paths with forward slashes, and replaces CRLF line endings
     /// with LF.
     fn normalize_platform_differences(output: &str) -> String {
-        /// Used to find Windows paths.
-        ///
-        /// It's not possible to detect paths in the error messages generally, but this is a
-        /// decent enough heuristic.
-        static PATH_BACKSLASH_RE: Lazy<Regex> = Lazy::new(|| {
-            Regex::new(
+        let output = output.replace(r"\\", r"\");
+
+        // Used to find Windows paths.
+        //
+        // It's not possible to detect paths in the error messages generally, but this is a
+        // decent enough heuristic.
+        static_regex!(
                 r#"(?x)
                 (?:
                   # Match paths that don't include spaces.
@@ -4663,14 +4652,8 @@ impl<'test> TestCx<'test> {
                 |
                   # If the path starts with a well-known root, then allow spaces and no file extension.
                   \$(?:DIR|SRC_DIR|TEST_BUILD_DIR|BUILD_DIR|LIB_DIR)(?:\\[\pL\pN\.\-_'\ ]+)+
-                )"#,
+                )"#
             )
-            .unwrap()
-        });
-
-        let output = output.replace(r"\\", r"\");
-
-        PATH_BACKSLASH_RE
             .replace_all(&output, |caps: &Captures<'_>| {
                 println!("{}", &caps[0]);
                 caps[0].replace(r"\", "/")
diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version
index a6433a8e286..a45ecda15c4 100644
--- a/src/tools/miri/rust-version
+++ b/src/tools/miri/rust-version
@@ -1 +1 @@
-cb3752d20e0f5d24348062211102a08d46fbecff
+6acb9e75ebc936df737381a9d0b7a7bccd6f0b2f
diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
index cbe70cbffee..8b286871774 100644
--- a/src/tools/miri/src/machine.rs
+++ b/src/tools/miri/src/machine.rs
@@ -31,7 +31,7 @@ use rustc_target::spec::abi::Abi;
 
 use crate::{
     concurrency::{data_race, weak_memory},
-    shims::unix::FdTable,
+    shims::unix,
     *,
 };
 
@@ -439,8 +439,7 @@ pub struct MiriMachine<'mir, 'tcx> {
     /// Ptr-int-cast module global data.
     pub alloc_addresses: alloc_addresses::GlobalState,
 
-    /// Environment variables set by `setenv`.
-    /// Miri does not expose env vars from the host to the emulated program.
+    /// Environment variables.
     pub(crate) env_vars: EnvVars<'tcx>,
 
     /// Return place of the main function.
@@ -465,9 +464,9 @@ pub struct MiriMachine<'mir, 'tcx> {
     pub(crate) validate: bool,
 
     /// The table of file descriptors.
-    pub(crate) fds: shims::unix::FdTable,
+    pub(crate) fds: unix::FdTable,
     /// The table of directory descriptors.
-    pub(crate) dirs: shims::unix::DirTable,
+    pub(crate) dirs: unix::DirTable,
 
     /// This machine's monotone clock.
     pub(crate) clock: Clock,
@@ -642,7 +641,7 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
             tls: TlsData::default(),
             isolated_op: config.isolated_op,
             validate: config.validate,
-            fds: FdTable::new(config.mute_stdout_stderr),
+            fds: unix::FdTable::new(config.mute_stdout_stderr),
             dirs: Default::default(),
             layouts,
             threads: ThreadManager::default(),
diff --git a/src/tools/miri/src/shims/env.rs b/src/tools/miri/src/shims/env.rs
index 298fefdb0f3..fc0160fdf21 100644
--- a/src/tools/miri/src/shims/env.rs
+++ b/src/tools/miri/src/shims/env.rs
@@ -1,33 +1,24 @@
-use std::env;
-use std::ffi::{OsStr, OsString};
-use std::io::ErrorKind;
-use std::mem;
+use std::ffi::OsString;
 
 use rustc_data_structures::fx::FxHashMap;
-use rustc_middle::ty::layout::LayoutOf;
-use rustc_middle::ty::Ty;
-use rustc_target::abi::Size;
 
 use crate::*;
-use helpers::windows_check_buffer_size;
+use shims::{unix::UnixEnvVars, windows::WindowsEnvVars};
 
 #[derive(Default)]
-pub struct EnvVars<'tcx> {
-    /// Stores pointers to the environment variables. These variables must be stored as
-    /// null-terminated target strings (c_str or wide_str) with the `"{name}={value}"` format.
-    map: FxHashMap<OsString, Pointer<Option<Provenance>>>,
-
-    /// Place where the `environ` static is stored. Lazily initialized, but then never changes.
-    pub(crate) environ: Option<MPlaceTy<'tcx, Provenance>>,
+pub enum EnvVars<'tcx> {
+    #[default]
+    Uninit,
+    Unix(UnixEnvVars<'tcx>),
+    Windows(WindowsEnvVars),
 }
 
 impl VisitProvenance for EnvVars<'_> {
     fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
-        let EnvVars { map, environ } = self;
-
-        environ.visit_provenance(visit);
-        for ptr in map.values() {
-            ptr.visit_provenance(visit);
+        match self {
+            EnvVars::Uninit => {}
+            EnvVars::Unix(env) => env.visit_provenance(visit),
+            EnvVars::Windows(env) => env.visit_provenance(visit),
         }
     }
 }
@@ -39,517 +30,73 @@ impl<'tcx> EnvVars<'tcx> {
     ) -> InterpResult<'tcx> {
         // Initialize the `env_vars` map.
         // Skip the loop entirely if we don't want to forward anything.
+        let mut env_vars = FxHashMap::default();
         if ecx.machine.communicate() || !config.forwarded_env_vars.is_empty() {
             for (name, value) in &config.env {
                 let forward = ecx.machine.communicate()
                     || config.forwarded_env_vars.iter().any(|v| **v == *name);
                 if forward {
-                    add_env_var(ecx, name, value)?;
+                    env_vars.insert(OsString::from(name), OsString::from(value));
                 }
             }
         }
 
         for (name, value) in &config.set_env_vars {
-            add_env_var(ecx, OsStr::new(name), OsStr::new(value))?;
+            env_vars.insert(OsString::from(name), OsString::from(value));
         }
 
-        // Initialize the `environ` pointer when needed.
-        if ecx.target_os_is_unix() {
-            // This is memory backing an extern static, hence `ExternStatic`, not `Env`.
-            let layout = ecx.machine.layouts.mut_raw_ptr;
-            let place = ecx.allocate(layout, MiriMemoryKind::ExternStatic.into())?;
-            ecx.write_null(&place)?;
-            ecx.machine.env_vars.environ = Some(place);
-            ecx.update_environ()?;
-        }
+        let env_vars = if ecx.target_os_is_unix() {
+            EnvVars::Unix(UnixEnvVars::new(ecx, env_vars)?)
+        } else if ecx.tcx.sess.target.os == "windows" {
+            EnvVars::Windows(WindowsEnvVars::new(ecx, env_vars)?)
+        } else {
+            // Used e.g. for wasi
+            EnvVars::Uninit
+        };
+        ecx.machine.env_vars = env_vars;
+
         Ok(())
     }
 
     pub(crate) fn cleanup<'mir>(
         ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
     ) -> InterpResult<'tcx> {
-        // Deallocate individual env vars.
-        let env_vars = mem::take(&mut ecx.machine.env_vars.map);
-        for (_name, ptr) in env_vars {
-            ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?;
-        }
-        // Deallocate environ var list.
-        if ecx.target_os_is_unix() {
-            let environ = ecx.machine.env_vars.environ.as_ref().unwrap();
-            let old_vars_ptr = ecx.read_pointer(environ)?;
-            ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
-        }
-        Ok(())
-    }
-}
-
-fn add_env_var<'mir, 'tcx>(
-    ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
-    name: &OsStr,
-    value: &OsStr,
-) -> InterpResult<'tcx, ()> {
-    let var_ptr = match ecx.tcx.sess.target.os.as_ref() {
-        _ if ecx.target_os_is_unix() => alloc_env_var_as_c_str(name, value, ecx)?,
-        "windows" => alloc_env_var_as_wide_str(name, value, ecx)?,
-        unsupported =>
-            throw_unsup_format!(
-                "environment support for target OS `{}` not yet available",
-                unsupported
-            ),
-    };
-    ecx.machine.env_vars.map.insert(name.to_os_string(), var_ptr);
-    Ok(())
-}
-
-fn alloc_env_var_as_c_str<'mir, 'tcx>(
-    name: &OsStr,
-    value: &OsStr,
-    ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
-) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
-    let mut name_osstring = name.to_os_string();
-    name_osstring.push("=");
-    name_osstring.push(value);
-    ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into())
-}
-
-fn alloc_env_var_as_wide_str<'mir, 'tcx>(
-    name: &OsStr,
-    value: &OsStr,
-    ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
-) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
-    let mut name_osstring = name.to_os_string();
-    name_osstring.push("=");
-    name_osstring.push(value);
-    ecx.alloc_os_str_as_wide_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into())
-}
-
-impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
-pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
-    fn getenv(
-        &mut self,
-        name_op: &OpTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
-        let this = self.eval_context_mut();
-        this.assert_target_os_is_unix("getenv");
-
-        let name_ptr = this.read_pointer(name_op)?;
-        let name = this.read_os_str_from_c_str(name_ptr)?;
-        this.read_environ()?;
-        Ok(match this.machine.env_vars.map.get(name) {
-            Some(var_ptr) => {
-                // The offset is used to strip the "{name}=" part of the string.
-                var_ptr.offset(
-                    Size::from_bytes(u64::try_from(name.len()).unwrap().checked_add(1).unwrap()),
-                    this,
-                )?
-            }
-            None => Pointer::null(),
-        })
-    }
-
-    #[allow(non_snake_case)]
-    fn GetEnvironmentVariableW(
-        &mut self,
-        name_op: &OpTy<'tcx, Provenance>, // LPCWSTR
-        buf_op: &OpTy<'tcx, Provenance>,  // LPWSTR
-        size_op: &OpTy<'tcx, Provenance>, // DWORD
-    ) -> InterpResult<'tcx, Scalar<Provenance>> {
-        // ^ Returns DWORD (u32 on Windows)
-
-        let this = self.eval_context_mut();
-        this.assert_target_os("windows", "GetEnvironmentVariableW");
-
-        let name_ptr = this.read_pointer(name_op)?;
-        let buf_ptr = this.read_pointer(buf_op)?;
-        let buf_size = this.read_scalar(size_op)?.to_u32()?; // in characters
-
-        let name = this.read_os_str_from_wide_str(name_ptr)?;
-        Ok(match this.machine.env_vars.map.get(&name) {
-            Some(&var_ptr) => {
-                // The offset is used to strip the "{name}=" part of the string.
-                #[rustfmt::skip]
-                let name_offset_bytes = u64::try_from(name.len()).unwrap()
-                    .checked_add(1).unwrap()
-                    .checked_mul(2).unwrap();
-                let var_ptr = var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?;
-                let var = this.read_os_str_from_wide_str(var_ptr)?;
-
-                Scalar::from_u32(windows_check_buffer_size(this.write_os_str_to_wide_str(
-                    &var,
-                    buf_ptr,
-                    buf_size.into(),
-                )?))
-                // This can in fact return 0. It is up to the caller to set last_error to 0
-                // beforehand and check it afterwards to exclude that case.
-            }
-            None => {
-                let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND");
-                this.set_last_error(envvar_not_found)?;
-                Scalar::from_u32(0) // return zero upon failure
-            }
-        })
-    }
-
-    #[allow(non_snake_case)]
-    fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
-        let this = self.eval_context_mut();
-        this.assert_target_os("windows", "GetEnvironmentStringsW");
-
-        // Info on layout of environment blocks in Windows:
-        // https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables
-        let mut env_vars = std::ffi::OsString::new();
-        for &item in this.machine.env_vars.map.values() {
-            let env_var = this.read_os_str_from_wide_str(item)?;
-            env_vars.push(env_var);
-            env_vars.push("\0");
-        }
-        // Allocate environment block & Store environment variables to environment block.
-        // Final null terminator(block terminator) is added by `alloc_os_str_to_wide_str`.
-        let envblock_ptr =
-            this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::Runtime.into())?;
-        // If the function succeeds, the return value is a pointer to the environment block of the current process.
-        Ok(envblock_ptr)
-    }
-
-    #[allow(non_snake_case)]
-    fn FreeEnvironmentStringsW(
-        &mut self,
-        env_block_op: &OpTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, Scalar<Provenance>> {
-        let this = self.eval_context_mut();
-        this.assert_target_os("windows", "FreeEnvironmentStringsW");
-
-        let env_block_ptr = this.read_pointer(env_block_op)?;
-        let result = this.deallocate_ptr(env_block_ptr, None, MiriMemoryKind::Runtime.into());
-        // If the function succeeds, the return value is nonzero.
-        Ok(Scalar::from_i32(i32::from(result.is_ok())))
-    }
-
-    fn setenv(
-        &mut self,
-        name_op: &OpTy<'tcx, Provenance>,
-        value_op: &OpTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, i32> {
-        let this = self.eval_context_mut();
-        this.assert_target_os_is_unix("setenv");
-
-        let name_ptr = this.read_pointer(name_op)?;
-        let value_ptr = this.read_pointer(value_op)?;
-
-        let mut new = None;
-        if !this.ptr_is_null(name_ptr)? {
-            let name = this.read_os_str_from_c_str(name_ptr)?;
-            if !name.is_empty() && !name.to_string_lossy().contains('=') {
-                let value = this.read_os_str_from_c_str(value_ptr)?;
-                new = Some((name.to_owned(), value.to_owned()));
-            }
-        }
-        if let Some((name, value)) = new {
-            let var_ptr = alloc_env_var_as_c_str(&name, &value, this)?;
-            if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) {
-                this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
-            }
-            this.update_environ()?;
-            Ok(0) // return zero on success
-        } else {
-            // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
-            let einval = this.eval_libc("EINVAL");
-            this.set_last_error(einval)?;
-            Ok(-1)
-        }
-    }
-
-    #[allow(non_snake_case)]
-    fn SetEnvironmentVariableW(
-        &mut self,
-        name_op: &OpTy<'tcx, Provenance>,  // LPCWSTR
-        value_op: &OpTy<'tcx, Provenance>, // LPCWSTR
-    ) -> InterpResult<'tcx, Scalar<Provenance>> {
-        let this = self.eval_context_mut();
-        this.assert_target_os("windows", "SetEnvironmentVariableW");
-
-        let name_ptr = this.read_pointer(name_op)?;
-        let value_ptr = this.read_pointer(value_op)?;
-
-        if this.ptr_is_null(name_ptr)? {
-            // ERROR CODE is not clearly explained in docs.. For now, throw UB instead.
-            throw_ub_format!("pointer to environment variable name is NULL");
-        }
-
-        let name = this.read_os_str_from_wide_str(name_ptr)?;
-        if name.is_empty() {
-            throw_unsup_format!("environment variable name is an empty string");
-        } else if name.to_string_lossy().contains('=') {
-            throw_unsup_format!("environment variable name contains '='");
-        } else if this.ptr_is_null(value_ptr)? {
-            // Delete environment variable `{name}`
-            if let Some(var) = this.machine.env_vars.map.remove(&name) {
-                this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
-            }
-            Ok(this.eval_windows("c", "TRUE"))
-        } else {
-            let value = this.read_os_str_from_wide_str(value_ptr)?;
-            let var_ptr = alloc_env_var_as_wide_str(&name, &value, this)?;
-            if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) {
-                this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
-            }
-            Ok(this.eval_windows("c", "TRUE"))
-        }
-    }
-
-    fn unsetenv(&mut self, name_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
-        let this = self.eval_context_mut();
-        this.assert_target_os_is_unix("unsetenv");
-
-        let name_ptr = this.read_pointer(name_op)?;
-        let mut success = None;
-        if !this.ptr_is_null(name_ptr)? {
-            let name = this.read_os_str_from_c_str(name_ptr)?.to_owned();
-            if !name.is_empty() && !name.to_string_lossy().contains('=') {
-                success = Some(this.machine.env_vars.map.remove(&name));
-            }
-        }
-        if let Some(old) = success {
-            if let Some(var) = old {
-                this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
-            }
-            this.update_environ()?;
-            Ok(0)
-        } else {
-            // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
-            let einval = this.eval_libc("EINVAL");
-            this.set_last_error(einval)?;
-            Ok(-1)
+        let this = ecx.eval_context_mut();
+        match this.machine.env_vars {
+            EnvVars::Unix(_) => UnixEnvVars::cleanup(this),
+            EnvVars::Windows(_) => Ok(()), // no cleanup needed
+            EnvVars::Uninit => Ok(()),
         }
     }
 
-    fn getcwd(
-        &mut self,
-        buf_op: &OpTy<'tcx, Provenance>,
-        size_op: &OpTy<'tcx, Provenance>,
-    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
-        let this = self.eval_context_mut();
-        this.assert_target_os_is_unix("getcwd");
-
-        let buf = this.read_pointer(buf_op)?;
-        let size = this.read_target_usize(size_op)?;
-
-        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
-            this.reject_in_isolation("`getcwd`", reject_with)?;
-            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
-            return Ok(Pointer::null());
+    pub(crate) fn unix(&self) -> &UnixEnvVars<'tcx> {
+        match self {
+            EnvVars::Unix(env) => env,
+            _ => unreachable!(),
         }
-
-        // If we cannot get the current directory, we return null
-        match env::current_dir() {
-            Ok(cwd) => {
-                if this.write_path_to_c_str(&cwd, buf, size)?.0 {
-                    return Ok(buf);
-                }
-                let erange = this.eval_libc("ERANGE");
-                this.set_last_error(erange)?;
-            }
-            Err(e) => this.set_last_error_from_io_error(e.kind())?,
-        }
-
-        Ok(Pointer::null())
     }
 
-    #[allow(non_snake_case)]
-    fn GetCurrentDirectoryW(
-        &mut self,
-        size_op: &OpTy<'tcx, Provenance>, // DWORD
-        buf_op: &OpTy<'tcx, Provenance>,  // LPTSTR
-    ) -> InterpResult<'tcx, Scalar<Provenance>> {
-        let this = self.eval_context_mut();
-        this.assert_target_os("windows", "GetCurrentDirectoryW");
-
-        let size = u64::from(this.read_scalar(size_op)?.to_u32()?);
-        let buf = this.read_pointer(buf_op)?;
-
-        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
-            this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?;
-            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
-            return Ok(Scalar::from_u32(0));
+    pub(crate) fn unix_mut(&mut self) -> &mut UnixEnvVars<'tcx> {
+        match self {
+            EnvVars::Unix(env) => env,
+            _ => unreachable!(),
         }
-
-        // If we cannot get the current directory, we return 0
-        match env::current_dir() {
-            Ok(cwd) => {
-                // This can in fact return 0. It is up to the caller to set last_error to 0
-                // beforehand and check it afterwards to exclude that case.
-                return Ok(Scalar::from_u32(windows_check_buffer_size(
-                    this.write_path_to_wide_str(&cwd, buf, size)?,
-                )));
-            }
-            Err(e) => this.set_last_error_from_io_error(e.kind())?,
-        }
-        Ok(Scalar::from_u32(0))
     }
 
-    fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
-        let this = self.eval_context_mut();
-        this.assert_target_os_is_unix("chdir");
-
-        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
-
-        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
-            this.reject_in_isolation("`chdir`", reject_with)?;
-            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
-
-            return Ok(-1);
-        }
-
-        match env::set_current_dir(path) {
-            Ok(()) => Ok(0),
-            Err(e) => {
-                this.set_last_error_from_io_error(e.kind())?;
-                Ok(-1)
-            }
+    pub(crate) fn windows(&self) -> &WindowsEnvVars {
+        match self {
+            EnvVars::Windows(env) => env,
+            _ => unreachable!(),
         }
     }
 
-    #[allow(non_snake_case)]
-    fn SetCurrentDirectoryW(
-        &mut self,
-        path_op: &OpTy<'tcx, Provenance>, // LPCTSTR
-    ) -> InterpResult<'tcx, Scalar<Provenance>> {
-        // ^ Returns BOOL (i32 on Windows)
-
-        let this = self.eval_context_mut();
-        this.assert_target_os("windows", "SetCurrentDirectoryW");
-
-        let path = this.read_path_from_wide_str(this.read_pointer(path_op)?)?;
-
-        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
-            this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?;
-            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
-
-            return Ok(this.eval_windows("c", "FALSE"));
-        }
-
-        match env::set_current_dir(path) {
-            Ok(()) => Ok(this.eval_windows("c", "TRUE")),
-            Err(e) => {
-                this.set_last_error_from_io_error(e.kind())?;
-                Ok(this.eval_windows("c", "FALSE"))
-            }
+    pub(crate) fn windows_mut(&mut self) -> &mut WindowsEnvVars {
+        match self {
+            EnvVars::Windows(env) => env,
+            _ => unreachable!(),
         }
     }
-
-    /// Updates the `environ` static.
-    /// The first time it gets called, also initializes `extra.environ`.
-    fn update_environ(&mut self) -> InterpResult<'tcx> {
-        let this = self.eval_context_mut();
-        // Deallocate the old environ list, if any.
-        let environ = this.machine.env_vars.environ.as_ref().unwrap().clone();
-        let old_vars_ptr = this.read_pointer(&environ)?;
-        if !this.ptr_is_null(old_vars_ptr)? {
-            this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
-        }
-
-        // Collect all the pointers to each variable in a vector.
-        let mut vars: Vec<Pointer<Option<Provenance>>> =
-            this.machine.env_vars.map.values().copied().collect();
-        // Add the trailing null pointer.
-        vars.push(Pointer::null());
-        // Make an array with all these pointers inside Miri.
-        let tcx = this.tcx;
-        let vars_layout = this.layout_of(Ty::new_array(
-            tcx.tcx,
-            this.machine.layouts.mut_raw_ptr.ty,
-            u64::try_from(vars.len()).unwrap(),
-        ))?;
-        let vars_place = this.allocate(vars_layout, MiriMemoryKind::Runtime.into())?;
-        for (idx, var) in vars.into_iter().enumerate() {
-            let place = this.project_field(&vars_place, idx)?;
-            this.write_pointer(var, &place)?;
-        }
-        this.write_pointer(vars_place.ptr(), &environ)?;
-
-        Ok(())
-    }
-
-    /// Reads from the `environ` static.
-    /// We don't actually care about the result, but we care about this potentially causing a data race.
-    fn read_environ(&self) -> InterpResult<'tcx> {
-        let this = self.eval_context_ref();
-        let environ = this.machine.env_vars.environ.as_ref().unwrap();
-        let _vars_ptr = this.read_pointer(environ)?;
-        Ok(())
-    }
-
-    fn getpid(&mut self) -> InterpResult<'tcx, i32> {
-        let this = self.eval_context_mut();
-        this.assert_target_os_is_unix("getpid");
-
-        this.check_no_isolation("`getpid`")?;
-
-        // The reason we need to do this wacky of a conversion is because
-        // `libc::getpid` returns an i32, however, `std::process::id()` return an u32.
-        // So we un-do the conversion that stdlib does and turn it back into an i32.
-        #[allow(clippy::cast_possible_wrap)]
-        Ok(std::process::id() as i32)
-    }
-
-    #[allow(non_snake_case)]
-    fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> {
-        let this = self.eval_context_mut();
-        this.assert_target_os("windows", "GetCurrentProcessId");
-        this.check_no_isolation("`GetCurrentProcessId`")?;
-
-        Ok(std::process::id())
-    }
-
-    #[allow(non_snake_case)]
-    fn GetUserProfileDirectoryW(
-        &mut self,
-        token: &OpTy<'tcx, Provenance>, // HANDLE
-        buf: &OpTy<'tcx, Provenance>,   // LPWSTR
-        size: &OpTy<'tcx, Provenance>,  // LPDWORD
-    ) -> InterpResult<'tcx, Scalar<Provenance>> // returns BOOL
-    {
-        let this = self.eval_context_mut();
-        this.assert_target_os("windows", "GetUserProfileDirectoryW");
-        this.check_no_isolation("`GetUserProfileDirectoryW`")?;
-
-        let token = this.read_target_isize(token)?;
-        let buf = this.read_pointer(buf)?;
-        let size = this.deref_pointer(size)?;
-
-        if token != -4 {
-            throw_unsup_format!(
-                "GetUserProfileDirectoryW: only CURRENT_PROCESS_TOKEN is supported"
-            );
-        }
-
-        // See <https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-getuserprofiledirectoryw> for docs.
-        Ok(match directories::UserDirs::new() {
-            Some(dirs) => {
-                let home = dirs.home_dir();
-                let size_avail = if this.ptr_is_null(size.ptr())? {
-                    0 // if the buf pointer is null, we can't write to it; `size` will be updated to the required length
-                } else {
-                    this.read_scalar(&size)?.to_u32()?
-                };
-                // Of course we cannot use `windows_check_buffer_size` here since this uses
-                // a different method for dealing with a too-small buffer than the other functions...
-                let (success, len) = this.write_path_to_wide_str(home, buf, size_avail.into())?;
-                // The Windows docs just say that this is written on failure. But std
-                // seems to rely on it always being written.
-                this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?;
-                if success {
-                    Scalar::from_i32(1) // return TRUE
-                } else {
-                    this.set_last_error(this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"))?;
-                    Scalar::from_i32(0) // return FALSE
-                }
-            }
-            None => {
-                // We have to pick some error code.
-                this.set_last_error(this.eval_windows("c", "ERROR_BAD_USER_PROFILE"))?;
-                Scalar::from_i32(0) // return FALSE
-            }
-        })
-    }
 }
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {}
diff --git a/src/tools/miri/src/shims/extern_static.rs b/src/tools/miri/src/shims/extern_static.rs
index 7c4a54fb461..442338a6117 100644
--- a/src/tools/miri/src/shims/extern_static.rs
+++ b/src/tools/miri/src/shims/extern_static.rs
@@ -47,20 +47,14 @@ impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
                     &["__cxa_thread_atexit_impl", "getrandom", "statx", "__clock_gettime64"],
                 )?;
                 // "environ"
-                Self::add_extern_static(
-                    this,
-                    "environ",
-                    this.machine.env_vars.environ.as_ref().unwrap().ptr(),
-                );
+                let environ = this.machine.env_vars.unix().environ();
+                Self::add_extern_static(this, "environ", environ);
             }
             "freebsd" => {
                 Self::null_ptr_extern_statics(this, &["__cxa_thread_atexit_impl"])?;
                 // "environ"
-                Self::add_extern_static(
-                    this,
-                    "environ",
-                    this.machine.env_vars.environ.as_ref().unwrap().ptr(),
-                );
+                let environ = this.machine.env_vars.unix().environ();
+                Self::add_extern_static(this, "environ", environ);
             }
             "android" => {
                 Self::null_ptr_extern_statics(this, &["bsd_signal"])?;
diff --git a/src/tools/miri/src/shims/unix/env.rs b/src/tools/miri/src/shims/unix/env.rs
new file mode 100644
index 00000000000..128f0dcafa9
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/env.rs
@@ -0,0 +1,276 @@
+use std::env;
+use std::ffi::{OsStr, OsString};
+use std::io::ErrorKind;
+use std::mem;
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::Ty;
+use rustc_target::abi::Size;
+
+use crate::*;
+
+pub struct UnixEnvVars<'tcx> {
+    /// Stores pointers to the environment variables. These variables must be stored as
+    /// null-terminated target strings (c_str or wide_str) with the `"{name}={value}"` format.
+    map: FxHashMap<OsString, Pointer<Option<Provenance>>>,
+
+    /// Place where the `environ` static is stored. Lazily initialized, but then never changes.
+    environ: MPlaceTy<'tcx, Provenance>,
+}
+
+impl VisitProvenance for UnixEnvVars<'_> {
+    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
+        let UnixEnvVars { map, environ } = self;
+
+        environ.visit_provenance(visit);
+        for ptr in map.values() {
+            ptr.visit_provenance(visit);
+        }
+    }
+}
+
+impl<'tcx> UnixEnvVars<'tcx> {
+    pub(crate) fn new<'mir>(
+        ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
+        env_vars: FxHashMap<OsString, OsString>,
+    ) -> InterpResult<'tcx, Self> {
+        // Allocate memory for all these env vars.
+        let mut env_vars_machine = FxHashMap::default();
+        for (name, val) in env_vars.into_iter() {
+            let ptr = alloc_env_var(ecx, &name, &val)?;
+            env_vars_machine.insert(name, ptr);
+        }
+
+        // This is memory backing an extern static, hence `ExternStatic`, not `Env`.
+        let layout = ecx.machine.layouts.mut_raw_ptr;
+        let environ = ecx.allocate(layout, MiriMemoryKind::ExternStatic.into())?;
+        let environ_block = alloc_environ_block(ecx, env_vars_machine.values().copied().collect())?;
+        ecx.write_pointer(environ_block, &environ)?;
+
+        Ok(UnixEnvVars { map: env_vars_machine, environ })
+    }
+
+    pub(crate) fn cleanup<'mir>(
+        ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
+    ) -> InterpResult<'tcx> {
+        // Deallocate individual env vars.
+        let env_vars = mem::take(&mut ecx.machine.env_vars.unix_mut().map);
+        for (_name, ptr) in env_vars {
+            ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?;
+        }
+        // Deallocate environ var list.
+        let environ = &ecx.machine.env_vars.unix().environ;
+        let old_vars_ptr = ecx.read_pointer(environ)?;
+        ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
+
+        Ok(())
+    }
+
+    pub(crate) fn environ(&self) -> Pointer<Option<Provenance>> {
+        self.environ.ptr()
+    }
+}
+
+fn alloc_env_var<'mir, 'tcx>(
+    ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
+    name: &OsStr,
+    value: &OsStr,
+) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+    let mut name_osstring = name.to_os_string();
+    name_osstring.push("=");
+    name_osstring.push(value);
+    ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into())
+}
+
+/// Allocates an `environ` block with the given list of pointers.
+fn alloc_environ_block<'mir, 'tcx>(
+    ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
+    mut vars: Vec<Pointer<Option<Provenance>>>,
+) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+    // Add trailing null.
+    vars.push(Pointer::null());
+    // Make an array with all these pointers inside Miri.
+    let vars_layout = ecx.layout_of(Ty::new_array(
+        *ecx.tcx,
+        ecx.machine.layouts.mut_raw_ptr.ty,
+        u64::try_from(vars.len()).unwrap(),
+    ))?;
+    let vars_place = ecx.allocate(vars_layout, MiriMemoryKind::Runtime.into())?;
+    for (idx, var) in vars.into_iter().enumerate() {
+        let place = ecx.project_field(&vars_place, idx)?;
+        ecx.write_pointer(var, &place)?;
+    }
+    Ok(vars_place.ptr())
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    fn getenv(
+        &mut self,
+        name_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("getenv");
+
+        let name_ptr = this.read_pointer(name_op)?;
+        let name = this.read_os_str_from_c_str(name_ptr)?;
+
+        // We don't care about the value as we have the `map` to keep track of everything,
+        // but we do want to do this read so it shows up as a data race.
+        let _vars_ptr = this.read_pointer(&this.machine.env_vars.unix().environ)?;
+        Ok(match this.machine.env_vars.unix().map.get(name) {
+            Some(var_ptr) => {
+                // The offset is used to strip the "{name}=" part of the string.
+                var_ptr.offset(
+                    Size::from_bytes(u64::try_from(name.len()).unwrap().checked_add(1).unwrap()),
+                    this,
+                )?
+            }
+            None => Pointer::null(),
+        })
+    }
+
+    fn setenv(
+        &mut self,
+        name_op: &OpTy<'tcx, Provenance>,
+        value_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("setenv");
+
+        let name_ptr = this.read_pointer(name_op)?;
+        let value_ptr = this.read_pointer(value_op)?;
+
+        let mut new = None;
+        if !this.ptr_is_null(name_ptr)? {
+            let name = this.read_os_str_from_c_str(name_ptr)?;
+            if !name.is_empty() && !name.to_string_lossy().contains('=') {
+                let value = this.read_os_str_from_c_str(value_ptr)?;
+                new = Some((name.to_owned(), value.to_owned()));
+            }
+        }
+        if let Some((name, value)) = new {
+            let var_ptr = alloc_env_var(this, &name, &value)?;
+            if let Some(var) = this.machine.env_vars.unix_mut().map.insert(name, var_ptr) {
+                this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
+            }
+            this.update_environ()?;
+            Ok(0) // return zero on success
+        } else {
+            // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
+            let einval = this.eval_libc("EINVAL");
+            this.set_last_error(einval)?;
+            Ok(-1)
+        }
+    }
+
+    fn unsetenv(&mut self, name_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("unsetenv");
+
+        let name_ptr = this.read_pointer(name_op)?;
+        let mut success = None;
+        if !this.ptr_is_null(name_ptr)? {
+            let name = this.read_os_str_from_c_str(name_ptr)?.to_owned();
+            if !name.is_empty() && !name.to_string_lossy().contains('=') {
+                success = Some(this.machine.env_vars.unix_mut().map.remove(&name));
+            }
+        }
+        if let Some(old) = success {
+            if let Some(var) = old {
+                this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
+            }
+            this.update_environ()?;
+            Ok(0)
+        } else {
+            // name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
+            let einval = this.eval_libc("EINVAL");
+            this.set_last_error(einval)?;
+            Ok(-1)
+        }
+    }
+
+    fn getcwd(
+        &mut self,
+        buf_op: &OpTy<'tcx, Provenance>,
+        size_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("getcwd");
+
+        let buf = this.read_pointer(buf_op)?;
+        let size = this.read_target_usize(size_op)?;
+
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`getcwd`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+            return Ok(Pointer::null());
+        }
+
+        // If we cannot get the current directory, we return null
+        match env::current_dir() {
+            Ok(cwd) => {
+                if this.write_path_to_c_str(&cwd, buf, size)?.0 {
+                    return Ok(buf);
+                }
+                let erange = this.eval_libc("ERANGE");
+                this.set_last_error(erange)?;
+            }
+            Err(e) => this.set_last_error_from_io_error(e.kind())?,
+        }
+
+        Ok(Pointer::null())
+    }
+
+    fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("chdir");
+
+        let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
+
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`chdir`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+
+            return Ok(-1);
+        }
+
+        match env::set_current_dir(path) {
+            Ok(()) => Ok(0),
+            Err(e) => {
+                this.set_last_error_from_io_error(e.kind())?;
+                Ok(-1)
+            }
+        }
+    }
+
+    /// Updates the `environ` static.
+    fn update_environ(&mut self) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+        // Deallocate the old environ list.
+        let environ = this.machine.env_vars.unix().environ.clone();
+        let old_vars_ptr = this.read_pointer(&environ)?;
+        this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
+
+        // Write the new list.
+        let vals = this.machine.env_vars.unix().map.values().copied().collect();
+        let environ_block = alloc_environ_block(this, vals)?;
+        this.write_pointer(environ_block, &environ)?;
+
+        Ok(())
+    }
+
+    fn getpid(&mut self) -> InterpResult<'tcx, i32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os_is_unix("getpid");
+
+        this.check_no_isolation("`getpid`")?;
+
+        // The reason we need to do this wacky of a conversion is because
+        // `libc::getpid` returns an i32, however, `std::process::id()` return an u32.
+        // So we un-do the conversion that stdlib does and turn it back into an i32.
+        #[allow(clippy::cast_possible_wrap)]
+        Ok(std::process::id() as i32)
+    }
+}
diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
index 53a02bf5e0b..66a8dce753f 100644
--- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
@@ -74,15 +74,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             // Environment related shims
             "_NSGetEnviron" => {
                 let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
-                this.write_pointer(
-                    this.machine
-                        .env_vars
-                        .environ
-                        .as_ref()
-                        .expect("machine must be initialized")
-                        .ptr(),
-                    dest,
-                )?;
+                let environ = this.machine.env_vars.unix().environ();
+                this.write_pointer(environ, dest)?;
             }
 
             // Time related shims
diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs
index 2bc41e1a62d..144593aa2fc 100644
--- a/src/tools/miri/src/shims/unix/mod.rs
+++ b/src/tools/miri/src/shims/unix/mod.rs
@@ -1,5 +1,6 @@
 pub mod foreign_items;
 
+mod env;
 mod fd;
 mod fs;
 mod mem;
@@ -11,9 +12,11 @@ mod freebsd;
 mod linux;
 mod macos;
 
+pub use env::UnixEnvVars;
 pub use fd::{FdTable, FileDescriptor};
 pub use fs::DirTable;
-// All the unix-specific extension traits
+// All the Unix-specific extension traits
+pub use env::EvalContextExt as _;
 pub use fd::EvalContextExt as _;
 pub use fs::EvalContextExt as _;
 pub use mem::EvalContextExt as _;
diff --git a/src/tools/miri/src/shims/windows/env.rs b/src/tools/miri/src/shims/windows/env.rs
new file mode 100644
index 00000000000..e91623ac871
--- /dev/null
+++ b/src/tools/miri/src/shims/windows/env.rs
@@ -0,0 +1,257 @@
+use std::env;
+use std::ffi::OsString;
+use std::io::ErrorKind;
+
+use rustc_data_structures::fx::FxHashMap;
+
+use crate::*;
+use helpers::windows_check_buffer_size;
+
+#[derive(Default)]
+pub struct WindowsEnvVars {
+    /// Stores the environment varialbles.
+    map: FxHashMap<OsString, OsString>,
+}
+
+impl VisitProvenance for WindowsEnvVars {
+    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
+        let WindowsEnvVars { map: _ } = self;
+    }
+}
+
+impl WindowsEnvVars {
+    pub(crate) fn new<'mir, 'tcx>(
+        _ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
+        env_vars: FxHashMap<OsString, OsString>,
+    ) -> InterpResult<'tcx, Self> {
+        Ok(Self { map: env_vars })
+    }
+}
+
+impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
+pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
+    #[allow(non_snake_case)]
+    fn GetEnvironmentVariableW(
+        &mut self,
+        name_op: &OpTy<'tcx, Provenance>, // LPCWSTR
+        buf_op: &OpTy<'tcx, Provenance>,  // LPWSTR
+        size_op: &OpTy<'tcx, Provenance>, // DWORD
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        // ^ Returns DWORD (u32 on Windows)
+
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "GetEnvironmentVariableW");
+
+        let name_ptr = this.read_pointer(name_op)?;
+        let buf_ptr = this.read_pointer(buf_op)?;
+        let buf_size = this.read_scalar(size_op)?.to_u32()?; // in characters
+
+        let name = this.read_os_str_from_wide_str(name_ptr)?;
+        Ok(match this.machine.env_vars.windows().map.get(&name).cloned() {
+            Some(val) => {
+                Scalar::from_u32(windows_check_buffer_size(this.write_os_str_to_wide_str(
+                    &val,
+                    buf_ptr,
+                    buf_size.into(),
+                )?))
+                // This can in fact return 0. It is up to the caller to set last_error to 0
+                // beforehand and check it afterwards to exclude that case.
+            }
+            None => {
+                let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND");
+                this.set_last_error(envvar_not_found)?;
+                Scalar::from_u32(0) // return zero upon failure
+            }
+        })
+    }
+
+    #[allow(non_snake_case)]
+    fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "GetEnvironmentStringsW");
+
+        // Info on layout of environment blocks in Windows:
+        // https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables
+        let mut env_vars = std::ffi::OsString::new();
+        for (name, value) in this.machine.env_vars.windows().map.iter() {
+            env_vars.push(name);
+            env_vars.push("=");
+            env_vars.push(value);
+            env_vars.push("\0");
+        }
+        // Allocate environment block & Store environment variables to environment block.
+        // Final null terminator(block terminator) is added by `alloc_os_str_to_wide_str`.
+        let envblock_ptr =
+            this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::Runtime.into())?;
+        // If the function succeeds, the return value is a pointer to the environment block of the current process.
+        Ok(envblock_ptr)
+    }
+
+    #[allow(non_snake_case)]
+    fn FreeEnvironmentStringsW(
+        &mut self,
+        env_block_op: &OpTy<'tcx, Provenance>,
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "FreeEnvironmentStringsW");
+
+        let env_block_ptr = this.read_pointer(env_block_op)?;
+        this.deallocate_ptr(env_block_ptr, None, MiriMemoryKind::Runtime.into())?;
+        // If the function succeeds, the return value is nonzero.
+        Ok(Scalar::from_i32(1))
+    }
+
+    #[allow(non_snake_case)]
+    fn SetEnvironmentVariableW(
+        &mut self,
+        name_op: &OpTy<'tcx, Provenance>,  // LPCWSTR
+        value_op: &OpTy<'tcx, Provenance>, // LPCWSTR
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "SetEnvironmentVariableW");
+
+        let name_ptr = this.read_pointer(name_op)?;
+        let value_ptr = this.read_pointer(value_op)?;
+
+        if this.ptr_is_null(name_ptr)? {
+            // ERROR CODE is not clearly explained in docs.. For now, throw UB instead.
+            throw_ub_format!("pointer to environment variable name is NULL");
+        }
+
+        let name = this.read_os_str_from_wide_str(name_ptr)?;
+        if name.is_empty() {
+            throw_unsup_format!("environment variable name is an empty string");
+        } else if name.to_string_lossy().contains('=') {
+            throw_unsup_format!("environment variable name contains '='");
+        } else if this.ptr_is_null(value_ptr)? {
+            // Delete environment variable `{name}` if it exists.
+            this.machine.env_vars.windows_mut().map.remove(&name);
+            Ok(this.eval_windows("c", "TRUE"))
+        } else {
+            let value = this.read_os_str_from_wide_str(value_ptr)?;
+            this.machine.env_vars.windows_mut().map.insert(name, value);
+            Ok(this.eval_windows("c", "TRUE"))
+        }
+    }
+
+    #[allow(non_snake_case)]
+    fn GetCurrentDirectoryW(
+        &mut self,
+        size_op: &OpTy<'tcx, Provenance>, // DWORD
+        buf_op: &OpTy<'tcx, Provenance>,  // LPTSTR
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "GetCurrentDirectoryW");
+
+        let size = u64::from(this.read_scalar(size_op)?.to_u32()?);
+        let buf = this.read_pointer(buf_op)?;
+
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+            return Ok(Scalar::from_u32(0));
+        }
+
+        // If we cannot get the current directory, we return 0
+        match env::current_dir() {
+            Ok(cwd) => {
+                // This can in fact return 0. It is up to the caller to set last_error to 0
+                // beforehand and check it afterwards to exclude that case.
+                return Ok(Scalar::from_u32(windows_check_buffer_size(
+                    this.write_path_to_wide_str(&cwd, buf, size)?,
+                )));
+            }
+            Err(e) => this.set_last_error_from_io_error(e.kind())?,
+        }
+        Ok(Scalar::from_u32(0))
+    }
+
+    #[allow(non_snake_case)]
+    fn SetCurrentDirectoryW(
+        &mut self,
+        path_op: &OpTy<'tcx, Provenance>, // LPCTSTR
+    ) -> InterpResult<'tcx, Scalar<Provenance>> {
+        // ^ Returns BOOL (i32 on Windows)
+
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "SetCurrentDirectoryW");
+
+        let path = this.read_path_from_wide_str(this.read_pointer(path_op)?)?;
+
+        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
+            this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?;
+            this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
+
+            return Ok(this.eval_windows("c", "FALSE"));
+        }
+
+        match env::set_current_dir(path) {
+            Ok(()) => Ok(this.eval_windows("c", "TRUE")),
+            Err(e) => {
+                this.set_last_error_from_io_error(e.kind())?;
+                Ok(this.eval_windows("c", "FALSE"))
+            }
+        }
+    }
+
+    #[allow(non_snake_case)]
+    fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> {
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "GetCurrentProcessId");
+        this.check_no_isolation("`GetCurrentProcessId`")?;
+
+        Ok(std::process::id())
+    }
+
+    #[allow(non_snake_case)]
+    fn GetUserProfileDirectoryW(
+        &mut self,
+        token: &OpTy<'tcx, Provenance>, // HANDLE
+        buf: &OpTy<'tcx, Provenance>,   // LPWSTR
+        size: &OpTy<'tcx, Provenance>,  // LPDWORD
+    ) -> InterpResult<'tcx, Scalar<Provenance>> // returns BOOL
+    {
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "GetUserProfileDirectoryW");
+        this.check_no_isolation("`GetUserProfileDirectoryW`")?;
+
+        let token = this.read_target_isize(token)?;
+        let buf = this.read_pointer(buf)?;
+        let size = this.deref_pointer(size)?;
+
+        if token != -4 {
+            throw_unsup_format!(
+                "GetUserProfileDirectoryW: only CURRENT_PROCESS_TOKEN is supported"
+            );
+        }
+
+        // See <https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-getuserprofiledirectoryw> for docs.
+        Ok(match directories::UserDirs::new() {
+            Some(dirs) => {
+                let home = dirs.home_dir();
+                let size_avail = if this.ptr_is_null(size.ptr())? {
+                    0 // if the buf pointer is null, we can't write to it; `size` will be updated to the required length
+                } else {
+                    this.read_scalar(&size)?.to_u32()?
+                };
+                // Of course we cannot use `windows_check_buffer_size` here since this uses
+                // a different method for dealing with a too-small buffer than the other functions...
+                let (success, len) = this.write_path_to_wide_str(home, buf, size_avail.into())?;
+                // The Windows docs just say that this is written on failure. But std
+                // seems to rely on it always being written.
+                this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?;
+                if success {
+                    Scalar::from_i32(1) // return TRUE
+                } else {
+                    this.set_last_error(this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"))?;
+                    Scalar::from_i32(0) // return FALSE
+                }
+            }
+            None => {
+                // We have to pick some error code.
+                this.set_last_error(this.eval_windows("c", "ERROR_BAD_USER_PROFILE"))?;
+                Scalar::from_i32(0) // return FALSE
+            }
+        })
+    }
+}
diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs
index 24f7cd18e7a..e8ae80261c6 100644
--- a/src/tools/miri/src/shims/windows/foreign_items.rs
+++ b/src/tools/miri/src/shims/windows/foreign_items.rs
@@ -10,11 +10,10 @@ use rustc_target::spec::abi::Abi;
 
 use crate::shims::alloc::EvalContextExt as _;
 use crate::shims::os_str::bytes_to_os_str;
+use crate::shims::windows::*;
 use crate::*;
 use shims::foreign_items::EmulateForeignItemResult;
-use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
-use shims::windows::sync::EvalContextExt as _;
-use shims::windows::thread::EvalContextExt as _;
+use shims::windows::handle::{Handle, PseudoHandle};
 
 fn is_dyn_sym(name: &str) -> bool {
     // std does dynamic detection for these symbols
diff --git a/src/tools/miri/src/shims/windows/mod.rs b/src/tools/miri/src/shims/windows/mod.rs
index 7688abe412b..65f682b9dad 100644
--- a/src/tools/miri/src/shims/windows/mod.rs
+++ b/src/tools/miri/src/shims/windows/mod.rs
@@ -1,5 +1,13 @@
 pub mod foreign_items;
 
+mod env;
 mod handle;
 mod sync;
 mod thread;
+
+pub use env::WindowsEnvVars;
+// All the Windows-specific extension traits
+pub use env::EvalContextExt as _;
+pub use handle::EvalContextExt as _;
+pub use sync::EvalContextExt as _;
+pub use thread::EvalContextExt as _;
diff --git a/src/tools/miri/tests/pass/path.rs b/src/tools/miri/tests/pass/path.rs
new file mode 100644
index 00000000000..fe99d38e073
--- /dev/null
+++ b/src/tools/miri/tests/pass/path.rs
@@ -0,0 +1,60 @@
+//@compile-flags: -Zmiri-disable-isolation
+use std::path::{absolute, Path, PathBuf};
+
+#[path = "../utils/mod.rs"]
+mod utils;
+
+#[track_caller]
+fn assert_absolute_eq(in_: &str, out: &str) {
+    assert_eq!(absolute(in_).unwrap().as_os_str(), Path::new(out).as_os_str());
+}
+
+fn test_absolute() {
+    if cfg!(unix) {
+        assert_absolute_eq("/a/b/c", "/a/b/c");
+        assert_absolute_eq("/a/b/c", "/a/b/c");
+        assert_absolute_eq("/a//b/c", "/a/b/c");
+        assert_absolute_eq("//a/b/c", "//a/b/c");
+        assert_absolute_eq("///a/b/c", "/a/b/c");
+        assert_absolute_eq("/a/b/c/", "/a/b/c/");
+        assert_absolute_eq("/a/./b/../c/.././..", "/a/b/../c/../..");
+    } else if cfg!(windows) {
+        // Test that all these are unchanged
+        assert_absolute_eq(r"C:\path\to\file", r"C:\path\to\file");
+        assert_absolute_eq(r"C:\path\to\file\", r"C:\path\to\file\");
+        assert_absolute_eq(r"\\server\share\to\file", r"\\server\share\to\file");
+        assert_absolute_eq(r"\\server.\share.\to\file", r"\\server.\share.\to\file");
+        assert_absolute_eq(r"\\.\PIPE\name", r"\\.\PIPE\name");
+        assert_absolute_eq(r"\\.\C:\path\to\COM1", r"\\.\C:\path\to\COM1");
+        assert_absolute_eq(r"\\?\C:\path\to\file", r"\\?\C:\path\to\file");
+        assert_absolute_eq(r"\\?\UNC\server\share\to\file", r"\\?\UNC\server\share\to\file");
+        assert_absolute_eq(r"\\?\PIPE\name", r"\\?\PIPE\name");
+        // Verbatim paths are always unchanged, no matter what.
+        assert_absolute_eq(r"\\?\path.\to/file..", r"\\?\path.\to/file..");
+
+        assert_absolute_eq(r"C:\path..\to.\file.", r"C:\path..\to\file");
+        assert_absolute_eq(r"COM1", r"\\.\COM1");
+    } else {
+        panic!("unsupported OS");
+    }
+}
+
+fn buf_smoke(mut p: PathBuf) {
+    for _c in p.components() {}
+
+    p.push("hello");
+    for _c in p.components() {}
+
+    if cfg!(windows) {
+        p.push(r"C:\mydir");
+    } else {
+        p.push(r"/mydir");
+    }
+    for _c in p.components() {}
+}
+
+fn main() {
+    buf_smoke(PathBuf::new());
+    buf_smoke(utils::tmp());
+    test_absolute();
+}
diff --git a/src/tools/miri/tests/pass/shims/env/var.rs b/src/tools/miri/tests/pass/shims/env/var.rs
index 23a3724ff7f..babaf00578a 100644
--- a/src/tools/miri/tests/pass/shims/env/var.rs
+++ b/src/tools/miri/tests/pass/shims/env/var.rs
@@ -1,4 +1,5 @@
 use std::env;
+use std::thread;
 
 fn main() {
     // Test that miri environment is isolated when communication is disabled.
@@ -23,4 +24,11 @@ fn main() {
     env::remove_var("MIRI_TEST");
     assert_eq!(env::var("MIRI_TEST"), Err(env::VarError::NotPresent));
     println!("{:#?}", env::vars().collect::<Vec<_>>());
+
+    // Do things concurrently, to make sure there's no data race.
+    let t = thread::spawn(|| {
+        env::set_var("MIRI_TEST", "42");
+    });
+    env::set_var("MIRI_TEST", "42");
+    t.join().unwrap();
 }
diff --git a/src/tools/miri/tests/pass/shims/path.rs b/src/tools/miri/tests/pass/shims/path.rs
deleted file mode 100644
index cadbeb476bd..00000000000
--- a/src/tools/miri/tests/pass/shims/path.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-//@compile-flags: -Zmiri-disable-isolation
-use std::path::{absolute, Path};
-
-#[track_caller]
-fn test_absolute(in_: &str, out: &str) {
-    assert_eq!(absolute(in_).unwrap().as_os_str(), Path::new(out).as_os_str());
-}
-
-fn main() {
-    if cfg!(unix) {
-        test_absolute("/a/b/c", "/a/b/c");
-        test_absolute("/a/b/c", "/a/b/c");
-        test_absolute("/a//b/c", "/a/b/c");
-        test_absolute("//a/b/c", "//a/b/c");
-        test_absolute("///a/b/c", "/a/b/c");
-        test_absolute("/a/b/c/", "/a/b/c/");
-        test_absolute("/a/./b/../c/.././..", "/a/b/../c/../..");
-    } else if cfg!(windows) {
-        // Test that all these are unchanged
-        test_absolute(r"C:\path\to\file", r"C:\path\to\file");
-        test_absolute(r"C:\path\to\file\", r"C:\path\to\file\");
-        test_absolute(r"\\server\share\to\file", r"\\server\share\to\file");
-        test_absolute(r"\\server.\share.\to\file", r"\\server.\share.\to\file");
-        test_absolute(r"\\.\PIPE\name", r"\\.\PIPE\name");
-        test_absolute(r"\\.\C:\path\to\COM1", r"\\.\C:\path\to\COM1");
-        test_absolute(r"\\?\C:\path\to\file", r"\\?\C:\path\to\file");
-        test_absolute(r"\\?\UNC\server\share\to\file", r"\\?\UNC\server\share\to\file");
-        test_absolute(r"\\?\PIPE\name", r"\\?\PIPE\name");
-        // Verbatim paths are always unchanged, no matter what.
-        test_absolute(r"\\?\path.\to/file..", r"\\?\path.\to/file..");
-
-        test_absolute(r"C:\path..\to.\file.", r"C:\path..\to\file");
-        test_absolute(r"COM1", r"\\.\COM1");
-    } else {
-        panic!("unsupported OS");
-    }
-}
diff --git a/src/tools/tidy/src/allowed_run_make_makefiles.txt b/src/tools/tidy/src/allowed_run_make_makefiles.txt
index 9b3c0d0f1a5..1b560ee352c 100644
--- a/src/tools/tidy/src/allowed_run_make_makefiles.txt
+++ b/src/tools/tidy/src/allowed_run_make_makefiles.txt
@@ -220,7 +220,6 @@ run-make/pretty-print-to-file/Makefile
 run-make/pretty-print-with-dep-file/Makefile
 run-make/print-calling-conventions/Makefile
 run-make/print-cfg/Makefile
-run-make/print-native-static-libs/Makefile
 run-make/print-target-list/Makefile
 run-make/profile/Makefile
 run-make/prune-link-args/Makefile
diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt
index dff15265fae..4f02a61f7bc 100644
--- a/src/tools/tidy/src/issues.txt
+++ b/src/tools/tidy/src/issues.txt
@@ -439,7 +439,6 @@ ui/closures/issue-11873.rs
 ui/closures/issue-1460.rs
 ui/closures/issue-22864-1.rs
 ui/closures/issue-22864-2.rs
-ui/closures/issue-23012-supertrait-signature-inference.rs
 ui/closures/issue-25439.rs
 ui/closures/issue-41366.rs
 ui/closures/issue-42463.rs
diff --git a/src/version b/src/version
index b3a8c61e6a8..aaceec04e04 100644
--- a/src/version
+++ b/src/version
@@ -1 +1 @@
-1.79.0
+1.80.0
diff --git a/tests/crashes/123863.rs b/tests/crashes/123863.rs
deleted file mode 100644
index e0f3ac9dcd7..00000000000
--- a/tests/crashes/123863.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-//@ known-bug: #123863
-const fn concat_strs<const A: &'static str>() -> &'static str {
-    struct Inner<const A: &'static str>;
-    Inner::concat_strs::<"a">::A
-}
-pub fn main() {}
diff --git a/tests/crashes/124262.rs b/tests/crashes/124262.rs
new file mode 100644
index 00000000000..b9dac5eca22
--- /dev/null
+++ b/tests/crashes/124262.rs
@@ -0,0 +1,5 @@
+//@ known-bug: #124262
+//@ edition:2021
+
+struct Foo(<&[fn()] as ::core::ops::Deref>::Target);
+const _: *const Foo = 0 as _;
diff --git a/tests/crashes/124340.rs b/tests/crashes/124340.rs
new file mode 100644
index 00000000000..cdf24fa0395
--- /dev/null
+++ b/tests/crashes/124340.rs
@@ -0,0 +1,17 @@
+//@ known-bug: #124340
+#![feature(anonymous_lifetime_in_impl_trait)]
+
+trait Producer {
+    type Output;
+    fn produce(self) -> Self::Output;
+}
+
+trait SomeTrait<'a> {}
+
+fn force_same_lifetime<'a>(_x: &'a i32, _y: impl SomeTrait<'a>) {
+    unimplemented!()
+}
+
+fn foo<'a>(s: &'a i32, producer: impl Producer<Output: SomeTrait<'_>>) {
+    force_same_lifetime(s, producer.produce());
+}
diff --git a/tests/crashes/124342.rs b/tests/crashes/124342.rs
new file mode 100644
index 00000000000..ae51b3db96f
--- /dev/null
+++ b/tests/crashes/124342.rs
@@ -0,0 +1,6 @@
+//@ known-bug: #124342
+trait Trait2 : Trait {
+   reuse <() as Trait>::async {
+        (async || {}).await;
+    };
+}
diff --git a/tests/crashes/124347.rs b/tests/crashes/124347.rs
new file mode 100644
index 00000000000..d2bc555fe1c
--- /dev/null
+++ b/tests/crashes/124347.rs
@@ -0,0 +1,4 @@
+//@ known-bug: #124347
+trait Trait: ToReuse {
+    reuse Trait::lolno { &self.0 };
+}
diff --git a/tests/crashes/124348.rs b/tests/crashes/124348.rs
new file mode 100644
index 00000000000..554f383026c
--- /dev/null
+++ b/tests/crashes/124348.rs
@@ -0,0 +1,7 @@
+//@ known-bug: #124348
+enum Eek {
+    TheConst,
+    UnusedByTheConst(Sum),
+}
+
+const EEK_ZERO: &[Eek] = &[];
diff --git a/tests/crashes/124350.rs b/tests/crashes/124350.rs
new file mode 100644
index 00000000000..d6038f280cf
--- /dev/null
+++ b/tests/crashes/124350.rs
@@ -0,0 +1,17 @@
+//@ known-bug: #124350
+
+struct Node<const D: usize> {}
+
+impl Node<D>
+where
+    SmallVec<{ D * 2 }>:,
+{
+    fn new() -> Self {
+        let mut node = Node::new();
+        (&a, 0)();
+
+        node
+    }
+}
+
+struct SmallVec<T1, T2> {}
diff --git a/tests/crashes/124352.rs b/tests/crashes/124352.rs
new file mode 100644
index 00000000000..e9eb4419e6a
--- /dev/null
+++ b/tests/crashes/124352.rs
@@ -0,0 +1,4 @@
+//@ known-bug: #124352
+#![rustc_never_type_options(: Unsize<U> = "hi")]
+
+fn main() {}
diff --git a/tests/crashes/124375.rs b/tests/crashes/124375.rs
new file mode 100644
index 00000000000..7165655178d
--- /dev/null
+++ b/tests/crashes/124375.rs
@@ -0,0 +1,11 @@
+//@ known-bug: #124375
+//@ compile-flags: -Zmir-opt-level=0
+//@ only-x86_64
+#![crate_type = "lib"]
+#![feature(naked_functions)]
+use std::arch::asm;
+
+#[naked]
+pub unsafe extern "C" fn naked_with_args_and_return(a: isize, b: isize) -> isize {
+    asm!("lea rax, [rdi + rsi]", "ret", options(noreturn));
+}
diff --git a/tests/crashes/92470.rs b/tests/crashes/92470.rs
new file mode 100644
index 00000000000..a3c518f5ec6
--- /dev/null
+++ b/tests/crashes/92470.rs
@@ -0,0 +1,31 @@
+//@ known-bug: #92470
+fn main() {
+    encode(&mut EncoderImpl);
+}
+
+pub trait Encoder {
+    type W;
+
+    fn writer(&self) -> Self::W;
+}
+
+fn encode<E: Encoder>(mut encoder: E) {
+    encoder.writer();
+    encode(&mut encoder);
+}
+
+struct EncoderImpl;
+
+impl Encoder for EncoderImpl {
+    type W = ();
+
+    fn writer(&self) {}
+}
+
+impl<'a, T: Encoder> Encoder for &'a mut T {
+    type W = T::W;
+
+    fn writer(&self) -> Self::W {
+        panic!()
+    }
+}
diff --git a/tests/incremental/slice-pattern-const-ice-83085.rs b/tests/incremental/slice-pattern-const-ice-83085.rs
new file mode 100644
index 00000000000..4d318fd7ec1
--- /dev/null
+++ b/tests/incremental/slice-pattern-const-ice-83085.rs
@@ -0,0 +1,39 @@
+//@ compile-flags: -Zincremental-verify-ich=yes
+// issue: rust-lang/rust#83085 incremental ICE: forcing query with already existing `DepNode`
+// this used to fail to build straight away without needing any kind of
+// stage1/2 builds but tidy demands it
+//@ revisions:rpass1 rpass2
+
+fn main() {
+    const BOO: &[u8; 0] = &[];
+    match &[] {
+        BOO => (),
+        b"" => (),
+        _ => (),
+    }
+}
+
+#[derive(PartialEq, Eq)]
+struct Id<'a> {
+    ns: &'a str,
+}
+fn visit_struct() {
+    let id = Id { ns: "random1" };
+    const FLAG: Id<'static> = Id {
+        ns: "needs_to_be_the_same",
+    };
+    match id {
+        FLAG => {}
+        _ => {}
+    }
+}
+fn visit_struct2() {
+    let id = Id { ns: "random2" };
+    const FLAG: Id<'static> = Id {
+        ns: "needs_to_be_the_same",
+    };
+    match id {
+        FLAG => {}
+        _ => {}
+    }
+}
diff --git a/tests/run-make/print-native-static-libs/Makefile b/tests/run-make/print-native-static-libs/Makefile
deleted file mode 100644
index a16c8b0f2a4..00000000000
--- a/tests/run-make/print-native-static-libs/Makefile
+++ /dev/null
@@ -1,19 +0,0 @@
-include ../tools.mk
-
-# ignore-cross-compile
-# ignore-wasm
-
-all:
-	$(RUSTC) --crate-type rlib -lbar_cli bar.rs
-	$(RUSTC) foo.rs -lfoo_cli -lfoo_cli --crate-type staticlib --print native-static-libs 2>&1 \
-		| grep 'note: native-static-libs: ' \
-		| sed 's/note: native-static-libs: \(.*\)/\1/' > $(TMPDIR)/libs.txt
-
-	cat $(TMPDIR)/libs.txt | grep -F "glib-2.0" # in bar.rs
-	cat $(TMPDIR)/libs.txt | grep -F "systemd" # in foo.rs
-	cat $(TMPDIR)/libs.txt | grep -F "bar_cli"
-	cat $(TMPDIR)/libs.txt | grep -F "foo_cli"
-
-    # make sure that foo_cli and glib-2.0 are not consecutively present
-	cat $(TMPDIR)/libs.txt | grep -Fv "foo_cli -lfoo_cli"
-	cat $(TMPDIR)/libs.txt | grep -Fv "glib-2.0 -lglib-2.0"
diff --git a/tests/run-make/print-native-static-libs/rmake.rs b/tests/run-make/print-native-static-libs/rmake.rs
new file mode 100644
index 00000000000..fc8701777d1
--- /dev/null
+++ b/tests/run-make/print-native-static-libs/rmake.rs
@@ -0,0 +1,76 @@
+//! This checks the output of `--print=native-static-libs`
+//!
+//! Specifically, this test makes sure that one and only one
+//! note is emitted with the text "native-static-libs:" as prefix
+//! that the note contains the link args given in the source code
+//! and cli of the current crate and downstream crates.
+//!
+//! It also checks that there aren't any duplicated consecutive
+//! args, as they are useless and suboptimal for debugability.
+//! See https://github.com/rust-lang/rust/issues/113209.
+
+//@ ignore-cross-compile
+//@ ignore-wasm
+
+extern crate run_make_support;
+
+use std::io::BufRead;
+
+use run_make_support::{rustc, is_msvc};
+
+fn main() {
+    // build supporting crate
+    rustc()
+        .input("bar.rs")
+        .crate_type("rlib")
+        .arg("-lbar_cli")
+        .run();
+
+    // build main crate as staticlib
+    let output = rustc()
+        .input("foo.rs")
+        .crate_type("staticlib")
+        .arg("-lfoo_cli")
+        .arg("-lfoo_cli") // 2nd time
+        .print("native-static-libs")
+        .run();
+
+    let mut found_note = false;
+    for l in output.stderr.lines() {
+        let l = l.expect("utf-8 string");
+
+        let Some(args) = l.strip_prefix("note: native-static-libs:") else { continue; };
+        assert!(!found_note);
+        found_note = true;
+
+        let args: Vec<&str> = args.trim().split_ascii_whitespace().collect();
+
+        macro_rules! assert_contains_lib {
+            ($lib:literal in $args:ident) => {{
+                let lib = format!(
+                    "{}{}{}",
+                    if !is_msvc() { "-l" } else { "" },
+                    $lib,
+                    if !is_msvc() { "" } else { ".lib" },
+                );
+                let found = $args.contains(&&*lib);
+                assert!(found, "unable to find lib `{}` in those linker args: {:?}", lib, $args);
+            }}
+        }
+
+        assert_contains_lib!("glib-2.0" in args); // in bar.rs
+        assert_contains_lib!("systemd" in args);  // in foo.rs
+        assert_contains_lib!("bar_cli" in args);
+        assert_contains_lib!("foo_cli" in args);
+
+        // make sure that no args are consecutively present
+        let dedup_args: Vec<&str> = {
+            let mut args = args.clone();
+            args.dedup();
+            args
+        };
+        assert_eq!(args, dedup_args);
+    }
+
+    assert!(found_note);
+}
diff --git a/tests/ui/closures/deduce-signature/deduce-from-opaque-type-after-norm.rs b/tests/ui/closures/deduce-signature/deduce-from-opaque-type-after-norm.rs
new file mode 100644
index 00000000000..9885d381d24
--- /dev/null
+++ b/tests/ui/closures/deduce-signature/deduce-from-opaque-type-after-norm.rs
@@ -0,0 +1,11 @@
+//@ revisions: current next
+//@ ignore-compare-mode-next-solver (explicit revisions)
+//@[next] compile-flags: -Znext-solver
+//@ check-pass
+trait Foo {
+    fn test() -> impl Fn(u32) -> u32 {
+        |x| x.count_ones()
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/closures/deduce-signature/deduce-from-opaque-type.rs b/tests/ui/closures/deduce-signature/deduce-from-opaque-type.rs
new file mode 100644
index 00000000000..3185a431f0b
--- /dev/null
+++ b/tests/ui/closures/deduce-signature/deduce-from-opaque-type.rs
@@ -0,0 +1,9 @@
+//@ revisions: current next
+//@ ignore-compare-mode-next-solver (explicit revisions)
+//@[next] compile-flags: -Znext-solver
+//@ check-pass
+fn foo() -> impl FnOnce(u32) -> u32 {
+    |x| x.leading_zeros()
+}
+
+fn main() {}
diff --git a/tests/ui/closures/deduce-signature/infer-higher-ranked-signature.rs b/tests/ui/closures/deduce-signature/infer-higher-ranked-signature.rs
new file mode 100644
index 00000000000..d9ab0149087
--- /dev/null
+++ b/tests/ui/closures/deduce-signature/infer-higher-ranked-signature.rs
@@ -0,0 +1,20 @@
+//@ revisions: current next
+//@ ignore-compare-mode-next-solver (explicit revisions)
+//@[next] compile-flags: -Znext-solver
+//@ check-pass
+
+trait Foo {}
+fn needs_foo<T>(_: T)
+where
+    Wrap<T>: Foo,
+{
+}
+
+struct Wrap<T>(T);
+impl<T> Foo for Wrap<T> where T: for<'a> Fn(&'a i32) {}
+
+fn main() {
+    needs_foo(|x| {
+        x.to_string();
+    });
+}
diff --git a/tests/ui/closures/infer-signature-from-impl.rs b/tests/ui/closures/deduce-signature/infer-signature-from-impl.rs
index fa455c15ec7..20802ce37ee 100644
--- a/tests/ui/closures/infer-signature-from-impl.rs
+++ b/tests/ui/closures/deduce-signature/infer-signature-from-impl.rs
@@ -1,8 +1,7 @@
 //@ revisions: current next
 //@ ignore-compare-mode-next-solver (explicit revisions)
 //@[next] compile-flags: -Znext-solver
-//@[next] known-bug: trait-system-refactor-initiative#71
-//@[current] check-pass
+//@ check-pass
 
 trait Foo {}
 fn needs_foo<T>(_: T)
diff --git a/tests/ui/closures/deduce-signature/obligation-with-leaking-placeholders.current.stderr b/tests/ui/closures/deduce-signature/obligation-with-leaking-placeholders.current.stderr
new file mode 100644
index 00000000000..eaa0d32e75d
--- /dev/null
+++ b/tests/ui/closures/deduce-signature/obligation-with-leaking-placeholders.current.stderr
@@ -0,0 +1,15 @@
+error: implementation of `Foo` is not general enough
+  --> $DIR/obligation-with-leaking-placeholders.rs:18:5
+   |
+LL | /     needs_foo(|x| {
+LL | |
+LL | |
+LL | |         x.to_string();
+LL | |     });
+   | |______^ implementation of `Foo` is not general enough
+   |
+   = note: `Wrap<{closure@$DIR/obligation-with-leaking-placeholders.rs:18:15: 18:18}>` must implement `Foo<'0>`, for any lifetime `'0`...
+   = note: ...but it actually implements `Foo<'1>`, for some specific lifetime `'1`
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/closures/infer-signature-from-impl.next.stderr b/tests/ui/closures/deduce-signature/obligation-with-leaking-placeholders.next.stderr
index 332917eaaff..3d667f12371 100644
--- a/tests/ui/closures/infer-signature-from-impl.next.stderr
+++ b/tests/ui/closures/deduce-signature/obligation-with-leaking-placeholders.next.stderr
@@ -1,8 +1,9 @@
 error[E0282]: type annotations needed
-  --> $DIR/infer-signature-from-impl.rs:18:16
+  --> $DIR/obligation-with-leaking-placeholders.rs:18:16
    |
 LL |     needs_foo(|x| {
    |                ^
+...
 LL |         x.to_string();
    |         - type must be known at this point
    |
diff --git a/tests/ui/closures/deduce-signature/obligation-with-leaking-placeholders.rs b/tests/ui/closures/deduce-signature/obligation-with-leaking-placeholders.rs
new file mode 100644
index 00000000000..deb888ec286
--- /dev/null
+++ b/tests/ui/closures/deduce-signature/obligation-with-leaking-placeholders.rs
@@ -0,0 +1,23 @@
+//@ revisions: current next
+//@ ignore-compare-mode-next-solver (explicit revisions)
+//@[next] compile-flags: -Znext-solver
+
+// See #124385 for more details.
+
+trait Foo<'a> {}
+fn needs_foo<T>(_: T)
+where
+    for<'a> Wrap<T>: Foo<'a>,
+{
+}
+
+struct Wrap<T>(T);
+impl<'a, T> Foo<'a> for Wrap<T> where T: Fn(&'a i32) {}
+
+fn main() {
+    needs_foo(|x| {
+        //[current]~^ implementation of `Foo` is not general enough
+        //[next]~^^ ERROR type annotations needed
+        x.to_string();
+    });
+}
diff --git a/tests/ui/closures/issue-23012-supertrait-signature-inference.rs b/tests/ui/closures/deduce-signature/supertrait-signature-inference-issue-23012.rs
index 732f687309c..16890b28acd 100644
--- a/tests/ui/closures/issue-23012-supertrait-signature-inference.rs
+++ b/tests/ui/closures/deduce-signature/supertrait-signature-inference-issue-23012.rs
@@ -1,3 +1,6 @@
+//@ revisions: current next
+//@ ignore-compare-mode-next-solver (explicit revisions)
+//@[next] compile-flags: -Znext-solver
 //@ check-pass
 // Checks that we can infer a closure signature even if the `FnOnce` bound is
 // a supertrait of the obligations we have currently registered for the Ty var.
diff --git a/tests/ui/consts/const-eval/ice-unhandled-type-122191.rs b/tests/ui/consts/const-eval/ice-unhandled-type-122191.rs
new file mode 100644
index 00000000000..a92b99976e2
--- /dev/null
+++ b/tests/ui/consts/const-eval/ice-unhandled-type-122191.rs
@@ -0,0 +1,18 @@
+type Foo = impl Send;
+//~^ ERROR `impl Trait` in type aliases is unstable
+
+struct A;
+
+const VALUE: Foo = value();
+//~^ ERROR cannot find function `value` in this scope
+
+fn test() {
+    match VALUE {
+        0 | 0 => {}
+//~^ ERROR mismatched types
+//~| ERROR mismatched types
+        _ => (),
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/consts/const-eval/ice-unhandled-type-122191.stderr b/tests/ui/consts/const-eval/ice-unhandled-type-122191.stderr
new file mode 100644
index 00000000000..daf0ccaa776
--- /dev/null
+++ b/tests/ui/consts/const-eval/ice-unhandled-type-122191.stderr
@@ -0,0 +1,58 @@
+error[E0658]: `impl Trait` in type aliases is unstable
+  --> $DIR/ice-unhandled-type-122191.rs:1:12
+   |
+LL | type Foo = impl Send;
+   |            ^^^^^^^^^
+   |
+   = note: see issue #63063 <https://github.com/rust-lang/rust/issues/63063> for more information
+   = help: add `#![feature(type_alias_impl_trait)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+
+error[E0425]: cannot find function `value` in this scope
+  --> $DIR/ice-unhandled-type-122191.rs:6:20
+   |
+LL | const VALUE: Foo = value();
+   |                    ^^^^^ not found in this scope
+
+error[E0308]: mismatched types
+  --> $DIR/ice-unhandled-type-122191.rs:11:9
+   |
+LL | type Foo = impl Send;
+   |            --------- the expected opaque type
+...
+LL |     match VALUE {
+   |           ----- this expression has type `Foo`
+LL |         0 | 0 => {}
+   |         ^ expected opaque type, found integer
+   |
+   = note: expected opaque type `Foo`
+                     found type `{integer}`
+note: this item must have the opaque type in its signature in order to be able to register hidden types
+  --> $DIR/ice-unhandled-type-122191.rs:9:4
+   |
+LL | fn test() {
+   |    ^^^^
+
+error[E0308]: mismatched types
+  --> $DIR/ice-unhandled-type-122191.rs:11:13
+   |
+LL | type Foo = impl Send;
+   |            --------- the expected opaque type
+...
+LL |     match VALUE {
+   |           ----- this expression has type `Foo`
+LL |         0 | 0 => {}
+   |             ^ expected opaque type, found integer
+   |
+   = note: expected opaque type `Foo`
+                     found type `{integer}`
+note: this item must have the opaque type in its signature in order to be able to register hidden types
+  --> $DIR/ice-unhandled-type-122191.rs:9:4
+   |
+LL | fn test() {
+   |    ^^^^
+
+error: aborting due to 4 previous errors
+
+Some errors have detailed explanations: E0308, E0425, E0658.
+For more information about an error, try `rustc --explain E0308`.
diff --git a/tests/ui/consts/mono-reachable-invalid-const.rs b/tests/ui/consts/mono-reachable-invalid-const.rs
new file mode 100644
index 00000000000..aabdb071bc9
--- /dev/null
+++ b/tests/ui/consts/mono-reachable-invalid-const.rs
@@ -0,0 +1,23 @@
+//@ build-fail
+
+struct Bar<const BITS: usize>;
+
+impl<const BITS: usize> Bar<BITS> {
+    const ASSERT: bool = {
+        let b = std::convert::identity(1);
+        ["oops"][b]; //~ ERROR evaluation of `Bar::<0>::ASSERT` failed
+        true
+    };
+
+    fn assert() {
+        let val = Self::ASSERT;
+        if val {
+            std::convert::identity(val);
+        }
+    }
+}
+
+
+fn main() {
+    Bar::<0>::assert();
+}
diff --git a/tests/ui/consts/mono-reachable-invalid-const.stderr b/tests/ui/consts/mono-reachable-invalid-const.stderr
new file mode 100644
index 00000000000..6b7b25b59b8
--- /dev/null
+++ b/tests/ui/consts/mono-reachable-invalid-const.stderr
@@ -0,0 +1,29 @@
+error[E0080]: evaluation of `Bar::<0>::ASSERT` failed
+  --> $DIR/mono-reachable-invalid-const.rs:8:9
+   |
+LL |         ["oops"][b];
+   |         ^^^^^^^^^^^ index out of bounds: the length is 1 but the index is 1
+
+note: erroneous constant encountered
+  --> $DIR/mono-reachable-invalid-const.rs:13:19
+   |
+LL |         let val = Self::ASSERT;
+   |                   ^^^^^^^^^^^^
+
+note: erroneous constant encountered
+  --> $DIR/mono-reachable-invalid-const.rs:13:19
+   |
+LL |         let val = Self::ASSERT;
+   |                   ^^^^^^^^^^^^
+   |
+   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
+
+note: the above error was encountered while instantiating `fn Bar::<0>::assert`
+  --> $DIR/mono-reachable-invalid-const.rs:22:5
+   |
+LL |     Bar::<0>::assert();
+   |     ^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/tests/ui/feature-gates/feature-gate-optimize_attribute.stderr b/tests/ui/feature-gates/feature-gate-optimize_attribute.stderr
index 815013733a9..9bab366f7fe 100644
--- a/tests/ui/feature-gates/feature-gate-optimize_attribute.stderr
+++ b/tests/ui/feature-gates/feature-gate-optimize_attribute.stderr
@@ -1,5 +1,5 @@
 error[E0658]: the `#[optimize]` attribute is an experimental feature
-  --> $DIR/feature-gate-optimize_attribute.rs:7:1
+  --> $DIR/feature-gate-optimize_attribute.rs:4:1
    |
 LL | #[optimize(size)]
    | ^^^^^^^^^^^^^^^^^
@@ -9,30 +9,30 @@ LL | #[optimize(size)]
    = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
 
 error[E0658]: the `#[optimize]` attribute is an experimental feature
-  --> $DIR/feature-gate-optimize_attribute.rs:10:1
+  --> $DIR/feature-gate-optimize_attribute.rs:7:1
    |
-LL | #[optimize(speed)]
-   | ^^^^^^^^^^^^^^^^^^
+LL | #[optimize(size)]
+   | ^^^^^^^^^^^^^^^^^
    |
    = note: see issue #54882 <https://github.com/rust-lang/rust/issues/54882> for more information
    = help: add `#![feature(optimize_attribute)]` to the crate attributes to enable
    = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
 
 error[E0658]: the `#[optimize]` attribute is an experimental feature
-  --> $DIR/feature-gate-optimize_attribute.rs:13:1
+  --> $DIR/feature-gate-optimize_attribute.rs:10:1
    |
-LL | #[optimize(banana)]
-   | ^^^^^^^^^^^^^^^^^^^
+LL | #[optimize(speed)]
+   | ^^^^^^^^^^^^^^^^^^
    |
    = note: see issue #54882 <https://github.com/rust-lang/rust/issues/54882> for more information
    = help: add `#![feature(optimize_attribute)]` to the crate attributes to enable
    = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
 
 error[E0658]: the `#[optimize]` attribute is an experimental feature
-  --> $DIR/feature-gate-optimize_attribute.rs:4:1
+  --> $DIR/feature-gate-optimize_attribute.rs:13:1
    |
-LL | #[optimize(size)]
-   | ^^^^^^^^^^^^^^^^^
+LL | #[optimize(banana)]
+   | ^^^^^^^^^^^^^^^^^^^
    |
    = note: see issue #54882 <https://github.com/rust-lang/rust/issues/54882> for more information
    = help: add `#![feature(optimize_attribute)]` to the crate attributes to enable
diff --git a/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr b/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr
index 677fef3a926..e4cc088e2cd 100644
--- a/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr
+++ b/tests/ui/feature-gates/issue-43106-gating-of-stable.stderr
@@ -1,4 +1,10 @@
 error[E0734]: stability attributes may not be used outside of the standard library
+  --> $DIR/issue-43106-gating-of-stable.rs:10:1
+   |
+LL | #[stable()]
+   | ^^^^^^^^^^^
+
+error[E0734]: stability attributes may not be used outside of the standard library
   --> $DIR/issue-43106-gating-of-stable.rs:14:9
    |
 LL |         #![stable()]
@@ -29,12 +35,6 @@ LL |     #[stable()]
    |     ^^^^^^^^^^^
 
 error[E0734]: stability attributes may not be used outside of the standard library
-  --> $DIR/issue-43106-gating-of-stable.rs:10:1
-   |
-LL | #[stable()]
-   | ^^^^^^^^^^^
-
-error[E0734]: stability attributes may not be used outside of the standard library
   --> $DIR/issue-43106-gating-of-stable.rs:7:1
    |
 LL | #![stable()]
diff --git a/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr b/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr
index a2f361878c6..f7c6e631cd1 100644
--- a/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr
+++ b/tests/ui/feature-gates/issue-43106-gating-of-unstable.stderr
@@ -1,4 +1,10 @@
 error[E0734]: stability attributes may not be used outside of the standard library
+  --> $DIR/issue-43106-gating-of-unstable.rs:10:1
+   |
+LL | #[unstable()]
+   | ^^^^^^^^^^^^^
+
+error[E0734]: stability attributes may not be used outside of the standard library
   --> $DIR/issue-43106-gating-of-unstable.rs:14:9
    |
 LL |         #![unstable()]
@@ -29,12 +35,6 @@ LL |     #[unstable()]
    |     ^^^^^^^^^^^^^
 
 error[E0734]: stability attributes may not be used outside of the standard library
-  --> $DIR/issue-43106-gating-of-unstable.rs:10:1
-   |
-LL | #[unstable()]
-   | ^^^^^^^^^^^^^
-
-error[E0734]: stability attributes may not be used outside of the standard library
   --> $DIR/issue-43106-gating-of-unstable.rs:7:1
    |
 LL | #![unstable()]
diff --git a/tests/ui/parser/issues/issue-32505.stderr b/tests/ui/parser/issues/issue-32505.stderr
index 27ad2c3e5be..0eaa5efd525 100644
--- a/tests/ui/parser/issues/issue-32505.stderr
+++ b/tests/ui/parser/issues/issue-32505.stderr
@@ -9,7 +9,7 @@ LL |     foo(|_|)
 help: you might have meant to open the body of the closure
    |
 LL |     foo(|_| {})
-   |            ++
+   |             ++
 
 error[E0425]: cannot find function `foo` in this scope
   --> $DIR/issue-32505.rs:2:5
diff --git a/tests/ui/traits/next-solver/deduce-closure-signature-after-normalization.rs b/tests/ui/traits/next-solver/deduce-closure-signature-after-normalization.rs
deleted file mode 100644
index a0fe7f0d0a5..00000000000
--- a/tests/ui/traits/next-solver/deduce-closure-signature-after-normalization.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-//@ compile-flags: -Znext-solver
-// FIXME(-Znext-solver): This test is currently broken because the `deduce_closure_signature`
-// is unable to look at nested obligations.
-trait Foo {
-    fn test() -> impl Fn(u32) -> u32 {
-        |x| x.count_ones()
-        //~^ ERROR type annotations needed
-    }
-}
-
-fn main() {}
diff --git a/tests/ui/traits/next-solver/deduce-closure-signature-after-normalization.stderr b/tests/ui/traits/next-solver/deduce-closure-signature-after-normalization.stderr
deleted file mode 100644
index 3d7cd1af467..00000000000
--- a/tests/ui/traits/next-solver/deduce-closure-signature-after-normalization.stderr
+++ /dev/null
@@ -1,14 +0,0 @@
-error[E0282]: type annotations needed
-  --> $DIR/deduce-closure-signature-after-normalization.rs:6:10
-   |
-LL |         |x| x.count_ones()
-   |          ^  - type must be known at this point
-   |
-help: consider giving this closure parameter an explicit type
-   |
-LL |         |x: /* Type */| x.count_ones()
-   |           ++++++++++++
-
-error: aborting due to 1 previous error
-
-For more information about this error, try `rustc --explain E0282`.
diff --git a/tests/ui/typeck/ice-unexpected-region-123863.rs b/tests/ui/typeck/ice-unexpected-region-123863.rs
new file mode 100644
index 00000000000..d0242df5fd2
--- /dev/null
+++ b/tests/ui/typeck/ice-unexpected-region-123863.rs
@@ -0,0 +1,9 @@
+const fn concat_strs<const A: &'static str>() -> &'static str {
+//~^ ERROR &'static str` is forbidden as the type of a const generic parameter
+    struct Inner<const A: &'static str>;
+//~^ ERROR &'static str` is forbidden as the type of a const generic parameter
+    Inner::concat_strs::<"a">::A
+//~^ ERROR ambiguous associated type
+}
+
+pub fn main() {}
diff --git a/tests/ui/typeck/ice-unexpected-region-123863.stderr b/tests/ui/typeck/ice-unexpected-region-123863.stderr
new file mode 100644
index 00000000000..08f1ede95b4
--- /dev/null
+++ b/tests/ui/typeck/ice-unexpected-region-123863.stderr
@@ -0,0 +1,38 @@
+error: `&'static str` is forbidden as the type of a const generic parameter
+  --> $DIR/ice-unexpected-region-123863.rs:1:31
+   |
+LL | const fn concat_strs<const A: &'static str>() -> &'static str {
+   |                               ^^^^^^^^^^^^
+   |
+   = note: the only supported types are integers, `bool` and `char`
+help: add `#![feature(adt_const_params)]` to the crate attributes to enable more complex and user defined types
+   |
+LL + #![feature(adt_const_params)]
+   |
+
+error: `&'static str` is forbidden as the type of a const generic parameter
+  --> $DIR/ice-unexpected-region-123863.rs:3:27
+   |
+LL |     struct Inner<const A: &'static str>;
+   |                           ^^^^^^^^^^^^
+   |
+   = note: the only supported types are integers, `bool` and `char`
+help: add `#![feature(adt_const_params)]` to the crate attributes to enable more complex and user defined types
+   |
+LL + #![feature(adt_const_params)]
+   |
+
+error[E0223]: ambiguous associated type
+  --> $DIR/ice-unexpected-region-123863.rs:5:5
+   |
+LL |     Inner::concat_strs::<"a">::A
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: if there were a trait named `Example` with associated type `concat_strs` implemented for `Inner<_>`, you could use the fully-qualified path
+   |
+LL |     <Inner<_> as Example>::concat_strs::A
+   |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: aborting due to 3 previous errors
+
+For more information about this error, try `rustc --explain E0223`.
diff --git a/triagebot.toml b/triagebot.toml
index 903569bf445..499ba6e470c 100644
--- a/triagebot.toml
+++ b/triagebot.toml
@@ -529,6 +529,10 @@ cc = ["@compiler-errors", "@lcnr"]
 message = "changes to the core type system"
 cc = ["@compiler-errors", "@lcnr"]
 
+[mentions."compiler/rustc_hir_analysis/src/fn_ctxt/inspect_obligations.rs"]
+message = "changes to `inspect_obligations.rs`"
+cc = ["@compiler-errors", "@lcnr"]
+
 [mentions."compiler/rustc_middle/src/mir/interpret"]
 message = "Some changes occurred to the CTFE / Miri engine"
 cc = ["@rust-lang/miri"]