about summary refs log tree commit diff
path: root/compiler/rustc_expand
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_expand')
-rw-r--r--compiler/rustc_expand/Cargo.toml1
-rw-r--r--compiler/rustc_expand/messages.ftl15
-rw-r--r--compiler/rustc_expand/src/base.rs347
-rw-r--r--compiler/rustc_expand/src/build.rs33
-rw-r--r--compiler/rustc_expand/src/config.rs43
-rw-r--r--compiler/rustc_expand/src/errors.rs32
-rw-r--r--compiler/rustc_expand/src/expand.rs189
-rw-r--r--compiler/rustc_expand/src/lib.rs51
-rw-r--r--compiler/rustc_expand/src/mbe.rs17
-rw-r--r--compiler/rustc_expand/src/mbe/diagnostics.rs51
-rw-r--r--compiler/rustc_expand/src/mbe/macro_check.rs24
-rw-r--r--compiler/rustc_expand/src/mbe/macro_parser.rs16
-rw-r--r--compiler/rustc_expand/src/mbe/macro_rules.rs38
-rw-r--r--compiler/rustc_expand/src/mbe/metavar_expr.rs11
-rw-r--r--compiler/rustc_expand/src/mbe/quoted.rs103
-rw-r--r--compiler/rustc_expand/src/mbe/transcribe.rs187
-rw-r--r--compiler/rustc_expand/src/mut_visit/tests.rs72
-rw-r--r--compiler/rustc_expand/src/parse/tests.rs382
-rw-r--r--compiler/rustc_expand/src/placeholders.rs13
-rw-r--r--compiler/rustc_expand/src/proc_macro.rs2
-rw-r--r--compiler/rustc_expand/src/proc_macro_server.rs35
-rw-r--r--compiler/rustc_expand/src/tests.rs1055
-rw-r--r--compiler/rustc_expand/src/tokenstream/tests.rs109
23 files changed, 582 insertions, 2244 deletions
diff --git a/compiler/rustc_expand/Cargo.toml b/compiler/rustc_expand/Cargo.toml
index 63247f9d051..ce014364b0d 100644
--- a/compiler/rustc_expand/Cargo.toml
+++ b/compiler/rustc_expand/Cargo.toml
@@ -25,7 +25,6 @@ rustc_serialize = { path = "../rustc_serialize" }
 rustc_session = { path = "../rustc_session" }
 rustc_span = { path = "../rustc_span" }
 smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
-termcolor = "1.2"
 thin-vec = "0.2.12"
 tracing = "0.1"
 # tidy-alphabetical-end
diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl
index fdd1a87cae8..530b37aadb1 100644
--- a/compiler/rustc_expand/messages.ftl
+++ b/compiler/rustc_expand/messages.ftl
@@ -10,6 +10,11 @@ expand_attribute_meta_item =
 expand_attribute_single_word =
     attribute must only be a single word
 
+expand_attributes_on_expressions_experimental =
+    attributes on expressions are experimental
+    .help_outer_doc = `///` is used for outer documentation comments; for a plain comment, use `//`
+    .help_inner_doc = `//!` is used for inner documentation comments; for a plain comment, use `//` by removing the `!` or inserting a space in between them: `// !`
+
 expand_attributes_wrong_form =
     attribute must be of form: `attributes(foo, bar)`
 
@@ -30,8 +35,8 @@ expand_duplicate_matcher_binding = duplicate matcher binding
     .label = duplicate binding
     .label2 = previous binding
 
-expand_expected_comma_in_list =
-    expected token: `,`
+expand_empty_delegation_list =
+    empty list delegation is not supported
 
 expand_expected_paren_or_brace =
     expected `(` or `{"{"}`, found `{$token}`
@@ -116,9 +121,6 @@ expand_must_repeat_once =
 expand_not_a_meta_item =
     not a meta item
 
-expand_only_one_argument =
-    {$name} takes 1 argument
-
 expand_only_one_word =
     must only be one word
 
@@ -146,9 +148,6 @@ expand_remove_node_not_supported =
 expand_resolve_relative_path =
     cannot resolve relative path in non-file source `{$path}`
 
-expand_takes_no_arguments =
-    {$name} takes no arguments
-
 expand_trace_macro = trace_macro
 
 expand_unsupported_key_value =
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index b25dd8fe67b..91af8758e51 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -5,27 +5,26 @@ use crate::module::DirOwnership;
 
 use rustc_ast::attr::MarkedAttrs;
 use rustc_ast::ptr::P;
-use rustc_ast::token::{self, Nonterminal};
+use rustc_ast::token::Nonterminal;
 use rustc_ast::tokenstream::TokenStream;
 use rustc_ast::visit::{AssocCtxt, Visitor};
 use rustc_ast::{self as ast, AttrVec, Attribute, HasAttrs, Item, NodeId, PatKind};
 use rustc_attr::{self as attr, Deprecation, Stability};
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::sync::{self, Lrc};
-use rustc_errors::{Applicability, Diag, DiagCtxt, ErrorGuaranteed, PResult};
+use rustc_errors::{DiagCtxt, ErrorGuaranteed, PResult};
 use rustc_feature::Features;
 use rustc_lint_defs::builtin::PROC_MACRO_BACK_COMPAT;
 use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiag, RegisteredTools};
 use rustc_parse::{parser, MACRO_ARGUMENTS};
 use rustc_session::config::CollapseMacroDebuginfo;
-use rustc_session::errors::report_lit_error;
 use rustc_session::{parse::ParseSess, Limit, Session};
 use rustc_span::def_id::{CrateNum, DefId, LocalDefId};
 use rustc_span::edition::Edition;
-use rustc_span::hygiene::{AstPass, ExpnData, ExpnKind, LocalExpnId};
+use rustc_span::hygiene::{AstPass, ExpnData, ExpnKind, LocalExpnId, MacroKind};
 use rustc_span::source_map::SourceMap;
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
-use rustc_span::{BytePos, FileName, Span, DUMMY_SP};
+use rustc_span::{FileName, Span, DUMMY_SP};
 use smallvec::{smallvec, SmallVec};
 use std::default::Default;
 use std::iter;
@@ -33,8 +32,6 @@ use std::path::{Path, PathBuf};
 use std::rc::Rc;
 use thin_vec::ThinVec;
 
-pub(crate) use rustc_span::hygiene::MacroKind;
-
 // When adding new variants, make sure to
 // adjust the `visit_*` / `flat_map_*` calls in `InvocationCollector`
 // to use `assign_id!`
@@ -574,35 +571,6 @@ impl DummyResult {
             tokens: None,
         })
     }
-
-    /// A plain dummy pattern.
-    pub fn raw_pat(sp: Span) -> ast::Pat {
-        ast::Pat { id: ast::DUMMY_NODE_ID, kind: PatKind::Wild, span: sp, tokens: None }
-    }
-
-    /// A plain dummy type.
-    pub fn raw_ty(sp: Span) -> P<ast::Ty> {
-        // FIXME(nnethercote): you might expect `ast::TyKind::Dummy` to be used here, but some
-        // values produced here end up being lowered to HIR, which `ast::TyKind::Dummy` does not
-        // support, so we use an empty tuple instead.
-        P(ast::Ty {
-            id: ast::DUMMY_NODE_ID,
-            kind: ast::TyKind::Tup(ThinVec::new()),
-            span: sp,
-            tokens: None,
-        })
-    }
-
-    /// A plain dummy crate.
-    pub fn raw_crate() -> ast::Crate {
-        ast::Crate {
-            attrs: Default::default(),
-            items: Default::default(),
-            spans: Default::default(),
-            id: ast::DUMMY_NODE_ID,
-            is_placeholder: Default::default(),
-        }
-    }
 }
 
 impl MacResult for DummyResult {
@@ -611,7 +579,12 @@ impl MacResult for DummyResult {
     }
 
     fn make_pat(self: Box<DummyResult>) -> Option<P<ast::Pat>> {
-        Some(P(DummyResult::raw_pat(self.span)))
+        Some(P(ast::Pat {
+            id: ast::DUMMY_NODE_ID,
+            kind: PatKind::Wild,
+            span: self.span,
+            tokens: None,
+        }))
     }
 
     fn make_items(self: Box<DummyResult>) -> Option<SmallVec<[P<ast::Item>; 1]>> {
@@ -639,7 +612,15 @@ impl MacResult for DummyResult {
     }
 
     fn make_ty(self: Box<DummyResult>) -> Option<P<ast::Ty>> {
-        Some(DummyResult::raw_ty(self.span))
+        // FIXME(nnethercote): you might expect `ast::TyKind::Dummy` to be used here, but some
+        // values produced here end up being lowered to HIR, which `ast::TyKind::Dummy` does not
+        // support, so we use an empty tuple instead.
+        Some(P(ast::Ty {
+            id: ast::DUMMY_NODE_ID,
+            kind: ast::TyKind::Tup(ThinVec::new()),
+            span: self.span,
+            tokens: None,
+        }))
     }
 
     fn make_arms(self: Box<DummyResult>) -> Option<SmallVec<[ast::Arm; 1]>> {
@@ -671,7 +652,13 @@ impl MacResult for DummyResult {
     }
 
     fn make_crate(self: Box<DummyResult>) -> Option<ast::Crate> {
-        Some(DummyResult::raw_crate())
+        Some(ast::Crate {
+            attrs: Default::default(),
+            items: Default::default(),
+            spans: Default::default(),
+            id: ast::DUMMY_NODE_ID,
+            is_placeholder: Default::default(),
+        })
     }
 }
 
@@ -789,55 +776,50 @@ impl SyntaxExtension {
         }
     }
 
-    fn collapse_debuginfo_by_name(sess: &Session, attr: &Attribute) -> CollapseMacroDebuginfo {
-        use crate::errors::CollapseMacroDebuginfoIllegal;
-        // #[collapse_debuginfo] without enum value (#[collapse_debuginfo(no/external/yes)])
-        // considered as `yes`
-        attr.meta_item_list().map_or(CollapseMacroDebuginfo::Yes, |l| {
-            let [NestedMetaItem::MetaItem(item)] = &l[..] else {
-                sess.dcx().emit_err(CollapseMacroDebuginfoIllegal { span: attr.span });
-                return CollapseMacroDebuginfo::Unspecified;
-            };
-            if !item.is_word() {
-                sess.dcx().emit_err(CollapseMacroDebuginfoIllegal { span: item.span });
-                CollapseMacroDebuginfo::Unspecified
-            } else {
-                match item.name_or_empty() {
-                    sym::no => CollapseMacroDebuginfo::No,
-                    sym::external => CollapseMacroDebuginfo::External,
-                    sym::yes => CollapseMacroDebuginfo::Yes,
-                    _ => {
-                        sess.dcx().emit_err(CollapseMacroDebuginfoIllegal { span: item.span });
-                        CollapseMacroDebuginfo::Unspecified
-                    }
-                }
-            }
-        })
+    fn collapse_debuginfo_by_name(attr: &Attribute) -> Result<CollapseMacroDebuginfo, Span> {
+        let list = attr.meta_item_list();
+        let Some([NestedMetaItem::MetaItem(item)]) = list.as_deref() else {
+            return Err(attr.span);
+        };
+        if !item.is_word() {
+            return Err(item.span);
+        }
+
+        match item.name_or_empty() {
+            sym::no => Ok(CollapseMacroDebuginfo::No),
+            sym::external => Ok(CollapseMacroDebuginfo::External),
+            sym::yes => Ok(CollapseMacroDebuginfo::Yes),
+            _ => Err(item.path.span),
+        }
     }
 
     /// if-ext - if macro from different crate (related to callsite code)
     /// | cmd \ attr    | no  | (unspecified) | external | yes |
     /// | no            | no  | no            | no       | no  |
-    /// | (unspecified) | no  | no            | if-ext   | yes |
+    /// | (unspecified) | no  | if-ext        | if-ext   | yes |
     /// | external      | no  | if-ext        | if-ext   | yes |
     /// | yes           | yes | yes           | yes      | yes |
-    fn get_collapse_debuginfo(sess: &Session, attrs: &[ast::Attribute], is_local: bool) -> bool {
-        let mut collapse_debuginfo_attr = attr::find_by_name(attrs, sym::collapse_debuginfo)
-            .map(|v| Self::collapse_debuginfo_by_name(sess, v))
-            .unwrap_or(CollapseMacroDebuginfo::Unspecified);
-        if collapse_debuginfo_attr == CollapseMacroDebuginfo::Unspecified
-            && attr::contains_name(attrs, sym::rustc_builtin_macro)
-        {
-            collapse_debuginfo_attr = CollapseMacroDebuginfo::Yes;
-        }
-
-        let flag = sess.opts.unstable_opts.collapse_macro_debuginfo;
-        let attr = collapse_debuginfo_attr;
-        let ext = !is_local;
+    fn get_collapse_debuginfo(sess: &Session, attrs: &[ast::Attribute], ext: bool) -> bool {
+        let flag = sess.opts.cg.collapse_macro_debuginfo;
+        let attr = attr::find_by_name(attrs, sym::collapse_debuginfo)
+            .and_then(|attr| {
+                Self::collapse_debuginfo_by_name(attr)
+                    .map_err(|span| {
+                        sess.dcx().emit_err(errors::CollapseMacroDebuginfoIllegal { span })
+                    })
+                    .ok()
+            })
+            .unwrap_or_else(|| {
+                if attr::contains_name(attrs, sym::rustc_builtin_macro) {
+                    CollapseMacroDebuginfo::Yes
+                } else {
+                    CollapseMacroDebuginfo::Unspecified
+                }
+            });
         #[rustfmt::skip]
         let collapse_table = [
             [false, false, false, false],
-            [false, false, ext,   true],
+            [false, ext,   ext,   true],
             [false, ext,   ext,   true],
             [true,  true,  true,  true],
         ];
@@ -864,7 +846,7 @@ impl SyntaxExtension {
         let local_inner_macros = attr::find_by_name(attrs, sym::macro_export)
             .and_then(|macro_export| macro_export.meta_item_list())
             .is_some_and(|l| attr::list_contains_name(&l, sym::local_inner_macros));
-        let collapse_debuginfo = Self::get_collapse_debuginfo(sess, attrs, is_local);
+        let collapse_debuginfo = Self::get_collapse_debuginfo(sess, attrs, !is_local);
         tracing::debug!(?name, ?local_inner_macros, ?collapse_debuginfo, ?allow_internal_unsafe);
 
         let (builtin_name, helper_attrs) = attr::find_by_name(attrs, sym::rustc_builtin_macro)
@@ -968,7 +950,12 @@ impl SyntaxExtension {
 /// Error type that denotes indeterminacy.
 pub struct Indeterminate;
 
-pub type DeriveResolutions = Vec<(ast::Path, Annotatable, Option<Lrc<SyntaxExtension>>, bool)>;
+pub struct DeriveResolution {
+    pub path: ast::Path,
+    pub item: Annotatable,
+    pub exts: Option<Lrc<SyntaxExtension>>,
+    pub is_const: bool,
+}
 
 pub trait ResolverExpand {
     fn next_node_id(&mut self) -> NodeId;
@@ -1011,11 +998,11 @@ pub trait ResolverExpand {
         &mut self,
         expn_id: LocalExpnId,
         force: bool,
-        derive_paths: &dyn Fn() -> DeriveResolutions,
+        derive_paths: &dyn Fn() -> Vec<DeriveResolution>,
     ) -> Result<(), Indeterminate>;
     /// Take resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId`
     /// back from resolver.
-    fn take_derive_resolutions(&mut self, expn_id: LocalExpnId) -> Option<DeriveResolutions>;
+    fn take_derive_resolutions(&mut self, expn_id: LocalExpnId) -> Option<Vec<DeriveResolution>>;
     /// Path resolution logic for `#[cfg_accessible(path)]`.
     fn cfg_accessible(
         &mut self,
@@ -1269,181 +1256,6 @@ pub fn resolve_path(sess: &Session, path: impl Into<PathBuf>, span: Span) -> PRe
     }
 }
 
-/// `Ok` represents successfully retrieving the string literal at the correct
-/// position, e.g., `println("abc")`.
-type ExprToSpannedStringResult<'a> = Result<(Symbol, ast::StrStyle, Span), UnexpectedExprKind<'a>>;
-
-/// - `Ok` is returned when the conversion to a string literal is unsuccessful,
-/// but another type of expression is obtained instead.
-/// - `Err` is returned when the conversion process fails.
-type UnexpectedExprKind<'a> = Result<(Diag<'a>, bool /* has_suggestions */), ErrorGuaranteed>;
-
-/// Extracts a string literal from the macro expanded version of `expr`,
-/// returning a diagnostic error of `err_msg` if `expr` is not a string literal.
-/// The returned bool indicates whether an applicable suggestion has already been
-/// added to the diagnostic to avoid emitting multiple suggestions. `Err(Err(ErrorGuaranteed))`
-/// indicates that an ast error was encountered.
-// FIXME(Nilstrieb) Make this function setup translatable
-#[allow(rustc::untranslatable_diagnostic)]
-pub fn expr_to_spanned_string<'a>(
-    cx: &'a mut ExtCtxt<'_>,
-    expr: P<ast::Expr>,
-    err_msg: &'static str,
-) -> ExpandResult<ExprToSpannedStringResult<'a>, ()> {
-    if !cx.force_mode
-        && let ast::ExprKind::MacCall(m) = &expr.kind
-        && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err()
-    {
-        return ExpandResult::Retry(());
-    }
-
-    // Perform eager expansion on the expression.
-    // We want to be able to handle e.g., `concat!("foo", "bar")`.
-    let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
-
-    ExpandResult::Ready(Err(match expr.kind {
-        ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
-            Ok(ast::LitKind::Str(s, style)) => {
-                return ExpandResult::Ready(Ok((s, style, expr.span)));
-            }
-            Ok(ast::LitKind::ByteStr(..)) => {
-                let mut err = cx.dcx().struct_span_err(expr.span, err_msg);
-                let span = expr.span.shrink_to_lo();
-                err.span_suggestion(
-                    span.with_hi(span.lo() + BytePos(1)),
-                    "consider removing the leading `b`",
-                    "",
-                    Applicability::MaybeIncorrect,
-                );
-                Ok((err, true))
-            }
-            Ok(ast::LitKind::Err(guar)) => Err(guar),
-            Err(err) => Err(report_lit_error(&cx.sess.psess, err, token_lit, expr.span)),
-            _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
-        },
-        ast::ExprKind::Err(guar) => Err(guar),
-        ast::ExprKind::Dummy => {
-            cx.dcx().span_bug(expr.span, "tried to get a string literal from `ExprKind::Dummy`")
-        }
-        _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
-    }))
-}
-
-/// Extracts a string literal from the macro expanded version of `expr`,
-/// emitting `err_msg` if `expr` is not a string literal. This does not stop
-/// compilation on error, merely emits a non-fatal error and returns `Err`.
-pub fn expr_to_string(
-    cx: &mut ExtCtxt<'_>,
-    expr: P<ast::Expr>,
-    err_msg: &'static str,
-) -> ExpandResult<Result<(Symbol, ast::StrStyle), ErrorGuaranteed>, ()> {
-    expr_to_spanned_string(cx, expr, err_msg).map(|res| {
-        res.map_err(|err| match err {
-            Ok((err, _)) => err.emit(),
-            Err(guar) => guar,
-        })
-        .map(|(symbol, style, _)| (symbol, style))
-    })
-}
-
-/// Non-fatally assert that `tts` is empty. Note that this function
-/// returns even when `tts` is non-empty, macros that *need* to stop
-/// compilation should call `cx.diagnostic().abort_if_errors()`
-/// (this should be done as rarely as possible).
-pub fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str) {
-    if !tts.is_empty() {
-        cx.dcx().emit_err(errors::TakesNoArguments { span, name });
-    }
-}
-
-/// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`.
-pub fn parse_expr(p: &mut parser::Parser<'_>) -> Result<P<ast::Expr>, ErrorGuaranteed> {
-    let guar = match p.parse_expr() {
-        Ok(expr) => return Ok(expr),
-        Err(err) => err.emit(),
-    };
-    while p.token != token::Eof {
-        p.bump();
-    }
-    Err(guar)
-}
-
-/// Interpreting `tts` as a comma-separated sequence of expressions,
-/// expect exactly one string literal, or emit an error and return `Err`.
-pub fn get_single_str_from_tts(
-    cx: &mut ExtCtxt<'_>,
-    span: Span,
-    tts: TokenStream,
-    name: &str,
-) -> ExpandResult<Result<Symbol, ErrorGuaranteed>, ()> {
-    get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s))
-}
-
-pub fn get_single_str_spanned_from_tts(
-    cx: &mut ExtCtxt<'_>,
-    span: Span,
-    tts: TokenStream,
-    name: &str,
-) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> {
-    let mut p = cx.new_parser_from_tts(tts);
-    if p.token == token::Eof {
-        let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
-        return ExpandResult::Ready(Err(guar));
-    }
-    let ret = match parse_expr(&mut p) {
-        Ok(ret) => ret,
-        Err(guar) => return ExpandResult::Ready(Err(guar)),
-    };
-    let _ = p.eat(&token::Comma);
-
-    if p.token != token::Eof {
-        cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
-    }
-    expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
-        res.map_err(|err| match err {
-            Ok((err, _)) => err.emit(),
-            Err(guar) => guar,
-        })
-        .map(|(symbol, _style, span)| (symbol, span))
-    })
-}
-
-/// Extracts comma-separated expressions from `tts`.
-/// On error, emit it, and return `Err`.
-pub fn get_exprs_from_tts(
-    cx: &mut ExtCtxt<'_>,
-    tts: TokenStream,
-) -> ExpandResult<Result<Vec<P<ast::Expr>>, ErrorGuaranteed>, ()> {
-    let mut p = cx.new_parser_from_tts(tts);
-    let mut es = Vec::new();
-    while p.token != token::Eof {
-        let expr = match parse_expr(&mut p) {
-            Ok(expr) => expr,
-            Err(guar) => return ExpandResult::Ready(Err(guar)),
-        };
-        if !cx.force_mode
-            && let ast::ExprKind::MacCall(m) = &expr.kind
-            && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err()
-        {
-            return ExpandResult::Retry(());
-        }
-
-        // Perform eager expansion on the expression.
-        // We want to be able to handle e.g., `concat!("foo", "bar")`.
-        let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
-
-        es.push(expr);
-        if p.eat(&token::Comma) {
-            continue;
-        }
-        if p.token != token::Eof {
-            let guar = cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span });
-            return ExpandResult::Ready(Err(guar));
-        }
-    }
-    ExpandResult::Ready(Ok(es))
-}
-
 pub fn parse_macro_name_and_helper_attrs(
     dcx: &rustc_errors::DiagCtxt,
     attr: &Attribute,
@@ -1552,18 +1364,15 @@ fn pretty_printing_compatibility_hack(item: &Item, sess: &Session) -> bool {
                             };
 
                             if crate_matches {
-                                // FIXME: make this translatable
-                                #[allow(rustc::untranslatable_diagnostic)]
-                                sess.psess.buffer_lint_with_diagnostic(
-                                        PROC_MACRO_BACK_COMPAT,
-                                        item.ident.span,
-                                        ast::CRATE_NODE_ID,
-                                        "using an old version of `rental`",
-                                        BuiltinLintDiag::ProcMacroBackCompat(
-                                        "older versions of the `rental` crate will stop compiling in future versions of Rust; \
-                                        please update to `rental` v0.5.6, or switch to one of the `rental` alternatives".to_string()
-                                        )
-                                    );
+                                sess.psess.buffer_lint(
+                                    PROC_MACRO_BACK_COMPAT,
+                                    item.ident.span,
+                                    ast::CRATE_NODE_ID,
+                                    BuiltinLintDiag::ProcMacroBackCompat {
+                                        crate_name: "rental".to_string(),
+                                        fixed_version: "0.5.6".to_string(),
+                                    },
+                                );
                                 return true;
                             }
                         }
diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs
index cdcf67b26f8..1b6e191c2eb 100644
--- a/compiler/rustc_expand/src/build.rs
+++ b/compiler/rustc_expand/src/build.rs
@@ -175,20 +175,6 @@ impl<'a> ExtCtxt<'a> {
         ast::Stmt { id: ast::DUMMY_NODE_ID, span: expr.span, kind: ast::StmtKind::Expr(expr) }
     }
 
-    pub fn stmt_let_pat(&self, sp: Span, pat: P<ast::Pat>, ex: P<ast::Expr>) -> ast::Stmt {
-        let local = P(ast::Local {
-            pat,
-            ty: None,
-            id: ast::DUMMY_NODE_ID,
-            kind: LocalKind::Init(ex),
-            span: sp,
-            colon_sp: None,
-            attrs: AttrVec::new(),
-            tokens: None,
-        });
-        self.stmt_local(local, sp)
-    }
-
     pub fn stmt_let(&self, sp: Span, mutbl: bool, ident: Ident, ex: P<ast::Expr>) -> ast::Stmt {
         self.stmt_let_ty(sp, mutbl, ident, None, ex)
     }
@@ -202,7 +188,7 @@ impl<'a> ExtCtxt<'a> {
         ex: P<ast::Expr>,
     ) -> ast::Stmt {
         let pat = if mutbl {
-            self.pat_ident_binding_mode(sp, ident, ast::BindingAnnotation::MUT)
+            self.pat_ident_binding_mode(sp, ident, ast::BindingMode::MUT)
         } else {
             self.pat_ident(sp, ident)
         };
@@ -278,10 +264,6 @@ impl<'a> ExtCtxt<'a> {
         self.expr_ident(span, Ident::with_dummy_span(kw::SelfLower))
     }
 
-    pub fn expr_field(&self, span: Span, expr: P<Expr>, field: Ident) -> P<ast::Expr> {
-        self.expr(span, ast::ExprKind::Field(expr, field))
-    }
-
     pub fn expr_macro_call(&self, span: Span, call: P<ast::MacCall>) -> P<ast::Expr> {
         self.expr(span, ast::ExprKind::MacCall(call))
     }
@@ -394,11 +376,6 @@ impl<'a> ExtCtxt<'a> {
         self.expr(span, ast::ExprKind::Lit(lit))
     }
 
-    pub fn expr_char(&self, span: Span, ch: char) -> P<ast::Expr> {
-        let lit = token::Lit::new(token::Char, literal::escape_char_symbol(ch), None);
-        self.expr(span, ast::ExprKind::Lit(lit))
-    }
-
     pub fn expr_byte_str(&self, span: Span, bytes: Vec<u8>) -> P<ast::Expr> {
         let lit = token::Lit::new(token::ByteStr, literal::escape_byte_str_symbol(&bytes), None);
         self.expr(span, ast::ExprKind::Lit(lit))
@@ -414,10 +391,6 @@ impl<'a> ExtCtxt<'a> {
         self.expr_addr_of(sp, self.expr_array(sp, exprs))
     }
 
-    pub fn expr_cast(&self, sp: Span, expr: P<ast::Expr>, ty: P<ast::Ty>) -> P<ast::Expr> {
-        self.expr(sp, ast::ExprKind::Cast(expr, ty))
-    }
-
     pub fn expr_some(&self, sp: Span, expr: P<ast::Expr>) -> P<ast::Expr> {
         let some = self.std_path(&[sym::option, sym::Option, sym::Some]);
         self.expr_call_global(sp, some, thin_vec![expr])
@@ -490,14 +463,14 @@ impl<'a> ExtCtxt<'a> {
         self.pat(span, PatKind::Lit(expr))
     }
     pub fn pat_ident(&self, span: Span, ident: Ident) -> P<ast::Pat> {
-        self.pat_ident_binding_mode(span, ident, ast::BindingAnnotation::NONE)
+        self.pat_ident_binding_mode(span, ident, ast::BindingMode::NONE)
     }
 
     pub fn pat_ident_binding_mode(
         &self,
         span: Span,
         ident: Ident,
-        ann: ast::BindingAnnotation,
+        ann: ast::BindingMode,
     ) -> P<ast::Pat> {
         let pat = PatKind::Ident(ann, ident.with_span_pos(span), None);
         self.pat(span, pat)
diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index c95d7cdeb73..badfa6d3aa3 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -14,12 +14,14 @@ use rustc_attr as attr;
 use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
 use rustc_feature::Features;
 use rustc_feature::{ACCEPTED_FEATURES, REMOVED_FEATURES, UNSTABLE_FEATURES};
+use rustc_lint_defs::BuiltinLintDiag;
 use rustc_parse::validate_attr;
 use rustc_session::parse::feature_err;
 use rustc_session::Session;
 use rustc_span::symbol::{sym, Symbol};
 use rustc_span::Span;
 use thin_vec::ThinVec;
+use tracing::instrument;
 
 /// A folder that strips out items that do not belong in the current configuration.
 pub struct StripUnconfigured<'a> {
@@ -98,10 +100,11 @@ pub fn features(sess: &Session, krate_attrs: &[Attribute], crate_name: Symbol) -
             // If the declared feature is unstable, record it.
             if let Some(f) = UNSTABLE_FEATURES.iter().find(|f| name == f.feature.name) {
                 (f.set_enabled)(&mut features);
-                // When the ICE comes from core, alloc or std (approximation of the standard library), there's a chance
-                // that the person hitting the ICE may be using -Zbuild-std or similar with an untested target.
-                // The bug is probably in the standard library and not the compiler in that case, but that doesn't
-                // really matter - we want a bug report.
+                // When the ICE comes from core, alloc or std (approximation of the standard
+                // library), there's a chance that the person hitting the ICE may be using
+                // -Zbuild-std or similar with an untested target. The bug is probably in the
+                // standard library and not the compiler in that case, but that doesn't really
+                // matter - we want a bug report.
                 if features.internal(name)
                     && ![sym::core, sym::alloc, sym::std].contains(&crate_name)
                 {
@@ -199,10 +202,17 @@ impl<'a> StripUnconfigured<'a> {
                     inner = self.configure_tokens(&inner);
                     Some(AttrTokenTree::Delimited(sp, spacing, delim, inner)).into_iter()
                 }
-                AttrTokenTree::Token(ref token, _)
-                    if let TokenKind::Interpolated(nt) = &token.kind =>
-                {
-                    panic!("Nonterminal should have been flattened at {:?}: {:?}", token.span, nt);
+                AttrTokenTree::Token(
+                    Token {
+                        kind:
+                            TokenKind::NtIdent(..)
+                            | TokenKind::NtLifetime(..)
+                            | TokenKind::Interpolated(..),
+                        ..
+                    },
+                    _,
+                ) => {
+                    panic!("Nonterminal should have been flattened: {:?}", tree);
                 }
                 AttrTokenTree::Token(token, spacing) => {
                     Some(AttrTokenTree::Token(token, spacing)).into_iter()
@@ -239,7 +249,6 @@ impl<'a> StripUnconfigured<'a> {
     /// Gives a compiler warning when the `cfg_attr` contains no attributes and
     /// is in the original source file. Gives a compiler error if the syntax of
     /// the attribute is incorrect.
-    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     pub(crate) fn expand_cfg_attr(&self, attr: &Attribute, recursive: bool) -> Vec<Attribute> {
         let Some((cfg_predicate, expanded_attrs)) =
             rustc_parse::parse_cfg_attr(attr, &self.sess.psess)
@@ -253,7 +262,7 @@ impl<'a> StripUnconfigured<'a> {
                 rustc_lint_defs::builtin::UNUSED_ATTRIBUTES,
                 attr.span,
                 ast::CRATE_NODE_ID,
-                "`#[cfg_attr]` does not expand to any attributes",
+                BuiltinLintDiag::CfgAttrNoAttributes,
             );
         }
 
@@ -274,7 +283,6 @@ impl<'a> StripUnconfigured<'a> {
         }
     }
 
-    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     fn expand_cfg_attr_item(
         &self,
         attr: &Attribute,
@@ -337,7 +345,7 @@ impl<'a> StripUnconfigured<'a> {
                 rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME,
                 attr.span,
                 ast::CRATE_NODE_ID,
-                "`crate_type` within an `#![cfg_attr] attribute is deprecated`",
+                BuiltinLintDiag::CrateTypeInCfgAttr,
             );
         }
         if attr.has_name(sym::crate_name) {
@@ -345,7 +353,7 @@ impl<'a> StripUnconfigured<'a> {
                 rustc_lint_defs::builtin::DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME,
                 attr.span,
                 ast::CRATE_NODE_ID,
-                "`crate_name` within an `#![cfg_attr] attribute is deprecated`",
+                BuiltinLintDiag::CrateNameInCfgAttr,
             );
         }
         attr
@@ -373,7 +381,6 @@ impl<'a> StripUnconfigured<'a> {
     }
 
     /// If attributes are not allowed on expressions, emit an error for `attr`
-    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
     #[instrument(level = "trace", skip(self))]
     pub(crate) fn maybe_emit_expr_attr_err(&self, attr: &Attribute) {
         if self.features.is_some_and(|features| !features.stmt_expr_attributes)
@@ -383,11 +390,15 @@ impl<'a> StripUnconfigured<'a> {
                 &self.sess,
                 sym::stmt_expr_attributes,
                 attr.span,
-                "attributes on expressions are experimental",
+                crate::fluent_generated::expand_attributes_on_expressions_experimental,
             );
 
             if attr.is_doc_comment() {
-                err.help("`///` is for documentation comments. For a plain comment, use `//`.");
+                err.help(if attr.style == AttrStyle::Outer {
+                    crate::fluent_generated::expand_help_outer_doc
+                } else {
+                    crate::fluent_generated::expand_help_inner_doc
+                });
             }
 
             err.emit();
diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs
index 21ce5e1d81e..a5fc9e9d89c 100644
--- a/compiler/rustc_expand/src/errors.rs
+++ b/compiler/rustc_expand/src/errors.rs
@@ -1,6 +1,6 @@
 use rustc_ast::ast;
 use rustc_errors::codes::*;
-use rustc_macros::Diagnostic;
+use rustc_macros::{Diagnostic, Subdiagnostic};
 use rustc_session::Limit;
 use rustc_span::symbol::{Ident, MacroRulesNormalizedIdent};
 use rustc_span::{Span, Symbol};
@@ -153,29 +153,6 @@ pub(crate) struct HelperAttributeNameInvalid {
 }
 
 #[derive(Diagnostic)]
-#[diag(expand_expected_comma_in_list)]
-pub(crate) struct ExpectedCommaInList {
-    #[primary_span]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
-#[diag(expand_only_one_argument)]
-pub(crate) struct OnlyOneArgument<'a> {
-    #[primary_span]
-    pub span: Span,
-    pub name: &'a str,
-}
-
-#[derive(Diagnostic)]
-#[diag(expand_takes_no_arguments)]
-pub(crate) struct TakesNoArguments<'a> {
-    #[primary_span]
-    pub span: Span,
-    pub name: &'a str,
-}
-
-#[derive(Diagnostic)]
 #[diag(expand_feature_removed, code = E0557)]
 pub(crate) struct FeatureRemoved<'a> {
     #[primary_span]
@@ -456,3 +433,10 @@ pub struct ExpectedParenOrBrace<'a> {
     pub span: Span,
     pub token: Cow<'a, str>,
 }
+
+#[derive(Diagnostic)]
+#[diag(expand_empty_delegation_list)]
+pub(crate) struct EmptyDelegationList {
+    #[primary_span]
+    pub span: Span,
+}
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index 6029caa965c..d8f0f221189 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -1,10 +1,9 @@
 use crate::base::*;
 use crate::config::StripUnconfigured;
 use crate::errors::{
-    IncompleteParse, RecursionLimitReached, RemoveExprNotSupported, RemoveNodeNotSupported,
-    UnsupportedKeyValue, WrongFragmentKind,
+    EmptyDelegationList, IncompleteParse, RecursionLimitReached, RemoveExprNotSupported,
+    RemoveNodeNotSupported, UnsupportedKeyValue, WrongFragmentKind,
 };
-use crate::hygiene::SyntaxContext;
 use crate::mbe::diagnostics::annotate_err_with_kind;
 use crate::module::{mod_dir_path, parse_external_mod, DirOwnership, ParsedExternalMod};
 use crate::placeholders::{placeholder, PlaceholderExpander};
@@ -32,6 +31,7 @@ use rustc_session::lint::builtin::{UNUSED_ATTRIBUTES, UNUSED_DOC_COMMENTS};
 use rustc_session::lint::BuiltinLintDiag;
 use rustc_session::parse::feature_err;
 use rustc_session::{Limit, Session};
+use rustc_span::hygiene::SyntaxContext;
 use rustc_span::symbol::{sym, Ident};
 use rustc_span::{ErrorGuaranteed, FileName, LocalExpnId, Span};
 
@@ -87,7 +87,7 @@ macro_rules! ast_fragments {
         }
 
         impl AstFragment {
-            pub fn add_placeholders(&mut self, placeholders: &[NodeId]) {
+            fn add_placeholders(&mut self, placeholders: &[NodeId]) {
                 if placeholders.is_empty() {
                     return;
                 }
@@ -100,14 +100,14 @@ macro_rules! ast_fragments {
                 }
             }
 
-            pub fn make_opt_expr(self) -> Option<P<ast::Expr>> {
+            pub(crate) fn make_opt_expr(self) -> Option<P<ast::Expr>> {
                 match self {
                     AstFragment::OptExpr(expr) => expr,
                     _ => panic!("AstFragment::make_* called on the wrong kind of fragment"),
                 }
             }
 
-            pub fn make_method_receiver_expr(self) -> P<ast::Expr> {
+            pub(crate) fn make_method_receiver_expr(self) -> P<ast::Expr> {
                 match self {
                     AstFragment::MethodReceiverExpr(expr) => expr,
                     _ => panic!("AstFragment::make_* called on the wrong kind of fragment"),
@@ -125,7 +125,7 @@ macro_rules! ast_fragments {
                 T::fragment_to_output(self)
             }
 
-            pub fn mut_visit_with<F: MutVisitor>(&mut self, vis: &mut F) {
+            pub(crate) fn mut_visit_with<F: MutVisitor>(&mut self, vis: &mut F) {
                 match self {
                     AstFragment::OptExpr(opt_expr) => {
                         visit_clobber(opt_expr, |opt_expr| {
@@ -372,6 +372,14 @@ impl Invocation {
             InvocationKind::Derive { path, .. } => path.span,
         }
     }
+
+    fn span_mut(&mut self) -> &mut Span {
+        match &mut self.kind {
+            InvocationKind::Bang { span, .. } => span,
+            InvocationKind::Attr { attr, .. } => &mut attr.span,
+            InvocationKind::Derive { path, .. } => &mut path.span,
+        }
+    }
 }
 
 pub struct MacroExpander<'a, 'b> {
@@ -432,7 +440,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                     break;
                 }
                 invocations = mem::take(&mut undetermined_invocations);
-                force = !mem::replace(&mut progress, false);
+                force = !progress;
+                progress = false;
                 if force && self.monotonic {
                     self.cx.dcx().span_delayed_bug(
                         invocations.last().unwrap().0.span(),
@@ -471,7 +480,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
             self.cx.force_mode = force;
 
             let fragment_kind = invoc.fragment_kind;
-            let (expanded_fragment, new_invocations) = match self.expand_invoc(invoc, &ext.kind) {
+            match self.expand_invoc(invoc, &ext.kind) {
                 ExpandResult::Ready(fragment) => {
                     let mut derive_invocations = Vec::new();
                     let derive_placeholders = self
@@ -482,7 +491,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                             derive_invocations.reserve(derives.len());
                             derives
                                 .into_iter()
-                                .map(|(path, item, _exts, is_const)| {
+                                .map(|DeriveResolution { path, item, exts: _, is_const }| {
                                     // FIXME: Consider using the derive resolutions (`_exts`)
                                     // instead of enqueuing the derives to be resolved again later.
                                     let expn_id = LocalExpnId::fresh_empty();
@@ -503,12 +512,19 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                         })
                         .unwrap_or_default();
 
-                    let (fragment, collected_invocations) =
+                    let (expanded_fragment, collected_invocations) =
                         self.collect_invocations(fragment, &derive_placeholders);
-                    // We choose to expand any derive invocations associated with this macro invocation
-                    // *before* any macro invocations collected from the output fragment
+                    // We choose to expand any derive invocations associated with this macro
+                    // invocation *before* any macro invocations collected from the output
+                    // fragment.
                     derive_invocations.extend(collected_invocations);
-                    (fragment, derive_invocations)
+
+                    progress = true;
+                    if expanded_fragments.len() < depth {
+                        expanded_fragments.push(Vec::new());
+                    }
+                    expanded_fragments[depth - 1].push((expn_id, expanded_fragment));
+                    invocations.extend(derive_invocations.into_iter().rev());
                 }
                 ExpandResult::Retry(invoc) => {
                     if force {
@@ -519,17 +535,9 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                     } else {
                         // Cannot expand, will retry this invocation later.
                         undetermined_invocations.push((invoc, Some(ext)));
-                        continue;
                     }
                 }
-            };
-
-            progress = true;
-            if expanded_fragments.len() < depth {
-                expanded_fragments.push(Vec::new());
             }
-            expanded_fragments[depth - 1].push((expn_id, expanded_fragment));
-            invocations.extend(new_invocations.into_iter().rev());
         }
 
         self.cx.current_expansion = orig_expansion_data;
@@ -590,11 +598,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
                 for (invoc, _) in invocations.iter_mut() {
                     let expn_id = invoc.expansion_data.id;
                     let parent_def = self.cx.resolver.invocation_parent(expn_id);
-                    let span = match &mut invoc.kind {
-                        InvocationKind::Bang { span, .. } => span,
-                        InvocationKind::Attr { attr, .. } => &mut attr.span,
-                        InvocationKind::Derive { path, .. } => &mut path.span,
-                    };
+                    let span = invoc.span_mut();
                     *span = span.with_parent(Some(parent_def));
                 }
             }
@@ -957,7 +961,7 @@ pub fn parse_ast_fragment<'a>(
     })
 }
 
-pub fn ensure_complete_parse<'a>(
+pub(crate) fn ensure_complete_parse<'a>(
     parser: &Parser<'a>,
     macro_path: &ast::Path,
     kind_name: &str,
@@ -1037,6 +1041,7 @@ enum AddSemicolon {
 trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized {
     type OutputTy = SmallVec<[Self; 1]>;
     type AttrsTy: Deref<Target = [ast::Attribute]> = ast::AttrVec;
+    type ItemKind = ItemKind;
     const KIND: AstFragmentKind;
     fn to_annotatable(self) -> Annotatable;
     fn fragment_to_output(fragment: AstFragment) -> Self::OutputTy;
@@ -1055,6 +1060,18 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized {
     fn take_mac_call(self) -> (P<ast::MacCall>, Self::AttrsTy, AddSemicolon) {
         unreachable!()
     }
+    fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
+        None
+    }
+    fn delegation_item_kind(_deleg: Box<ast::Delegation>) -> Self::ItemKind {
+        unreachable!()
+    }
+    fn from_item(_item: ast::Item<Self::ItemKind>) -> Self {
+        unreachable!()
+    }
+    fn flatten_outputs(_outputs: impl Iterator<Item = Self::OutputTy>) -> Self::OutputTy {
+        unreachable!()
+    }
     fn pre_flat_map_node_collect_attr(_cfg: &StripUnconfigured<'_>, _attr: &ast::Attribute) {}
     fn post_flat_map_node_collect_bang(_output: &mut Self::OutputTy, _add_semicolon: AddSemicolon) {
     }
@@ -1102,6 +1119,21 @@ impl InvocationCollectorNode for P<ast::Item> {
             _ => unreachable!(),
         }
     }
+    fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
+        match &self.kind {
+            ItemKind::DelegationMac(deleg) => Some((deleg, self)),
+            _ => None,
+        }
+    }
+    fn delegation_item_kind(deleg: Box<ast::Delegation>) -> Self::ItemKind {
+        ItemKind::Delegation(deleg)
+    }
+    fn from_item(item: ast::Item<Self::ItemKind>) -> Self {
+        P(item)
+    }
+    fn flatten_outputs(items: impl Iterator<Item = Self::OutputTy>) -> Self::OutputTy {
+        items.flatten().collect()
+    }
     fn wrap_flat_map_node_noop_flat_map(
         mut node: Self,
         collector: &mut InvocationCollector<'_, '_>,
@@ -1190,8 +1222,8 @@ impl InvocationCollectorNode for P<ast::Item> {
                 match &ut.kind {
                     ast::UseTreeKind::Glob => {}
                     ast::UseTreeKind::Simple(_) => idents.push(ut.ident()),
-                    ast::UseTreeKind::Nested(nested) => {
-                        for (ut, _) in nested {
+                    ast::UseTreeKind::Nested { items, .. } => {
+                        for (ut, _) in items {
                             collect_use_tree_leaves(ut, idents);
                         }
                     }
@@ -1210,6 +1242,7 @@ impl InvocationCollectorNode for P<ast::Item> {
 struct TraitItemTag;
 impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, TraitItemTag> {
     type OutputTy = SmallVec<[P<ast::AssocItem>; 1]>;
+    type ItemKind = AssocItemKind;
     const KIND: AstFragmentKind = AstFragmentKind::TraitItems;
     fn to_annotatable(self) -> Annotatable {
         Annotatable::TraitItem(self.wrapped)
@@ -1218,7 +1251,7 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, TraitItemTag>
         fragment.make_trait_items()
     }
     fn noop_flat_map<V: MutVisitor>(self, visitor: &mut V) -> Self::OutputTy {
-        noop_flat_map_assoc_item(self.wrapped, visitor)
+        noop_flat_map_item(self.wrapped, visitor)
     }
     fn is_mac_call(&self) -> bool {
         matches!(self.wrapped.kind, AssocItemKind::MacCall(..))
@@ -1230,11 +1263,27 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, TraitItemTag>
             _ => unreachable!(),
         }
     }
+    fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
+        match &self.wrapped.kind {
+            AssocItemKind::DelegationMac(deleg) => Some((deleg, &self.wrapped)),
+            _ => None,
+        }
+    }
+    fn delegation_item_kind(deleg: Box<ast::Delegation>) -> Self::ItemKind {
+        AssocItemKind::Delegation(deleg)
+    }
+    fn from_item(item: ast::Item<Self::ItemKind>) -> Self {
+        AstNodeWrapper::new(P(item), TraitItemTag)
+    }
+    fn flatten_outputs(items: impl Iterator<Item = Self::OutputTy>) -> Self::OutputTy {
+        items.flatten().collect()
+    }
 }
 
 struct ImplItemTag;
 impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, ImplItemTag> {
     type OutputTy = SmallVec<[P<ast::AssocItem>; 1]>;
+    type ItemKind = AssocItemKind;
     const KIND: AstFragmentKind = AstFragmentKind::ImplItems;
     fn to_annotatable(self) -> Annotatable {
         Annotatable::ImplItem(self.wrapped)
@@ -1243,7 +1292,7 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, ImplItemTag>
         fragment.make_impl_items()
     }
     fn noop_flat_map<V: MutVisitor>(self, visitor: &mut V) -> Self::OutputTy {
-        noop_flat_map_assoc_item(self.wrapped, visitor)
+        noop_flat_map_item(self.wrapped, visitor)
     }
     fn is_mac_call(&self) -> bool {
         matches!(self.wrapped.kind, AssocItemKind::MacCall(..))
@@ -1255,6 +1304,21 @@ impl InvocationCollectorNode for AstNodeWrapper<P<ast::AssocItem>, ImplItemTag>
             _ => unreachable!(),
         }
     }
+    fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
+        match &self.wrapped.kind {
+            AssocItemKind::DelegationMac(deleg) => Some((deleg, &self.wrapped)),
+            _ => None,
+        }
+    }
+    fn delegation_item_kind(deleg: Box<ast::Delegation>) -> Self::ItemKind {
+        AssocItemKind::Delegation(deleg)
+    }
+    fn from_item(item: ast::Item<Self::ItemKind>) -> Self {
+        AstNodeWrapper::new(P(item), ImplItemTag)
+    }
+    fn flatten_outputs(items: impl Iterator<Item = Self::OutputTy>) -> Self::OutputTy {
+        items.flatten().collect()
+    }
 }
 
 impl InvocationCollectorNode for P<ast::ForeignItem> {
@@ -1266,7 +1330,7 @@ impl InvocationCollectorNode for P<ast::ForeignItem> {
         fragment.make_foreign_items()
     }
     fn noop_flat_map<V: MutVisitor>(self, visitor: &mut V) -> Self::OutputTy {
-        noop_flat_map_foreign_item(self, visitor)
+        noop_flat_map_item(self, visitor)
     }
     fn is_mac_call(&self) -> bool {
         matches!(self.kind, ForeignItemKind::MacCall(..))
@@ -1416,6 +1480,24 @@ impl InvocationCollectorNode for ast::Stmt {
         };
         (mac, attrs, if add_semicolon { AddSemicolon::Yes } else { AddSemicolon::No })
     }
+    fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item<Self::ItemKind>)> {
+        match &self.kind {
+            StmtKind::Item(item) => match &item.kind {
+                ItemKind::DelegationMac(deleg) => Some((deleg, item)),
+                _ => None,
+            },
+            _ => None,
+        }
+    }
+    fn delegation_item_kind(deleg: Box<ast::Delegation>) -> Self::ItemKind {
+        ItemKind::Delegation(deleg)
+    }
+    fn from_item(item: ast::Item<Self::ItemKind>) -> Self {
+        ast::Stmt { id: ast::DUMMY_NODE_ID, span: item.span, kind: StmtKind::Item(P(item)) }
+    }
+    fn flatten_outputs(items: impl Iterator<Item = Self::OutputTy>) -> Self::OutputTy {
+        items.flatten().collect()
+    }
     fn post_flat_map_node_collect_bang(stmts: &mut Self::OutputTy, add_semicolon: AddSemicolon) {
         // If this is a macro invocation with a semicolon, then apply that
         // semicolon to the final statement produced by expansion.
@@ -1717,11 +1799,10 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
             }
 
             if attr.is_doc_comment() {
-                self.cx.sess.psess.buffer_lint_with_diagnostic(
+                self.cx.sess.psess.buffer_lint(
                     UNUSED_DOC_COMMENTS,
                     current_span,
                     self.cx.current_expansion.lint_node_id,
-                    "unused doc comment",
                     BuiltinLintDiag::UnusedDocComment(attr.span),
                 );
             } else if rustc_attr::is_builtin_attr(attr) {
@@ -1729,11 +1810,10 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
                 // `#[cfg]` and `#[cfg_attr]` are special - they are
                 // eagerly evaluated.
                 if attr_name != sym::cfg && attr_name != sym::cfg_attr {
-                    self.cx.sess.psess.buffer_lint_with_diagnostic(
+                    self.cx.sess.psess.buffer_lint(
                         UNUSED_ATTRIBUTES,
                         attr.span,
                         self.cx.current_expansion.lint_node_id,
-                        format!("unused attribute `{attr_name}`"),
                         BuiltinLintDiag::UnusedBuiltinAttribute {
                             attr_name,
                             macro_name: pprust::path_to_string(&call.path),
@@ -1814,6 +1894,40 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
                     Node::post_flat_map_node_collect_bang(&mut res, add_semicolon);
                     res
                 }
+                None if let Some((deleg, item)) = node.delegation_list() => {
+                    if deleg.suffixes.is_empty() {
+                        // Report an error for now, to avoid keeping stem for resolution and
+                        // stability checks.
+                        self.cx.dcx().emit_err(EmptyDelegationList { span: item.span });
+                    }
+
+                    Node::flatten_outputs(deleg.suffixes.iter().map(|&(ident, rename)| {
+                        let mut path = deleg.prefix.clone();
+                        path.segments.push(ast::PathSegment {
+                            ident,
+                            id: ast::DUMMY_NODE_ID,
+                            args: None,
+                        });
+
+                        let mut item = Node::from_item(ast::Item {
+                            attrs: item.attrs.clone(),
+                            id: ast::DUMMY_NODE_ID,
+                            span: ident.span,
+                            vis: item.vis.clone(),
+                            ident: rename.unwrap_or(ident),
+                            kind: Node::delegation_item_kind(Box::new(ast::Delegation {
+                                id: ast::DUMMY_NODE_ID,
+                                qself: deleg.qself.clone(),
+                                path,
+                                rename,
+                                body: deleg.body.clone(),
+                            })),
+                            tokens: None,
+                        });
+
+                        assign_id!(self, item.node_id_mut(), || item.noop_flat_map(self))
+                    }))
+                }
                 None => {
                     match Node::wrap_flat_map_node_noop_flat_map(node, self, |mut node, this| {
                         assign_id!(this, node.node_id_mut(), || node.noop_flat_map(this))
@@ -1862,6 +1976,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
                         self.collect_bang(mac, Node::KIND).make_ast::<Node>()
                     })
                 }
+                None if node.delegation_list().is_some() => unreachable!(),
                 None => {
                     assign_id!(self, node.node_id_mut(), || node.noop_visit(self))
                 }
diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs
index c9a3aeedd02..4222c9fe906 100644
--- a/compiler/rustc_expand/src/lib.rs
+++ b/compiler/rustc_expand/src/lib.rs
@@ -1,68 +1,37 @@
+// tidy-alphabetical-start
+#![allow(internal_features)]
+#![allow(rustc::diagnostic_outside_of_impl)]
 #![doc(rust_logo)]
-#![feature(rustdoc_internals)]
 #![feature(array_windows)]
-#![cfg_attr(bootstrap, feature(associated_type_bounds))]
 #![feature(associated_type_defaults)]
 #![feature(if_let_guard)]
 #![feature(let_chains)]
-#![feature(lint_reasons)]
 #![feature(macro_metavar_expr)]
 #![feature(map_try_insert)]
 #![feature(proc_macro_diagnostic)]
 #![feature(proc_macro_internals)]
-#![feature(proc_macro_span)]
+#![feature(rustdoc_internals)]
 #![feature(try_blocks)]
 #![feature(yeet_expr)]
-#![allow(rustc::diagnostic_outside_of_impl)]
-#![allow(internal_features)]
-
-#[macro_use]
-extern crate rustc_macros;
-
-#[macro_use]
-extern crate tracing;
+// tidy-alphabetical-end
 
 extern crate proc_macro as pm;
 
+mod build;
+mod errors;
+// FIXME(Nilstrieb) Translate macro_rules diagnostics
+#[allow(rustc::untranslatable_diagnostic)]
+mod mbe;
 mod placeholders;
 mod proc_macro_server;
 
 pub use mbe::macro_rules::compile_declarative_macro;
-pub(crate) use rustc_span::hygiene;
 pub mod base;
-pub mod build;
-#[macro_use]
 pub mod config;
-pub mod errors;
 pub mod expand;
 pub mod module;
-
 // FIXME(Nilstrieb) Translate proc_macro diagnostics
 #[allow(rustc::untranslatable_diagnostic)]
 pub mod proc_macro;
 
-// FIXME(Nilstrieb) Translate macro_rules diagnostics
-#[allow(rustc::untranslatable_diagnostic)]
-pub(crate) mod mbe;
-
-// HACK(Centril, #64197): These shouldn't really be here.
-// Rather, they should be with their respective modules which are defined in other crates.
-// However, since for now constructing a `ParseSess` sorta requires `config` from this crate,
-// these tests will need to live here in the interim.
-
-#[cfg(test)]
-mod tests;
-#[cfg(test)]
-mod parse {
-    mod tests;
-}
-#[cfg(test)]
-mod tokenstream {
-    mod tests;
-}
-#[cfg(test)]
-mod mut_visit {
-    mod tests;
-}
-
 rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
diff --git a/compiler/rustc_expand/src/mbe.rs b/compiler/rustc_expand/src/mbe.rs
index ca4a1f327ad..08d4a039454 100644
--- a/compiler/rustc_expand/src/mbe.rs
+++ b/compiler/rustc_expand/src/mbe.rs
@@ -4,16 +4,18 @@
 //! official terminology: "declarative macros".
 
 pub(crate) mod diagnostics;
-pub(crate) mod macro_check;
-pub(crate) mod macro_parser;
 pub(crate) mod macro_rules;
-pub(crate) mod metavar_expr;
-pub(crate) mod quoted;
-pub(crate) mod transcribe;
+
+mod macro_check;
+mod macro_parser;
+mod metavar_expr;
+mod quoted;
+mod transcribe;
 
 use metavar_expr::MetaVarExpr;
 use rustc_ast::token::{Delimiter, NonterminalKind, Token, TokenKind};
 use rustc_ast::tokenstream::{DelimSpacing, DelimSpan};
+use rustc_macros::{Decodable, Encodable};
 use rustc_span::symbol::Ident;
 use rustc_span::Span;
 
@@ -66,12 +68,15 @@ pub(crate) enum KleeneOp {
 /// `MetaVarExpr` are "first-class" token trees. Useful for parsing macros.
 #[derive(Debug, PartialEq, Encodable, Decodable)]
 enum TokenTree {
+    /// A token. Unlike `tokenstream::TokenTree::Token` this lacks a `Spacing`.
+    /// See the comments about `Spacing` in the `transcribe` function.
     Token(Token),
     /// A delimited sequence, e.g. `($e:expr)` (RHS) or `{ $e }` (LHS).
     Delimited(DelimSpan, DelimSpacing, Delimited),
     /// A kleene-style repetition sequence, e.g. `$($e:expr)*` (RHS) or `$($e),*` (LHS).
     Sequence(DelimSpan, SequenceRepetition),
-    /// e.g., `$var`.
+    /// e.g., `$var`. The span covers the leading dollar and the ident. (The span within the ident
+    /// only covers the ident, e.g. `var`.)
     MetaVar(Span, Ident),
     /// e.g., `$var:expr`. Only appears on the LHS.
     MetaVarDecl(Span, Ident /* name to bind */, Option<NonterminalKind>),
diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs
index 15193298cca..442fd654b6a 100644
--- a/compiler/rustc_expand/src/mbe/diagnostics.rs
+++ b/compiler/rustc_expand/src/mbe/diagnostics.rs
@@ -8,11 +8,13 @@ use rustc_ast::token::{self, Token, TokenKind};
 use rustc_ast::tokenstream::TokenStream;
 use rustc_ast_pretty::pprust;
 use rustc_errors::{Applicability, Diag, DiagCtxt, DiagMessage};
+use rustc_macros::Subdiagnostic;
 use rustc_parse::parser::{Parser, Recovery};
 use rustc_span::source_map::SourceMap;
 use rustc_span::symbol::Ident;
 use rustc_span::{ErrorGuaranteed, Span};
 use std::borrow::Cow;
+use tracing::debug;
 
 use super::macro_rules::{parser_from_cx, NoopTracker};
 
@@ -26,7 +28,8 @@ pub(super) fn failed_to_match_macro<'cx>(
 ) -> Box<dyn MacResult + 'cx> {
     let psess = &cx.sess.psess;
 
-    // An error occurred, try the expansion again, tracking the expansion closely for better diagnostics.
+    // An error occurred, try the expansion again, tracking the expansion closely for better
+    // diagnostics.
     let mut tracker = CollectTrackerAndEmitter::new(cx, sp);
 
     let try_success_result = try_match_macro(psess, name, &arg, lhses, &mut tracker);
@@ -52,7 +55,7 @@ pub(super) fn failed_to_match_macro<'cx>(
 
     let span = token.span.substitute_dummy(sp);
 
-    let mut err = cx.dcx().struct_span_err(span, parse_failure_msg(&token));
+    let mut err = cx.dcx().struct_span_err(span, parse_failure_msg(&token, None));
     err.span_label(span, label);
     if !def_span.is_dummy() && !cx.source_map().is_imported(def_span) {
         err.span_label(cx.source_map().guess_head_span(def_span), "when calling this macro");
@@ -70,12 +73,6 @@ pub(super) fn failed_to_match_macro<'cx>(
         && (matches!(expected_token.kind, TokenKind::Interpolated(_))
             || matches!(token.kind, TokenKind::Interpolated(_)))
     {
-        if let TokenKind::Interpolated(node) = &expected_token.kind {
-            err.span_label(node.1, "");
-        }
-        if let TokenKind::Interpolated(node) = &token.kind {
-            err.span_label(node.1, "");
-        }
         err.note("captured metavariables except for `:tt`, `:ident` and `:lifetime` cannot be compared to other tokens");
         err.note("see <https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment> for more information");
 
@@ -203,9 +200,17 @@ impl<'a, 'cx> CollectTrackerAndEmitter<'a, 'cx, '_> {
 }
 
 /// Currently used by macro_rules! compilation to extract a little information from the `Failure` case.
-pub struct FailureForwarder;
+pub struct FailureForwarder<'matcher> {
+    expected_token: Option<&'matcher Token>,
+}
 
-impl<'matcher> Tracker<'matcher> for FailureForwarder {
+impl<'matcher> FailureForwarder<'matcher> {
+    pub fn new() -> Self {
+        Self { expected_token: None }
+    }
+}
+
+impl<'matcher> Tracker<'matcher> for FailureForwarder<'matcher> {
     type Failure = (Token, usize, &'static str);
 
     fn build_failure(tok: Token, position: usize, msg: &'static str) -> Self::Failure {
@@ -215,6 +220,14 @@ impl<'matcher> Tracker<'matcher> for FailureForwarder {
     fn description() -> &'static str {
         "failure-forwarder"
     }
+
+    fn set_expected_token(&mut self, tok: &'matcher Token) {
+        self.expected_token = Some(tok);
+    }
+
+    fn get_expected_token(&self) -> Option<&'matcher Token> {
+        self.expected_token
+    }
 }
 
 pub(super) fn emit_frag_parse_err(
@@ -323,9 +336,19 @@ pub(super) fn annotate_doc_comment(dcx: &DiagCtxt, err: &mut Diag<'_>, sm: &Sour
 
 /// Generates an appropriate parsing failure message. For EOF, this is "unexpected end...". For
 /// other tokens, this is "unexpected token...".
-pub(super) fn parse_failure_msg(tok: &Token) -> Cow<'static, str> {
-    match tok.kind {
-        token::Eof => Cow::from("unexpected end of macro invocation"),
-        _ => Cow::from(format!("no rules expected the token `{}`", pprust::token_to_string(tok))),
+pub(super) fn parse_failure_msg(tok: &Token, expected_token: Option<&Token>) -> Cow<'static, str> {
+    if let Some(expected_token) = expected_token {
+        Cow::from(format!(
+            "expected `{}`, found `{}`",
+            pprust::token_to_string(expected_token),
+            pprust::token_to_string(tok),
+        ))
+    } else {
+        match tok.kind {
+            token::Eof => Cow::from("unexpected end of macro invocation"),
+            _ => {
+                Cow::from(format!("no rules expected the token `{}`", pprust::token_to_string(tok)))
+            }
+        }
     }
 }
diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs
index dce8e0c36ed..72dbbde54b3 100644
--- a/compiler/rustc_expand/src/mbe/macro_check.rs
+++ b/compiler/rustc_expand/src/mbe/macro_check.rs
@@ -110,7 +110,8 @@ use crate::mbe::{KleeneToken, TokenTree};
 use rustc_ast::token::{Delimiter, IdentIsRaw, Token, TokenKind};
 use rustc_ast::{NodeId, DUMMY_NODE_ID};
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{DiagMessage, MultiSpan};
+use rustc_errors::MultiSpan;
+use rustc_lint_defs::BuiltinLintDiag;
 use rustc_session::lint::builtin::{META_VARIABLE_MISUSE, MISSING_FRAGMENT_SPECIFIER};
 use rustc_session::parse::ParseSess;
 use rustc_span::symbol::kw;
@@ -252,7 +253,7 @@ fn check_binders(
                 // 1. The meta-variable is already bound in the current LHS: This is an error.
                 let mut span = MultiSpan::from_span(span);
                 span.push_span_label(prev_info.span, "previous declaration");
-                buffer_lint(psess, span, node_id, "duplicate matcher binding");
+                buffer_lint(psess, span, node_id, BuiltinLintDiag::DuplicateMatcherBinding);
             } else if get_binder_info(macros, binders, name).is_none() {
                 // 2. The meta-variable is free: This is a binder.
                 binders.insert(name, BinderInfo { span, ops: ops.into() });
@@ -271,7 +272,7 @@ fn check_binders(
                     MISSING_FRAGMENT_SPECIFIER,
                     span,
                     node_id,
-                    "missing fragment specifier",
+                    BuiltinLintDiag::MissingFragmentSpecifier,
                 );
             }
             if !macros.is_empty() {
@@ -595,7 +596,7 @@ fn check_ops_is_prefix(
             return;
         }
     }
-    buffer_lint(psess, span.into(), node_id, format!("unknown macro variable `{name}`"));
+    buffer_lint(psess, span.into(), node_id, BuiltinLintDiag::UnknownMacroVariable(name));
 }
 
 /// Returns whether `binder_ops` is a prefix of `occurrence_ops`.
@@ -628,8 +629,7 @@ fn ops_is_prefix(
         if i >= occurrence_ops.len() {
             let mut span = MultiSpan::from_span(span);
             span.push_span_label(binder.span, "expected repetition");
-            let message = format!("variable '{name}' is still repeating at this depth");
-            buffer_lint(psess, span, node_id, message);
+            buffer_lint(psess, span, node_id, BuiltinLintDiag::MetaVariableStillRepeating(name));
             return;
         }
         let occurrence = &occurrence_ops[i];
@@ -637,21 +637,15 @@ fn ops_is_prefix(
             let mut span = MultiSpan::from_span(span);
             span.push_span_label(binder.span, "expected repetition");
             span.push_span_label(occurrence.span, "conflicting repetition");
-            let message = "meta-variable repeats with different Kleene operator";
-            buffer_lint(psess, span, node_id, message);
+            buffer_lint(psess, span, node_id, BuiltinLintDiag::MetaVariableWrongOperator);
             return;
         }
     }
 }
 
-fn buffer_lint(
-    psess: &ParseSess,
-    span: MultiSpan,
-    node_id: NodeId,
-    message: impl Into<DiagMessage>,
-) {
+fn buffer_lint(psess: &ParseSess, span: MultiSpan, node_id: NodeId, diag: BuiltinLintDiag) {
     // Macros loaded from other crates have dummy node ids.
     if node_id != DUMMY_NODE_ID {
-        psess.buffer_lint(META_VARIABLE_MISUSE, span, node_id, message);
+        psess.buffer_lint(META_VARIABLE_MISUSE, span, node_id, diag);
     }
 }
diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs
index 9fff00ffeae..2fbd09fd9ae 100644
--- a/compiler/rustc_expand/src/mbe/macro_parser.rs
+++ b/compiler/rustc_expand/src/mbe/macro_parser.rs
@@ -75,10 +75,9 @@ pub(crate) use ParseResult::*;
 
 use crate::mbe::{macro_rules::Tracker, KleeneOp, TokenTree};
 
-use rustc_ast::token::{self, DocComment, Nonterminal, NonterminalKind, Token};
+use rustc_ast::token::{self, DocComment, NonterminalKind, Token};
 use rustc_ast_pretty::pprust;
 use rustc_data_structures::fx::FxHashMap;
-use rustc_data_structures::sync::Lrc;
 use rustc_errors::ErrorGuaranteed;
 use rustc_lint_defs::pluralize;
 use rustc_parse::parser::{ParseNtResult, Parser};
@@ -266,7 +265,7 @@ struct MatcherPos {
 }
 
 // This type is used a lot. Make sure it doesn't unintentionally get bigger.
-#[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64"), target_pointer_width = "64"))]
+#[cfg(target_pointer_width = "64")]
 rustc_data_structures::static_assert_size!(MatcherPos, 16);
 
 impl MatcherPos {
@@ -392,7 +391,7 @@ pub(super) fn count_metavar_decls(matcher: &[TokenTree]) -> usize {
 #[derive(Debug, Clone)]
 pub(crate) enum NamedMatch {
     MatchedSeq(Vec<NamedMatch>),
-    MatchedSingle(ParseNtResult<Lrc<(Nonterminal, Span)>>),
+    MatchedSingle(ParseNtResult),
 }
 
 /// Performs a token equality check, ignoring syntax context (that is, an unhygienic comparison)
@@ -542,6 +541,8 @@ impl TtParser {
                         // The separator matches the current token. Advance past it.
                         mp.idx += 1;
                         self.next_mps.push(mp);
+                    } else {
+                        track.set_expected_token(separator);
                     }
                 }
                 &MatcherLoc::SequenceKleeneOpAfterSep { idx_first } => {
@@ -633,6 +634,7 @@ impl TtParser {
                 parser.approx_token_stream_pos(),
                 track,
             );
+
             if let Some(res) = res {
                 return res;
             }
@@ -686,11 +688,7 @@ impl TtParser {
                             }
                             Ok(nt) => nt,
                         };
-                        mp.push_match(
-                            next_metavar,
-                            seq_depth,
-                            MatchedSingle(nt.map_nt(|nt| (Lrc::new((nt, span))))),
-                        );
+                        mp.push_match(next_metavar, seq_depth, MatchedSingle(nt));
                         mp.idx += 1;
                     } else {
                         unreachable!()
diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs
index 7099f1b0d35..5d3ba5d3223 100644
--- a/compiler/rustc_expand/src/mbe/macro_rules.rs
+++ b/compiler/rustc_expand/src/mbe/macro_rules.rs
@@ -29,6 +29,7 @@ use rustc_span::edition::Edition;
 use rustc_span::hygiene::Transparency;
 use rustc_span::symbol::{kw, sym, Ident, MacroRulesNormalizedIdent};
 use rustc_span::Span;
+use tracing::{debug, instrument, trace, trace_span};
 
 use std::borrow::Cow;
 use std::collections::hash_map::Entry;
@@ -78,11 +79,10 @@ impl<'a> ParserAnyMacro<'a> {
         // but `m!()` is allowed in expression positions (cf. issue #34706).
         if kind == AstFragmentKind::Expr && parser.token == token::Semi {
             if is_local {
-                parser.psess.buffer_lint_with_diagnostic(
+                parser.psess.buffer_lint(
                     SEMICOLON_IN_EXPRESSIONS_FROM_MACROS,
                     parser.token.span,
                     lint_node_id,
-                    "trailing semicolon in macro used in expression position",
                     BuiltinLintDiag::TrailingMacro(is_trailing_mac, macro_ident),
                 );
             }
@@ -156,8 +156,8 @@ pub(super) trait Tracker<'matcher> {
     /// This is called before trying to match next MatcherLoc on the current token.
     fn before_match_loc(&mut self, _parser: &TtParser, _matcher: &'matcher MatcherLoc) {}
 
-    /// This is called after an arm has been parsed, either successfully or unsuccessfully. When this is called,
-    /// `before_match_loc` was called at least once (with a `MatcherLoc::Eof`).
+    /// This is called after an arm has been parsed, either successfully or unsuccessfully. When
+    /// this is called, `before_match_loc` was called at least once (with a `MatcherLoc::Eof`).
     fn after_arm(&mut self, _result: &NamedParseResult<Self::Failure>) {}
 
     /// For tracing.
@@ -166,9 +166,15 @@ pub(super) trait Tracker<'matcher> {
     fn recovery() -> Recovery {
         Recovery::Forbidden
     }
+
+    fn set_expected_token(&mut self, _tok: &'matcher Token) {}
+    fn get_expected_token(&self) -> Option<&'matcher Token> {
+        None
+    }
 }
 
-/// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to monomorphization.
+/// A noop tracker that is used in the hot path of the expansion, has zero overhead thanks to
+/// monomorphization.
 pub(super) struct NoopTracker;
 
 impl<'matcher> Tracker<'matcher> for NoopTracker {
@@ -217,7 +223,8 @@ fn expand_macro<'cx>(
             let arm_span = rhses[i].span();
 
             // rhs has holes ( `$id` and `$(...)` that need filled)
-            let tts = match transcribe(cx, &named_matches, rhs, rhs_span, transparency) {
+            let id = cx.current_expansion.id;
+            let tts = match transcribe(psess, &named_matches, rhs, rhs_span, transparency, id) {
                 Ok(tts) => tts,
                 Err(err) => {
                     let guar = err.emit();
@@ -445,16 +452,14 @@ pub fn compile_declarative_macro(
                 // For this we need to reclone the macro body as the previous parser consumed it.
                 let retry_parser = create_parser();
 
-                let parse_result = tt_parser.parse_tt(
-                    &mut Cow::Owned(retry_parser),
-                    &argument_gram,
-                    &mut diagnostics::FailureForwarder,
-                );
+                let mut track = diagnostics::FailureForwarder::new();
+                let parse_result =
+                    tt_parser.parse_tt(&mut Cow::Owned(retry_parser), &argument_gram, &mut track);
                 let Failure((token, _, msg)) = parse_result else {
                     unreachable!("matcher returned something other than Failure after retry");
                 };
 
-                let s = parse_failure_msg(&token);
+                let s = parse_failure_msg(&token, track.get_expected_token());
                 let sp = token.span.substitute_dummy(def.span);
                 let mut err = sess.dcx().struct_span_err(sp, s);
                 err.span_label(sp, msg);
@@ -491,7 +496,7 @@ pub fn compile_declarative_macro(
                     .pop()
                     .unwrap();
                     // We don't handle errors here, the driver will abort
-                    // after parsing/expansion. we can report every error in every macro this way.
+                    // after parsing/expansion. We can report every error in every macro this way.
                     check_emission(check_lhs_nt_follows(sess, def, &tt));
                     return tt;
                 }
@@ -527,7 +532,7 @@ pub fn compile_declarative_macro(
         check_emission(check_rhs(sess, rhs));
     }
 
-    // don't abort iteration early, so that errors for multiple lhses can be reported
+    // Don't abort iteration early, so that errors for multiple lhses can be reported.
     for lhs in &lhses {
         check_emission(check_lhs_no_empty_seq(sess, slice::from_ref(lhs)));
     }
@@ -1149,11 +1154,10 @@ fn check_matcher_core<'tt>(
                             name,
                             Some(NonterminalKind::PatParam { inferred: false }),
                         ));
-                        sess.psess.buffer_lint_with_diagnostic(
+                        sess.psess.buffer_lint(
                             RUST_2021_INCOMPATIBLE_OR_PATTERNS,
                             span,
                             ast::CRATE_NODE_ID,
-                            "the meaning of the `pat` fragment specifier is changing in Rust 2021, which may affect this macro",
                             BuiltinLintDiag::OrPatternsBackCompat(span, suggestion),
                         );
                     }
@@ -1288,7 +1292,7 @@ fn is_in_follow(tok: &mbe::TokenTree, kind: NonterminalKind) -> IsInFollow {
                 // maintain
                 IsInFollow::Yes
             }
-            NonterminalKind::Stmt | NonterminalKind::Expr => {
+            NonterminalKind::Stmt | NonterminalKind::Expr | NonterminalKind::Expr2021 => {
                 const TOKENS: &[&str] = &["`=>`", "`,`", "`;`"];
                 match tok {
                     TokenTree::Token(token) => match token.kind {
diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs
index 81e1de5b095..128e9f48ff5 100644
--- a/compiler/rustc_expand/src/mbe/metavar_expr.rs
+++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs
@@ -3,6 +3,7 @@ use rustc_ast::tokenstream::{RefTokenTreeCursor, TokenStream, TokenTree};
 use rustc_ast::{LitIntType, LitKind};
 use rustc_ast_pretty::pprust;
 use rustc_errors::{Applicability, PResult};
+use rustc_macros::{Decodable, Encodable};
 use rustc_session::parse::ParseSess;
 use rustc_span::symbol::Ident;
 use rustc_span::Span;
@@ -22,7 +23,7 @@ pub(crate) enum MetaVarExpr {
 
     /// The length of the repetition at a particular depth, where 0 is the inner-most
     /// repetition. The `usize` is the depth.
-    Length(usize),
+    Len(usize),
 }
 
 impl MetaVarExpr {
@@ -47,13 +48,13 @@ impl MetaVarExpr {
                 MetaVarExpr::Ignore(parse_ident(&mut iter, psess, ident.span)?)
             }
             "index" => MetaVarExpr::Index(parse_depth(&mut iter, psess, ident.span)?),
-            "length" => MetaVarExpr::Length(parse_depth(&mut iter, psess, ident.span)?),
+            "len" => MetaVarExpr::Len(parse_depth(&mut iter, psess, ident.span)?),
             _ => {
                 let err_msg = "unrecognized meta-variable expression";
                 let mut err = psess.dcx.struct_span_err(ident.span, err_msg);
                 err.span_suggestion(
                     ident.span,
-                    "supported expressions are count, ignore, index and length",
+                    "supported expressions are count, ignore, index and len",
                     "",
                     Applicability::MachineApplicable,
                 );
@@ -67,7 +68,7 @@ impl MetaVarExpr {
     pub(crate) fn ident(&self) -> Option<Ident> {
         match *self {
             MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(ident),
-            MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None,
+            MetaVarExpr::Index(..) | MetaVarExpr::Len(..) => None,
         }
     }
 }
@@ -110,7 +111,7 @@ fn parse_count<'psess>(
     Ok(MetaVarExpr::Count(ident, depth))
 }
 
-/// Parses the depth used by index(depth) and length(depth).
+/// Parses the depth used by index(depth) and len(depth).
 fn parse_depth<'psess>(
     iter: &mut RefTokenTreeCursor<'_>,
     psess: &'psess ParseSess,
diff --git a/compiler/rustc_expand/src/mbe/quoted.rs b/compiler/rustc_expand/src/mbe/quoted.rs
index 06c1612ddba..8ad7cb15c92 100644
--- a/compiler/rustc_expand/src/mbe/quoted.rs
+++ b/compiler/rustc_expand/src/mbe/quoted.rs
@@ -16,6 +16,10 @@ use rustc_span::Span;
 const VALID_FRAGMENT_NAMES_MSG: &str = "valid fragment specifiers are \
                                         `ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, \
                                         `literal`, `path`, `meta`, `tt`, `item` and `vis`";
+const VALID_FRAGMENT_NAMES_MSG_2021: &str = "valid fragment specifiers are \
+                                             `ident`, `block`, `stmt`, `expr`, `expr_2021`, `pat`, \
+                                             `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, \
+                                             `item` and `vis`";
 
 /// Takes a `tokenstream::TokenStream` and returns a `Vec<self::TokenTree>`. Specifically, this
 /// takes a generic `TokenStream`, such as is used in the rest of the compiler, and returns a
@@ -58,49 +62,79 @@ pub(super) fn parse(
         match tree {
             TokenTree::MetaVar(start_sp, ident) if parsing_patterns => {
                 let span = match trees.next() {
-                    Some(&tokenstream::TokenTree::Token(Token { kind: token::Colon, span }, _)) => {
+                    Some(&tokenstream::TokenTree::Token(
+                        Token { kind: token::Colon, span: colon_span },
+                        _,
+                    )) => {
                         match trees.next() {
                             Some(tokenstream::TokenTree::Token(token, _)) => match token.ident() {
                                 Some((fragment, _)) => {
                                     let span = token.span.with_lo(start_sp.lo());
-
+                                    let edition = || {
+                                        // FIXME(#85708) - once we properly decode a foreign
+                                        // crate's `SyntaxContext::root`, then we can replace
+                                        // this with just `span.edition()`. A
+                                        // `SyntaxContext::root()` from the current crate will
+                                        // have the edition of the current crate, and a
+                                        // `SyntaxContext::root()` from a foreign crate will
+                                        // have the edition of that crate (which we manually
+                                        // retrieve via the `edition` parameter).
+                                        if !span.from_expansion() {
+                                            edition
+                                        } else {
+                                            span.edition()
+                                        }
+                                    };
                                     let kind =
-                                        token::NonterminalKind::from_symbol(fragment.name, || {
-                                            // FIXME(#85708) - once we properly decode a foreign
-                                            // crate's `SyntaxContext::root`, then we can replace
-                                            // this with just `span.edition()`. A
-                                            // `SyntaxContext::root()` from the current crate will
-                                            // have the edition of the current crate, and a
-                                            // `SyntaxContext::root()` from a foreign crate will
-                                            // have the edition of that crate (which we manually
-                                            // retrieve via the `edition` parameter).
-                                            if !span.from_expansion() {
-                                                edition
-                                            } else {
-                                                span.edition()
-                                            }
-                                        })
-                                        .unwrap_or_else(
-                                            || {
+                                        token::NonterminalKind::from_symbol(fragment.name, edition)
+                                            .unwrap_or_else(|| {
+                                                let help = match fragment.name {
+                                                    sym::expr_2021 => {
+                                                        format!(
+                                                            "fragment specifier `expr_2021` \
+                                                             requires Rust 2021 or later\n\
+                                                             {VALID_FRAGMENT_NAMES_MSG}"
+                                                        )
+                                                    }
+                                                    _ if edition().at_least_rust_2021()
+                                                        && features
+                                                            .expr_fragment_specifier_2024 =>
+                                                    {
+                                                        VALID_FRAGMENT_NAMES_MSG_2021.into()
+                                                    }
+                                                    _ => VALID_FRAGMENT_NAMES_MSG.into(),
+                                                };
                                                 sess.dcx().emit_err(
                                                     errors::InvalidFragmentSpecifier {
                                                         span,
                                                         fragment,
-                                                        help: VALID_FRAGMENT_NAMES_MSG.into(),
+                                                        help,
                                                     },
                                                 );
                                                 token::NonterminalKind::Ident
-                                            },
-                                        );
+                                            });
+                                    if kind == token::NonterminalKind::Expr2021
+                                        && !features.expr_fragment_specifier_2024
+                                    {
+                                        rustc_session::parse::feature_err(
+                                            sess,
+                                            sym::expr_fragment_specifier_2024,
+                                            span,
+                                            "fragment specifier `expr_2021` is unstable",
+                                        )
+                                        .emit();
+                                    }
                                     result.push(TokenTree::MetaVarDecl(span, ident, Some(kind)));
                                     continue;
                                 }
                                 _ => token.span,
                             },
-                            tree => tree.map_or(span, tokenstream::TokenTree::span),
+                            Some(tree) => tree.span(),
+                            None => colon_span,
                         }
                     }
-                    tree => tree.map_or(start_sp, tokenstream::TokenTree::span),
+                    Some(tree) => tree.span(),
+                    None => start_sp,
                 };
 
                 result.push(TokenTree::MetaVarDecl(span, ident, None));
@@ -147,7 +181,7 @@ fn parse_tree<'a>(
     // Depending on what `tree` is, we could be parsing different parts of a macro
     match tree {
         // `tree` is a `$` token. Look at the next token in `trees`
-        &tokenstream::TokenTree::Token(Token { kind: token::Dollar, span }, _) => {
+        &tokenstream::TokenTree::Token(Token { kind: token::Dollar, span: dollar_span }, _) => {
             // FIXME: Handle `Invisible`-delimited groups in a more systematic way
             // during parsing.
             let mut next = outer_trees.next();
@@ -180,7 +214,7 @@ fn parse_tree<'a>(
                                         err.emit();
                                         // Returns early the same read `$` to avoid spanning
                                         // unrelated diagnostics that could be performed afterwards
-                                        return TokenTree::token(token::Dollar, span);
+                                        return TokenTree::token(token::Dollar, dollar_span);
                                     }
                                     Ok(elem) => {
                                         maybe_emit_macro_metavar_expr_feature(
@@ -222,7 +256,7 @@ fn parse_tree<'a>(
                 // special metavariable that names the crate of the invocation.
                 Some(tokenstream::TokenTree::Token(token, _)) if token.is_ident() => {
                     let (ident, is_raw) = token.ident().unwrap();
-                    let span = ident.span.with_lo(span.lo());
+                    let span = ident.span.with_lo(dollar_span.lo());
                     if ident.name == kw::Crate && matches!(is_raw, IdentIsRaw::No) {
                         TokenTree::token(token::Ident(kw::DollarCrate, is_raw), span)
                     } else {
@@ -231,16 +265,19 @@ fn parse_tree<'a>(
                 }
 
                 // `tree` is followed by another `$`. This is an escaped `$`.
-                Some(&tokenstream::TokenTree::Token(Token { kind: token::Dollar, span }, _)) => {
+                Some(&tokenstream::TokenTree::Token(
+                    Token { kind: token::Dollar, span: dollar_span2 },
+                    _,
+                )) => {
                     if parsing_patterns {
                         span_dollar_dollar_or_metavar_in_the_lhs_err(
                             sess,
-                            &Token { kind: token::Dollar, span },
+                            &Token { kind: token::Dollar, span: dollar_span2 },
                         );
                     } else {
-                        maybe_emit_macro_metavar_expr_feature(features, sess, span);
+                        maybe_emit_macro_metavar_expr_feature(features, sess, dollar_span2);
                     }
-                    TokenTree::token(token::Dollar, span)
+                    TokenTree::token(token::Dollar, dollar_span2)
                 }
 
                 // `tree` is followed by some other token. This is an error.
@@ -252,7 +289,7 @@ fn parse_tree<'a>(
                 }
 
                 // There are no more tokens. Just return the `$` we already have.
-                None => TokenTree::token(token::Dollar, span),
+                None => TokenTree::token(token::Dollar, dollar_span),
             }
         }
 
@@ -357,7 +394,7 @@ fn parse_sep_and_kleene_op<'a>(
 
 // `$$` or a meta-variable is the lhs of a macro but shouldn't.
 //
-// For example, `macro_rules! foo { ( ${length()} ) => {} }`
+// For example, `macro_rules! foo { ( ${len()} ) => {} }`
 fn span_dollar_dollar_or_metavar_in_the_lhs_err(sess: &Session, token: &Token) {
     sess.dcx()
         .span_err(token.span, format!("unexpected token: {}", pprust::token_to_string(token)));
diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs
index dad83984c8b..e1f50876b05 100644
--- a/compiler/rustc_expand/src/mbe/transcribe.rs
+++ b/compiler/rustc_expand/src/mbe/transcribe.rs
@@ -1,4 +1,3 @@
-use crate::base::ExtCtxt;
 use crate::errors::{
     CountRepetitionMisplaced, MetaVarExprUnrecognizedVar, MetaVarsDifSeqMatchers, MustRepeatOnce,
     NoSyntaxVarsExprRepeat, VarStillRepeating,
@@ -9,12 +8,13 @@ use rustc_ast::mut_visit::{self, MutVisitor};
 use rustc_ast::token::{self, Delimiter, Token, TokenKind};
 use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{pluralize, Diag, PResult};
+use rustc_errors::{pluralize, Diag, DiagCtxt, PResult};
 use rustc_parse::parser::ParseNtResult;
 use rustc_span::hygiene::{LocalExpnId, Transparency};
 use rustc_span::symbol::{sym, Ident, MacroRulesNormalizedIdent};
 use rustc_span::{with_metavar_spans, Span, SyntaxContext};
 
+use rustc_session::parse::ParseSess;
 use smallvec::{smallvec, SmallVec};
 use std::mem;
 
@@ -39,26 +39,32 @@ impl MutVisitor for Marker {
 }
 
 /// An iterator over the token trees in a delimited token tree (`{ ... }`) or a sequence (`$(...)`).
-enum Frame<'a> {
-    Delimited {
-        tts: &'a [mbe::TokenTree],
-        idx: usize,
-        delim: Delimiter,
-        span: DelimSpan,
-        spacing: DelimSpacing,
-    },
-    Sequence {
-        tts: &'a [mbe::TokenTree],
-        idx: usize,
-        sep: Option<Token>,
-        kleene_op: KleeneOp,
-    },
+struct Frame<'a> {
+    tts: &'a [mbe::TokenTree],
+    idx: usize,
+    kind: FrameKind,
+}
+
+enum FrameKind {
+    Delimited { delim: Delimiter, span: DelimSpan, spacing: DelimSpacing },
+    Sequence { sep: Option<Token>, kleene_op: KleeneOp },
 }
 
 impl<'a> Frame<'a> {
-    /// Construct a new frame around the delimited set of tokens.
-    fn new(src: &'a mbe::Delimited, span: DelimSpan, spacing: DelimSpacing) -> Frame<'a> {
-        Frame::Delimited { tts: &src.tts, idx: 0, delim: src.delim, span, spacing }
+    fn new_delimited(src: &'a mbe::Delimited, span: DelimSpan, spacing: DelimSpacing) -> Frame<'a> {
+        Frame {
+            tts: &src.tts,
+            idx: 0,
+            kind: FrameKind::Delimited { delim: src.delim, span, spacing },
+        }
+    }
+
+    fn new_sequence(
+        src: &'a mbe::SequenceRepetition,
+        sep: Option<Token>,
+        kleene_op: KleeneOp,
+    ) -> Frame<'a> {
+        Frame { tts: &src.tts, idx: 0, kind: FrameKind::Sequence { sep, kleene_op } }
     }
 }
 
@@ -66,13 +72,9 @@ impl<'a> Iterator for Frame<'a> {
     type Item = &'a mbe::TokenTree;
 
     fn next(&mut self) -> Option<&'a mbe::TokenTree> {
-        match self {
-            Frame::Delimited { tts, idx, .. } | Frame::Sequence { tts, idx, .. } => {
-                let res = tts.get(*idx);
-                *idx += 1;
-                res
-            }
-        }
+        let res = self.tts.get(self.idx);
+        self.idx += 1;
+        res
     }
 }
 
@@ -97,11 +99,12 @@ impl<'a> Iterator for Frame<'a> {
 ///
 /// Along the way, we do some additional error checking.
 pub(super) fn transcribe<'a>(
-    cx: &ExtCtxt<'a>,
+    psess: &'a ParseSess,
     interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
     src: &mbe::Delimited,
     src_span: DelimSpan,
     transparency: Transparency,
+    expand_id: LocalExpnId,
 ) -> PResult<'a, TokenStream> {
     // Nothing for us to transcribe...
     if src.tts.is_empty() {
@@ -111,13 +114,16 @@ pub(super) fn transcribe<'a>(
     // 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(src, src_span, DelimSpacing::new(Spacing::Alone, Spacing::Alone))];
+    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::new();
+    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
@@ -132,8 +138,9 @@ pub(super) fn transcribe<'a>(
     // again, and we are done transcribing.
     let mut result: Vec<TokenTree> = Vec::new();
     let mut result_stack = Vec::new();
-    let mut marker = Marker(cx.current_expansion.id, transparency, Default::default());
+    let mut marker = Marker(expand_id, transparency, Default::default());
 
+    let dcx = &psess.dcx;
     loop {
         // Look at the last frame on the stack.
         // If it still has a TokenTree we have not looked at yet, use that tree.
@@ -142,11 +149,12 @@ pub(super) fn transcribe<'a>(
 
             // Otherwise, if we have just reached the end of a sequence and we can keep repeating,
             // go back to the beginning of the sequence.
-            if let Frame::Sequence { idx, sep, .. } = stack.last_mut().unwrap() {
+            let frame = stack.last_mut().unwrap();
+            if let FrameKind::Sequence { sep, .. } = &frame.kind {
                 let (repeat_idx, repeat_len) = repeats.last_mut().unwrap();
                 *repeat_idx += 1;
                 if repeat_idx < repeat_len {
-                    *idx = 0;
+                    frame.idx = 0;
                     if let Some(sep) = sep {
                         result.push(TokenTree::Token(sep.clone(), Spacing::Alone));
                     }
@@ -157,16 +165,16 @@ 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() {
+            match stack.pop().unwrap().kind {
                 // Done with a sequence. Pop from repeats.
-                Frame::Sequence { .. } => {
+                FrameKind::Sequence { .. } => {
                     repeats.pop();
                 }
 
                 // We are done processing a Delimited. If this is the top-level delimited, we are
                 // done. Otherwise, we unwind the result_stack to append what we have produced to
                 // any previous results.
-                Frame::Delimited { delim, span, mut spacing, .. } => {
+                FrameKind::Delimited { delim, span, mut spacing, .. } => {
                     // Hack to force-insert a space after `]` in certain case.
                     // See discussion of the `hex-literal` crate in #114571.
                     if delim == Delimiter::Bracket {
@@ -192,12 +200,10 @@ pub(super) fn transcribe<'a>(
             // 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.
-            seq @ mbe::TokenTree::Sequence(_, delimited) => {
+            seq @ mbe::TokenTree::Sequence(_, seq_rep) => {
                 match lockstep_iter_size(seq, interp, &repeats) {
                     LockstepIterSize::Unconstrained => {
-                        return Err(cx
-                            .dcx()
-                            .create_err(NoSyntaxVarsExprRepeat { span: seq.span() }));
+                        return Err(dcx.create_err(NoSyntaxVarsExprRepeat { span: seq.span() }));
                     }
 
                     LockstepIterSize::Contradiction(msg) => {
@@ -205,9 +211,9 @@ pub(super) fn transcribe<'a>(
                         // 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(cx
-                            .dcx()
-                            .create_err(MetaVarsDifSeqMatchers { span: seq.span(), msg }));
+                        return Err(
+                            dcx.create_err(MetaVarsDifSeqMatchers { span: seq.span(), msg })
+                        );
                     }
 
                     LockstepIterSize::Constraint(len, _) => {
@@ -221,9 +227,7 @@ pub(super) fn transcribe<'a>(
                                 // 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(cx
-                                    .dcx()
-                                    .create_err(MustRepeatOnce { span: sp.entire() }));
+                                return Err(dcx.create_err(MustRepeatOnce { span: sp.entire() }));
                             }
                         } else {
                             // 0 is the initial counter (we have done 0 repetitions so far). `len`
@@ -233,12 +237,11 @@ pub(super) fn transcribe<'a>(
                             // 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::Sequence {
-                                idx: 0,
-                                sep: seq.separator.clone(),
-                                tts: &delimited.tts,
-                                kleene_op: seq.kleene.op,
-                            });
+                            stack.push(Frame::new_sequence(
+                                seq_rep,
+                                seq.separator.clone(),
+                                seq.kleene.op,
+                            ));
                         }
                     }
                 }
@@ -248,13 +251,38 @@ pub(super) fn transcribe<'a>(
             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) {
+                    // njn: explain the use of alone here
                     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.
-                            maybe_use_metavar_location(cx, &stack, sp, tt, &mut marker)
+                            maybe_use_metavar_location(psess, &stack, sp, tt, &mut marker)
+                        }
+                        MatchedSingle(ParseNtResult::Ident(ident, is_raw)) => {
+                            marker.visit_span(&mut sp);
+                            let kind = token::NtIdent(*ident, *is_raw);
+                            TokenTree::token_alone(kind, sp)
+                        }
+                        MatchedSingle(ParseNtResult::Lifetime(ident)) => {
+                            marker.visit_span(&mut sp);
+                            let kind = token::NtLifetime(*ident);
+                            TokenTree::token_alone(kind, sp)
                         }
                         MatchedSingle(ParseNtResult::Nt(nt)) => {
                             // Other variables are emitted into the output stream as groups with
@@ -265,7 +293,7 @@ pub(super) fn transcribe<'a>(
                         }
                         MatchedSeq(..) => {
                             // We were unable to descend far enough. This is an error.
-                            return Err(cx.dcx().create_err(VarStillRepeating { span: sp, ident }));
+                            return Err(dcx.create_err(VarStillRepeating { span: sp, ident }));
                         }
                     };
                     result.push(tt)
@@ -284,7 +312,7 @@ pub(super) fn transcribe<'a>(
 
             // Replace meta-variable expressions with the result of their expansion.
             mbe::TokenTree::MetaVarExpr(sp, expr) => {
-                transcribe_metavar_expr(cx, expr, interp, &mut marker, &repeats, &mut result, sp)?;
+                transcribe_metavar_expr(dcx, expr, interp, &mut marker, &repeats, &mut result, sp)?;
             }
 
             // If we are entering a new delimiter, we push its contents to the `stack` to be
@@ -294,13 +322,7 @@ pub(super) fn transcribe<'a>(
             // the previous results (from outside the Delimited).
             mbe::TokenTree::Delimited(mut span, spacing, delimited) => {
                 mut_visit::visit_delim_span(&mut span, &mut marker);
-                stack.push(Frame::Delimited {
-                    tts: &delimited.tts,
-                    delim: delimited.delim,
-                    idx: 0,
-                    span,
-                    spacing: *spacing,
-                });
+                stack.push(Frame::new_delimited(delimited, span, *spacing));
                 result_stack.push(mem::take(&mut result));
             }
 
@@ -350,7 +372,7 @@ pub(super) fn transcribe<'a>(
 ///   combine with each other and not with tokens outside of the sequence.
 /// - The metavariable span comes from a different crate, then we prefer the more local span.
 fn maybe_use_metavar_location(
-    cx: &ExtCtxt<'_>,
+    psess: &ParseSess,
     stack: &[Frame<'_>],
     mut metavar_span: Span,
     orig_tt: &TokenTree,
@@ -358,10 +380,13 @@ fn maybe_use_metavar_location(
 ) -> TokenTree {
     let undelimited_seq = matches!(
         stack.last(),
-        Some(Frame::Sequence {
+        Some(Frame {
             tts: [_],
-            sep: None,
-            kleene_op: KleeneOp::ZeroOrMore | KleeneOp::OneOrMore,
+            kind: FrameKind::Sequence {
+                sep: None,
+                kleene_op: KleeneOp::ZeroOrMore | KleeneOp::OneOrMore,
+                ..
+            },
             ..
         })
     );
@@ -385,7 +410,7 @@ fn maybe_use_metavar_location(
                 && insert(mspans, dspan.entire(), metavar_span)
         }),
     };
-    if no_collision || cx.source_map().is_imported(metavar_span) {
+    if no_collision || psess.source_map().is_imported(metavar_span) {
         return orig_tt.clone();
     }
 
@@ -546,7 +571,7 @@ fn lockstep_iter_size(
 /// * `[ $( ${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>(
-    cx: &ExtCtxt<'a>,
+    dcx: &'a DiagCtxt,
     depth_user: usize,
     mut matched: &NamedMatch,
     repeats: &[(usize, usize)],
@@ -583,7 +608,7 @@ fn count_repetitions<'a>(
         .and_then(|el| el.checked_sub(repeats.len()))
         .unwrap_or_default();
     if depth_user > depth_max {
-        return Err(out_of_bounds_err(cx, depth_max + 1, sp.entire(), "count"));
+        return Err(out_of_bounds_err(dcx, depth_max + 1, sp.entire(), "count"));
     }
 
     // `repeats` records all of the nested levels at which we are currently
@@ -599,7 +624,7 @@ fn count_repetitions<'a>(
     }
 
     if let MatchedSingle(_) = matched {
-        return Err(cx.dcx().create_err(CountRepetitionMisplaced { span: sp.entire() }));
+        return Err(dcx.create_err(CountRepetitionMisplaced { span: sp.entire() }));
     }
 
     count(depth_user, depth_max, matched)
@@ -607,7 +632,7 @@ fn count_repetitions<'a>(
 
 /// Returns a `NamedMatch` item declared on the LHS given an arbitrary [Ident]
 fn matched_from_ident<'ctx, 'interp, 'rslt>(
-    cx: &ExtCtxt<'ctx>,
+    dcx: &'ctx DiagCtxt,
     ident: Ident,
     interp: &'interp FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
 ) -> PResult<'ctx, &'rslt NamedMatch>
@@ -616,12 +641,12 @@ where
 {
     let span = ident.span;
     let key = MacroRulesNormalizedIdent::new(ident);
-    interp.get(&key).ok_or_else(|| cx.dcx().create_err(MetaVarExprUnrecognizedVar { span, key }))
+    interp.get(&key).ok_or_else(|| dcx.create_err(MetaVarExprUnrecognizedVar { span, key }))
 }
 
 /// Used by meta-variable expressions when an user input is out of the actual declared bounds. For
 /// example, index(999999) in an repetition of only three elements.
-fn out_of_bounds_err<'a>(cx: &ExtCtxt<'a>, max: usize, span: Span, ty: &str) -> Diag<'a> {
+fn out_of_bounds_err<'a>(dcx: &'a DiagCtxt, max: usize, span: Span, ty: &str) -> Diag<'a> {
     let msg = if max == 0 {
         format!(
             "meta-variable expression `{ty}` with depth parameter \
@@ -633,11 +658,11 @@ fn out_of_bounds_err<'a>(cx: &ExtCtxt<'a>, max: usize, span: Span, ty: &str) ->
              must be less than {max}"
         )
     };
-    cx.dcx().struct_span_err(span, msg)
+    dcx.struct_span_err(span, msg)
 }
 
 fn transcribe_metavar_expr<'a>(
-    cx: &ExtCtxt<'a>,
+    dcx: &'a DiagCtxt,
     expr: &MetaVarExpr,
     interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>,
     marker: &mut Marker,
@@ -652,8 +677,8 @@ fn transcribe_metavar_expr<'a>(
     };
     match *expr {
         MetaVarExpr::Count(original_ident, depth) => {
-            let matched = matched_from_ident(cx, original_ident, interp)?;
-            let count = count_repetitions(cx, depth, matched, repeats, sp)?;
+            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(),
@@ -662,7 +687,7 @@ fn transcribe_metavar_expr<'a>(
         }
         MetaVarExpr::Ignore(original_ident) => {
             // Used to ensure that `original_ident` is present in the LHS
-            let _ = matched_from_ident(cx, original_ident, interp)?;
+            let _ = matched_from_ident(dcx, original_ident, interp)?;
         }
         MetaVarExpr::Index(depth) => match repeats.iter().nth_back(depth) {
             Some((index, _)) => {
@@ -671,16 +696,16 @@ fn transcribe_metavar_expr<'a>(
                     visited_span(),
                 ));
             }
-            None => return Err(out_of_bounds_err(cx, repeats.len(), sp.entire(), "index")),
+            None => return Err(out_of_bounds_err(dcx, repeats.len(), sp.entire(), "index")),
         },
-        MetaVarExpr::Length(depth) => match repeats.iter().nth_back(depth) {
+        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(cx, repeats.len(), sp.entire(), "length")),
+            None => return Err(out_of_bounds_err(dcx, repeats.len(), sp.entire(), "len")),
         },
     }
     Ok(())
diff --git a/compiler/rustc_expand/src/mut_visit/tests.rs b/compiler/rustc_expand/src/mut_visit/tests.rs
deleted file mode 100644
index 8974d45b4d8..00000000000
--- a/compiler/rustc_expand/src/mut_visit/tests.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-use crate::tests::{matches_codepattern, string_to_crate};
-
-use rustc_ast as ast;
-use rustc_ast::mut_visit::MutVisitor;
-use rustc_ast_pretty::pprust;
-use rustc_span::create_default_session_globals_then;
-use rustc_span::symbol::Ident;
-
-// This version doesn't care about getting comments or doc-strings in.
-fn print_crate_items(krate: &ast::Crate) -> String {
-    krate.items.iter().map(|i| pprust::item_to_string(i)).collect::<Vec<_>>().join(" ")
-}
-
-// Change every identifier to "zz".
-struct ToZzIdentMutVisitor;
-
-impl MutVisitor for ToZzIdentMutVisitor {
-    const VISIT_TOKENS: bool = true;
-
-    fn visit_ident(&mut self, ident: &mut Ident) {
-        *ident = Ident::from_str("zz");
-    }
-}
-
-// Maybe add to `expand.rs`.
-macro_rules! assert_pred {
-    ($pred:expr, $predname:expr, $a:expr , $b:expr) => {{
-        let pred_val = $pred;
-        let a_val = $a;
-        let b_val = $b;
-        if !(pred_val(&a_val, &b_val)) {
-            panic!("expected args satisfying {}, got {} and {}", $predname, a_val, b_val);
-        }
-    }};
-}
-
-// Make sure idents get transformed everywhere.
-#[test]
-fn ident_transformation() {
-    create_default_session_globals_then(|| {
-        let mut zz_visitor = ToZzIdentMutVisitor;
-        let mut krate =
-            string_to_crate("#[a] mod b {fn c (d : e, f : g) {h!(i,j,k);l;m}}".to_string());
-        zz_visitor.visit_crate(&mut krate);
-        assert_pred!(
-            matches_codepattern,
-            "matches_codepattern",
-            print_crate_items(&krate),
-            "#[zz]mod zz{fn zz(zz:zz,zz:zz){zz!(zz,zz,zz);zz;zz}}".to_string()
-        );
-    })
-}
-
-// Make sure idents get transformed even inside macro defs.
-#[test]
-fn ident_transformation_in_defs() {
-    create_default_session_globals_then(|| {
-        let mut zz_visitor = ToZzIdentMutVisitor;
-        let mut krate = string_to_crate(
-            "macro_rules! a {(b $c:expr $(d $e:token)f+ => \
-            (g $(d $d $e)+))} "
-                .to_string(),
-        );
-        zz_visitor.visit_crate(&mut krate);
-        assert_pred!(
-            matches_codepattern,
-            "matches_codepattern",
-            print_crate_items(&krate),
-            "macro_rules! zz{(zz$zz:zz$(zz $zz:zz)zz+=>(zz$(zz$zz$zz)+))}".to_string()
-        );
-    })
-}
diff --git a/compiler/rustc_expand/src/parse/tests.rs b/compiler/rustc_expand/src/parse/tests.rs
deleted file mode 100644
index 066afd7a41d..00000000000
--- a/compiler/rustc_expand/src/parse/tests.rs
+++ /dev/null
@@ -1,382 +0,0 @@
-use crate::tests::{
-    matches_codepattern, psess, string_to_stream, with_error_checking_parse,
-    with_expected_parse_error,
-};
-
-use ast::token::IdentIsRaw;
-use rustc_ast::ptr::P;
-use rustc_ast::token::{self, Delimiter, Token};
-use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree};
-use rustc_ast::visit;
-use rustc_ast::{self as ast, PatKind};
-use rustc_ast_pretty::pprust::item_to_string;
-use rustc_errors::PResult;
-use rustc_parse::new_parser_from_source_str;
-use rustc_parse::parser::ForceCollect;
-use rustc_session::parse::ParseSess;
-use rustc_span::create_default_session_globals_then;
-use rustc_span::symbol::{kw, sym, Symbol};
-use rustc_span::{BytePos, FileName, Pos, Span};
-use std::path::PathBuf;
-
-/// Parses an item.
-///
-/// Returns `Ok(Some(item))` when successful, `Ok(None)` when no item was found, and `Err`
-/// when a syntax error occurred.
-fn parse_item_from_source_str(
-    name: FileName,
-    source: String,
-    psess: &ParseSess,
-) -> PResult<'_, Option<P<ast::Item>>> {
-    new_parser_from_source_str(psess, name, source).parse_item(ForceCollect::No)
-}
-
-// Produces a `rustc_span::span`.
-fn sp(a: u32, b: u32) -> Span {
-    Span::with_root_ctxt(BytePos(a), BytePos(b))
-}
-
-/// Parses a string, return an expression.
-fn string_to_expr(source_str: String) -> P<ast::Expr> {
-    with_error_checking_parse(source_str, &psess(), |p| p.parse_expr())
-}
-
-/// Parses a string, returns an item.
-fn string_to_item(source_str: String) -> Option<P<ast::Item>> {
-    with_error_checking_parse(source_str, &psess(), |p| p.parse_item(ForceCollect::No))
-}
-
-#[test]
-fn bad_path_expr_1() {
-    // This should trigger error: expected identifier, found keyword `return`
-    create_default_session_globals_then(|| {
-        with_expected_parse_error(
-            "::abc::def::return",
-            "expected identifier, found keyword `return`",
-            |p| p.parse_expr(),
-        );
-    })
-}
-
-// Checks the token-tree-ization of macros.
-#[test]
-fn string_to_tts_macro() {
-    create_default_session_globals_then(|| {
-        let stream = string_to_stream("macro_rules! zip (($a)=>($a))".to_string());
-        let tts = &stream.trees().collect::<Vec<_>>()[..];
-
-        match tts {
-            [
-                TokenTree::Token(
-                    Token { kind: token::Ident(name_macro_rules, IdentIsRaw::No), .. },
-                    _,
-                ),
-                TokenTree::Token(Token { kind: token::Not, .. }, _),
-                TokenTree::Token(Token { kind: token::Ident(name_zip, IdentIsRaw::No), .. }, _),
-                TokenTree::Delimited(.., macro_delim, macro_tts),
-            ] if name_macro_rules == &kw::MacroRules && name_zip.as_str() == "zip" => {
-                let tts = &macro_tts.trees().collect::<Vec<_>>();
-                match &tts[..] {
-                    [
-                        TokenTree::Delimited(.., first_delim, first_tts),
-                        TokenTree::Token(Token { kind: token::FatArrow, .. }, _),
-                        TokenTree::Delimited(.., second_delim, second_tts),
-                    ] if macro_delim == &Delimiter::Parenthesis => {
-                        let tts = &first_tts.trees().collect::<Vec<_>>();
-                        match &tts[..] {
-                            [
-                                TokenTree::Token(Token { kind: token::Dollar, .. }, _),
-                                TokenTree::Token(
-                                    Token { kind: token::Ident(name, IdentIsRaw::No), .. },
-                                    _,
-                                ),
-                            ] if first_delim == &Delimiter::Parenthesis && name.as_str() == "a" => {
-                            }
-                            _ => panic!("value 3: {:?} {:?}", first_delim, first_tts),
-                        }
-                        let tts = &second_tts.trees().collect::<Vec<_>>();
-                        match &tts[..] {
-                            [
-                                TokenTree::Token(Token { kind: token::Dollar, .. }, _),
-                                TokenTree::Token(
-                                    Token { kind: token::Ident(name, IdentIsRaw::No), .. },
-                                    _,
-                                ),
-                            ] if second_delim == &Delimiter::Parenthesis
-                                && name.as_str() == "a" => {}
-                            _ => panic!("value 4: {:?} {:?}", second_delim, second_tts),
-                        }
-                    }
-                    _ => panic!("value 2: {:?} {:?}", macro_delim, macro_tts),
-                }
-            }
-            _ => panic!("value: {:?}", tts),
-        }
-    })
-}
-
-#[test]
-fn string_to_tts_1() {
-    create_default_session_globals_then(|| {
-        let tts = string_to_stream("fn a(b: i32) { b; }".to_string());
-
-        let expected = TokenStream::new(vec![
-            TokenTree::token_alone(token::Ident(kw::Fn, IdentIsRaw::No), sp(0, 2)),
-            TokenTree::token_joint_hidden(
-                token::Ident(Symbol::intern("a"), IdentIsRaw::No),
-                sp(3, 4),
-            ),
-            TokenTree::Delimited(
-                DelimSpan::from_pair(sp(4, 5), sp(11, 12)),
-                // `JointHidden` because the `(` is followed immediately by
-                // `b`, `Alone` because the `)` is followed by whitespace.
-                DelimSpacing::new(Spacing::JointHidden, Spacing::Alone),
-                Delimiter::Parenthesis,
-                TokenStream::new(vec![
-                    TokenTree::token_joint(
-                        token::Ident(Symbol::intern("b"), IdentIsRaw::No),
-                        sp(5, 6),
-                    ),
-                    TokenTree::token_alone(token::Colon, sp(6, 7)),
-                    // `JointHidden` because the `i32` is immediately followed by the `)`.
-                    TokenTree::token_joint_hidden(
-                        token::Ident(sym::i32, IdentIsRaw::No),
-                        sp(8, 11),
-                    ),
-                ])
-                .into(),
-            ),
-            TokenTree::Delimited(
-                DelimSpan::from_pair(sp(13, 14), sp(18, 19)),
-                // First `Alone` because the `{` is followed by whitespace,
-                // second `Alone` because the `}` is followed immediately by
-                // EOF.
-                DelimSpacing::new(Spacing::Alone, Spacing::Alone),
-                Delimiter::Brace,
-                TokenStream::new(vec![
-                    TokenTree::token_joint(
-                        token::Ident(Symbol::intern("b"), IdentIsRaw::No),
-                        sp(15, 16),
-                    ),
-                    // `Alone` because the `;` is followed by whitespace.
-                    TokenTree::token_alone(token::Semi, sp(16, 17)),
-                ])
-                .into(),
-            ),
-        ]);
-
-        assert_eq!(tts, expected);
-    })
-}
-
-#[test]
-fn parse_use() {
-    create_default_session_globals_then(|| {
-        let use_s = "use foo::bar::baz;";
-        let vitem = string_to_item(use_s.to_string()).unwrap();
-        let vitem_s = item_to_string(&vitem);
-        assert_eq!(&vitem_s[..], use_s);
-
-        let use_s = "use foo::bar as baz;";
-        let vitem = string_to_item(use_s.to_string()).unwrap();
-        let vitem_s = item_to_string(&vitem);
-        assert_eq!(&vitem_s[..], use_s);
-    })
-}
-
-#[test]
-fn parse_extern_crate() {
-    create_default_session_globals_then(|| {
-        let ex_s = "extern crate foo;";
-        let vitem = string_to_item(ex_s.to_string()).unwrap();
-        let vitem_s = item_to_string(&vitem);
-        assert_eq!(&vitem_s[..], ex_s);
-
-        let ex_s = "extern crate foo as bar;";
-        let vitem = string_to_item(ex_s.to_string()).unwrap();
-        let vitem_s = item_to_string(&vitem);
-        assert_eq!(&vitem_s[..], ex_s);
-    })
-}
-
-fn get_spans_of_pat_idents(src: &str) -> Vec<Span> {
-    let item = string_to_item(src.to_string()).unwrap();
-
-    struct PatIdentVisitor {
-        spans: Vec<Span>,
-    }
-    impl<'a> visit::Visitor<'a> for PatIdentVisitor {
-        fn visit_pat(&mut self, p: &'a ast::Pat) {
-            match &p.kind {
-                PatKind::Ident(_, ident, _) => {
-                    self.spans.push(ident.span);
-                }
-                _ => {
-                    visit::walk_pat(self, p);
-                }
-            }
-        }
-    }
-    let mut v = PatIdentVisitor { spans: Vec::new() };
-    visit::walk_item(&mut v, &item);
-    return v.spans;
-}
-
-#[test]
-fn span_of_self_arg_pat_idents_are_correct() {
-    create_default_session_globals_then(|| {
-        let srcs = [
-            "impl z { fn a (&self, &myarg: i32) {} }",
-            "impl z { fn a (&mut self, &myarg: i32) {} }",
-            "impl z { fn a (&'a self, &myarg: i32) {} }",
-            "impl z { fn a (self, &myarg: i32) {} }",
-            "impl z { fn a (self: Foo, &myarg: i32) {} }",
-        ];
-
-        for src in srcs {
-            let spans = get_spans_of_pat_idents(src);
-            let (lo, hi) = (spans[0].lo(), spans[0].hi());
-            assert!(
-                "self" == &src[lo.to_usize()..hi.to_usize()],
-                "\"{}\" != \"self\". src=\"{}\"",
-                &src[lo.to_usize()..hi.to_usize()],
-                src
-            )
-        }
-    })
-}
-
-#[test]
-fn parse_exprs() {
-    create_default_session_globals_then(|| {
-        // just make sure that they parse....
-        string_to_expr("3 + 4".to_string());
-        string_to_expr("a::z.froob(b,&(987+3))".to_string());
-    })
-}
-
-#[test]
-fn attrs_fix_bug() {
-    create_default_session_globals_then(|| {
-        string_to_item(
-            "pub fn mk_file_writer(path: &Path, flags: &[FileFlag])
-                -> Result<Box<Writer>, String> {
-#[cfg(windows)]
-fn wb() -> c_int {
-    (O_WRONLY | libc::consts::os::extra::O_BINARY) as c_int
-}
-
-#[cfg(unix)]
-fn wb() -> c_int { O_WRONLY as c_int }
-
-let mut fflags: c_int = wb();
-}"
-            .to_string(),
-        );
-    })
-}
-
-#[test]
-fn crlf_doc_comments() {
-    create_default_session_globals_then(|| {
-        let psess = psess();
-
-        let name_1 = FileName::Custom("crlf_source_1".to_string());
-        let source = "/// doc comment\r\nfn foo() {}".to_string();
-        let item = parse_item_from_source_str(name_1, source, &psess).unwrap().unwrap();
-        let doc = item.attrs.iter().filter_map(|at| at.doc_str()).next().unwrap();
-        assert_eq!(doc.as_str(), " doc comment");
-
-        let name_2 = FileName::Custom("crlf_source_2".to_string());
-        let source = "/// doc comment\r\n/// line 2\r\nfn foo() {}".to_string();
-        let item = parse_item_from_source_str(name_2, source, &psess).unwrap().unwrap();
-        let docs = item.attrs.iter().filter_map(|at| at.doc_str()).collect::<Vec<_>>();
-        let b: &[_] = &[Symbol::intern(" doc comment"), Symbol::intern(" line 2")];
-        assert_eq!(&docs[..], b);
-
-        let name_3 = FileName::Custom("clrf_source_3".to_string());
-        let source = "/** doc comment\r\n *  with CRLF */\r\nfn foo() {}".to_string();
-        let item = parse_item_from_source_str(name_3, source, &psess).unwrap().unwrap();
-        let doc = item.attrs.iter().filter_map(|at| at.doc_str()).next().unwrap();
-        assert_eq!(doc.as_str(), " doc comment\n *  with CRLF ");
-    });
-}
-
-#[test]
-fn ttdelim_span() {
-    fn parse_expr_from_source_str(
-        name: FileName,
-        source: String,
-        psess: &ParseSess,
-    ) -> PResult<'_, P<ast::Expr>> {
-        new_parser_from_source_str(psess, name, source).parse_expr()
-    }
-
-    create_default_session_globals_then(|| {
-        let psess = psess();
-        let expr = parse_expr_from_source_str(
-            PathBuf::from("foo").into(),
-            "foo!( fn main() { body } )".to_string(),
-            &psess,
-        )
-        .unwrap();
-
-        let ast::ExprKind::MacCall(mac) = &expr.kind else { panic!("not a macro") };
-        let span = mac.args.tokens.trees().last().unwrap().span();
-
-        match psess.source_map().span_to_snippet(span) {
-            Ok(s) => assert_eq!(&s[..], "{ body }"),
-            Err(_) => panic!("could not get snippet"),
-        }
-    });
-}
-
-// This tests that when parsing a string (rather than a file) we don't try
-// and read in a file for a module declaration and just parse a stub.
-// See `recurse_into_file_modules` in the parser.
-#[test]
-fn out_of_line_mod() {
-    create_default_session_globals_then(|| {
-        let item = parse_item_from_source_str(
-            PathBuf::from("foo").into(),
-            "mod foo { struct S; mod this_does_not_exist; }".to_owned(),
-            &psess(),
-        )
-        .unwrap()
-        .unwrap();
-
-        let ast::ItemKind::Mod(_, mod_kind) = &item.kind else { panic!() };
-        assert!(matches!(mod_kind, ast::ModKind::Loaded(items, ..) if items.len() == 2));
-    });
-}
-
-#[test]
-fn eqmodws() {
-    assert_eq!(matches_codepattern("", ""), true);
-    assert_eq!(matches_codepattern("", "a"), false);
-    assert_eq!(matches_codepattern("a", ""), false);
-    assert_eq!(matches_codepattern("a", "a"), true);
-    assert_eq!(matches_codepattern("a b", "a   \n\t\r  b"), true);
-    assert_eq!(matches_codepattern("a b ", "a   \n\t\r  b"), true);
-    assert_eq!(matches_codepattern("a b", "a   \n\t\r  b "), false);
-    assert_eq!(matches_codepattern("a   b", "a b"), true);
-    assert_eq!(matches_codepattern("ab", "a b"), false);
-    assert_eq!(matches_codepattern("a   b", "ab"), true);
-    assert_eq!(matches_codepattern(" a   b", "ab"), true);
-}
-
-#[test]
-fn pattern_whitespace() {
-    assert_eq!(matches_codepattern("", "\x0C"), false);
-    assert_eq!(matches_codepattern("a b ", "a   \u{0085}\n\t\r  b"), true);
-    assert_eq!(matches_codepattern("a b", "a   \u{0085}\n\t\r  b "), false);
-}
-
-#[test]
-fn non_pattern_whitespace() {
-    // These have the property 'White_Space' but not 'Pattern_White_Space'
-    assert_eq!(matches_codepattern("a b", "a\u{2002}b"), false);
-    assert_eq!(matches_codepattern("a   b", "a\u{2002}b"), false);
-    assert_eq!(matches_codepattern("\u{205F}a   b", "ab"), false);
-    assert_eq!(matches_codepattern("a  \u{3000}b", "ab"), false);
-}
diff --git a/compiler/rustc_expand/src/placeholders.rs b/compiler/rustc_expand/src/placeholders.rs
index 2c4187031ca..e21f041d69a 100644
--- a/compiler/rustc_expand/src/placeholders.rs
+++ b/compiler/rustc_expand/src/placeholders.rs
@@ -9,7 +9,7 @@ use rustc_span::DUMMY_SP;
 use smallvec::{smallvec, SmallVec};
 use thin_vec::ThinVec;
 
-pub fn placeholder(
+pub(crate) fn placeholder(
     kind: AstFragmentKind,
     id: ast::NodeId,
     vis: Option<ast::Visibility>,
@@ -174,7 +174,10 @@ pub fn placeholder(
         }]),
         AstFragmentKind::Variants => AstFragment::Variants(smallvec![ast::Variant {
             attrs: Default::default(),
-            data: ast::VariantData::Struct { fields: Default::default(), recovered: false },
+            data: ast::VariantData::Struct {
+                fields: Default::default(),
+                recovered: ast::Recovered::No
+            },
             disr_expr: None,
             id,
             ident,
@@ -271,14 +274,14 @@ impl MutVisitor for PlaceholderExpander {
     fn flat_map_trait_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
         match item.kind {
             ast::AssocItemKind::MacCall(_) => self.remove(item.id).make_trait_items(),
-            _ => noop_flat_map_assoc_item(item, self),
+            _ => noop_flat_map_item(item, self),
         }
     }
 
     fn flat_map_impl_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
         match item.kind {
             ast::AssocItemKind::MacCall(_) => self.remove(item.id).make_impl_items(),
-            _ => noop_flat_map_assoc_item(item, self),
+            _ => noop_flat_map_item(item, self),
         }
     }
 
@@ -288,7 +291,7 @@ impl MutVisitor for PlaceholderExpander {
     ) -> SmallVec<[P<ast::ForeignItem>; 1]> {
         match item.kind {
             ast::ForeignItemKind::MacCall(_) => self.remove(item.id).make_foreign_items(),
-            _ => noop_flat_map_foreign_item(item, self),
+            _ => noop_flat_map_item(item, self),
         }
     }
 
diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs
index 4b5c148cb55..530059e53c2 100644
--- a/compiler/rustc_expand/src/proc_macro.rs
+++ b/compiler/rustc_expand/src/proc_macro.rs
@@ -127,7 +127,7 @@ impl MultiItemModifier for DeriveProcMacro {
                 Annotatable::Stmt(stmt) => token::NtStmt(stmt),
                 _ => unreachable!(),
             };
-            TokenStream::token_alone(token::Interpolated(Lrc::new((nt, span))), DUMMY_SP)
+            TokenStream::token_alone(token::Interpolated(Lrc::new(nt)), DUMMY_SP)
         } else {
             item.to_tokens()
         };
diff --git a/compiler/rustc_expand/src/proc_macro_server.rs b/compiler/rustc_expand/src/proc_macro_server.rs
index 5a66b0fbdef..ec7e4416b91 100644
--- a/compiler/rustc_expand/src/proc_macro_server.rs
+++ b/compiler/rustc_expand/src/proc_macro_server.rs
@@ -220,6 +220,12 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre
                 Ident(sym, is_raw) => {
                     trees.push(TokenTree::Ident(Ident { sym, is_raw: is_raw.into(), span }))
                 }
+                NtIdent(ident, is_raw) => trees.push(TokenTree::Ident(Ident {
+                    sym: ident.name,
+                    is_raw: is_raw.into(),
+                    span: ident.span,
+                })),
+
                 Lifetime(name) => {
                     let ident = symbol::Ident::new(name, span).without_first_quote();
                     trees.extend([
@@ -227,6 +233,15 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre
                         TokenTree::Ident(Ident { sym: ident.name, is_raw: false, span }),
                     ]);
                 }
+                NtLifetime(ident) => {
+                    let stream = TokenStream::token_alone(token::Lifetime(ident.name), ident.span);
+                    trees.push(TokenTree::Group(Group {
+                        delimiter: pm::Delimiter::None,
+                        stream: Some(stream),
+                        span: DelimSpan::from_single(span),
+                    }))
+                }
+
                 Literal(token::Lit { kind, symbol, suffix }) => {
                     trees.push(TokenTree::Literal(self::Literal {
                         kind: FromInternal::from_internal(kind),
@@ -259,23 +274,15 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre
                     }));
                 }
 
-                Interpolated(ref nt) if let NtIdent(ident, is_raw) = &nt.0 => {
-                    trees.push(TokenTree::Ident(Ident {
-                        sym: ident.name,
-                        is_raw: matches!(is_raw, IdentIsRaw::Yes),
-                        span: ident.span,
-                    }))
-                }
-
                 Interpolated(nt) => {
-                    let stream = TokenStream::from_nonterminal_ast(&nt.0);
+                    let stream = TokenStream::from_nonterminal_ast(&nt);
                     // A hack used to pass AST fragments to attribute and derive
                     // macros as a single nonterminal token instead of a token
                     // stream. Such token needs to be "unwrapped" and not
                     // represented as a delimited group.
                     // FIXME: It needs to be removed, but there are some
                     // compatibility issues (see #73345).
-                    if crate::base::nt_pretty_printing_compatibility_hack(&nt.0, rustc.ecx.sess) {
+                    if crate::base::nt_pretty_printing_compatibility_hack(&nt, rustc.ecx.sess) {
                         trees.extend(Self::from_internal((stream, rustc)));
                     } else {
                         trees.push(TokenTree::Group(Group {
@@ -302,10 +309,10 @@ impl ToInternal<SmallVec<[tokenstream::TokenTree; 2]>>
         use rustc_ast::token::*;
 
         // The code below is conservative, using `token_alone`/`Spacing::Alone`
-        // in most places. When the resulting code is pretty-printed by
-        // `print_tts` it ends up with spaces between most tokens, which is
-        // safe but ugly. It's hard in general to do better when working at the
-        // token level.
+        // in most places. It's hard in general to do better when working at
+        // the token level. When the resulting code is pretty-printed by
+        // `print_tts` the `space_between` function helps avoid a lot of
+        // unnecessary whitespace, so the results aren't too bad.
         let (tree, rustc) = self;
         match tree {
             TokenTree::Punct(Punct { ch, joint, span }) => {
diff --git a/compiler/rustc_expand/src/tests.rs b/compiler/rustc_expand/src/tests.rs
deleted file mode 100644
index 2b0b58eb1d9..00000000000
--- a/compiler/rustc_expand/src/tests.rs
+++ /dev/null
@@ -1,1055 +0,0 @@
-use rustc_ast as ast;
-use rustc_ast::tokenstream::TokenStream;
-use rustc_parse::{new_parser_from_source_str, parser::Parser, source_file_to_stream};
-use rustc_session::parse::ParseSess;
-use rustc_span::create_default_session_globals_then;
-use rustc_span::source_map::{FilePathMapping, SourceMap};
-use rustc_span::{BytePos, Span};
-
-use rustc_data_structures::sync::Lrc;
-use rustc_errors::emitter::HumanEmitter;
-use rustc_errors::{DiagCtxt, MultiSpan, PResult};
-use termcolor::WriteColor;
-
-use std::io;
-use std::io::prelude::*;
-use std::iter::Peekable;
-use std::path::{Path, PathBuf};
-use std::str;
-use std::sync::{Arc, Mutex};
-
-pub(crate) fn psess() -> ParseSess {
-    ParseSess::new(vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE])
-}
-
-/// Map string to parser (via tts).
-fn string_to_parser(psess: &ParseSess, source_str: String) -> Parser<'_> {
-    new_parser_from_source_str(psess, PathBuf::from("bogofile").into(), source_str)
-}
-
-fn create_test_handler() -> (DiagCtxt, Lrc<SourceMap>, Arc<Mutex<Vec<u8>>>) {
-    let output = Arc::new(Mutex::new(Vec::new()));
-    let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
-    let fallback_bundle = rustc_errors::fallback_fluent_bundle(
-        vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE],
-        false,
-    );
-    let emitter = HumanEmitter::new(Box::new(Shared { data: output.clone() }), fallback_bundle)
-        .sm(Some(source_map.clone()))
-        .diagnostic_width(Some(140));
-    let dcx = DiagCtxt::new(Box::new(emitter));
-    (dcx, source_map, output)
-}
-
-/// Returns the result of parsing the given string via the given callback.
-///
-/// If there are any errors, this will panic.
-pub(crate) fn with_error_checking_parse<'a, T, F>(s: String, psess: &'a ParseSess, f: F) -> T
-where
-    F: FnOnce(&mut Parser<'a>) -> PResult<'a, T>,
-{
-    let mut p = string_to_parser(&psess, s);
-    let x = f(&mut p).unwrap();
-    p.psess.dcx.abort_if_errors();
-    x
-}
-
-/// Verifies that parsing the given string using the given callback will
-/// generate an error that contains the given text.
-pub(crate) fn with_expected_parse_error<T, F>(source_str: &str, expected_output: &str, f: F)
-where
-    F: for<'a> FnOnce(&mut Parser<'a>) -> PResult<'a, T>,
-{
-    let (handler, source_map, output) = create_test_handler();
-    let psess = ParseSess::with_dcx(handler, source_map);
-    let mut p = string_to_parser(&psess, source_str.to_string());
-    let result = f(&mut p);
-    assert!(result.is_ok());
-
-    let bytes = output.lock().unwrap();
-    let actual_output = str::from_utf8(&bytes).unwrap();
-    println!("expected output:\n------\n{}------", expected_output);
-    println!("actual output:\n------\n{}------", actual_output);
-
-    assert!(actual_output.contains(expected_output))
-}
-
-/// Maps a string to tts, using a made-up filename.
-pub(crate) fn string_to_stream(source_str: String) -> TokenStream {
-    let psess = psess();
-    source_file_to_stream(
-        &psess,
-        psess.source_map().new_source_file(PathBuf::from("bogofile").into(), source_str),
-        None,
-    )
-}
-
-/// Parses a string, returns a crate.
-pub(crate) fn string_to_crate(source_str: String) -> ast::Crate {
-    let psess = psess();
-    with_error_checking_parse(source_str, &psess, |p| p.parse_crate_mod())
-}
-
-/// Does the given string match the pattern? whitespace in the first string
-/// may be deleted or replaced with other whitespace to match the pattern.
-/// This function is relatively Unicode-ignorant; fortunately, the careful design
-/// of UTF-8 mitigates this ignorance. It doesn't do NKF-normalization(?).
-pub(crate) fn matches_codepattern(a: &str, b: &str) -> bool {
-    let mut a_iter = a.chars().peekable();
-    let mut b_iter = b.chars().peekable();
-
-    loop {
-        let (a, b) = match (a_iter.peek(), b_iter.peek()) {
-            (None, None) => return true,
-            (None, _) => return false,
-            (Some(&a), None) => {
-                if rustc_lexer::is_whitespace(a) {
-                    break; // Trailing whitespace check is out of loop for borrowck.
-                } else {
-                    return false;
-                }
-            }
-            (Some(&a), Some(&b)) => (a, b),
-        };
-
-        if rustc_lexer::is_whitespace(a) && rustc_lexer::is_whitespace(b) {
-            // Skip whitespace for `a` and `b`.
-            scan_for_non_ws_or_end(&mut a_iter);
-            scan_for_non_ws_or_end(&mut b_iter);
-        } else if rustc_lexer::is_whitespace(a) {
-            // Skip whitespace for `a`.
-            scan_for_non_ws_or_end(&mut a_iter);
-        } else if a == b {
-            a_iter.next();
-            b_iter.next();
-        } else {
-            return false;
-        }
-    }
-
-    // Check if a has *only* trailing whitespace.
-    a_iter.all(rustc_lexer::is_whitespace)
-}
-
-/// Advances the given peekable `Iterator` until it reaches a non-whitespace character.
-fn scan_for_non_ws_or_end<I: Iterator<Item = char>>(iter: &mut Peekable<I>) {
-    while iter.peek().copied().is_some_and(rustc_lexer::is_whitespace) {
-        iter.next();
-    }
-}
-
-/// Identifies a position in the text by the n'th occurrence of a string.
-struct Position {
-    string: &'static str,
-    count: usize,
-}
-
-struct SpanLabel {
-    start: Position,
-    end: Position,
-    label: &'static str,
-}
-
-pub(crate) struct Shared<T: Write> {
-    pub data: Arc<Mutex<T>>,
-}
-
-impl<T: Write> WriteColor for Shared<T> {
-    fn supports_color(&self) -> bool {
-        false
-    }
-
-    fn set_color(&mut self, _spec: &termcolor::ColorSpec) -> io::Result<()> {
-        Ok(())
-    }
-
-    fn reset(&mut self) -> io::Result<()> {
-        Ok(())
-    }
-}
-
-impl<T: Write> Write for Shared<T> {
-    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        self.data.lock().unwrap().write(buf)
-    }
-
-    fn flush(&mut self) -> io::Result<()> {
-        self.data.lock().unwrap().flush()
-    }
-}
-
-#[allow(rustc::untranslatable_diagnostic)] // no translation needed for tests
-fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &str) {
-    create_default_session_globals_then(|| {
-        let (handler, source_map, output) = create_test_handler();
-        source_map.new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned());
-
-        let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end);
-        let mut msp = MultiSpan::from_span(primary_span);
-        for span_label in span_labels {
-            let span = make_span(&file_text, &span_label.start, &span_label.end);
-            msp.push_span_label(span, span_label.label);
-            println!("span: {:?} label: {:?}", span, span_label.label);
-            println!("text: {:?}", source_map.span_to_snippet(span));
-        }
-
-        handler.span_err(msp, "foo");
-
-        assert!(
-            expected_output.chars().next() == Some('\n'),
-            "expected output should begin with newline"
-        );
-        let expected_output = &expected_output[1..];
-
-        let bytes = output.lock().unwrap();
-        let actual_output = str::from_utf8(&bytes).unwrap();
-        println!("expected output:\n------\n{}------", expected_output);
-        println!("actual output:\n------\n{}------", actual_output);
-
-        assert!(expected_output == actual_output)
-    })
-}
-
-fn make_span(file_text: &str, start: &Position, end: &Position) -> Span {
-    let start = make_pos(file_text, start);
-    let end = make_pos(file_text, end) + end.string.len(); // just after matching thing ends
-    assert!(start <= end);
-    Span::with_root_ctxt(BytePos(start as u32), BytePos(end as u32))
-}
-
-fn make_pos(file_text: &str, pos: &Position) -> usize {
-    let mut remainder = file_text;
-    let mut offset = 0;
-    for _ in 0..pos.count {
-        if let Some(n) = remainder.find(&pos.string) {
-            offset += n;
-            remainder = &remainder[n + 1..];
-        } else {
-            panic!("failed to find {} instances of {:?} in {:?}", pos.count, pos.string, file_text);
-        }
-    }
-    offset
-}
-
-#[test]
-fn ends_on_col0() {
-    test_harness(
-        r#"
-fn foo() {
-}
-"#,
-        vec![SpanLabel {
-            start: Position { string: "{", count: 1 },
-            end: Position { string: "}", count: 1 },
-            label: "test",
-        }],
-        r#"
-error: foo
- --> test.rs:2:10
-  |
-2 |   fn foo() {
-  |  __________^
-3 | | }
-  | |_^ test
-
-"#,
-    );
-}
-
-#[test]
-fn ends_on_col2() {
-    test_harness(
-        r#"
-fn foo() {
-
-
-  }
-"#,
-        vec![SpanLabel {
-            start: Position { string: "{", count: 1 },
-            end: Position { string: "}", count: 1 },
-            label: "test",
-        }],
-        r#"
-error: foo
- --> test.rs:2:10
-  |
-2 |   fn foo() {
-  |  __________^
-... |
-5 | |   }
-  | |___^ test
-
-"#,
-    );
-}
-#[test]
-fn non_nested() {
-    test_harness(
-        r#"
-fn foo() {
-  X0 Y0
-  X1 Y1
-  X2 Y2
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "X0", count: 1 },
-                end: Position { string: "X2", count: 1 },
-                label: "`X` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "Y0", count: 1 },
-                end: Position { string: "Y2", count: 1 },
-                label: "`Y` is a good letter too",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 |      X0 Y0
-  |   ___^__-
-  |  |___|
-  | ||
-4 | ||   X1 Y1
-5 | ||   X2 Y2
-  | ||____^__- `Y` is a good letter too
-  | |_____|
-  |       `X` is a good letter
-
-"#,
-    );
-}
-
-#[test]
-fn nested() {
-    test_harness(
-        r#"
-fn foo() {
-  X0 Y0
-  Y1 X1
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "X0", count: 1 },
-                end: Position { string: "X1", count: 1 },
-                label: "`X` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "Y0", count: 1 },
-                end: Position { string: "Y1", count: 1 },
-                label: "`Y` is a good letter too",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 |      X0 Y0
-  |   ___^__-
-  |  |___|
-  | ||
-4 | ||   Y1 X1
-  | ||____-__^ `X` is a good letter
-  |  |____|
-  |       `Y` is a good letter too
-
-"#,
-    );
-}
-
-#[test]
-fn different_overlap() {
-    test_harness(
-        r#"
-fn foo() {
-  X0 Y0 Z0
-  X1 Y1 Z1
-  X2 Y2 Z2
-  X3 Y3 Z3
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "Y0", count: 1 },
-                end: Position { string: "X2", count: 1 },
-                label: "`X` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "Z1", count: 1 },
-                end: Position { string: "X3", count: 1 },
-                label: "`Y` is a good letter too",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:6
-  |
-3 |      X0 Y0 Z0
-  |  _______^
-4 | |    X1 Y1 Z1
-  | | _________-
-5 | ||   X2 Y2 Z2
-  | ||____^ `X` is a good letter
-6 |  |   X3 Y3 Z3
-  |  |____- `Y` is a good letter too
-
-"#,
-    );
-}
-
-#[test]
-fn triple_overlap() {
-    test_harness(
-        r#"
-fn foo() {
-  X0 Y0 Z0
-  X1 Y1 Z1
-  X2 Y2 Z2
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "X0", count: 1 },
-                end: Position { string: "X2", count: 1 },
-                label: "`X` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "Y0", count: 1 },
-                end: Position { string: "Y2", count: 1 },
-                label: "`Y` is a good letter too",
-            },
-            SpanLabel {
-                start: Position { string: "Z0", count: 1 },
-                end: Position { string: "Z2", count: 1 },
-                label: "`Z` label",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 |       X0 Y0 Z0
-  |    ___^__-__-
-  |   |___|__|
-  |  ||___|
-  | |||
-4 | |||   X1 Y1 Z1
-5 | |||   X2 Y2 Z2
-  | |||____^__-__- `Z` label
-  | ||_____|__|
-  | |______|  `Y` is a good letter too
-  |        `X` is a good letter
-
-"#,
-    );
-}
-
-#[test]
-fn triple_exact_overlap() {
-    test_harness(
-        r#"
-fn foo() {
-  X0 Y0 Z0
-  X1 Y1 Z1
-  X2 Y2 Z2
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "X0", count: 1 },
-                end: Position { string: "X2", count: 1 },
-                label: "`X` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "X0", count: 1 },
-                end: Position { string: "X2", count: 1 },
-                label: "`Y` is a good letter too",
-            },
-            SpanLabel {
-                start: Position { string: "X0", count: 1 },
-                end: Position { string: "X2", count: 1 },
-                label: "`Z` label",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 | /   X0 Y0 Z0
-4 | |   X1 Y1 Z1
-5 | |   X2 Y2 Z2
-  | |    ^
-  | |    |
-  | |    `X` is a good letter
-  | |____`Y` is a good letter too
-  |      `Z` label
-
-"#,
-    );
-}
-
-#[test]
-fn minimum_depth() {
-    test_harness(
-        r#"
-fn foo() {
-  X0 Y0 Z0
-  X1 Y1 Z1
-  X2 Y2 Z2
-  X3 Y3 Z3
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "Y0", count: 1 },
-                end: Position { string: "X1", count: 1 },
-                label: "`X` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "Y1", count: 1 },
-                end: Position { string: "Z2", count: 1 },
-                label: "`Y` is a good letter too",
-            },
-            SpanLabel {
-                start: Position { string: "X2", count: 1 },
-                end: Position { string: "Y3", count: 1 },
-                label: "`Z`",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:6
-  |
-3 |      X0 Y0 Z0
-  |  _______^
-4 | |    X1 Y1 Z1
-  | | ____^_-
-  | ||____|
-  |  |    `X` is a good letter
-5 |  |   X2 Y2 Z2
-  |  |___-______- `Y` is a good letter too
-  |   ___|
-  |  |
-6 |  |   X3 Y3 Z3
-  |  |_______- `Z`
-
-"#,
-    );
-}
-
-#[test]
-fn non_overlapping() {
-    test_harness(
-        r#"
-fn foo() {
-  X0 Y0 Z0
-  X1 Y1 Z1
-  X2 Y2 Z2
-  X3 Y3 Z3
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "X0", count: 1 },
-                end: Position { string: "X1", count: 1 },
-                label: "`X` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "Y2", count: 1 },
-                end: Position { string: "Z3", count: 1 },
-                label: "`Y` is a good letter too",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 | /   X0 Y0 Z0
-4 | |   X1 Y1 Z1
-  | |____^ `X` is a good letter
-5 |     X2 Y2 Z2
-  |  ______-
-6 | |   X3 Y3 Z3
-  | |__________- `Y` is a good letter too
-
-"#,
-    );
-}
-
-#[test]
-fn overlapping_start_and_end() {
-    test_harness(
-        r#"
-fn foo() {
-  X0 Y0 Z0
-  X1 Y1 Z1
-  X2 Y2 Z2
-  X3 Y3 Z3
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "Y0", count: 1 },
-                end: Position { string: "X1", count: 1 },
-                label: "`X` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "Z1", count: 1 },
-                end: Position { string: "Z3", count: 1 },
-                label: "`Y` is a good letter too",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:6
-  |
-3 |      X0 Y0 Z0
-  |  _______^
-4 | |    X1 Y1 Z1
-  | | ____^____-
-  | ||____|
-  |  |    `X` is a good letter
-5 |  |   X2 Y2 Z2
-6 |  |   X3 Y3 Z3
-  |  |__________- `Y` is a good letter too
-
-"#,
-    );
-}
-
-#[test]
-fn multiple_labels_primary_without_message() {
-    test_harness(
-        r#"
-fn foo() {
-  a { b { c } d }
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "b", count: 1 },
-                end: Position { string: "}", count: 1 },
-                label: "",
-            },
-            SpanLabel {
-                start: Position { string: "a", count: 1 },
-                end: Position { string: "d", count: 1 },
-                label: "`a` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "c", count: 1 },
-                end: Position { string: "c", count: 1 },
-                label: "",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:7
-  |
-3 |   a { b { c } d }
-  |   ----^^^^-^^-- `a` is a good letter
-
-"#,
-    );
-}
-
-#[test]
-fn multiple_labels_secondary_without_message() {
-    test_harness(
-        r#"
-fn foo() {
-  a { b { c } d }
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "a", count: 1 },
-                end: Position { string: "d", count: 1 },
-                label: "`a` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "b", count: 1 },
-                end: Position { string: "}", count: 1 },
-                label: "",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 |   a { b { c } d }
-  |   ^^^^-------^^ `a` is a good letter
-
-"#,
-    );
-}
-
-#[test]
-fn multiple_labels_primary_without_message_2() {
-    test_harness(
-        r#"
-fn foo() {
-  a { b { c } d }
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "b", count: 1 },
-                end: Position { string: "}", count: 1 },
-                label: "`b` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "a", count: 1 },
-                end: Position { string: "d", count: 1 },
-                label: "",
-            },
-            SpanLabel {
-                start: Position { string: "c", count: 1 },
-                end: Position { string: "c", count: 1 },
-                label: "",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:7
-  |
-3 |   a { b { c } d }
-  |   ----^^^^-^^--
-  |       |
-  |       `b` is a good letter
-
-"#,
-    );
-}
-
-#[test]
-fn multiple_labels_secondary_without_message_2() {
-    test_harness(
-        r#"
-fn foo() {
-  a { b { c } d }
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "a", count: 1 },
-                end: Position { string: "d", count: 1 },
-                label: "",
-            },
-            SpanLabel {
-                start: Position { string: "b", count: 1 },
-                end: Position { string: "}", count: 1 },
-                label: "`b` is a good letter",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 |   a { b { c } d }
-  |   ^^^^-------^^
-  |       |
-  |       `b` is a good letter
-
-"#,
-    );
-}
-
-#[test]
-fn multiple_labels_secondary_without_message_3() {
-    test_harness(
-        r#"
-fn foo() {
-  a  bc  d
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "a", count: 1 },
-                end: Position { string: "b", count: 1 },
-                label: "`a` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "c", count: 1 },
-                end: Position { string: "d", count: 1 },
-                label: "",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 |   a  bc  d
-  |   ^^^^----
-  |   |
-  |   `a` is a good letter
-
-"#,
-    );
-}
-
-#[test]
-fn multiple_labels_without_message() {
-    test_harness(
-        r#"
-fn foo() {
-  a { b { c } d }
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "a", count: 1 },
-                end: Position { string: "d", count: 1 },
-                label: "",
-            },
-            SpanLabel {
-                start: Position { string: "b", count: 1 },
-                end: Position { string: "}", count: 1 },
-                label: "",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 |   a { b { c } d }
-  |   ^^^^-------^^
-
-"#,
-    );
-}
-
-#[test]
-fn multiple_labels_without_message_2() {
-    test_harness(
-        r#"
-fn foo() {
-  a { b { c } d }
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "b", count: 1 },
-                end: Position { string: "}", count: 1 },
-                label: "",
-            },
-            SpanLabel {
-                start: Position { string: "a", count: 1 },
-                end: Position { string: "d", count: 1 },
-                label: "",
-            },
-            SpanLabel {
-                start: Position { string: "c", count: 1 },
-                end: Position { string: "c", count: 1 },
-                label: "",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:7
-  |
-3 |   a { b { c } d }
-  |   ----^^^^-^^--
-
-"#,
-    );
-}
-
-#[test]
-fn multiple_labels_with_message() {
-    test_harness(
-        r#"
-fn foo() {
-  a { b { c } d }
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "a", count: 1 },
-                end: Position { string: "d", count: 1 },
-                label: "`a` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "b", count: 1 },
-                end: Position { string: "}", count: 1 },
-                label: "`b` is a good letter",
-            },
-        ],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 |   a { b { c } d }
-  |   ^^^^-------^^
-  |   |   |
-  |   |   `b` is a good letter
-  |   `a` is a good letter
-
-"#,
-    );
-}
-
-#[test]
-fn single_label_with_message() {
-    test_harness(
-        r#"
-fn foo() {
-  a { b { c } d }
-}
-"#,
-        vec![SpanLabel {
-            start: Position { string: "a", count: 1 },
-            end: Position { string: "d", count: 1 },
-            label: "`a` is a good letter",
-        }],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 |   a { b { c } d }
-  |   ^^^^^^^^^^^^^ `a` is a good letter
-
-"#,
-    );
-}
-
-#[test]
-fn single_label_without_message() {
-    test_harness(
-        r#"
-fn foo() {
-  a { b { c } d }
-}
-"#,
-        vec![SpanLabel {
-            start: Position { string: "a", count: 1 },
-            end: Position { string: "d", count: 1 },
-            label: "",
-        }],
-        r#"
-error: foo
- --> test.rs:3:3
-  |
-3 |   a { b { c } d }
-  |   ^^^^^^^^^^^^^
-
-"#,
-    );
-}
-
-#[test]
-fn long_snippet() {
-    test_harness(
-        r#"
-fn foo() {
-  X0 Y0 Z0
-  X1 Y1 Z1
-1
-2
-3
-4
-5
-6
-7
-8
-9
-10
-  X2 Y2 Z2
-  X3 Y3 Z3
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "Y0", count: 1 },
-                end: Position { string: "X1", count: 1 },
-                label: "`X` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "Z1", count: 1 },
-                end: Position { string: "Z3", count: 1 },
-                label: "`Y` is a good letter too",
-            },
-        ],
-        r#"
-error: foo
-  --> test.rs:3:6
-   |
-3  |      X0 Y0 Z0
-   |  _______^
-4  | |    X1 Y1 Z1
-   | | ____^____-
-   | ||____|
-   |  |    `X` is a good letter
-5  |  | 1
-6  |  | 2
-7  |  | 3
-...   |
-15 |  |   X2 Y2 Z2
-16 |  |   X3 Y3 Z3
-   |  |__________- `Y` is a good letter too
-
-"#,
-    );
-}
-
-#[test]
-fn long_snippet_multiple_spans() {
-    test_harness(
-        r#"
-fn foo() {
-  X0 Y0 Z0
-1
-2
-3
-  X1 Y1 Z1
-4
-5
-6
-  X2 Y2 Z2
-7
-8
-9
-10
-  X3 Y3 Z3
-}
-"#,
-        vec![
-            SpanLabel {
-                start: Position { string: "Y0", count: 1 },
-                end: Position { string: "Y3", count: 1 },
-                label: "`Y` is a good letter",
-            },
-            SpanLabel {
-                start: Position { string: "Z1", count: 1 },
-                end: Position { string: "Z2", count: 1 },
-                label: "`Z` is a good letter too",
-            },
-        ],
-        r#"
-error: foo
-  --> test.rs:3:6
-   |
-3  |      X0 Y0 Z0
-   |  _______^
-4  | |  1
-5  | |  2
-6  | |  3
-7  | |    X1 Y1 Z1
-   | | _________-
-8  | || 4
-9  | || 5
-10 | || 6
-11 | ||   X2 Y2 Z2
-   | ||__________- `Z` is a good letter too
-...  |
-15 | |  10
-16 | |    X3 Y3 Z3
-   | |________^ `Y` is a good letter
-
-"#,
-    );
-}
diff --git a/compiler/rustc_expand/src/tokenstream/tests.rs b/compiler/rustc_expand/src/tokenstream/tests.rs
deleted file mode 100644
index 78795e86fd5..00000000000
--- a/compiler/rustc_expand/src/tokenstream/tests.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-use crate::tests::string_to_stream;
-
-use rustc_ast::token::{self, IdentIsRaw};
-use rustc_ast::tokenstream::{TokenStream, TokenTree};
-use rustc_span::create_default_session_globals_then;
-use rustc_span::{BytePos, Span, Symbol};
-
-fn string_to_ts(string: &str) -> TokenStream {
-    string_to_stream(string.to_owned())
-}
-
-fn sp(a: u32, b: u32) -> Span {
-    Span::with_root_ctxt(BytePos(a), BytePos(b))
-}
-
-#[test]
-fn test_concat() {
-    create_default_session_globals_then(|| {
-        let test_res = string_to_ts("foo::bar::baz");
-        let test_fst = string_to_ts("foo::bar");
-        let test_snd = string_to_ts("::baz");
-        let mut eq_res = TokenStream::default();
-        eq_res.push_stream(test_fst);
-        eq_res.push_stream(test_snd);
-        assert_eq!(test_res.trees().count(), 5);
-        assert_eq!(eq_res.trees().count(), 5);
-        assert_eq!(test_res.eq_unspanned(&eq_res), true);
-    })
-}
-
-#[test]
-fn test_to_from_bijection() {
-    create_default_session_globals_then(|| {
-        let test_start = string_to_ts("foo::bar(baz)");
-        let test_end = test_start.trees().cloned().collect();
-        assert_eq!(test_start, test_end)
-    })
-}
-
-#[test]
-fn test_eq_0() {
-    create_default_session_globals_then(|| {
-        let test_res = string_to_ts("foo");
-        let test_eqs = string_to_ts("foo");
-        assert_eq!(test_res, test_eqs)
-    })
-}
-
-#[test]
-fn test_eq_1() {
-    create_default_session_globals_then(|| {
-        let test_res = string_to_ts("::bar::baz");
-        let test_eqs = string_to_ts("::bar::baz");
-        assert_eq!(test_res, test_eqs)
-    })
-}
-
-#[test]
-fn test_eq_3() {
-    create_default_session_globals_then(|| {
-        let test_res = string_to_ts("");
-        let test_eqs = string_to_ts("");
-        assert_eq!(test_res, test_eqs)
-    })
-}
-
-#[test]
-fn test_diseq_0() {
-    create_default_session_globals_then(|| {
-        let test_res = string_to_ts("::bar::baz");
-        let test_eqs = string_to_ts("bar::baz");
-        assert_eq!(test_res == test_eqs, false)
-    })
-}
-
-#[test]
-fn test_diseq_1() {
-    create_default_session_globals_then(|| {
-        let test_res = string_to_ts("(bar,baz)");
-        let test_eqs = string_to_ts("bar,baz");
-        assert_eq!(test_res == test_eqs, false)
-    })
-}
-
-#[test]
-fn test_is_empty() {
-    create_default_session_globals_then(|| {
-        let test0 = TokenStream::default();
-        let test1 =
-            TokenStream::token_alone(token::Ident(Symbol::intern("a"), IdentIsRaw::No), sp(0, 1));
-        let test2 = string_to_ts("foo(bar::baz)");
-
-        assert_eq!(test0.is_empty(), true);
-        assert_eq!(test1.is_empty(), false);
-        assert_eq!(test2.is_empty(), false);
-    })
-}
-
-#[test]
-fn test_dotdotdot() {
-    create_default_session_globals_then(|| {
-        let mut stream = TokenStream::default();
-        stream.push_tree(TokenTree::token_joint(token::Dot, sp(0, 1)));
-        stream.push_tree(TokenTree::token_joint(token::Dot, sp(1, 2)));
-        stream.push_tree(TokenTree::token_alone(token::Dot, sp(2, 3)));
-        assert!(stream.eq_unspanned(&string_to_ts("...")));
-        assert_eq!(stream.trees().count(), 1);
-    })
-}