about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2025-06-20 23:09:48 +0000
committerbors <bors@rust-lang.org>2025-06-20 23:09:48 +0000
commit15c701fbc995eb6c5b3a86021c18185f8eee020d (patch)
tree0d4b164e407ebce78c37e09f104e5371e9aa149c
parent5526a2f47cd676ceeedc08cf71ae75ce2e9284ae (diff)
parent61f491872a01b0906fcedc3862d0e11cb3f6de01 (diff)
downloadrust-15c701fbc995eb6c5b3a86021c18185f8eee020d.tar.gz
rust-15c701fbc995eb6c5b3a86021c18185f8eee020d.zip
Auto merge of #142794 - tgross35:rollup-iae7okj, r=tgross35
Rollup of 9 pull requests

Successful merges:

 - rust-lang/rust#142331 (Add `trim_prefix` and `trim_suffix` methods for both `slice` and `str` types.)
 - rust-lang/rust#142491 (Rework #[cold] attribute parser)
 - rust-lang/rust#142494 (Fix missing docs in `rustc_attr_parsing`)
 - rust-lang/rust#142495 (Better template for `#[repr]` attributes)
 - rust-lang/rust#142497 (Fix random failure when JS code is executed when the whole file was not read yet)
 - rust-lang/rust#142575 (Ensure copy* intrinsics also perform the static self-init checks)
 - rust-lang/rust#142650 (Refactor Translator)
 - rust-lang/rust#142713 (mbe: Refactor transcription)
 - rust-lang/rust#142755 (rustdoc: Remove `FormatRenderer::cache`)

r? `@ghost`
`@rustbot` modify labels: rollup
-rw-r--r--compiler/rustc_attr_data_structures/src/attributes.rs3
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs18
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/mod.rs19
-rw-r--r--compiler/rustc_attr_parsing/src/attributes/repr.rs3
-rw-r--r--compiler/rustc_attr_parsing/src/context.rs13
-rw-r--r--compiler/rustc_attr_parsing/src/session_diagnostics.rs5
-rw-r--r--compiler/rustc_codegen_ssa/src/back/write.rs20
-rw-r--r--compiler/rustc_codegen_ssa/src/codegen_attrs.rs19
-rw-r--r--compiler/rustc_const_eval/src/interpret/memory.rs7
-rw-r--r--compiler/rustc_driver_impl/src/lib.rs10
-rw-r--r--compiler/rustc_error_messages/src/lib.rs10
-rw-r--r--compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs36
-rw-r--r--compiler/rustc_errors/src/emitter.rs88
-rw-r--r--compiler/rustc_errors/src/json.rs40
-rw-r--r--compiler/rustc_errors/src/json/tests.rs6
-rw-r--r--compiler/rustc_errors/src/lib.rs43
-rw-r--r--compiler/rustc_errors/src/tests.rs43
-rw-r--r--compiler/rustc_errors/src/translation.rs43
-rw-r--r--compiler/rustc_expand/src/mbe/transcribe.rs767
-rw-r--r--compiler/rustc_interface/src/interface.rs6
-rw-r--r--compiler/rustc_parse/src/parser/tests.rs6
-rw-r--r--compiler/rustc_parse/src/validate_attr.rs1
-rw-r--r--compiler/rustc_passes/src/check_attr.rs16
-rw-r--r--compiler/rustc_session/src/parse.rs22
-rw-r--r--compiler/rustc_session/src/session.rs44
-rw-r--r--library/core/src/slice/mod.rs83
-rw-r--r--library/core/src/str/mod.rs77
-rw-r--r--src/librustdoc/core.rs9
-rw-r--r--src/librustdoc/doctest/make.rs9
-rw-r--r--src/librustdoc/formats/renderer.rs2
-rw-r--r--src/librustdoc/html/render/context.rs4
-rw-r--r--src/librustdoc/html/static/js/search.js78
-rw-r--r--src/librustdoc/json/conversions.rs3
-rw-r--r--src/librustdoc/json/mod.rs4
-rw-r--r--src/librustdoc/passes/lint/check_code_block_syntax.rs28
-rw-r--r--src/rustdoc-json-types/lib.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs5
-rw-r--r--src/tools/rustfmt/src/parse/session.rs56
-rw-r--r--tests/ui/attributes/expected-word.rs3
-rw-r--r--tests/ui/attributes/expected-word.stderr12
-rw-r--r--tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr16
-rw-r--r--tests/ui/issues/issue-43988.stderr4
-rw-r--r--tests/ui/lint/unused/unused-attr-duplicate.stderr24
-rw-r--r--tests/ui/repr/repr.stderr6
-rw-r--r--tests/ui/statics/read_before_init.rs22
-rw-r--r--tests/ui/statics/read_before_init.stderr17
46 files changed, 997 insertions, 757 deletions
diff --git a/compiler/rustc_attr_data_structures/src/attributes.rs b/compiler/rustc_attr_data_structures/src/attributes.rs
index 65061059a02..9ebf4c1793d 100644
--- a/compiler/rustc_attr_data_structures/src/attributes.rs
+++ b/compiler/rustc_attr_data_structures/src/attributes.rs
@@ -202,6 +202,9 @@ pub enum AttributeKind {
         span: Span,
     },
 
+    /// Represents `#[cold]`.
+    Cold(Span),
+
     /// Represents `#[rustc_confusables]`.
     Confusables {
         symbols: ThinVec<Symbol>,
diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
index ddcf82cbf7c..1b03525a5ce 100644
--- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
@@ -38,3 +38,21 @@ impl<S: Stage> SingleAttributeParser<S> for OptimizeParser {
         Some(AttributeKind::Optimize(res, cx.attr_span))
     }
 }
+
+pub(crate) struct ColdParser;
+
+impl<S: Stage> SingleAttributeParser<S> for ColdParser {
+    const PATH: &[rustc_span::Symbol] = &[sym::cold];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepLast;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
+    const TEMPLATE: AttributeTemplate = template!(Word);
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        if !args.no_args() {
+            cx.expected_no_args(args.span().unwrap_or(cx.attr_span));
+            return None;
+        };
+
+        Some(AttributeKind::Cold(cx.attr_span))
+    }
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs
index 3bb4c163d32..78a696afa27 100644
--- a/compiler/rustc_attr_parsing/src/attributes/mod.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs
@@ -87,8 +87,19 @@ pub(crate) trait AttributeParser<S: Stage>: Default + 'static {
 /// [`SingleAttributeParser`] can only convert attributes one-to-one, and cannot combine multiple
 /// attributes together like is necessary for `#[stable()]` and `#[unstable()]` for example.
 pub(crate) trait SingleAttributeParser<S: Stage>: 'static {
+    /// The single path of the attribute this parser accepts.
+    ///
+    /// If you need the parser to accept more than one path, use [`AttributeParser`] instead
     const PATH: &[Symbol];
+
+    /// Configures the precedence of attributes with the same `PATH` on a syntax node.
     const ATTRIBUTE_ORDER: AttributeOrder;
+
+    /// Configures what to do when when the same attribute is
+    /// applied more than once on the same syntax node.
+    ///
+    /// [`ATTRIBUTE_ORDER`](Self::ATTRIBUTE_ORDER) specified which one is assumed to be correct,
+    /// and this specified whether to, for example, warn or error on the other one.
     const ON_DUPLICATE: OnDuplicate<S>;
 
     /// The template this attribute parser should implement. Used for diagnostics.
@@ -98,6 +109,8 @@ pub(crate) trait SingleAttributeParser<S: Stage>: 'static {
     fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind>;
 }
 
+/// Use in combination with [`SingleAttributeParser`].
+/// `Single<T: SingleAttributeParser>` implements [`AttributeParser`].
 pub(crate) struct Single<T: SingleAttributeParser<S>, S: Stage>(
     PhantomData<(S, T)>,
     Option<(AttributeKind, Span)>,
@@ -230,6 +243,10 @@ pub(crate) trait CombineAttributeParser<S: Stage>: 'static {
     const PATH: &[rustc_span::Symbol];
 
     type Item;
+    /// A function that converts individual items (of type [`Item`](Self::Item)) into the final attribute.
+    ///
+    /// For example, individual representations fomr `#[repr(...)]` attributes into an `AttributeKind::Repr(x)`,
+    ///  where `x` is a vec of these individual reprs.
     const CONVERT: ConvertFn<Self::Item>;
 
     /// The template this attribute parser should implement. Used for diagnostics.
@@ -242,6 +259,8 @@ pub(crate) trait CombineAttributeParser<S: Stage>: 'static {
     ) -> impl IntoIterator<Item = Self::Item> + 'c;
 }
 
+/// Use in combination with [`CombineAttributeParser`].
+/// `Combine<T: CombineAttributeParser>` implements [`AttributeParser`].
 pub(crate) struct Combine<T: CombineAttributeParser<S>, S: Stage>(
     PhantomData<(S, T)>,
     ThinVec<<T as CombineAttributeParser<S>>::Item>,
diff --git a/compiler/rustc_attr_parsing/src/attributes/repr.rs b/compiler/rustc_attr_parsing/src/attributes/repr.rs
index eb16c3f95f0..4aa27043e98 100644
--- a/compiler/rustc_attr_parsing/src/attributes/repr.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/repr.rs
@@ -25,7 +25,8 @@ impl<S: Stage> CombineAttributeParser<S> for ReprParser {
     const PATH: &[Symbol] = &[sym::repr];
     const CONVERT: ConvertFn<Self::Item> = AttributeKind::Repr;
     // FIXME(jdonszelmann): never used
-    const TEMPLATE: AttributeTemplate = template!(List: "C");
+    const TEMPLATE: AttributeTemplate =
+        template!(List: "C | Rust | align(...) | packed(...) | <integer type> | transparent");
 
     fn extend<'c>(
         cx: &'c mut AcceptContext<'_, '_, S>,
diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs
index 7925c43d62f..8311fccacd8 100644
--- a/compiler/rustc_attr_parsing/src/context.rs
+++ b/compiler/rustc_attr_parsing/src/context.rs
@@ -15,7 +15,7 @@ use rustc_session::Session;
 use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
 
 use crate::attributes::allow_unstable::{AllowConstFnUnstableParser, AllowInternalUnstableParser};
-use crate::attributes::codegen_attrs::OptimizeParser;
+use crate::attributes::codegen_attrs::{ColdParser, OptimizeParser};
 use crate::attributes::confusables::ConfusablesParser;
 use crate::attributes::deprecation::DeprecationParser;
 use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
@@ -106,6 +106,7 @@ attribute_parsers!(
 
         // tidy-alphabetical-start
         Single<AsPtrParser>,
+        Single<ColdParser>,
         Single<ConstStabilityIndirectParser>,
         Single<DeprecationParser>,
         Single<InlineParser>,
@@ -234,6 +235,16 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
         })
     }
 
+    pub(crate) fn expected_no_args(&self, args_span: Span) -> ErrorGuaranteed {
+        self.emit_err(AttributeParseError {
+            span: args_span,
+            attr_span: self.attr_span,
+            template: self.template.clone(),
+            attribute: self.attr_path.clone(),
+            reason: AttributeParseErrorReason::ExpectedNoArgs,
+        })
+    }
+
     /// emit an error that a `name = value` pair was expected at this span. The symbol can be given for
     /// a nicer error message talking about the specific name that was found lacking a value.
     pub(crate) fn expected_name_value(&self, span: Span, name: Option<Symbol>) -> ErrorGuaranteed {
diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
index 337921a318c..29f2e44a98a 100644
--- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs
+++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs
@@ -474,6 +474,7 @@ pub(crate) struct UnrecognizedReprHint {
 }
 
 pub(crate) enum AttributeParseErrorReason {
+    ExpectedNoArgs,
     ExpectedStringLiteral { byte_string: Option<Span> },
     ExpectedSingleArgument,
     ExpectedList,
@@ -529,6 +530,10 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
                 diag.span_label(self.span, format!("didn't expect a literal here"));
                 diag.code(E0565);
             }
+            AttributeParseErrorReason::ExpectedNoArgs => {
+                diag.span_label(self.span, format!("didn't expect any arguments here"));
+                diag.code(E0565);
+            }
             AttributeParseErrorReason::ExpectedNameValue(None) => {
                 diag.span_label(
                     self.span,
diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs
index bbf9cceef2a..c3bfe4c13cd 100644
--- a/compiler/rustc_codegen_ssa/src/back/write.rs
+++ b/compiler/rustc_codegen_ssa/src/back/write.rs
@@ -14,10 +14,10 @@ use rustc_data_structures::jobserver::{self, Acquired};
 use rustc_data_structures::memmap::Mmap;
 use rustc_data_structures::profiling::{SelfProfilerRef, VerboseTimingGuard};
 use rustc_errors::emitter::Emitter;
-use rustc_errors::translation::Translate;
+use rustc_errors::translation::Translator;
 use rustc_errors::{
-    Diag, DiagArgMap, DiagCtxt, DiagMessage, ErrCode, FatalError, FluentBundle, Level, MultiSpan,
-    Style, Suggestions,
+    Diag, DiagArgMap, DiagCtxt, DiagMessage, ErrCode, FatalError, Level, MultiSpan, Style,
+    Suggestions,
 };
 use rustc_fs_util::link_or_copy;
 use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
@@ -1889,16 +1889,6 @@ impl SharedEmitter {
     }
 }
 
-impl Translate for SharedEmitter {
-    fn fluent_bundle(&self) -> Option<&FluentBundle> {
-        None
-    }
-
-    fn fallback_fluent_bundle(&self) -> &FluentBundle {
-        panic!("shared emitter attempted to translate a diagnostic");
-    }
-}
-
 impl Emitter for SharedEmitter {
     fn emit_diagnostic(
         &mut self,
@@ -1932,6 +1922,10 @@ impl Emitter for SharedEmitter {
     fn source_map(&self) -> Option<&SourceMap> {
         None
     }
+
+    fn translator(&self) -> &Translator {
+        panic!("shared emitter attempted to translate a diagnostic");
+    }
 }
 
 impl SharedEmitterMain {
diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index e855f1d3558..39818be5bde 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -4,7 +4,7 @@ use rustc_abi::ExternAbi;
 use rustc_ast::expand::autodiff_attrs::{AutoDiffAttrs, DiffActivity, DiffMode};
 use rustc_ast::{LitKind, MetaItem, MetaItemInner, attr};
 use rustc_attr_data_structures::{
-    AttributeKind, InlineAttr, InstructionSetAttr, OptimizeAttr, find_attr,
+    AttributeKind, InlineAttr, InstructionSetAttr, OptimizeAttr, ReprAttr, find_attr,
 };
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
@@ -110,8 +110,20 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
             }
         };
 
-        if let hir::Attribute::Parsed(AttributeKind::Align { align, .. }) = attr {
-            codegen_fn_attrs.alignment = Some(*align);
+        if let hir::Attribute::Parsed(p) = attr {
+            match p {
+                AttributeKind::Repr(reprs) => {
+                    codegen_fn_attrs.alignment = reprs
+                        .iter()
+                        .filter_map(
+                            |(r, _)| if let ReprAttr::ReprAlign(x) = r { Some(*x) } else { None },
+                        )
+                        .max();
+                }
+                AttributeKind::Cold(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD,
+                AttributeKind::Align { align, .. } => codegen_fn_attrs.alignment = Some(*align),
+                _ => {}
+            }
         }
 
         let Some(Ident { name, .. }) = attr.ident() else {
@@ -119,7 +131,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
         };
 
         match name {
-            sym::cold => codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD,
             sym::rustc_allocator => codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR,
             sym::ffi_pure => codegen_fn_attrs.flags |= CodegenFnAttrFlags::FFI_PURE,
             sym::ffi_const => codegen_fn_attrs.flags |= CodegenFnAttrFlags::FFI_CONST,
diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs
index 99a4bc1b7d6..36d1a413598 100644
--- a/compiler/rustc_const_eval/src/interpret/memory.rs
+++ b/compiler/rustc_const_eval/src/interpret/memory.rs
@@ -1412,8 +1412,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
         let src_alloc = self.get_alloc_raw(src_alloc_id)?;
         let src_range = alloc_range(src_offset, size);
         assert!(!self.memory.validation_in_progress.get(), "we can't be copying during validation");
-        // For the overlapping case, it is crucial that we trigger the read hook
+
+        // Trigger read hooks.
+        // For the overlapping case, it is crucial that we trigger the read hooks
         // before the write hook -- the aliasing model cares about the order.
+        if let Ok((alloc_id, ..)) = self.ptr_try_get_alloc_id(src, size.bytes() as i64) {
+            M::before_alloc_read(self, alloc_id)?;
+        }
         M::before_memory_read(
             tcx,
             &self.machine,
diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index d53126d0414..daeca43169d 100644
--- a/compiler/rustc_driver_impl/src/lib.rs
+++ b/compiler/rustc_driver_impl/src/lib.rs
@@ -38,6 +38,7 @@ use rustc_data_structures::profiling::{
 };
 use rustc_errors::emitter::stderr_destination;
 use rustc_errors::registry::Registry;
+use rustc_errors::translation::Translator;
 use rustc_errors::{ColorConfig, DiagCtxt, ErrCode, FatalError, PResult, markdown};
 use rustc_feature::find_gated_cfg;
 // This avoids a false positive with `-Wunused_crate_dependencies`.
@@ -109,6 +110,10 @@ use crate::session_diagnostics::{
 
 rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
 
+pub fn default_translator() -> Translator {
+    Translator::with_fallback_bundle(DEFAULT_LOCALE_RESOURCES.to_vec(), false)
+}
+
 pub static DEFAULT_LOCALE_RESOURCES: &[&str] = &[
     // tidy-alphabetical-start
     crate::DEFAULT_LOCALE_RESOURCE,
@@ -1413,11 +1418,10 @@ fn report_ice(
     extra_info: fn(&DiagCtxt),
     using_internal_features: &AtomicBool,
 ) {
-    let fallback_bundle =
-        rustc_errors::fallback_fluent_bundle(crate::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
+    let translator = default_translator();
     let emitter = Box::new(rustc_errors::emitter::HumanEmitter::new(
         stderr_destination(rustc_errors::ColorConfig::Auto),
-        fallback_bundle,
+        translator,
     ));
     let dcx = rustc_errors::DiagCtxt::new(emitter);
     let dcx = dcx.handle();
diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
index 1d3b5b20751..194fc2450ba 100644
--- a/compiler/rustc_error_messages/src/lib.rs
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -18,7 +18,7 @@ pub use fluent_bundle::{self, FluentArgs, FluentError, FluentValue};
 use fluent_syntax::parser::ParserError;
 use icu_provider_adapters::fallback::{LocaleFallbackProvider, LocaleFallbacker};
 use intl_memoizer::concurrent::IntlLangMemoizer;
-use rustc_data_structures::sync::IntoDynSyncSend;
+use rustc_data_structures::sync::{DynSend, IntoDynSyncSend};
 use rustc_macros::{Decodable, Encodable};
 use rustc_span::Span;
 use smallvec::SmallVec;
@@ -204,16 +204,16 @@ fn register_functions(bundle: &mut FluentBundle) {
 
 /// Type alias for the result of `fallback_fluent_bundle` - a reference-counted pointer to a lazily
 /// evaluated fluent bundle.
-pub type LazyFallbackBundle = Arc<LazyLock<FluentBundle, impl FnOnce() -> FluentBundle>>;
+pub type LazyFallbackBundle =
+    Arc<LazyLock<FluentBundle, Box<dyn FnOnce() -> FluentBundle + DynSend>>>;
 
 /// Return the default `FluentBundle` with standard "en-US" diagnostic messages.
 #[instrument(level = "trace", skip(resources))]
-#[define_opaque(LazyFallbackBundle)]
 pub fn fallback_fluent_bundle(
     resources: Vec<&'static str>,
     with_directionality_markers: bool,
 ) -> LazyFallbackBundle {
-    Arc::new(LazyLock::new(move || {
+    Arc::new(LazyLock::new(Box::new(move || {
         let mut fallback_bundle = new_bundle(vec![langid!("en-US")]);
 
         register_functions(&mut fallback_bundle);
@@ -228,7 +228,7 @@ pub fn fallback_fluent_bundle(
         }
 
         fallback_bundle
-    }))
+    })))
 }
 
 /// Identifier for the Fluent message/attribute corresponding to a diagnostic message.
diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
index f3aeb8d224b..2eb3c23259f 100644
--- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
+++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs
@@ -15,17 +15,15 @@ use rustc_span::source_map::SourceMap;
 use crate::emitter::FileWithAnnotatedLines;
 use crate::registry::Registry;
 use crate::snippet::Line;
-use crate::translation::{Translate, to_fluent_args};
+use crate::translation::{Translator, to_fluent_args};
 use crate::{
-    CodeSuggestion, DiagInner, DiagMessage, Emitter, ErrCode, FluentBundle, LazyFallbackBundle,
-    Level, MultiSpan, Style, Subdiag,
+    CodeSuggestion, DiagInner, DiagMessage, Emitter, ErrCode, Level, MultiSpan, Style, Subdiag,
 };
 
 /// Generates diagnostics using annotate-snippet
 pub struct AnnotateSnippetEmitter {
     source_map: Option<Arc<SourceMap>>,
-    fluent_bundle: Option<Arc<FluentBundle>>,
-    fallback_bundle: LazyFallbackBundle,
+    translator: Translator,
 
     /// If true, hides the longer explanation text
     short_message: bool,
@@ -35,16 +33,6 @@ pub struct AnnotateSnippetEmitter {
     macro_backtrace: bool,
 }
 
-impl Translate for AnnotateSnippetEmitter {
-    fn fluent_bundle(&self) -> Option<&FluentBundle> {
-        self.fluent_bundle.as_deref()
-    }
-
-    fn fallback_fluent_bundle(&self) -> &FluentBundle {
-        &self.fallback_bundle
-    }
-}
-
 impl Emitter for AnnotateSnippetEmitter {
     /// The entry point for the diagnostics generation
     fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
@@ -78,6 +66,10 @@ impl Emitter for AnnotateSnippetEmitter {
     fn should_show_explain(&self) -> bool {
         !self.short_message
     }
+
+    fn translator(&self) -> &Translator {
+        &self.translator
+    }
 }
 
 /// Provides the source string for the given `line` of `file`
@@ -104,19 +96,11 @@ fn annotation_level_for_level(level: Level) -> annotate_snippets::Level {
 impl AnnotateSnippetEmitter {
     pub fn new(
         source_map: Option<Arc<SourceMap>>,
-        fluent_bundle: Option<Arc<FluentBundle>>,
-        fallback_bundle: LazyFallbackBundle,
+        translator: Translator,
         short_message: bool,
         macro_backtrace: bool,
     ) -> Self {
-        Self {
-            source_map,
-            fluent_bundle,
-            fallback_bundle,
-            short_message,
-            ui_testing: false,
-            macro_backtrace,
-        }
+        Self { source_map, translator, short_message, ui_testing: false, macro_backtrace }
     }
 
     /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
@@ -137,7 +121,7 @@ impl AnnotateSnippetEmitter {
         _children: &[Subdiag],
         _suggestions: &[CodeSuggestion],
     ) {
-        let message = self.translate_messages(messages, args);
+        let message = self.translator.translate_messages(messages, args);
         if let Some(source_map) = &self.source_map {
             // Make sure our primary file comes first
             let primary_lo = if let Some(primary_span) = msp.primary_span().as_ref() {
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index 6ab6f96079e..e333de4b660 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -35,10 +35,10 @@ use crate::snippet::{
 };
 use crate::styled_buffer::StyledBuffer;
 use crate::timings::TimingRecord;
-use crate::translation::{Translate, to_fluent_args};
+use crate::translation::{Translator, to_fluent_args};
 use crate::{
-    CodeSuggestion, DiagInner, DiagMessage, ErrCode, FluentBundle, LazyFallbackBundle, Level,
-    MultiSpan, Subdiag, SubstitutionHighlight, SuggestionStyle, TerminalUrl,
+    CodeSuggestion, DiagInner, DiagMessage, ErrCode, Level, MultiSpan, Subdiag,
+    SubstitutionHighlight, SuggestionStyle, TerminalUrl,
 };
 
 /// Default column width, used in tests and when terminal dimensions cannot be determined.
@@ -175,7 +175,7 @@ const ANONYMIZED_LINE_NUM: &str = "LL";
 pub type DynEmitter = dyn Emitter + DynSend;
 
 /// Emitter trait for emitting errors and other structured information.
-pub trait Emitter: Translate {
+pub trait Emitter {
     /// Emit a structured diagnostic.
     fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
 
@@ -212,6 +212,8 @@ pub trait Emitter: Translate {
 
     fn source_map(&self) -> Option<&SourceMap>;
 
+    fn translator(&self) -> &Translator;
+
     /// Formats the substitutions of the primary_span
     ///
     /// There are a lot of conditions to this method, but in short:
@@ -224,13 +226,17 @@ pub trait Emitter: Translate {
     /// * If the current `DiagInner` has multiple suggestions,
     ///   we leave `primary_span` and the suggestions untouched.
     fn primary_span_formatted(
-        &mut self,
+        &self,
         primary_span: &mut MultiSpan,
         suggestions: &mut Vec<CodeSuggestion>,
         fluent_args: &FluentArgs<'_>,
     ) {
         if let Some((sugg, rest)) = suggestions.split_first() {
-            let msg = self.translate_message(&sugg.msg, fluent_args).map_err(Report::new).unwrap();
+            let msg = self
+                .translator()
+                .translate_message(&sugg.msg, fluent_args)
+                .map_err(Report::new)
+                .unwrap();
             if rest.is_empty()
                // ^ if there is only one suggestion
                // don't display multi-suggestions as labels
@@ -491,16 +497,6 @@ pub trait Emitter: Translate {
     }
 }
 
-impl Translate for HumanEmitter {
-    fn fluent_bundle(&self) -> Option<&FluentBundle> {
-        self.fluent_bundle.as_deref()
-    }
-
-    fn fallback_fluent_bundle(&self) -> &FluentBundle {
-        &self.fallback_bundle
-    }
-}
-
 impl Emitter for HumanEmitter {
     fn source_map(&self) -> Option<&SourceMap> {
         self.sm.as_deref()
@@ -538,39 +534,52 @@ impl Emitter for HumanEmitter {
     fn supports_color(&self) -> bool {
         self.dst.supports_color()
     }
+
+    fn translator(&self) -> &Translator {
+        &self.translator
+    }
 }
 
 /// An emitter that does nothing when emitting a non-fatal diagnostic.
 /// Fatal diagnostics are forwarded to `fatal_emitter` to avoid silent
 /// failures of rustc, as witnessed e.g. in issue #89358.
-pub struct SilentEmitter {
+pub struct FatalOnlyEmitter {
     pub fatal_emitter: Box<dyn Emitter + DynSend>,
     pub fatal_note: Option<String>,
-    pub emit_fatal_diagnostic: bool,
 }
 
-impl Translate for SilentEmitter {
-    fn fluent_bundle(&self) -> Option<&FluentBundle> {
+impl Emitter for FatalOnlyEmitter {
+    fn source_map(&self) -> Option<&SourceMap> {
         None
     }
 
-    fn fallback_fluent_bundle(&self) -> &FluentBundle {
-        self.fatal_emitter.fallback_fluent_bundle()
+    fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
+        if diag.level == Level::Fatal {
+            if let Some(fatal_note) = &self.fatal_note {
+                diag.sub(Level::Note, fatal_note.clone(), MultiSpan::new());
+            }
+            self.fatal_emitter.emit_diagnostic(diag, registry);
+        }
+    }
+
+    fn translator(&self) -> &Translator {
+        self.fatal_emitter.translator()
     }
 }
 
+pub struct SilentEmitter {
+    pub translator: Translator,
+}
+
 impl Emitter for SilentEmitter {
     fn source_map(&self) -> Option<&SourceMap> {
         None
     }
 
-    fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
-        if self.emit_fatal_diagnostic && diag.level == Level::Fatal {
-            if let Some(fatal_note) = &self.fatal_note {
-                diag.sub(Level::Note, fatal_note.clone(), MultiSpan::new());
-            }
-            self.fatal_emitter.emit_diagnostic(diag, registry);
-        }
+    fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {}
+
+    fn translator(&self) -> &Translator {
+        &self.translator
     }
 }
 
@@ -615,9 +624,8 @@ pub struct HumanEmitter {
     #[setters(skip)]
     dst: IntoDynSyncSend<Destination>,
     sm: Option<Arc<SourceMap>>,
-    fluent_bundle: Option<Arc<FluentBundle>>,
     #[setters(skip)]
-    fallback_bundle: LazyFallbackBundle,
+    translator: Translator,
     short_message: bool,
     ui_testing: bool,
     ignored_directories_in_source_blocks: Vec<String>,
@@ -637,12 +645,11 @@ pub(crate) struct FileWithAnnotatedLines {
 }
 
 impl HumanEmitter {
-    pub fn new(dst: Destination, fallback_bundle: LazyFallbackBundle) -> HumanEmitter {
+    pub fn new(dst: Destination, translator: Translator) -> HumanEmitter {
         HumanEmitter {
             dst: IntoDynSyncSend(dst),
             sm: None,
-            fluent_bundle: None,
-            fallback_bundle,
+            translator,
             short_message: false,
             ui_testing: false,
             ignored_directories_in_source_blocks: Vec::new(),
@@ -1433,7 +1440,7 @@ impl HumanEmitter {
         //                very *weird* formats
         //                see?
         for (text, style) in msgs.iter() {
-            let text = self.translate_message(text, args).map_err(Report::new).unwrap();
+            let text = self.translator.translate_message(text, args).map_err(Report::new).unwrap();
             let text = &normalize_whitespace(&text);
             let lines = text.split('\n').collect::<Vec<_>>();
             if lines.len() > 1 {
@@ -1528,7 +1535,8 @@ impl HumanEmitter {
             }
             let mut line = 0;
             for (text, style) in msgs.iter() {
-                let text = self.translate_message(text, args).map_err(Report::new).unwrap();
+                let text =
+                    self.translator.translate_message(text, args).map_err(Report::new).unwrap();
                 // Account for newlines to align output to its label.
                 for text in normalize_whitespace(&text).lines() {
                     buffer.append(
@@ -1560,7 +1568,7 @@ impl HumanEmitter {
                     .into_iter()
                     .filter_map(|label| match label.label {
                         Some(msg) if label.is_primary => {
-                            let text = self.translate_message(&msg, args).ok()?;
+                            let text = self.translator.translate_message(&msg, args).ok()?;
                             if !text.trim().is_empty() { Some(text.to_string()) } else { None }
                         }
                         _ => None,
@@ -3104,7 +3112,11 @@ impl FileWithAnnotatedLines {
 
                 let label = label.as_ref().map(|m| {
                     normalize_whitespace(
-                        &emitter.translate_message(m, args).map_err(Report::new).unwrap(),
+                        &emitter
+                            .translator()
+                            .translate_message(m, args)
+                            .map_err(Report::new)
+                            .unwrap(),
                     )
                 });
 
diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs
index d67e2ba2d60..6d600f896a0 100644
--- a/compiler/rustc_errors/src/json.rs
+++ b/compiler/rustc_errors/src/json.rs
@@ -32,11 +32,8 @@ use crate::emitter::{
 };
 use crate::registry::Registry;
 use crate::timings::{TimingRecord, TimingSection};
-use crate::translation::{Translate, to_fluent_args};
-use crate::{
-    CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, Subdiag, Suggestions,
-    TerminalUrl,
-};
+use crate::translation::{Translator, to_fluent_args};
+use crate::{CodeSuggestion, MultiSpan, SpanLabel, Subdiag, Suggestions, TerminalUrl};
 
 #[cfg(test)]
 mod tests;
@@ -47,9 +44,8 @@ pub struct JsonEmitter {
     dst: IntoDynSyncSend<Box<dyn Write + Send>>,
     #[setters(skip)]
     sm: Option<Arc<SourceMap>>,
-    fluent_bundle: Option<Arc<FluentBundle>>,
     #[setters(skip)]
-    fallback_bundle: LazyFallbackBundle,
+    translator: Translator,
     #[setters(skip)]
     pretty: bool,
     ui_testing: bool,
@@ -67,7 +63,7 @@ impl JsonEmitter {
     pub fn new(
         dst: Box<dyn Write + Send>,
         sm: Option<Arc<SourceMap>>,
-        fallback_bundle: LazyFallbackBundle,
+        translator: Translator,
         pretty: bool,
         json_rendered: HumanReadableErrorType,
         color_config: ColorConfig,
@@ -75,8 +71,7 @@ impl JsonEmitter {
         JsonEmitter {
             dst: IntoDynSyncSend(dst),
             sm,
-            fluent_bundle: None,
-            fallback_bundle,
+            translator,
             pretty,
             ui_testing: false,
             ignored_directories_in_source_blocks: Vec::new(),
@@ -110,16 +105,6 @@ enum EmitTyped<'a> {
     UnusedExtern(UnusedExterns<'a>),
 }
 
-impl Translate for JsonEmitter {
-    fn fluent_bundle(&self) -> Option<&FluentBundle> {
-        self.fluent_bundle.as_deref()
-    }
-
-    fn fallback_fluent_bundle(&self) -> &FluentBundle {
-        &self.fallback_bundle
-    }
-}
-
 impl Emitter for JsonEmitter {
     fn emit_diagnostic(&mut self, diag: crate::DiagInner, registry: &Registry) {
         let data = Diagnostic::from_errors_diagnostic(diag, self, registry);
@@ -194,6 +179,10 @@ impl Emitter for JsonEmitter {
     fn should_show_explain(&self) -> bool {
         !self.json_rendered.short()
     }
+
+    fn translator(&self) -> &Translator {
+        &self.translator
+    }
 }
 
 // The following data types are provided just for serialisation.
@@ -324,7 +313,7 @@ impl Diagnostic {
         let args = to_fluent_args(diag.args.iter());
         let sugg_to_diag = |sugg: &CodeSuggestion| {
             let translated_message =
-                je.translate_message(&sugg.msg, &args).map_err(Report::new).unwrap();
+                je.translator.translate_message(&sugg.msg, &args).map_err(Report::new).unwrap();
             Diagnostic {
                 message: translated_message.to_string(),
                 code: None,
@@ -368,7 +357,7 @@ impl Diagnostic {
             }
         }
 
-        let translated_message = je.translate_messages(&diag.messages, &args);
+        let translated_message = je.translator.translate_messages(&diag.messages, &args);
 
         let code = if let Some(code) = diag.code {
             Some(DiagnosticCode {
@@ -396,10 +385,9 @@ impl Diagnostic {
             ColorConfig::Always | ColorConfig::Auto => dst = Box::new(termcolor::Ansi::new(dst)),
             ColorConfig::Never => {}
         }
-        HumanEmitter::new(dst, Arc::clone(&je.fallback_bundle))
+        HumanEmitter::new(dst, je.translator.clone())
             .short_message(short)
             .sm(je.sm.clone())
-            .fluent_bundle(je.fluent_bundle.clone())
             .diagnostic_width(je.diagnostic_width)
             .macro_backtrace(je.macro_backtrace)
             .track_diagnostics(je.track_diagnostics)
@@ -430,7 +418,7 @@ impl Diagnostic {
         args: &FluentArgs<'_>,
         je: &JsonEmitter,
     ) -> Diagnostic {
-        let translated_message = je.translate_messages(&subdiag.messages, args);
+        let translated_message = je.translator.translate_messages(&subdiag.messages, args);
         Diagnostic {
             message: translated_message.to_string(),
             code: None,
@@ -454,7 +442,7 @@ impl DiagnosticSpan {
             span.is_primary,
             span.label
                 .as_ref()
-                .map(|m| je.translate_message(m, args).unwrap())
+                .map(|m| je.translator.translate_message(m, args).unwrap())
                 .map(|m| m.to_string()),
             suggestion,
             je,
diff --git a/compiler/rustc_errors/src/json/tests.rs b/compiler/rustc_errors/src/json/tests.rs
index 40973e8e5d8..8cf81f467d8 100644
--- a/compiler/rustc_errors/src/json/tests.rs
+++ b/compiler/rustc_errors/src/json/tests.rs
@@ -41,14 +41,14 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
     rustc_span::create_default_session_globals_then(|| {
         let sm = Arc::new(SourceMap::new(FilePathMapping::empty()));
         sm.new_source_file(Path::new("test.rs").to_owned().into(), code.to_owned());
-        let fallback_bundle =
-            crate::fallback_fluent_bundle(vec![crate::DEFAULT_LOCALE_RESOURCE], false);
+        let translator =
+            Translator::with_fallback_bundle(vec![crate::DEFAULT_LOCALE_RESOURCE], false);
 
         let output = Arc::new(Mutex::new(Vec::new()));
         let je = JsonEmitter::new(
             Box::new(Shared { data: output.clone() }),
             Some(sm),
-            fallback_bundle,
+            translator,
             true, // pretty
             HumanReadableErrorType::Short,
             ColorConfig::Never,
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 0bd259366de..207aed8c755 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -748,40 +748,10 @@ impl DiagCtxt {
         Self { inner: Lock::new(DiagCtxtInner::new(emitter)) }
     }
 
-    pub fn make_silent(&self, fatal_note: Option<String>, emit_fatal_diagnostic: bool) {
-        // An empty type that implements `Emitter` to temporarily swap in place of the real one,
-        // which will be used in constructing its replacement.
-        struct FalseEmitter;
-
-        impl Emitter for FalseEmitter {
-            fn emit_diagnostic(&mut self, _: DiagInner, _: &Registry) {
-                unimplemented!("false emitter must only used during `make_silent`")
-            }
-
-            fn source_map(&self) -> Option<&SourceMap> {
-                unimplemented!("false emitter must only used during `make_silent`")
-            }
-        }
-
-        impl translation::Translate for FalseEmitter {
-            fn fluent_bundle(&self) -> Option<&FluentBundle> {
-                unimplemented!("false emitter must only used during `make_silent`")
-            }
-
-            fn fallback_fluent_bundle(&self) -> &FluentBundle {
-                unimplemented!("false emitter must only used during `make_silent`")
-            }
-        }
-
+    pub fn make_silent(&self) {
         let mut inner = self.inner.borrow_mut();
-        let mut prev_emitter = Box::new(FalseEmitter) as Box<dyn Emitter + DynSend>;
-        std::mem::swap(&mut inner.emitter, &mut prev_emitter);
-        let new_emitter = Box::new(emitter::SilentEmitter {
-            fatal_emitter: prev_emitter,
-            fatal_note,
-            emit_fatal_diagnostic,
-        });
-        inner.emitter = new_emitter;
+        let translator = inner.emitter.translator().clone();
+        inner.emitter = Box::new(emitter::SilentEmitter { translator });
     }
 
     pub fn set_emitter(&self, emitter: Box<dyn Emitter + DynSend>) {
@@ -1771,7 +1741,12 @@ impl DiagCtxtInner {
         args: impl Iterator<Item = DiagArg<'a>>,
     ) -> String {
         let args = crate::translation::to_fluent_args(args);
-        self.emitter.translate_message(&message, &args).map_err(Report::new).unwrap().to_string()
+        self.emitter
+            .translator()
+            .translate_message(&message, &args)
+            .map_err(Report::new)
+            .unwrap()
+            .to_string()
     }
 
     fn eagerly_translate_for_subdiag(
diff --git a/compiler/rustc_errors/src/tests.rs b/compiler/rustc_errors/src/tests.rs
index 376fd24d57b..34ebac0fde1 100644
--- a/compiler/rustc_errors/src/tests.rs
+++ b/compiler/rustc_errors/src/tests.rs
@@ -1,3 +1,5 @@
+use std::sync::{Arc, LazyLock};
+
 use rustc_data_structures::sync::IntoDynSyncSend;
 use rustc_error_messages::fluent_bundle::resolver::errors::{ReferenceKind, ResolverError};
 use rustc_error_messages::{DiagMessage, langid};
@@ -5,23 +7,9 @@ use rustc_error_messages::{DiagMessage, langid};
 use crate::FluentBundle;
 use crate::error::{TranslateError, TranslateErrorKind};
 use crate::fluent_bundle::*;
-use crate::translation::Translate;
-
-struct Dummy {
-    bundle: FluentBundle,
-}
-
-impl Translate for Dummy {
-    fn fluent_bundle(&self) -> Option<&FluentBundle> {
-        None
-    }
+use crate::translation::Translator;
 
-    fn fallback_fluent_bundle(&self) -> &FluentBundle {
-        &self.bundle
-    }
-}
-
-fn make_dummy(ftl: &'static str) -> Dummy {
+fn make_translator(ftl: &'static str) -> Translator {
     let resource = FluentResource::try_new(ftl.into()).expect("Failed to parse an FTL string.");
 
     let langid_en = langid!("en-US");
@@ -33,12 +21,15 @@ fn make_dummy(ftl: &'static str) -> Dummy {
 
     bundle.add_resource(resource).expect("Failed to add FTL resources to the bundle.");
 
-    Dummy { bundle }
+    Translator {
+        fluent_bundle: None,
+        fallback_fluent_bundle: Arc::new(LazyLock::new(Box::new(|| bundle))),
+    }
 }
 
 #[test]
 fn wellformed_fluent() {
-    let dummy = make_dummy("mir_build_borrow_of_moved_value = borrow of moved value
+    let translator = make_translator("mir_build_borrow_of_moved_value = borrow of moved value
     .label = value moved into `{$name}` here
     .occurs_because_label = move occurs because `{$name}` has type `{$ty}` which does not implement the `Copy` trait
     .value_borrowed_label = value borrowed here after move
@@ -54,7 +45,7 @@ fn wellformed_fluent() {
         );
 
         assert_eq!(
-            dummy.translate_message(&message, &args).unwrap(),
+            translator.translate_message(&message, &args).unwrap(),
             "borrow this binding in the pattern to avoid moving the value"
         );
     }
@@ -66,7 +57,7 @@ fn wellformed_fluent() {
         );
 
         assert_eq!(
-            dummy.translate_message(&message, &args).unwrap(),
+            translator.translate_message(&message, &args).unwrap(),
             "value borrowed here after move"
         );
     }
@@ -78,7 +69,7 @@ fn wellformed_fluent() {
         );
 
         assert_eq!(
-            dummy.translate_message(&message, &args).unwrap(),
+            translator.translate_message(&message, &args).unwrap(),
             "move occurs because `\u{2068}Foo\u{2069}` has type `\u{2068}std::string::String\u{2069}` which does not implement the `Copy` trait"
         );
 
@@ -89,7 +80,7 @@ fn wellformed_fluent() {
             );
 
             assert_eq!(
-                dummy.translate_message(&message, &args).unwrap(),
+                translator.translate_message(&message, &args).unwrap(),
                 "value moved into `\u{2068}Foo\u{2069}` here"
             );
         }
@@ -98,7 +89,7 @@ fn wellformed_fluent() {
 
 #[test]
 fn misformed_fluent() {
-    let dummy = make_dummy("mir_build_borrow_of_moved_value = borrow of moved value
+    let translator = make_translator("mir_build_borrow_of_moved_value = borrow of moved value
     .label = value moved into `{name}` here
     .occurs_because_label = move occurs because `{$oops}` has type `{$ty}` which does not implement the `Copy` trait
     .suggestion = borrow this binding in the pattern to avoid moving the value");
@@ -112,7 +103,7 @@ fn misformed_fluent() {
             Some("value_borrowed_label".into()),
         );
 
-        let err = dummy.translate_message(&message, &args).unwrap_err();
+        let err = translator.translate_message(&message, &args).unwrap_err();
         assert!(
             matches!(
                 &err,
@@ -141,7 +132,7 @@ fn misformed_fluent() {
             Some("label".into()),
         );
 
-        let err = dummy.translate_message(&message, &args).unwrap_err();
+        let err = translator.translate_message(&message, &args).unwrap_err();
         if let TranslateError::Two {
             primary: box TranslateError::One { kind: TranslateErrorKind::PrimaryBundleMissing, .. },
             fallback: box TranslateError::One { kind: TranslateErrorKind::Fluent { errs }, .. },
@@ -168,7 +159,7 @@ fn misformed_fluent() {
             Some("occurs_because_label".into()),
         );
 
-        let err = dummy.translate_message(&message, &args).unwrap_err();
+        let err = translator.translate_message(&message, &args).unwrap_err();
         if let TranslateError::Two {
             primary: box TranslateError::One { kind: TranslateErrorKind::PrimaryBundleMissing, .. },
             fallback: box TranslateError::One { kind: TranslateErrorKind::Fluent { errs }, .. },
diff --git a/compiler/rustc_errors/src/translation.rs b/compiler/rustc_errors/src/translation.rs
index 156f5e5d26e..c0bcec093c7 100644
--- a/compiler/rustc_errors/src/translation.rs
+++ b/compiler/rustc_errors/src/translation.rs
@@ -1,8 +1,9 @@
 use std::borrow::Cow;
 use std::env;
 use std::error::Report;
+use std::sync::Arc;
 
-pub use rustc_error_messages::FluentArgs;
+pub use rustc_error_messages::{FluentArgs, LazyFallbackBundle};
 use tracing::{debug, trace};
 
 use crate::error::{TranslateError, TranslateErrorKind};
@@ -28,19 +29,33 @@ pub fn to_fluent_args<'iter>(iter: impl Iterator<Item = DiagArg<'iter>>) -> Flue
     args
 }
 
-pub trait Translate {
-    /// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no
-    /// language was requested by the user then this will be `None` and `fallback_fluent_bundle`
-    /// should be used.
-    fn fluent_bundle(&self) -> Option<&FluentBundle>;
-
+#[derive(Clone)]
+pub struct Translator {
+    /// Localized diagnostics for the locale requested by the user. If no language was requested by
+    /// the user then this will be `None` and `fallback_fluent_bundle` should be used.
+    pub fluent_bundle: Option<Arc<FluentBundle>>,
     /// Return `FluentBundle` with localized diagnostics for the default locale of the compiler.
     /// Used when the user has not requested a specific language or when a localized diagnostic is
     /// unavailable for the requested locale.
-    fn fallback_fluent_bundle(&self) -> &FluentBundle;
+    pub fallback_fluent_bundle: LazyFallbackBundle,
+}
+
+impl Translator {
+    pub fn with_fallback_bundle(
+        resources: Vec<&'static str>,
+        with_directionality_markers: bool,
+    ) -> Translator {
+        Translator {
+            fluent_bundle: None,
+            fallback_fluent_bundle: crate::fallback_fluent_bundle(
+                resources,
+                with_directionality_markers,
+            ),
+        }
+    }
 
     /// Convert `DiagMessage`s to a string, performing translation if necessary.
-    fn translate_messages(
+    pub fn translate_messages(
         &self,
         messages: &[(DiagMessage, Style)],
         args: &FluentArgs<'_>,
@@ -54,7 +69,7 @@ pub trait Translate {
     }
 
     /// Convert a `DiagMessage` to a string, performing translation if necessary.
-    fn translate_message<'a>(
+    pub fn translate_message<'a>(
         &'a self,
         message: &'a DiagMessage,
         args: &'a FluentArgs<'_>,
@@ -91,7 +106,7 @@ pub trait Translate {
             };
 
         try {
-            match self.fluent_bundle().map(|b| translate_with_bundle(b)) {
+            match self.fluent_bundle.as_ref().map(|b| translate_with_bundle(b)) {
                 // The primary bundle was present and translation succeeded
                 Some(Ok(t)) => t,
 
@@ -102,7 +117,7 @@ pub trait Translate {
                     primary @ TranslateError::One {
                         kind: TranslateErrorKind::MessageMissing, ..
                     },
-                )) => translate_with_bundle(self.fallback_fluent_bundle())
+                )) => translate_with_bundle(&self.fallback_fluent_bundle)
                     .map_err(|fallback| primary.and(fallback))?,
 
                 // Always yeet out for errors on debug (unless
@@ -118,11 +133,11 @@ pub trait Translate {
 
                 // ..otherwise, for end users, an error about this wouldn't be useful or actionable, so
                 // just hide it and try with the fallback bundle.
-                Some(Err(primary)) => translate_with_bundle(self.fallback_fluent_bundle())
+                Some(Err(primary)) => translate_with_bundle(&self.fallback_fluent_bundle)
                     .map_err(|fallback| primary.and(fallback))?,
 
                 // The primary bundle is missing, proceed to the fallback bundle
-                None => translate_with_bundle(self.fallback_fluent_bundle())
+                None => translate_with_bundle(&self.fallback_fluent_bundle)
                     .map_err(|fallback| TranslateError::primary(identifier, args).and(fallback))?,
             }
         }
diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs
index 2d3fd7702da..0520be5fbae 100644
--- a/compiler/rustc_expand/src/mbe/transcribe.rs
+++ b/compiler/rustc_expand/src/mbe/transcribe.rs
@@ -9,7 +9,7 @@ use rustc_data_structures::fx::FxHashMap;
 use rustc_errors::{Diag, DiagCtxtHandle, PResult, pluralize};
 use rustc_parse::lexer::nfc_normalize;
 use rustc_parse::parser::ParseNtResult;
-use rustc_session::parse::{ParseSess, SymbolGallery};
+use rustc_session::parse::ParseSess;
 use rustc_span::hygiene::{LocalExpnId, Transparency};
 use rustc_span::{
     Ident, MacroRulesNormalizedIdent, Span, Symbol, SyntaxContext, sym, with_metavar_spans,
@@ -25,20 +25,77 @@ use crate::mbe::macro_parser::NamedMatch::*;
 use crate::mbe::metavar_expr::{MetaVarExprConcatElem, RAW_IDENT_ERR};
 use crate::mbe::{self, KleeneOp, MetaVarExpr};
 
-// A Marker adds the given mark to the syntax context.
-struct Marker(LocalExpnId, Transparency, FxHashMap<SyntaxContext, SyntaxContext>);
+/// Context needed to perform transcription of metavariable expressions.
+struct TranscrCtx<'psess, 'itp> {
+    psess: &'psess ParseSess,
+
+    /// Map from metavars to matched tokens
+    interp: &'itp FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
+
+    /// Allow marking spans.
+    marker: Marker,
+
+    /// The stack of things yet to be completely expanded.
+    ///
+    /// We descend into the RHS (`src`), expanding things as we go. This stack contains the things
+    /// we have yet to expand/are still expanding. We start the stack off with the whole RHS. The
+    /// choice of spacing values doesn't matter.
+    stack: SmallVec<[Frame<'itp>; 1]>,
+
+    /// A stack of where we are in the repeat expansion.
+    ///
+    /// As we descend in the RHS, we will need to be able to match nested sequences of matchers.
+    /// `repeats` keeps track of where we are in matching at each level, with the last element
+    /// being the most deeply nested sequence. This is used as a stack.
+    repeats: Vec<(usize, usize)>,
+
+    /// The resulting token stream from the `TokenTree` we just finished processing.
+    ///
+    /// At the end, this will contain the full result of transcription, but at arbitrary points
+    /// during `transcribe`, `result` will contain subsets of the final result.
+    ///
+    /// Specifically, as we descend into each TokenTree, we will push the existing results onto the
+    /// `result_stack` and clear `results`. We will then produce the results of transcribing the
+    /// TokenTree into `results`. Then, as we unwind back out of the `TokenTree`, we will pop the
+    /// `result_stack` and append `results` too it to produce the new `results` up to that point.
+    ///
+    /// Thus, if we try to pop the `result_stack` and it is empty, we have reached the top-level
+    /// again, and we are done transcribing.
+    result: Vec<TokenTree>,
+
+    /// The in-progress `result` lives at the top of this stack. Each entered `TokenTree` adds a
+    /// new entry.
+    result_stack: Vec<Vec<TokenTree>>,
+}
+
+impl<'psess> TranscrCtx<'psess, '_> {
+    /// Span marked with the correct expansion and transparency.
+    fn visited_dspan(&mut self, dspan: DelimSpan) -> Span {
+        let mut span = dspan.entire();
+        self.marker.mark_span(&mut span);
+        span
+    }
+}
+
+/// A Marker adds the given mark to the syntax context.
+struct Marker {
+    expand_id: LocalExpnId,
+    transparency: Transparency,
+    cache: FxHashMap<SyntaxContext, SyntaxContext>,
+}
 
 impl Marker {
+    /// Mark a span with the stored expansion ID and transparency.
     fn mark_span(&mut self, span: &mut Span) {
         // `apply_mark` is a relatively expensive operation, both due to taking hygiene lock, and
         // by itself. All tokens in a macro body typically have the same syntactic context, unless
         // it's some advanced case with macro-generated macros. So if we cache the marked version
         // of that context once, we'll typically have a 100% cache hit rate after that.
-        let Marker(expn_id, transparency, ref mut cache) = *self;
         *span = span.map_ctxt(|ctxt| {
-            *cache
+            *self
+                .cache
                 .entry(ctxt)
-                .or_insert_with(|| ctxt.apply_mark(expn_id.to_expn_id(), transparency))
+                .or_insert_with(|| ctxt.apply_mark(self.expand_id.to_expn_id(), self.transparency))
         });
     }
 }
@@ -116,52 +173,36 @@ pub(super) fn transcribe<'a>(
         return Ok(TokenStream::default());
     }
 
-    // We descend into the RHS (`src`), expanding things as we go. This stack contains the things
-    // we have yet to expand/are still expanding. We start the stack off with the whole RHS. The
-    // choice of spacing values doesn't matter.
-    let mut stack: SmallVec<[Frame<'_>; 1]> = smallvec![Frame::new_delimited(
-        src,
-        src_span,
-        DelimSpacing::new(Spacing::Alone, Spacing::Alone)
-    )];
-
-    // As we descend in the RHS, we will need to be able to match nested sequences of matchers.
-    // `repeats` keeps track of where we are in matching at each level, with the last element being
-    // the most deeply nested sequence. This is used as a stack.
-    let mut repeats: Vec<(usize, usize)> = Vec::new();
-
-    // `result` contains resulting token stream from the TokenTree we just finished processing. At
-    // the end, this will contain the full result of transcription, but at arbitrary points during
-    // `transcribe`, `result` will contain subsets of the final result.
-    //
-    // Specifically, as we descend into each TokenTree, we will push the existing results onto the
-    // `result_stack` and clear `results`. We will then produce the results of transcribing the
-    // TokenTree into `results`. Then, as we unwind back out of the `TokenTree`, we will pop the
-    // `result_stack` and append `results` too it to produce the new `results` up to that point.
-    //
-    // Thus, if we try to pop the `result_stack` and it is empty, we have reached the top-level
-    // again, and we are done transcribing.
-    let mut result: Vec<TokenTree> = Vec::new();
-    let mut result_stack = Vec::new();
-    let mut marker = Marker(expand_id, transparency, Default::default());
-
-    let dcx = psess.dcx();
+    let mut tscx = TranscrCtx {
+        psess,
+        interp,
+        marker: Marker { expand_id, transparency, cache: Default::default() },
+        repeats: Vec::new(),
+        stack: smallvec![Frame::new_delimited(
+            src,
+            src_span,
+            DelimSpacing::new(Spacing::Alone, Spacing::Alone)
+        )],
+        result: Vec::new(),
+        result_stack: Vec::new(),
+    };
+
     loop {
         // Look at the last frame on the stack.
         // If it still has a TokenTree we have not looked at yet, use that tree.
-        let Some(tree) = stack.last_mut().unwrap().next() else {
+        let Some(tree) = tscx.stack.last_mut().unwrap().next() else {
             // This else-case never produces a value for `tree` (it `continue`s or `return`s).
 
             // Otherwise, if we have just reached the end of a sequence and we can keep repeating,
             // go back to the beginning of the sequence.
-            let frame = stack.last_mut().unwrap();
+            let frame = tscx.stack.last_mut().unwrap();
             if let FrameKind::Sequence { sep, .. } = &frame.kind {
-                let (repeat_idx, repeat_len) = repeats.last_mut().unwrap();
+                let (repeat_idx, repeat_len) = tscx.repeats.last_mut().unwrap();
                 *repeat_idx += 1;
                 if repeat_idx < repeat_len {
                     frame.idx = 0;
                     if let Some(sep) = sep {
-                        result.push(TokenTree::Token(*sep, Spacing::Alone));
+                        tscx.result.push(TokenTree::Token(*sep, Spacing::Alone));
                     }
                     continue;
                 }
@@ -170,10 +211,10 @@ pub(super) fn transcribe<'a>(
             // We are done with the top of the stack. Pop it. Depending on what it was, we do
             // different things. Note that the outermost item must be the delimited, wrapped RHS
             // that was passed in originally to `transcribe`.
-            match stack.pop().unwrap().kind {
+            match tscx.stack.pop().unwrap().kind {
                 // Done with a sequence. Pop from repeats.
                 FrameKind::Sequence { .. } => {
-                    repeats.pop();
+                    tscx.repeats.pop();
                 }
 
                 // We are done processing a Delimited. If this is the top-level delimited, we are
@@ -185,15 +226,16 @@ pub(super) fn transcribe<'a>(
                     if delim == Delimiter::Bracket {
                         spacing.close = Spacing::Alone;
                     }
-                    if result_stack.is_empty() {
+                    if tscx.result_stack.is_empty() {
                         // No results left to compute! We are back at the top-level.
-                        return Ok(TokenStream::new(result));
+                        return Ok(TokenStream::new(tscx.result));
                     }
 
                     // Step back into the parent Delimited.
-                    let tree = TokenTree::Delimited(span, spacing, delim, TokenStream::new(result));
-                    result = result_stack.pop().unwrap();
-                    result.push(tree);
+                    let tree =
+                        TokenTree::Delimited(span, spacing, delim, TokenStream::new(tscx.result));
+                    tscx.result = tscx.result_stack.pop().unwrap();
+                    tscx.result.push(tree);
                 }
             }
             continue;
@@ -202,223 +244,19 @@ pub(super) fn transcribe<'a>(
         // At this point, we know we are in the middle of a TokenTree (the last one on `stack`).
         // `tree` contains the next `TokenTree` to be processed.
         match tree {
-            // We are descending into a sequence. We first make sure that the matchers in the RHS
-            // and the matches in `interp` have the same shape. Otherwise, either the caller or the
-            // macro writer has made a mistake.
+            // Replace the sequence with its expansion.
             seq @ mbe::TokenTree::Sequence(_, seq_rep) => {
-                match lockstep_iter_size(seq, interp, &repeats) {
-                    LockstepIterSize::Unconstrained => {
-                        return Err(dcx.create_err(NoSyntaxVarsExprRepeat { span: seq.span() }));
-                    }
-
-                    LockstepIterSize::Contradiction(msg) => {
-                        // FIXME: this really ought to be caught at macro definition time... It
-                        // happens when two meta-variables are used in the same repetition in a
-                        // sequence, but they come from different sequence matchers and repeat
-                        // different amounts.
-                        return Err(
-                            dcx.create_err(MetaVarsDifSeqMatchers { span: seq.span(), msg })
-                        );
-                    }
-
-                    LockstepIterSize::Constraint(len, _) => {
-                        // We do this to avoid an extra clone above. We know that this is a
-                        // sequence already.
-                        let mbe::TokenTree::Sequence(sp, seq) = seq else { unreachable!() };
-
-                        // Is the repetition empty?
-                        if len == 0 {
-                            if seq.kleene.op == KleeneOp::OneOrMore {
-                                // FIXME: this really ought to be caught at macro definition
-                                // time... It happens when the Kleene operator in the matcher and
-                                // the body for the same meta-variable do not match.
-                                return Err(dcx.create_err(MustRepeatOnce { span: sp.entire() }));
-                            }
-                        } else {
-                            // 0 is the initial counter (we have done 0 repetitions so far). `len`
-                            // is the total number of repetitions we should generate.
-                            repeats.push((0, len));
-
-                            // The first time we encounter the sequence we push it to the stack. It
-                            // then gets reused (see the beginning of the loop) until we are done
-                            // repeating.
-                            stack.push(Frame::new_sequence(
-                                seq_rep,
-                                seq.separator.clone(),
-                                seq.kleene.op,
-                            ));
-                        }
-                    }
-                }
+                transcribe_sequence(&mut tscx, seq, seq_rep)?;
             }
 
             // Replace the meta-var with the matched token tree from the invocation.
-            &mbe::TokenTree::MetaVar(mut sp, mut original_ident) => {
-                // Find the matched nonterminal from the macro invocation, and use it to replace
-                // the meta-var.
-                //
-                // We use `Spacing::Alone` everywhere here, because that's the conservative choice
-                // and spacing of declarative macros is tricky. E.g. in this macro:
-                // ```
-                // macro_rules! idents {
-                //     ($($a:ident,)*) => { stringify!($($a)*) }
-                // }
-                // ```
-                // `$a` has no whitespace after it and will be marked `JointHidden`. If you then
-                // call `idents!(x,y,z,)`, each of `x`, `y`, and `z` will be marked as `Joint`. So
-                // if you choose to use `$x`'s spacing or the identifier's spacing, you'll end up
-                // producing "xyz", which is bad because it effectively merges tokens.
-                // `Spacing::Alone` is the safer option. Fortunately, `space_between` will avoid
-                // some of the unnecessary whitespace.
-                let ident = MacroRulesNormalizedIdent::new(original_ident);
-                if let Some(cur_matched) = lookup_cur_matched(ident, interp, &repeats) {
-                    // We wrap the tokens in invisible delimiters, unless they are already wrapped
-                    // in invisible delimiters with the same `MetaVarKind`. Because some proc
-                    // macros can't handle multiple layers of invisible delimiters of the same
-                    // `MetaVarKind`. This loses some span info, though it hopefully won't matter.
-                    let mut mk_delimited = |mk_span, mv_kind, mut stream: TokenStream| {
-                        if stream.len() == 1 {
-                            let tree = stream.iter().next().unwrap();
-                            if let TokenTree::Delimited(_, _, delim, inner) = tree
-                                && let Delimiter::Invisible(InvisibleOrigin::MetaVar(mvk)) = delim
-                                && mv_kind == *mvk
-                            {
-                                stream = inner.clone();
-                            }
-                        }
-
-                        // Emit as a token stream within `Delimiter::Invisible` to maintain
-                        // parsing priorities.
-                        marker.mark_span(&mut sp);
-                        with_metavar_spans(|mspans| mspans.insert(mk_span, sp));
-                        // Both the open delim and close delim get the same span, which covers the
-                        // `$foo` in the decl macro RHS.
-                        TokenTree::Delimited(
-                            DelimSpan::from_single(sp),
-                            DelimSpacing::new(Spacing::Alone, Spacing::Alone),
-                            Delimiter::Invisible(InvisibleOrigin::MetaVar(mv_kind)),
-                            stream,
-                        )
-                    };
-                    let tt = match cur_matched {
-                        MatchedSingle(ParseNtResult::Tt(tt)) => {
-                            // `tt`s are emitted into the output stream directly as "raw tokens",
-                            // without wrapping them into groups. Other variables are emitted into
-                            // the output stream as groups with `Delimiter::Invisible` to maintain
-                            // parsing priorities.
-                            maybe_use_metavar_location(psess, &stack, sp, tt, &mut marker)
-                        }
-                        MatchedSingle(ParseNtResult::Ident(ident, is_raw)) => {
-                            marker.mark_span(&mut sp);
-                            with_metavar_spans(|mspans| mspans.insert(ident.span, sp));
-                            let kind = token::NtIdent(*ident, *is_raw);
-                            TokenTree::token_alone(kind, sp)
-                        }
-                        MatchedSingle(ParseNtResult::Lifetime(ident, is_raw)) => {
-                            marker.mark_span(&mut sp);
-                            with_metavar_spans(|mspans| mspans.insert(ident.span, sp));
-                            let kind = token::NtLifetime(*ident, *is_raw);
-                            TokenTree::token_alone(kind, sp)
-                        }
-                        MatchedSingle(ParseNtResult::Item(item)) => {
-                            mk_delimited(item.span, MetaVarKind::Item, TokenStream::from_ast(item))
-                        }
-                        MatchedSingle(ParseNtResult::Block(block)) => mk_delimited(
-                            block.span,
-                            MetaVarKind::Block,
-                            TokenStream::from_ast(block),
-                        ),
-                        MatchedSingle(ParseNtResult::Stmt(stmt)) => {
-                            let stream = if let StmtKind::Empty = stmt.kind {
-                                // FIXME: Properly collect tokens for empty statements.
-                                TokenStream::token_alone(token::Semi, stmt.span)
-                            } else {
-                                TokenStream::from_ast(stmt)
-                            };
-                            mk_delimited(stmt.span, MetaVarKind::Stmt, stream)
-                        }
-                        MatchedSingle(ParseNtResult::Pat(pat, pat_kind)) => mk_delimited(
-                            pat.span,
-                            MetaVarKind::Pat(*pat_kind),
-                            TokenStream::from_ast(pat),
-                        ),
-                        MatchedSingle(ParseNtResult::Expr(expr, kind)) => {
-                            let (can_begin_literal_maybe_minus, can_begin_string_literal) =
-                                match &expr.kind {
-                                    ExprKind::Lit(_) => (true, true),
-                                    ExprKind::Unary(UnOp::Neg, e)
-                                        if matches!(&e.kind, ExprKind::Lit(_)) =>
-                                    {
-                                        (true, false)
-                                    }
-                                    _ => (false, false),
-                                };
-                            mk_delimited(
-                                expr.span,
-                                MetaVarKind::Expr {
-                                    kind: *kind,
-                                    can_begin_literal_maybe_minus,
-                                    can_begin_string_literal,
-                                },
-                                TokenStream::from_ast(expr),
-                            )
-                        }
-                        MatchedSingle(ParseNtResult::Literal(lit)) => {
-                            mk_delimited(lit.span, MetaVarKind::Literal, TokenStream::from_ast(lit))
-                        }
-                        MatchedSingle(ParseNtResult::Ty(ty)) => {
-                            let is_path = matches!(&ty.kind, TyKind::Path(None, _path));
-                            mk_delimited(
-                                ty.span,
-                                MetaVarKind::Ty { is_path },
-                                TokenStream::from_ast(ty),
-                            )
-                        }
-                        MatchedSingle(ParseNtResult::Meta(attr_item)) => {
-                            let has_meta_form = attr_item.meta_kind().is_some();
-                            mk_delimited(
-                                attr_item.span(),
-                                MetaVarKind::Meta { has_meta_form },
-                                TokenStream::from_ast(attr_item),
-                            )
-                        }
-                        MatchedSingle(ParseNtResult::Path(path)) => {
-                            mk_delimited(path.span, MetaVarKind::Path, TokenStream::from_ast(path))
-                        }
-                        MatchedSingle(ParseNtResult::Vis(vis)) => {
-                            mk_delimited(vis.span, MetaVarKind::Vis, TokenStream::from_ast(vis))
-                        }
-                        MatchedSeq(..) => {
-                            // We were unable to descend far enough. This is an error.
-                            return Err(dcx.create_err(VarStillRepeating { span: sp, ident }));
-                        }
-                    };
-                    result.push(tt)
-                } else {
-                    // If we aren't able to match the meta-var, we push it back into the result but
-                    // with modified syntax context. (I believe this supports nested macros).
-                    marker.mark_span(&mut sp);
-                    marker.mark_span(&mut original_ident.span);
-                    result.push(TokenTree::token_joint_hidden(token::Dollar, sp));
-                    result.push(TokenTree::Token(
-                        Token::from_ast_ident(original_ident),
-                        Spacing::Alone,
-                    ));
-                }
+            &mbe::TokenTree::MetaVar(sp, original_ident) => {
+                transcribe_metavar(&mut tscx, sp, original_ident)?;
             }
 
             // Replace meta-variable expressions with the result of their expansion.
-            mbe::TokenTree::MetaVarExpr(sp, expr) => {
-                transcribe_metavar_expr(
-                    dcx,
-                    expr,
-                    interp,
-                    &mut marker,
-                    &repeats,
-                    &mut result,
-                    sp,
-                    &psess.symbol_gallery,
-                )?;
+            mbe::TokenTree::MetaVarExpr(dspan, expr) => {
+                transcribe_metavar_expr(&mut tscx, *dspan, expr)?;
             }
 
             // If we are entering a new delimiter, we push its contents to the `stack` to be
@@ -427,21 +265,21 @@ pub(super) fn transcribe<'a>(
             // jump back out of the Delimited, pop the result_stack and add the new results back to
             // the previous results (from outside the Delimited).
             &mbe::TokenTree::Delimited(mut span, ref spacing, ref delimited) => {
-                marker.mark_span(&mut span.open);
-                marker.mark_span(&mut span.close);
-                stack.push(Frame::new_delimited(delimited, span, *spacing));
-                result_stack.push(mem::take(&mut result));
+                tscx.marker.mark_span(&mut span.open);
+                tscx.marker.mark_span(&mut span.close);
+                tscx.stack.push(Frame::new_delimited(delimited, span, *spacing));
+                tscx.result_stack.push(mem::take(&mut tscx.result));
             }
 
             // Nothing much to do here. Just push the token to the result, being careful to
             // preserve syntax context.
             &mbe::TokenTree::Token(mut token) => {
-                marker.mark_span(&mut token.span);
+                tscx.marker.mark_span(&mut token.span);
                 if let token::NtIdent(ident, _) | token::NtLifetime(ident, _) = &mut token.kind {
-                    marker.mark_span(&mut ident.span);
+                    tscx.marker.mark_span(&mut ident.span);
                 }
                 let tt = TokenTree::Token(token, Spacing::Alone);
-                result.push(tt);
+                tscx.result.push(tt);
             }
 
             // There should be no meta-var declarations in the invocation of a macro.
@@ -450,6 +288,305 @@ pub(super) fn transcribe<'a>(
     }
 }
 
+/// Turn `$(...)*` sequences into tokens.
+fn transcribe_sequence<'tx, 'itp>(
+    tscx: &mut TranscrCtx<'tx, 'itp>,
+    seq: &mbe::TokenTree,
+    seq_rep: &'itp mbe::SequenceRepetition,
+) -> PResult<'tx, ()> {
+    let dcx = tscx.psess.dcx();
+
+    // We are descending into a sequence. We first make sure that the matchers in the RHS
+    // and the matches in `interp` have the same shape. Otherwise, either the caller or the
+    // macro writer has made a mistake.
+    match lockstep_iter_size(seq, tscx.interp, &tscx.repeats) {
+        LockstepIterSize::Unconstrained => {
+            return Err(dcx.create_err(NoSyntaxVarsExprRepeat { span: seq.span() }));
+        }
+
+        LockstepIterSize::Contradiction(msg) => {
+            // FIXME: this really ought to be caught at macro definition time... It
+            // happens when two meta-variables are used in the same repetition in a
+            // sequence, but they come from different sequence matchers and repeat
+            // different amounts.
+            return Err(dcx.create_err(MetaVarsDifSeqMatchers { span: seq.span(), msg }));
+        }
+
+        LockstepIterSize::Constraint(len, _) => {
+            // We do this to avoid an extra clone above. We know that this is a
+            // sequence already.
+            let mbe::TokenTree::Sequence(sp, seq) = seq else { unreachable!() };
+
+            // Is the repetition empty?
+            if len == 0 {
+                if seq.kleene.op == KleeneOp::OneOrMore {
+                    // FIXME: this really ought to be caught at macro definition
+                    // time... It happens when the Kleene operator in the matcher and
+                    // the body for the same meta-variable do not match.
+                    return Err(dcx.create_err(MustRepeatOnce { span: sp.entire() }));
+                }
+            } else {
+                // 0 is the initial counter (we have done 0 repetitions so far). `len`
+                // is the total number of repetitions we should generate.
+                tscx.repeats.push((0, len));
+
+                // The first time we encounter the sequence we push it to the stack. It
+                // then gets reused (see the beginning of the loop) until we are done
+                // repeating.
+                tscx.stack.push(Frame::new_sequence(seq_rep, seq.separator.clone(), seq.kleene.op));
+            }
+        }
+    }
+
+    Ok(())
+}
+
+/// Find the matched nonterminal from the macro invocation, and use it to replace
+/// the meta-var.
+///
+/// We use `Spacing::Alone` everywhere here, because that's the conservative choice
+/// and spacing of declarative macros is tricky. E.g. in this macro:
+/// ```
+/// macro_rules! idents {
+///     ($($a:ident,)*) => { stringify!($($a)*) }
+/// }
+/// ```
+/// `$a` has no whitespace after it and will be marked `JointHidden`. If you then
+/// call `idents!(x,y,z,)`, each of `x`, `y`, and `z` will be marked as `Joint`. So
+/// if you choose to use `$x`'s spacing or the identifier's spacing, you'll end up
+/// producing "xyz", which is bad because it effectively merges tokens.
+/// `Spacing::Alone` is the safer option. Fortunately, `space_between` will avoid
+/// some of the unnecessary whitespace.
+fn transcribe_metavar<'tx>(
+    tscx: &mut TranscrCtx<'tx, '_>,
+    mut sp: Span,
+    mut original_ident: Ident,
+) -> PResult<'tx, ()> {
+    let dcx = tscx.psess.dcx();
+
+    let ident = MacroRulesNormalizedIdent::new(original_ident);
+    let Some(cur_matched) = lookup_cur_matched(ident, tscx.interp, &tscx.repeats) else {
+        // If we aren't able to match the meta-var, we push it back into the result but
+        // with modified syntax context. (I believe this supports nested macros).
+        tscx.marker.mark_span(&mut sp);
+        tscx.marker.mark_span(&mut original_ident.span);
+        tscx.result.push(TokenTree::token_joint_hidden(token::Dollar, sp));
+        tscx.result.push(TokenTree::Token(Token::from_ast_ident(original_ident), Spacing::Alone));
+        return Ok(());
+    };
+
+    // We wrap the tokens in invisible delimiters, unless they are already wrapped
+    // in invisible delimiters with the same `MetaVarKind`. Because some proc
+    // macros can't handle multiple layers of invisible delimiters of the same
+    // `MetaVarKind`. This loses some span info, though it hopefully won't matter.
+    let mut mk_delimited = |mk_span, mv_kind, mut stream: TokenStream| {
+        if stream.len() == 1 {
+            let tree = stream.iter().next().unwrap();
+            if let TokenTree::Delimited(_, _, delim, inner) = tree
+                && let Delimiter::Invisible(InvisibleOrigin::MetaVar(mvk)) = delim
+                && mv_kind == *mvk
+            {
+                stream = inner.clone();
+            }
+        }
+
+        // Emit as a token stream within `Delimiter::Invisible` to maintain
+        // parsing priorities.
+        tscx.marker.mark_span(&mut sp);
+        with_metavar_spans(|mspans| mspans.insert(mk_span, sp));
+        // Both the open delim and close delim get the same span, which covers the
+        // `$foo` in the decl macro RHS.
+        TokenTree::Delimited(
+            DelimSpan::from_single(sp),
+            DelimSpacing::new(Spacing::Alone, Spacing::Alone),
+            Delimiter::Invisible(InvisibleOrigin::MetaVar(mv_kind)),
+            stream,
+        )
+    };
+
+    let tt = match cur_matched {
+        MatchedSingle(ParseNtResult::Tt(tt)) => {
+            // `tt`s are emitted into the output stream directly as "raw tokens",
+            // without wrapping them into groups. Other variables are emitted into
+            // the output stream as groups with `Delimiter::Invisible` to maintain
+            // parsing priorities.
+            maybe_use_metavar_location(tscx.psess, &tscx.stack, sp, tt, &mut tscx.marker)
+        }
+        MatchedSingle(ParseNtResult::Ident(ident, is_raw)) => {
+            tscx.marker.mark_span(&mut sp);
+            with_metavar_spans(|mspans| mspans.insert(ident.span, sp));
+            let kind = token::NtIdent(*ident, *is_raw);
+            TokenTree::token_alone(kind, sp)
+        }
+        MatchedSingle(ParseNtResult::Lifetime(ident, is_raw)) => {
+            tscx.marker.mark_span(&mut sp);
+            with_metavar_spans(|mspans| mspans.insert(ident.span, sp));
+            let kind = token::NtLifetime(*ident, *is_raw);
+            TokenTree::token_alone(kind, sp)
+        }
+        MatchedSingle(ParseNtResult::Item(item)) => {
+            mk_delimited(item.span, MetaVarKind::Item, TokenStream::from_ast(item))
+        }
+        MatchedSingle(ParseNtResult::Block(block)) => {
+            mk_delimited(block.span, MetaVarKind::Block, TokenStream::from_ast(block))
+        }
+        MatchedSingle(ParseNtResult::Stmt(stmt)) => {
+            let stream = if let StmtKind::Empty = stmt.kind {
+                // FIXME: Properly collect tokens for empty statements.
+                TokenStream::token_alone(token::Semi, stmt.span)
+            } else {
+                TokenStream::from_ast(stmt)
+            };
+            mk_delimited(stmt.span, MetaVarKind::Stmt, stream)
+        }
+        MatchedSingle(ParseNtResult::Pat(pat, pat_kind)) => {
+            mk_delimited(pat.span, MetaVarKind::Pat(*pat_kind), TokenStream::from_ast(pat))
+        }
+        MatchedSingle(ParseNtResult::Expr(expr, kind)) => {
+            let (can_begin_literal_maybe_minus, can_begin_string_literal) = match &expr.kind {
+                ExprKind::Lit(_) => (true, true),
+                ExprKind::Unary(UnOp::Neg, e) if matches!(&e.kind, ExprKind::Lit(_)) => {
+                    (true, false)
+                }
+                _ => (false, false),
+            };
+            mk_delimited(
+                expr.span,
+                MetaVarKind::Expr {
+                    kind: *kind,
+                    can_begin_literal_maybe_minus,
+                    can_begin_string_literal,
+                },
+                TokenStream::from_ast(expr),
+            )
+        }
+        MatchedSingle(ParseNtResult::Literal(lit)) => {
+            mk_delimited(lit.span, MetaVarKind::Literal, TokenStream::from_ast(lit))
+        }
+        MatchedSingle(ParseNtResult::Ty(ty)) => {
+            let is_path = matches!(&ty.kind, TyKind::Path(None, _path));
+            mk_delimited(ty.span, MetaVarKind::Ty { is_path }, TokenStream::from_ast(ty))
+        }
+        MatchedSingle(ParseNtResult::Meta(attr_item)) => {
+            let has_meta_form = attr_item.meta_kind().is_some();
+            mk_delimited(
+                attr_item.span(),
+                MetaVarKind::Meta { has_meta_form },
+                TokenStream::from_ast(attr_item),
+            )
+        }
+        MatchedSingle(ParseNtResult::Path(path)) => {
+            mk_delimited(path.span, MetaVarKind::Path, TokenStream::from_ast(path))
+        }
+        MatchedSingle(ParseNtResult::Vis(vis)) => {
+            mk_delimited(vis.span, MetaVarKind::Vis, TokenStream::from_ast(vis))
+        }
+        MatchedSeq(..) => {
+            // We were unable to descend far enough. This is an error.
+            return Err(dcx.create_err(VarStillRepeating { span: sp, ident }));
+        }
+    };
+
+    tscx.result.push(tt);
+    Ok(())
+}
+
+/// Turn `${expr(...)}` metavariable expressionss into tokens.
+fn transcribe_metavar_expr<'tx>(
+    tscx: &mut TranscrCtx<'tx, '_>,
+    dspan: DelimSpan,
+    expr: &MetaVarExpr,
+) -> PResult<'tx, ()> {
+    let dcx = tscx.psess.dcx();
+    let tt = match *expr {
+        MetaVarExpr::Concat(ref elements) => metavar_expr_concat(tscx, dspan, elements)?,
+        MetaVarExpr::Count(original_ident, depth) => {
+            let matched = matched_from_ident(dcx, original_ident, tscx.interp)?;
+            let count = count_repetitions(dcx, depth, matched, &tscx.repeats, &dspan)?;
+            TokenTree::token_alone(
+                TokenKind::lit(token::Integer, sym::integer(count), None),
+                tscx.visited_dspan(dspan),
+            )
+        }
+        MetaVarExpr::Ignore(original_ident) => {
+            // Used to ensure that `original_ident` is present in the LHS
+            let _ = matched_from_ident(dcx, original_ident, tscx.interp)?;
+            return Ok(());
+        }
+        MetaVarExpr::Index(depth) => match tscx.repeats.iter().nth_back(depth) {
+            Some((index, _)) => TokenTree::token_alone(
+                TokenKind::lit(token::Integer, sym::integer(*index), None),
+                tscx.visited_dspan(dspan),
+            ),
+            None => {
+                return Err(out_of_bounds_err(dcx, tscx.repeats.len(), dspan.entire(), "index"));
+            }
+        },
+        MetaVarExpr::Len(depth) => match tscx.repeats.iter().nth_back(depth) {
+            Some((_, length)) => TokenTree::token_alone(
+                TokenKind::lit(token::Integer, sym::integer(*length), None),
+                tscx.visited_dspan(dspan),
+            ),
+            None => {
+                return Err(out_of_bounds_err(dcx, tscx.repeats.len(), dspan.entire(), "len"));
+            }
+        },
+    };
+    tscx.result.push(tt);
+    Ok(())
+}
+
+/// Handle the `${concat(...)}` metavariable expression.
+fn metavar_expr_concat<'tx>(
+    tscx: &mut TranscrCtx<'tx, '_>,
+    dspan: DelimSpan,
+    elements: &[MetaVarExprConcatElem],
+) -> PResult<'tx, TokenTree> {
+    let dcx = tscx.psess.dcx();
+    let mut concatenated = String::new();
+    for element in elements.into_iter() {
+        let symbol = match element {
+            MetaVarExprConcatElem::Ident(elem) => elem.name,
+            MetaVarExprConcatElem::Literal(elem) => *elem,
+            MetaVarExprConcatElem::Var(ident) => {
+                match matched_from_ident(dcx, *ident, tscx.interp)? {
+                    NamedMatch::MatchedSeq(named_matches) => {
+                        let Some((curr_idx, _)) = tscx.repeats.last() else {
+                            return Err(dcx.struct_span_err(dspan.entire(), "invalid syntax"));
+                        };
+                        match &named_matches[*curr_idx] {
+                            // FIXME(c410-f3r) Nested repetitions are unimplemented
+                            MatchedSeq(_) => unimplemented!(),
+                            MatchedSingle(pnr) => extract_symbol_from_pnr(dcx, pnr, ident.span)?,
+                        }
+                    }
+                    NamedMatch::MatchedSingle(pnr) => {
+                        extract_symbol_from_pnr(dcx, pnr, ident.span)?
+                    }
+                }
+            }
+        };
+        concatenated.push_str(symbol.as_str());
+    }
+    let symbol = nfc_normalize(&concatenated);
+    let concatenated_span = tscx.visited_dspan(dspan);
+    if !rustc_lexer::is_ident(symbol.as_str()) {
+        return Err(dcx.struct_span_err(
+            concatenated_span,
+            "`${concat(..)}` is not generating a valid identifier",
+        ));
+    }
+    tscx.psess.symbol_gallery.insert(symbol, concatenated_span);
+
+    // The current implementation marks the span as coming from the macro regardless of
+    // contexts of the concatenated identifiers but this behavior may change in the
+    // future.
+    Ok(TokenTree::Token(
+        Token::from_ast_ident(Ident::new(symbol, concatenated_span)),
+        Spacing::Alone,
+    ))
+}
+
 /// Store the metavariable span for this original span into a side table.
 /// FIXME: Try to put the metavariable span into `SpanData` instead of a side table (#118517).
 /// An optimal encoding for inlined spans will need to be selected to minimize regressions.
@@ -671,13 +808,13 @@ fn lockstep_iter_size(
 /// * `[ $( ${count(foo, 0)} ),* ]` will be the same as `[ $( ${count(foo)} ),* ]`
 /// * `[ $( ${count(foo, 1)} ),* ]` will return an error because `${count(foo, 1)}` is
 ///   declared inside a single repetition and the index `1` implies two nested repetitions.
-fn count_repetitions<'a>(
-    dcx: DiagCtxtHandle<'a>,
+fn count_repetitions<'dx>(
+    dcx: DiagCtxtHandle<'dx>,
     depth_user: usize,
     mut matched: &NamedMatch,
     repeats: &[(usize, usize)],
     sp: &DelimSpan,
-) -> PResult<'a, usize> {
+) -> PResult<'dx, usize> {
     // Recursively count the number of matches in `matched` at given depth
     // (or at the top-level of `matched` if no depth is given).
     fn count<'a>(depth_curr: usize, depth_max: usize, matched: &NamedMatch) -> PResult<'a, usize> {
@@ -762,102 +899,6 @@ fn out_of_bounds_err<'a>(dcx: DiagCtxtHandle<'a>, max: usize, span: Span, ty: &s
     dcx.struct_span_err(span, msg)
 }
 
-fn transcribe_metavar_expr<'a>(
-    dcx: DiagCtxtHandle<'a>,
-    expr: &MetaVarExpr,
-    interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
-    marker: &mut Marker,
-    repeats: &[(usize, usize)],
-    result: &mut Vec<TokenTree>,
-    sp: &DelimSpan,
-    symbol_gallery: &SymbolGallery,
-) -> PResult<'a, ()> {
-    let mut visited_span = || {
-        let mut span = sp.entire();
-        marker.mark_span(&mut span);
-        span
-    };
-    match *expr {
-        MetaVarExpr::Concat(ref elements) => {
-            let mut concatenated = String::new();
-            for element in elements.into_iter() {
-                let symbol = match element {
-                    MetaVarExprConcatElem::Ident(elem) => elem.name,
-                    MetaVarExprConcatElem::Literal(elem) => *elem,
-                    MetaVarExprConcatElem::Var(ident) => {
-                        match matched_from_ident(dcx, *ident, interp)? {
-                            NamedMatch::MatchedSeq(named_matches) => {
-                                let Some((curr_idx, _)) = repeats.last() else {
-                                    return Err(dcx.struct_span_err(sp.entire(), "invalid syntax"));
-                                };
-                                match &named_matches[*curr_idx] {
-                                    // FIXME(c410-f3r) Nested repetitions are unimplemented
-                                    MatchedSeq(_) => unimplemented!(),
-                                    MatchedSingle(pnr) => {
-                                        extract_symbol_from_pnr(dcx, pnr, ident.span)?
-                                    }
-                                }
-                            }
-                            NamedMatch::MatchedSingle(pnr) => {
-                                extract_symbol_from_pnr(dcx, pnr, ident.span)?
-                            }
-                        }
-                    }
-                };
-                concatenated.push_str(symbol.as_str());
-            }
-            let symbol = nfc_normalize(&concatenated);
-            let concatenated_span = visited_span();
-            if !rustc_lexer::is_ident(symbol.as_str()) {
-                return Err(dcx.struct_span_err(
-                    concatenated_span,
-                    "`${concat(..)}` is not generating a valid identifier",
-                ));
-            }
-            symbol_gallery.insert(symbol, concatenated_span);
-            // The current implementation marks the span as coming from the macro regardless of
-            // contexts of the concatenated identifiers but this behavior may change in the
-            // future.
-            result.push(TokenTree::Token(
-                Token::from_ast_ident(Ident::new(symbol, concatenated_span)),
-                Spacing::Alone,
-            ));
-        }
-        MetaVarExpr::Count(original_ident, depth) => {
-            let matched = matched_from_ident(dcx, original_ident, interp)?;
-            let count = count_repetitions(dcx, depth, matched, repeats, sp)?;
-            let tt = TokenTree::token_alone(
-                TokenKind::lit(token::Integer, sym::integer(count), None),
-                visited_span(),
-            );
-            result.push(tt);
-        }
-        MetaVarExpr::Ignore(original_ident) => {
-            // Used to ensure that `original_ident` is present in the LHS
-            let _ = matched_from_ident(dcx, original_ident, interp)?;
-        }
-        MetaVarExpr::Index(depth) => match repeats.iter().nth_back(depth) {
-            Some((index, _)) => {
-                result.push(TokenTree::token_alone(
-                    TokenKind::lit(token::Integer, sym::integer(*index), None),
-                    visited_span(),
-                ));
-            }
-            None => return Err(out_of_bounds_err(dcx, repeats.len(), sp.entire(), "index")),
-        },
-        MetaVarExpr::Len(depth) => match repeats.iter().nth_back(depth) {
-            Some((_, length)) => {
-                result.push(TokenTree::token_alone(
-                    TokenKind::lit(token::Integer, sym::integer(*length), None),
-                    visited_span(),
-                ));
-            }
-            None => return Err(out_of_bounds_err(dcx, repeats.len(), sp.entire(), "len")),
-        },
-    }
-    Ok(())
-}
-
 /// Extracts an metavariable symbol that can be an identifier, a token tree or a literal.
 fn extract_symbol_from_pnr<'a>(
     dcx: DiagCtxtHandle<'a>,
diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs
index e824e9d4aa9..d62bf7f85e0 100644
--- a/compiler/rustc_interface/src/interface.rs
+++ b/compiler/rustc_interface/src/interface.rs
@@ -52,10 +52,9 @@ pub struct Compiler {
 pub(crate) fn parse_cfg(dcx: DiagCtxtHandle<'_>, cfgs: Vec<String>) -> Cfg {
     cfgs.into_iter()
         .map(|s| {
-            let psess = ParseSess::with_silent_emitter(
+            let psess = ParseSess::with_fatal_emitter(
                 vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE],
                 format!("this error occurred on the command line: `--cfg={s}`"),
-                true,
             );
             let filename = FileName::cfg_spec_source_code(&s);
 
@@ -116,10 +115,9 @@ pub(crate) fn parse_check_cfg(dcx: DiagCtxtHandle<'_>, specs: Vec<String>) -> Ch
     let mut check_cfg = CheckCfg { exhaustive_names, exhaustive_values, ..CheckCfg::default() };
 
     for s in specs {
-        let psess = ParseSess::with_silent_emitter(
+        let psess = ParseSess::with_fatal_emitter(
             vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE],
             format!("this error occurred on the command line: `--check-cfg={s}`"),
-            true,
         );
         let filename = FileName::cfg_spec_source_code(&s);
 
diff --git a/compiler/rustc_parse/src/parser/tests.rs b/compiler/rustc_parse/src/parser/tests.rs
index 2a44c90abc1..15679d23bc5 100644
--- a/compiler/rustc_parse/src/parser/tests.rs
+++ b/compiler/rustc_parse/src/parser/tests.rs
@@ -14,6 +14,7 @@ use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, Toke
 use rustc_ast::{self as ast, PatKind, visit};
 use rustc_ast_pretty::pprust::item_to_string;
 use rustc_errors::emitter::{HumanEmitter, OutputTheme};
+use rustc_errors::translation::Translator;
 use rustc_errors::{DiagCtxt, MultiSpan, PResult};
 use rustc_session::parse::ParseSess;
 use rustc_span::source_map::{FilePathMapping, SourceMap};
@@ -41,9 +42,8 @@ fn string_to_parser(psess: &ParseSess, source_str: String) -> Parser<'_> {
 fn create_test_handler(theme: OutputTheme) -> (DiagCtxt, Arc<SourceMap>, Arc<Mutex<Vec<u8>>>) {
     let output = Arc::new(Mutex::new(Vec::new()));
     let source_map = Arc::new(SourceMap::new(FilePathMapping::empty()));
-    let fallback_bundle =
-        rustc_errors::fallback_fluent_bundle(vec![crate::DEFAULT_LOCALE_RESOURCE], false);
-    let mut emitter = HumanEmitter::new(Box::new(Shared { data: output.clone() }), fallback_bundle)
+    let translator = Translator::with_fallback_bundle(vec![crate::DEFAULT_LOCALE_RESOURCE], false);
+    let mut emitter = HumanEmitter::new(Box::new(Shared { data: output.clone() }), translator)
         .sm(Some(source_map.clone()))
         .diagnostic_width(Some(140));
     emitter = emitter.theme(theme);
diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs
index 646e996ff30..ed1737bee33 100644
--- a/compiler/rustc_parse/src/validate_attr.rs
+++ b/compiler/rustc_parse/src/validate_attr.rs
@@ -292,6 +292,7 @@ fn emit_malformed_attribute(
             | sym::align
             | sym::deprecated
             | sym::optimize
+            | sym::cold
     ) {
         return;
     }
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index e5d918b8b96..5fb9514e5ca 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -149,10 +149,12 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                 }
                 Attribute::Parsed(AttributeKind::Repr(_)) => { /* handled below this loop and elsewhere */
                 }
+                Attribute::Parsed(AttributeKind::Cold(attr_span)) => {
+                    self.check_cold(hir_id, *attr_span, span, target)
+                }
                 Attribute::Parsed(AttributeKind::Align { align, span: repr_span }) => {
                     self.check_align(span, target, *align, *repr_span)
                 }
-
                 Attribute::Parsed(
                     AttributeKind::BodyStability { .. }
                     | AttributeKind::ConstStabilityIndirect
@@ -245,7 +247,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                         [sym::ffi_pure, ..] => self.check_ffi_pure(attr.span(), attrs, target),
                         [sym::ffi_const, ..] => self.check_ffi_const(attr.span(), target),
                         [sym::link_ordinal, ..] => self.check_link_ordinal(attr, span, target),
-                        [sym::cold, ..] => self.check_cold(hir_id, attr, span, target),
                         [sym::link, ..] => self.check_link(hir_id, attr, span, target),
                         [sym::link_name, ..] => self.check_link_name(hir_id, attr, span, target),
                         [sym::link_section, ..] => self.check_link_section(hir_id, attr, span, target),
@@ -651,8 +652,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             sym::repr,
             sym::align,
             sym::rustc_std_internal_symbol,
-            // code generation
-            sym::cold,
             // documentation
             sym::doc,
         ];
@@ -688,7 +687,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                         Attribute::Parsed(
                             AttributeKind::Deprecation { .. }
                             | AttributeKind::Repr { .. }
-                            | AttributeKind::Align { .. },
+                            | AttributeKind::Align { .. }
+                            | AttributeKind::Cold(..),
                         ) => {
                             continue;
                         }
@@ -1640,7 +1640,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
     }
 
     /// Checks if `#[cold]` is applied to a non-function.
-    fn check_cold(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_cold(&self, hir_id: HirId, attr_span: Span, span: Span, target: Target) {
         match target {
             Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => {}
             // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
@@ -1648,7 +1648,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             // erroneously allowed it and some crates used it accidentally, to be compatible
             // with crates depending on them, we can't throw an error here.
             Target::Field | Target::Arm | Target::MacroDef => {
-                self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "cold");
+                self.inline_attr_str_error_with_macro_def(hir_id, attr_span, "cold");
             }
             _ => {
                 // FIXME: #[cold] was previously allowed on non-functions and some crates used
@@ -1656,7 +1656,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                 self.tcx.emit_node_span_lint(
                     UNUSED_ATTRIBUTES,
                     hir_id,
-                    attr.span(),
+                    attr_span,
                     errors::Cold { span, on_crate: hir_id == CRATE_HIR_ID },
                 );
             }
diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs
index 87c848cf857..0118cdb1fc2 100644
--- a/compiler/rustc_session/src/parse.rs
+++ b/compiler/rustc_session/src/parse.rs
@@ -8,10 +8,11 @@ use rustc_ast::attr::AttrIdGenerator;
 use rustc_ast::node_id::NodeId;
 use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
 use rustc_data_structures::sync::{AppendOnlyVec, Lock};
-use rustc_errors::emitter::{HumanEmitter, SilentEmitter, stderr_destination};
+use rustc_errors::emitter::{FatalOnlyEmitter, HumanEmitter, stderr_destination};
+use rustc_errors::translation::Translator;
 use rustc_errors::{
     ColorConfig, Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, EmissionGuarantee, MultiSpan,
-    StashKey, fallback_fluent_bundle,
+    StashKey,
 };
 use rustc_feature::{GateIssue, UnstableFeatures, find_feature_issue};
 use rustc_span::edition::Edition;
@@ -242,10 +243,10 @@ pub struct ParseSess {
 impl ParseSess {
     /// Used for testing.
     pub fn new(locale_resources: Vec<&'static str>) -> Self {
-        let fallback_bundle = fallback_fluent_bundle(locale_resources, false);
+        let translator = Translator::with_fallback_bundle(locale_resources, false);
         let sm = Arc::new(SourceMap::new(FilePathMapping::empty()));
         let emitter = Box::new(
-            HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle)
+            HumanEmitter::new(stderr_destination(ColorConfig::Auto), translator)
                 .sm(Some(Arc::clone(&sm))),
         );
         let dcx = DiagCtxt::new(emitter);
@@ -274,19 +275,14 @@ impl ParseSess {
         }
     }
 
-    pub fn with_silent_emitter(
-        locale_resources: Vec<&'static str>,
-        fatal_note: String,
-        emit_fatal_diagnostic: bool,
-    ) -> Self {
-        let fallback_bundle = fallback_fluent_bundle(locale_resources, false);
+    pub fn with_fatal_emitter(locale_resources: Vec<&'static str>, fatal_note: String) -> Self {
+        let translator = Translator::with_fallback_bundle(locale_resources, false);
         let sm = Arc::new(SourceMap::new(FilePathMapping::empty()));
         let fatal_emitter =
-            Box::new(HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle));
-        let dcx = DiagCtxt::new(Box::new(SilentEmitter {
+            Box::new(HumanEmitter::new(stderr_destination(ColorConfig::Auto), translator));
+        let dcx = DiagCtxt::new(Box::new(FatalOnlyEmitter {
             fatal_emitter,
             fatal_note: Some(fatal_note),
-            emit_fatal_diagnostic,
         }))
         .disable_warnings();
         ParseSess::with_dcx(dcx, sm)
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index ca42c5a4256..ad58c3c8f7d 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -19,9 +19,10 @@ use rustc_errors::emitter::{
 };
 use rustc_errors::json::JsonEmitter;
 use rustc_errors::timings::TimingSectionHandler;
+use rustc_errors::translation::Translator;
 use rustc_errors::{
     Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, Diagnostic, ErrorGuaranteed, FatalAbort,
-    FluentBundle, LazyFallbackBundle, TerminalUrl, fallback_fluent_bundle,
+    TerminalUrl, fallback_fluent_bundle,
 };
 use rustc_macros::HashStable_Generic;
 pub use rustc_span::def_id::StableCrateId;
@@ -948,8 +949,7 @@ impl Session {
 fn default_emitter(
     sopts: &config::Options,
     source_map: Arc<SourceMap>,
-    bundle: Option<Arc<FluentBundle>>,
-    fallback_bundle: LazyFallbackBundle,
+    translator: Translator,
 ) -> Box<DynEmitter> {
     let macro_backtrace = sopts.unstable_opts.macro_backtrace;
     let track_diagnostics = sopts.unstable_opts.track_diagnostics;
@@ -974,17 +974,11 @@ fn default_emitter(
             let short = kind.short();
 
             if let HumanReadableErrorType::AnnotateSnippet = kind {
-                let emitter = AnnotateSnippetEmitter::new(
-                    source_map,
-                    bundle,
-                    fallback_bundle,
-                    short,
-                    macro_backtrace,
-                );
+                let emitter =
+                    AnnotateSnippetEmitter::new(source_map, translator, short, macro_backtrace);
                 Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing))
             } else {
-                let emitter = HumanEmitter::new(stderr_destination(color_config), fallback_bundle)
-                    .fluent_bundle(bundle)
+                let emitter = HumanEmitter::new(stderr_destination(color_config), translator)
                     .sm(source_map)
                     .short_message(short)
                     .diagnostic_width(sopts.diagnostic_width)
@@ -1006,12 +1000,11 @@ fn default_emitter(
             JsonEmitter::new(
                 Box::new(io::BufWriter::new(io::stderr())),
                 source_map,
-                fallback_bundle,
+                translator,
                 pretty,
                 json_rendered,
                 color_config,
             )
-            .fluent_bundle(bundle)
             .ui_testing(sopts.unstable_opts.ui_testing)
             .ignored_directories_in_source_blocks(
                 sopts.unstable_opts.ignore_directory_in_diagnostics_source_blocks.clone(),
@@ -1030,7 +1023,7 @@ fn default_emitter(
 pub fn build_session(
     sopts: config::Options,
     io: CompilerIO,
-    bundle: Option<Arc<rustc_errors::FluentBundle>>,
+    fluent_bundle: Option<Arc<rustc_errors::FluentBundle>>,
     registry: rustc_errors::registry::Registry,
     fluent_resources: Vec<&'static str>,
     driver_lint_caps: FxHashMap<lint::LintId, lint::Level>,
@@ -1052,12 +1045,15 @@ pub fn build_session(
     let cap_lints_allow = sopts.lint_cap.is_some_and(|cap| cap == lint::Allow);
     let can_emit_warnings = !(warnings_allow || cap_lints_allow);
 
-    let fallback_bundle = fallback_fluent_bundle(
-        fluent_resources,
-        sopts.unstable_opts.translate_directionality_markers,
-    );
+    let translator = Translator {
+        fluent_bundle,
+        fallback_fluent_bundle: fallback_fluent_bundle(
+            fluent_resources,
+            sopts.unstable_opts.translate_directionality_markers,
+        ),
+    };
     let source_map = rustc_span::source_map::get_source_map().unwrap();
-    let emitter = default_emitter(&sopts, Arc::clone(&source_map), bundle, fallback_bundle);
+    let emitter = default_emitter(&sopts, Arc::clone(&source_map), translator);
 
     let mut dcx = DiagCtxt::new(emitter)
         .with_flags(sopts.unstable_opts.dcx_flags(can_emit_warnings))
@@ -1500,13 +1496,13 @@ impl EarlyDiagCtxt {
 fn mk_emitter(output: ErrorOutputType) -> Box<DynEmitter> {
     // FIXME(#100717): early errors aren't translated at the moment, so this is fine, but it will
     // need to reference every crate that might emit an early error for translation to work.
-    let fallback_bundle =
-        fallback_fluent_bundle(vec![rustc_errors::DEFAULT_LOCALE_RESOURCE], false);
+    let translator =
+        Translator::with_fallback_bundle(vec![rustc_errors::DEFAULT_LOCALE_RESOURCE], false);
     let emitter: Box<DynEmitter> = match output {
         config::ErrorOutputType::HumanReadable { kind, color_config } => {
             let short = kind.short();
             Box::new(
-                HumanEmitter::new(stderr_destination(color_config), fallback_bundle)
+                HumanEmitter::new(stderr_destination(color_config), translator)
                     .theme(if let HumanReadableErrorType::Unicode = kind {
                         OutputTheme::Unicode
                     } else {
@@ -1519,7 +1515,7 @@ fn mk_emitter(output: ErrorOutputType) -> Box<DynEmitter> {
             Box::new(JsonEmitter::new(
                 Box::new(io::BufWriter::new(io::stderr())),
                 Some(Arc::new(SourceMap::new(FilePathMapping::empty()))),
-                fallback_bundle,
+                translator,
                 pretty,
                 json_rendered,
                 color_config,
diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs
index c26bbad087a..3a3f44c6b85 100644
--- a/library/core/src/slice/mod.rs
+++ b/library/core/src/slice/mod.rs
@@ -2764,6 +2764,89 @@ impl<T> [T] {
         None
     }
 
+    /// Returns a subslice with the optional prefix removed.
+    ///
+    /// If the slice starts with `prefix`, returns the subslice after the prefix.  If `prefix`
+    /// is empty or the slice does not start with `prefix`, simply returns the original slice.
+    /// If `prefix` is equal to the original slice, returns an empty slice.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(trim_prefix_suffix)]
+    ///
+    /// let v = &[10, 40, 30];
+    ///
+    /// // Prefix present - removes it
+    /// assert_eq!(v.trim_prefix(&[10]), &[40, 30][..]);
+    /// assert_eq!(v.trim_prefix(&[10, 40]), &[30][..]);
+    /// assert_eq!(v.trim_prefix(&[10, 40, 30]), &[][..]);
+    ///
+    /// // Prefix absent - returns original slice
+    /// assert_eq!(v.trim_prefix(&[50]), &[10, 40, 30][..]);
+    /// assert_eq!(v.trim_prefix(&[10, 50]), &[10, 40, 30][..]);
+    ///
+    /// let prefix : &str = "he";
+    /// assert_eq!(b"hello".trim_prefix(prefix.as_bytes()), b"llo".as_ref());
+    /// ```
+    #[must_use = "returns the subslice without modifying the original"]
+    #[unstable(feature = "trim_prefix_suffix", issue = "142312")]
+    pub fn trim_prefix<P: SlicePattern<Item = T> + ?Sized>(&self, prefix: &P) -> &[T]
+    where
+        T: PartialEq,
+    {
+        // This function will need rewriting if and when SlicePattern becomes more sophisticated.
+        let prefix = prefix.as_slice();
+        let n = prefix.len();
+        if n <= self.len() {
+            let (head, tail) = self.split_at(n);
+            if head == prefix {
+                return tail;
+            }
+        }
+        self
+    }
+
+    /// Returns a subslice with the optional suffix removed.
+    ///
+    /// If the slice ends with `suffix`, returns the subslice before the suffix.  If `suffix`
+    /// is empty or the slice does not end with `suffix`, simply returns the original slice.
+    /// If `suffix` is equal to the original slice, returns an empty slice.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(trim_prefix_suffix)]
+    ///
+    /// let v = &[10, 40, 30];
+    ///
+    /// // Suffix present - removes it
+    /// assert_eq!(v.trim_suffix(&[30]), &[10, 40][..]);
+    /// assert_eq!(v.trim_suffix(&[40, 30]), &[10][..]);
+    /// assert_eq!(v.trim_suffix(&[10, 40, 30]), &[][..]);
+    ///
+    /// // Suffix absent - returns original slice
+    /// assert_eq!(v.trim_suffix(&[50]), &[10, 40, 30][..]);
+    /// assert_eq!(v.trim_suffix(&[50, 30]), &[10, 40, 30][..]);
+    /// ```
+    #[must_use = "returns the subslice without modifying the original"]
+    #[unstable(feature = "trim_prefix_suffix", issue = "142312")]
+    pub fn trim_suffix<P: SlicePattern<Item = T> + ?Sized>(&self, suffix: &P) -> &[T]
+    where
+        T: PartialEq,
+    {
+        // This function will need rewriting if and when SlicePattern becomes more sophisticated.
+        let suffix = suffix.as_slice();
+        let (len, n) = (self.len(), suffix.len());
+        if n <= len {
+            let (head, tail) = self.split_at(len - n);
+            if tail == suffix {
+                return head;
+            }
+        }
+        self
+    }
+
     /// Binary searches this slice for a given element.
     /// If the slice is not sorted, the returned result is unspecified and
     /// meaningless.
diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs
index 41834793d22..5051b2288fd 100644
--- a/library/core/src/str/mod.rs
+++ b/library/core/src/str/mod.rs
@@ -2426,6 +2426,83 @@ impl str {
         suffix.strip_suffix_of(self)
     }
 
+    /// Returns a string slice with the optional prefix removed.
+    ///
+    /// If the string starts with the pattern `prefix`, returns the substring after the prefix.
+    /// Unlike [`strip_prefix`], this method always returns `&str` for easy method chaining,
+    /// instead of returning [`Option<&str>`].
+    ///
+    /// If the string does not start with `prefix`, returns the original string unchanged.
+    ///
+    /// The [pattern] can be a `&str`, [`char`], a slice of [`char`]s, or a
+    /// function or closure that determines if a character matches.
+    ///
+    /// [`char`]: prim@char
+    /// [pattern]: self::pattern
+    /// [`strip_prefix`]: Self::strip_prefix
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(trim_prefix_suffix)]
+    ///
+    /// // Prefix present - removes it
+    /// assert_eq!("foo:bar".trim_prefix("foo:"), "bar");
+    /// assert_eq!("foofoo".trim_prefix("foo"), "foo");
+    ///
+    /// // Prefix absent - returns original string
+    /// assert_eq!("foo:bar".trim_prefix("bar"), "foo:bar");
+    ///
+    /// // Method chaining example
+    /// assert_eq!("<https://example.com/>".trim_prefix('<').trim_suffix('>'), "https://example.com/");
+    /// ```
+    #[must_use = "this returns the remaining substring as a new slice, \
+                  without modifying the original"]
+    #[unstable(feature = "trim_prefix_suffix", issue = "142312")]
+    pub fn trim_prefix<P: Pattern>(&self, prefix: P) -> &str {
+        prefix.strip_prefix_of(self).unwrap_or(self)
+    }
+
+    /// Returns a string slice with the optional suffix removed.
+    ///
+    /// If the string ends with the pattern `suffix`, returns the substring before the suffix.
+    /// Unlike [`strip_suffix`], this method always returns `&str` for easy method chaining,
+    /// instead of returning [`Option<&str>`].
+    ///
+    /// If the string does not end with `suffix`, returns the original string unchanged.
+    ///
+    /// The [pattern] can be a `&str`, [`char`], a slice of [`char`]s, or a
+    /// function or closure that determines if a character matches.
+    ///
+    /// [`char`]: prim@char
+    /// [pattern]: self::pattern
+    /// [`strip_suffix`]: Self::strip_suffix
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(trim_prefix_suffix)]
+    ///
+    /// // Suffix present - removes it
+    /// assert_eq!("bar:foo".trim_suffix(":foo"), "bar");
+    /// assert_eq!("foofoo".trim_suffix("foo"), "foo");
+    ///
+    /// // Suffix absent - returns original string
+    /// assert_eq!("bar:foo".trim_suffix("bar"), "bar:foo");
+    ///
+    /// // Method chaining example
+    /// assert_eq!("<https://example.com/>".trim_prefix('<').trim_suffix('>'), "https://example.com/");
+    /// ```
+    #[must_use = "this returns the remaining substring as a new slice, \
+                  without modifying the original"]
+    #[unstable(feature = "trim_prefix_suffix", issue = "142312")]
+    pub fn trim_suffix<P: Pattern>(&self, suffix: P) -> &str
+    where
+        for<'a> P::Searcher<'a>: ReverseSearcher<'a>,
+    {
+        suffix.strip_suffix_of(self).unwrap_or(self)
+    }
+
     /// Returns a string slice with all suffixes that match a pattern
     /// repeatedly removed.
     ///
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index d73600a4636..cf3c4ac97af 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -149,15 +149,12 @@ pub(crate) fn new_dcx(
     diagnostic_width: Option<usize>,
     unstable_opts: &UnstableOptions,
 ) -> rustc_errors::DiagCtxt {
-    let fallback_bundle = rustc_errors::fallback_fluent_bundle(
-        rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(),
-        false,
-    );
+    let translator = rustc_driver::default_translator();
     let emitter: Box<DynEmitter> = match error_format {
         ErrorOutputType::HumanReadable { kind, color_config } => {
             let short = kind.short();
             Box::new(
-                HumanEmitter::new(stderr_destination(color_config), fallback_bundle)
+                HumanEmitter::new(stderr_destination(color_config), translator)
                     .sm(source_map.map(|sm| sm as _))
                     .short_message(short)
                     .diagnostic_width(diagnostic_width)
@@ -178,7 +175,7 @@ pub(crate) fn new_dcx(
                 JsonEmitter::new(
                     Box::new(io::BufWriter::new(io::stderr())),
                     Some(source_map),
-                    fallback_bundle,
+                    translator,
                     pretty,
                     json_rendered,
                     color_config,
diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs
index 3ff6828e52f..f229f77c978 100644
--- a/src/librustdoc/doctest/make.rs
+++ b/src/librustdoc/doctest/make.rs
@@ -456,16 +456,13 @@ fn parse_source(
     let filename = FileName::anon_source_code(&wrapped_source);
 
     let sm = Arc::new(SourceMap::new(FilePathMapping::empty()));
-    let fallback_bundle = rustc_errors::fallback_fluent_bundle(
-        rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(),
-        false,
-    );
+    let translator = rustc_driver::default_translator();
     info.supports_color =
-        HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle.clone())
+        HumanEmitter::new(stderr_destination(ColorConfig::Auto), translator.clone())
             .supports_color();
     // Any errors in parsing should also appear when the doctest is compiled for real, so just
     // send all the errors that the parser emits directly into a `Sink` instead of stderr.
-    let emitter = HumanEmitter::new(Box::new(io::sink()), fallback_bundle);
+    let emitter = HumanEmitter::new(Box::new(io::sink()), translator);
 
     // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
     let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings();
diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs
index 48626171404..79ff1fa38c3 100644
--- a/src/librustdoc/formats/renderer.rs
+++ b/src/librustdoc/formats/renderer.rs
@@ -68,8 +68,6 @@ pub(crate) trait FormatRenderer<'tcx>: Sized {
 
     /// Post processing hook for cleanup and dumping output to files.
     fn after_krate(self) -> Result<(), Error>;
-
-    fn cache(&self) -> &Cache;
 }
 
 fn run_format_inner<'tcx, T: FormatRenderer<'tcx>>(
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 38214451657..3b4dae841ee 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -875,8 +875,4 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
 
         Ok(())
     }
-
-    fn cache(&self) -> &Cache {
-        &self.shared.cache
-    }
 }
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index b611a3e501d..a2c48708512 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -5394,43 +5394,6 @@ function updateCrate(ev) {
     search(true);
 }
 
-// @ts-expect-error
-function initSearch(searchIndx) {
-    rawSearchIndex = searchIndx;
-    if (typeof window !== "undefined") {
-        // @ts-expect-error
-        docSearch = new DocSearch(rawSearchIndex, ROOT_PATH, searchState);
-        registerSearchEvents();
-        // If there's a search term in the URL, execute the search now.
-        if (window.searchState.getQueryStringParams().search) {
-            search();
-        }
-    } else if (typeof exports !== "undefined") {
-        // @ts-expect-error
-        docSearch = new DocSearch(rawSearchIndex, ROOT_PATH, searchState);
-        exports.docSearch = docSearch;
-        exports.parseQuery = DocSearch.parseQuery;
-    }
-}
-
-if (typeof exports !== "undefined") {
-    exports.initSearch = initSearch;
-}
-
-if (typeof window !== "undefined") {
-    // @ts-expect-error
-    window.initSearch = initSearch;
-    // @ts-expect-error
-    if (window.searchIndex !== undefined) {
-        // @ts-expect-error
-        initSearch(window.searchIndex);
-    }
-} else {
-    // Running in Node, not a browser. Run initSearch just to produce the
-    // exports.
-    initSearch(new Map());
-}
-
 // Parts of this code are based on Lucene, which is licensed under the
 // Apache/2.0 license.
 // More information found here:
@@ -5909,3 +5872,44 @@ Lev1TParametricDescription.prototype.toStates3 = /*3 bits per value */ new Int32
 Lev1TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([
     0xa0fc0000,0x5555ba08,0x55555555,
 ]);
+
+// ====================
+// WARNING: Nothing should be added below this comment: we need the `initSearch` function to
+// be called ONLY when the whole file has been parsed and loaded.
+
+// @ts-expect-error
+function initSearch(searchIndx) {
+    rawSearchIndex = searchIndx;
+    if (typeof window !== "undefined") {
+        // @ts-expect-error
+        docSearch = new DocSearch(rawSearchIndex, ROOT_PATH, searchState);
+        registerSearchEvents();
+        // If there's a search term in the URL, execute the search now.
+        if (window.searchState.getQueryStringParams().search) {
+            search();
+        }
+    } else if (typeof exports !== "undefined") {
+        // @ts-expect-error
+        docSearch = new DocSearch(rawSearchIndex, ROOT_PATH, searchState);
+        exports.docSearch = docSearch;
+        exports.parseQuery = DocSearch.parseQuery;
+    }
+}
+
+if (typeof exports !== "undefined") {
+    exports.initSearch = initSearch;
+}
+
+if (typeof window !== "undefined") {
+    // @ts-expect-error
+    window.initSearch = initSearch;
+    // @ts-expect-error
+    if (window.searchIndex !== undefined) {
+        // @ts-expect-error
+        initSearch(window.searchIndex);
+    }
+} else {
+    // Running in Node, not a browser. Run initSearch just to produce the
+    // exports.
+    initSearch(new Map());
+}
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 6bdf3b5fe38..8b4be107ace 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -16,7 +16,6 @@ use rustdoc_json_types::*;
 use thin_vec::ThinVec;
 
 use crate::clean::{self, ItemId};
-use crate::formats::FormatRenderer;
 use crate::formats::item_type::ItemType;
 use crate::json::JsonRenderer;
 use crate::passes::collect_intra_doc_links::UrlFragment;
@@ -41,7 +40,7 @@ impl JsonRenderer<'_> {
             })
             .collect();
         let docs = item.opt_doc_value();
-        let attrs = item.attributes_and_repr(self.tcx, self.cache(), true);
+        let attrs = item.attributes_and_repr(self.tcx, &self.cache, true);
         let span = item.span(self.tcx);
         let visibility = item.visibility(self.tcx);
         let clean::ItemInner { name, item_id, .. } = *item.inner;
diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs
index 29c63a391e2..61493c1ed70 100644
--- a/src/librustdoc/json/mod.rs
+++ b/src/librustdoc/json/mod.rs
@@ -376,8 +376,4 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
             self.serialize_and_write(output_crate, BufWriter::new(stdout().lock()), "<stdout>")
         }
     }
-
-    fn cache(&self) -> &Cache {
-        &self.cache
-    }
 }
diff --git a/src/librustdoc/passes/lint/check_code_block_syntax.rs b/src/librustdoc/passes/lint/check_code_block_syntax.rs
index 9662dd85d67..91cddbe5a5b 100644
--- a/src/librustdoc/passes/lint/check_code_block_syntax.rs
+++ b/src/librustdoc/passes/lint/check_code_block_syntax.rs
@@ -6,8 +6,8 @@ use std::sync::Arc;
 use rustc_data_structures::sync::Lock;
 use rustc_errors::emitter::Emitter;
 use rustc_errors::registry::Registry;
-use rustc_errors::translation::{Translate, to_fluent_args};
-use rustc_errors::{Applicability, DiagCtxt, DiagInner, LazyFallbackBundle};
+use rustc_errors::translation::{Translator, to_fluent_args};
+use rustc_errors::{Applicability, DiagCtxt, DiagInner};
 use rustc_parse::{source_str_to_stream, unwrap_or_emit_fatal};
 use rustc_resolve::rustdoc::source_span_for_markdown_range;
 use rustc_session::parse::ParseSess;
@@ -36,11 +36,8 @@ fn check_rust_syntax(
     code_block: RustCodeBlock,
 ) {
     let buffer = Arc::new(Lock::new(Buffer::default()));
-    let fallback_bundle = rustc_errors::fallback_fluent_bundle(
-        rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(),
-        false,
-    );
-    let emitter = BufferEmitter { buffer: Arc::clone(&buffer), fallback_bundle };
+    let translator = rustc_driver::default_translator();
+    let emitter = BufferEmitter { buffer: Arc::clone(&buffer), translator };
 
     let sm = Arc::new(SourceMap::new(FilePathMapping::empty()));
     let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings();
@@ -149,17 +146,7 @@ struct Buffer {
 
 struct BufferEmitter {
     buffer: Arc<Lock<Buffer>>,
-    fallback_bundle: LazyFallbackBundle,
-}
-
-impl Translate for BufferEmitter {
-    fn fluent_bundle(&self) -> Option<&rustc_errors::FluentBundle> {
-        None
-    }
-
-    fn fallback_fluent_bundle(&self) -> &rustc_errors::FluentBundle {
-        &self.fallback_bundle
-    }
+    translator: Translator,
 }
 
 impl Emitter for BufferEmitter {
@@ -168,6 +155,7 @@ impl Emitter for BufferEmitter {
 
         let fluent_args = to_fluent_args(diag.args.iter());
         let translated_main_message = self
+            .translator
             .translate_message(&diag.messages[0].0, &fluent_args)
             .unwrap_or_else(|e| panic!("{e}"));
 
@@ -180,4 +168,8 @@ impl Emitter for BufferEmitter {
     fn source_map(&self) -> Option<&SourceMap> {
         None
     }
+
+    fn translator(&self) -> &Translator {
+        &self.translator
+    }
 }
diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs
index d5de43feb58..4d25124f9f2 100644
--- a/src/rustdoc-json-types/lib.rs
+++ b/src/rustdoc-json-types/lib.rs
@@ -37,8 +37,8 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
 // will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line
 // are deliberately not in a doc comment, because they need not be in public docs.)
 //
-// Latest feature: Pretty printing of optimize attributes changed
-pub const FORMAT_VERSION: u32 = 49;
+// Latest feature: Pretty printing of cold attributes changed
+pub const FORMAT_VERSION: u32 = 50;
 
 /// The root of the emitted JSON blob.
 ///
diff --git a/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs b/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs
index ec4538039a9..7ba11c20f45 100644
--- a/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs
+++ b/src/tools/clippy/clippy_lints/src/doc/needless_doctest_main.rs
@@ -42,9 +42,8 @@ pub fn check(
                 let mut test_attr_spans = vec![];
                 let filename = FileName::anon_source_code(&code);
 
-                let fallback_bundle =
-                    rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
-                let emitter = HumanEmitter::new(Box::new(io::sink()), fallback_bundle);
+                let translator = rustc_driver::default_translator();
+                let emitter = HumanEmitter::new(Box::new(io::sink()), translator);
                 let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings();
                 #[expect(clippy::arc_with_non_send_sync)] // `Arc` is expected by with_dcx
                 let sm = Arc::new(SourceMap::new(FilePathMapping::empty()));
diff --git a/src/tools/rustfmt/src/parse/session.rs b/src/tools/rustfmt/src/parse/session.rs
index afd847f9515..10e2809e58b 100644
--- a/src/tools/rustfmt/src/parse/session.rs
+++ b/src/tools/rustfmt/src/parse/session.rs
@@ -5,7 +5,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
 use rustc_data_structures::sync::IntoDynSyncSend;
 use rustc_errors::emitter::{DynEmitter, Emitter, HumanEmitter, SilentEmitter, stderr_destination};
 use rustc_errors::registry::Registry;
-use rustc_errors::translation::Translate;
+use rustc_errors::translation::Translator;
 use rustc_errors::{ColorConfig, Diag, DiagCtxt, DiagInner, Level as DiagnosticLevel};
 use rustc_session::parse::ParseSess as RawParseSess;
 use rustc_span::{
@@ -47,16 +47,6 @@ impl SilentOnIgnoredFilesEmitter {
     }
 }
 
-impl Translate for SilentOnIgnoredFilesEmitter {
-    fn fluent_bundle(&self) -> Option<&rustc_errors::FluentBundle> {
-        self.emitter.fluent_bundle()
-    }
-
-    fn fallback_fluent_bundle(&self) -> &rustc_errors::FluentBundle {
-        self.emitter.fallback_fluent_bundle()
-    }
-}
-
 impl Emitter for SilentOnIgnoredFilesEmitter {
     fn source_map(&self) -> Option<&SourceMap> {
         None
@@ -84,6 +74,10 @@ impl Emitter for SilentOnIgnoredFilesEmitter {
         }
         self.handle_non_ignoreable_error(diag, registry);
     }
+
+    fn translator(&self) -> &Translator {
+        self.emitter.translator()
+    }
 }
 
 impl From<Color> for ColorConfig {
@@ -110,23 +104,15 @@ fn default_dcx(
         ColorConfig::Never
     };
 
-    let fallback_bundle = rustc_errors::fallback_fluent_bundle(
-        rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(),
-        false,
-    );
-    let emitter = Box::new(
-        HumanEmitter::new(stderr_destination(emit_color), fallback_bundle)
-            .sm(Some(source_map.clone())),
-    );
-
-    let emitter: Box<DynEmitter> = if !show_parse_errors {
-        Box::new(SilentEmitter {
-            fatal_emitter: emitter,
-            fatal_note: None,
-            emit_fatal_diagnostic: false,
-        })
+    let translator = rustc_driver::default_translator();
+
+    let emitter: Box<DynEmitter> = if show_parse_errors {
+        Box::new(
+            HumanEmitter::new(stderr_destination(emit_color), translator)
+                .sm(Some(source_map.clone())),
+        )
     } else {
-        emitter
+        Box::new(SilentEmitter { translator })
     };
     DiagCtxt::new(Box::new(SilentOnIgnoredFilesEmitter {
         has_non_ignorable_parser_errors: false,
@@ -205,7 +191,7 @@ impl ParseSess {
     }
 
     pub(crate) fn set_silent_emitter(&mut self) {
-        self.raw_psess.dcx().make_silent(None, false);
+        self.raw_psess.dcx().make_silent();
     }
 
     pub(crate) fn span_to_filename(&self, span: Span) -> FileName {
@@ -335,16 +321,6 @@ mod tests {
             num_emitted_errors: Arc<AtomicU32>,
         }
 
-        impl Translate for TestEmitter {
-            fn fluent_bundle(&self) -> Option<&rustc_errors::FluentBundle> {
-                None
-            }
-
-            fn fallback_fluent_bundle(&self) -> &rustc_errors::FluentBundle {
-                panic!("test emitter attempted to translate a diagnostic");
-            }
-        }
-
         impl Emitter for TestEmitter {
             fn source_map(&self) -> Option<&SourceMap> {
                 None
@@ -353,6 +329,10 @@ mod tests {
             fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {
                 self.num_emitted_errors.fetch_add(1, Ordering::Release);
             }
+
+            fn translator(&self) -> &Translator {
+                panic!("test emitter attempted to translate a diagnostic");
+            }
         }
 
         fn build_diagnostic(level: DiagnosticLevel, span: Option<MultiSpan>) -> DiagInner {
diff --git a/tests/ui/attributes/expected-word.rs b/tests/ui/attributes/expected-word.rs
new file mode 100644
index 00000000000..246aa78db82
--- /dev/null
+++ b/tests/ui/attributes/expected-word.rs
@@ -0,0 +1,3 @@
+#[cold = true]
+//~^ ERROR malformed `cold` attribute input [E0565]
+fn main() {}
diff --git a/tests/ui/attributes/expected-word.stderr b/tests/ui/attributes/expected-word.stderr
new file mode 100644
index 00000000000..dcb10e7aee8
--- /dev/null
+++ b/tests/ui/attributes/expected-word.stderr
@@ -0,0 +1,12 @@
+error[E0565]: malformed `cold` attribute input
+  --> $DIR/expected-word.rs:1:1
+   |
+LL | #[cold = true]
+   | ^^^^^^^------^
+   | |      |
+   | |      didn't expect any arguments here
+   | help: must be of the form: `#[cold]`
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0565`.
diff --git a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr
index 1c6868dc95d..9280dfdf92e 100644
--- a/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr
+++ b/tests/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr
@@ -379,14 +379,6 @@ warning: `#[proc_macro_derive]` only has an effect on functions
 LL | #![proc_macro_derive()]
    | ^^^^^^^^^^^^^^^^^^^^^^^
 
-warning: attribute should be applied to a function definition
-  --> $DIR/issue-43106-gating-of-builtin-attrs.rs:62:1
-   |
-LL | #![cold]
-   | ^^^^^^^^ cannot be applied to crates
-   |
-   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
-
 warning: attribute should be applied to an `extern` block with non-Rust ABI
   --> $DIR/issue-43106-gating-of-builtin-attrs.rs:64:1
    |
@@ -417,6 +409,14 @@ warning: `#[must_use]` has no effect when applied to a module
 LL | #![must_use]
    | ^^^^^^^^^^^^
 
+warning: attribute should be applied to a function definition
+  --> $DIR/issue-43106-gating-of-builtin-attrs.rs:62:1
+   |
+LL | #![cold]
+   | ^^^^^^^^ cannot be applied to crates
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+
 warning: `#[macro_use]` only has an effect on `extern crate` and modules
   --> $DIR/issue-43106-gating-of-builtin-attrs.rs:176:5
    |
diff --git a/tests/ui/issues/issue-43988.stderr b/tests/ui/issues/issue-43988.stderr
index bd4eb8bbed3..fe61e136a51 100644
--- a/tests/ui/issues/issue-43988.stderr
+++ b/tests/ui/issues/issue-43988.stderr
@@ -38,7 +38,7 @@ LL |     #[repr]
    |     ^^^^^^^
    |     |
    |     expected this to be a list
-   |     help: must be of the form: `#[repr(C)]`
+   |     help: must be of the form: `#[repr(C | Rust | align(...) | packed(...) | <integer type> | transparent)]`
 
 error[E0539]: malformed `inline` attribute input
   --> $DIR/issue-43988.rs:30:5
@@ -64,7 +64,7 @@ LL |     let _z = #[repr] 1;
    |              ^^^^^^^
    |              |
    |              expected this to be a list
-   |              help: must be of the form: `#[repr(C)]`
+   |              help: must be of the form: `#[repr(C | Rust | align(...) | packed(...) | <integer type> | transparent)]`
 
 error[E0518]: attribute should be applied to function or closure
   --> $DIR/issue-43988.rs:5:5
diff --git a/tests/ui/lint/unused/unused-attr-duplicate.stderr b/tests/ui/lint/unused/unused-attr-duplicate.stderr
index e1c45e832af..03ce9757014 100644
--- a/tests/ui/lint/unused/unused-attr-duplicate.stderr
+++ b/tests/ui/lint/unused/unused-attr-duplicate.stderr
@@ -103,18 +103,6 @@ LL | #[automatically_derived]
    | ^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: unused attribute
-  --> $DIR/unused-attr-duplicate.rs:77:1
-   |
-LL | #[cold]
-   | ^^^^^^^ help: remove this attribute
-   |
-note: attribute also specified here
-  --> $DIR/unused-attr-duplicate.rs:76:1
-   |
-LL | #[cold]
-   | ^^^^^^^
-
-error: unused attribute
   --> $DIR/unused-attr-duplicate.rs:79:1
    |
 LL | #[track_caller]
@@ -289,5 +277,17 @@ LL | #[inline(always)]
    | ^^^^^^^^^^^^^^^^^
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 
+error: unused attribute
+  --> $DIR/unused-attr-duplicate.rs:77:1
+   |
+LL | #[cold]
+   | ^^^^^^^ help: remove this attribute
+   |
+note: attribute also specified here
+  --> $DIR/unused-attr-duplicate.rs:76:1
+   |
+LL | #[cold]
+   | ^^^^^^^
+
 error: aborting due to 23 previous errors
 
diff --git a/tests/ui/repr/repr.stderr b/tests/ui/repr/repr.stderr
index f3b11398eaa..9e581332278 100644
--- a/tests/ui/repr/repr.stderr
+++ b/tests/ui/repr/repr.stderr
@@ -5,7 +5,7 @@ LL | #[repr]
    | ^^^^^^^
    | |
    | expected this to be a list
-   | help: must be of the form: `#[repr(C)]`
+   | help: must be of the form: `#[repr(C | Rust | align(...) | packed(...) | <integer type> | transparent)]`
 
 error[E0539]: malformed `repr` attribute input
   --> $DIR/repr.rs:4:1
@@ -14,7 +14,7 @@ LL | #[repr = "B"]
    | ^^^^^^^^^^^^^
    | |
    | expected this to be a list
-   | help: must be of the form: `#[repr(C)]`
+   | help: must be of the form: `#[repr(C | Rust | align(...) | packed(...) | <integer type> | transparent)]`
 
 error[E0539]: malformed `repr` attribute input
   --> $DIR/repr.rs:7:1
@@ -23,7 +23,7 @@ LL | #[repr = "C"]
    | ^^^^^^^^^^^^^
    | |
    | expected this to be a list
-   | help: must be of the form: `#[repr(C)]`
+   | help: must be of the form: `#[repr(C | Rust | align(...) | packed(...) | <integer type> | transparent)]`
 
 error: aborting due to 3 previous errors
 
diff --git a/tests/ui/statics/read_before_init.rs b/tests/ui/statics/read_before_init.rs
new file mode 100644
index 00000000000..d779ef6dffa
--- /dev/null
+++ b/tests/ui/statics/read_before_init.rs
@@ -0,0 +1,22 @@
+//! This test checks the one code path that does not go through
+//! the regular CTFE memory access (as an optimization). We forgot
+//! to duplicate the static item self-initialization check, allowing
+//! reading from the uninitialized static memory before it was
+//! initialized at the end of the static initializer.
+//!
+//! https://github.com/rust-lang/rust/issues/142532
+
+use std::mem::MaybeUninit;
+
+pub static X: (i32, MaybeUninit<i32>) = (1, foo(&X.0));
+//~^ ERROR: encountered static that tried to initialize itself with itself
+
+const fn foo(x: &i32) -> MaybeUninit<i32> {
+    let mut temp = MaybeUninit::<i32>::uninit();
+    unsafe {
+        std::ptr::copy(x, temp.as_mut_ptr(), 1);
+    }
+    temp
+}
+
+fn main() {}
diff --git a/tests/ui/statics/read_before_init.stderr b/tests/ui/statics/read_before_init.stderr
new file mode 100644
index 00000000000..aeebcf7d9ce
--- /dev/null
+++ b/tests/ui/statics/read_before_init.stderr
@@ -0,0 +1,17 @@
+error[E0080]: encountered static that tried to initialize itself with itself
+  --> $DIR/read_before_init.rs:11:45
+   |
+LL | pub static X: (i32, MaybeUninit<i32>) = (1, foo(&X.0));
+   |                                             ^^^^^^^^^ evaluation of `X` failed inside this call
+   |
+note: inside `foo`
+  --> $DIR/read_before_init.rs:17:9
+   |
+LL |         std::ptr::copy(x, temp.as_mut_ptr(), 1);
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+note: inside `std::ptr::copy::<i32>`
+  --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL
+
+error: aborting due to 1 previous error
+
+For more information about this error, try `rustc --explain E0080`.