about summary refs log tree commit diff
path: root/compiler/rustc_builtin_macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_builtin_macros/src')
-rw-r--r--compiler/rustc_builtin_macros/src/alloc_error_handler.rs96
-rw-r--r--compiler/rustc_builtin_macros/src/asm.rs808
-rw-r--r--compiler/rustc_builtin_macros/src/assert.rs163
-rw-r--r--compiler/rustc_builtin_macros/src/assert/context.rs462
-rw-r--r--compiler/rustc_builtin_macros/src/cfg.rs54
-rw-r--r--compiler/rustc_builtin_macros/src/cfg_accessible.rs70
-rw-r--r--compiler/rustc_builtin_macros/src/cfg_eval.rs287
-rw-r--r--compiler/rustc_builtin_macros/src/cmdline_attrs.rs42
-rw-r--r--compiler/rustc_builtin_macros/src/compile_error.rs26
-rw-r--r--compiler/rustc_builtin_macros/src/concat.rs85
-rw-r--r--compiler/rustc_builtin_macros/src/concat_bytes.rs185
-rw-r--r--compiler/rustc_builtin_macros/src/concat_idents.rs72
-rw-r--r--compiler/rustc_builtin_macros/src/derive.rs172
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/bounds.rs52
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/clone.rs218
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs92
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs79
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs115
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs156
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/debug.rs240
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/decodable.rs226
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/default.rs250
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/encodable.rs301
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/generic/mod.rs1726
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/generic/ty.rs204
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/hash.rs78
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/mod.rs133
-rw-r--r--compiler/rustc_builtin_macros/src/deriving/smart_ptr.rs140
-rw-r--r--compiler/rustc_builtin_macros/src/edition_panic.rs86
-rw-r--r--compiler/rustc_builtin_macros/src/env.rs157
-rw-r--r--compiler/rustc_builtin_macros/src/errors.rs874
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs1015
-rw-r--r--compiler/rustc_builtin_macros/src/format_foreign.rs822
-rw-r--r--compiler/rustc_builtin_macros/src/format_foreign/printf/tests.rs145
-rw-r--r--compiler/rustc_builtin_macros/src/format_foreign/shell/tests.rs56
-rw-r--r--compiler/rustc_builtin_macros/src/global_allocator.rs174
-rw-r--r--compiler/rustc_builtin_macros/src/lib.rs135
-rw-r--r--compiler/rustc_builtin_macros/src/log_syntax.rs14
-rw-r--r--compiler/rustc_builtin_macros/src/pattern_type.rs29
-rw-r--r--compiler/rustc_builtin_macros/src/proc_macro_harness.rs387
-rw-r--r--compiler/rustc_builtin_macros/src/source_util.rs343
-rw-r--r--compiler/rustc_builtin_macros/src/standard_library_imports.rs105
-rw-r--r--compiler/rustc_builtin_macros/src/test.rs620
-rw-r--r--compiler/rustc_builtin_macros/src/test_harness.rs411
-rw-r--r--compiler/rustc_builtin_macros/src/trace_macros.rs30
-rw-r--r--compiler/rustc_builtin_macros/src/util.rs228
46 files changed, 12163 insertions, 0 deletions
diff --git a/compiler/rustc_builtin_macros/src/alloc_error_handler.rs b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs
new file mode 100644
index 00000000000..4721e74b955
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/alloc_error_handler.rs
@@ -0,0 +1,96 @@
+use crate::errors;
+use crate::util::check_builtin_macro_attribute;
+
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, FnHeader, FnSig, Generics, StmtKind};
+use rustc_ast::{Fn, ItemKind, Safety, Stmt, TyKind};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{kw, sym, Ident};
+use rustc_span::Span;
+use thin_vec::{thin_vec, ThinVec};
+
+pub(crate) fn expand(
+    ecx: &mut ExtCtxt<'_>,
+    _span: Span,
+    meta_item: &ast::MetaItem,
+    item: Annotatable,
+) -> Vec<Annotatable> {
+    check_builtin_macro_attribute(ecx, meta_item, sym::alloc_error_handler);
+
+    let orig_item = item.clone();
+
+    // Allow using `#[alloc_error_handler]` on an item statement
+    // FIXME - if we get deref patterns, use them to reduce duplication here
+    let (item, is_stmt, sig_span) = if let Annotatable::Item(item) = &item
+        && let ItemKind::Fn(fn_kind) = &item.kind
+    {
+        (item, false, ecx.with_def_site_ctxt(fn_kind.sig.span))
+    } else if let Annotatable::Stmt(stmt) = &item
+        && let StmtKind::Item(item) = &stmt.kind
+        && let ItemKind::Fn(fn_kind) = &item.kind
+    {
+        (item, true, ecx.with_def_site_ctxt(fn_kind.sig.span))
+    } else {
+        ecx.dcx().emit_err(errors::AllocErrorMustBeFn { span: item.span() });
+        return vec![orig_item];
+    };
+
+    // Generate a bunch of new items using the AllocFnFactory
+    let span = ecx.with_def_site_ctxt(item.span);
+
+    // Generate item statements for the allocator methods.
+    let stmts = thin_vec![generate_handler(ecx, item.ident, span, sig_span)];
+
+    // Generate anonymous constant serving as container for the allocator methods.
+    let const_ty = ecx.ty(sig_span, TyKind::Tup(ThinVec::new()));
+    let const_body = ecx.expr_block(ecx.block(span, stmts));
+    let const_item = ecx.item_const(span, Ident::new(kw::Underscore, span), const_ty, const_body);
+    let const_item = if is_stmt {
+        Annotatable::Stmt(P(ecx.stmt_item(span, const_item)))
+    } else {
+        Annotatable::Item(const_item)
+    };
+
+    // Return the original item and the new methods.
+    vec![orig_item, const_item]
+}
+
+// #[rustc_std_internal_symbol]
+// unsafe fn __rg_oom(size: usize, align: usize) -> ! {
+//     handler(core::alloc::Layout::from_size_align_unchecked(size, align))
+// }
+fn generate_handler(cx: &ExtCtxt<'_>, handler: Ident, span: Span, sig_span: Span) -> Stmt {
+    let usize = cx.path_ident(span, Ident::new(sym::usize, span));
+    let ty_usize = cx.ty_path(usize);
+    let size = Ident::from_str_and_span("size", span);
+    let align = Ident::from_str_and_span("align", span);
+
+    let layout_new = cx.std_path(&[sym::alloc, sym::Layout, sym::from_size_align_unchecked]);
+    let layout_new = cx.expr_path(cx.path(span, layout_new));
+    let layout = cx.expr_call(
+        span,
+        layout_new,
+        thin_vec![cx.expr_ident(span, size), cx.expr_ident(span, align)],
+    );
+
+    let call = cx.expr_call_ident(sig_span, handler, thin_vec![layout]);
+
+    let never = ast::FnRetTy::Ty(cx.ty(span, TyKind::Never));
+    let params = thin_vec![cx.param(span, size, ty_usize.clone()), cx.param(span, align, ty_usize)];
+    let decl = cx.fn_decl(params, never);
+    let header = FnHeader { safety: Safety::Unsafe(span), ..FnHeader::default() };
+    let sig = FnSig { decl, header, span: span };
+
+    let body = Some(cx.block_expr(call));
+    let kind = ItemKind::Fn(Box::new(Fn {
+        defaultness: ast::Defaultness::Final,
+        sig,
+        generics: Generics::default(),
+        body,
+    }));
+
+    let attrs = thin_vec![cx.attr_word(sym::rustc_std_internal_symbol, span)];
+
+    let item = cx.item(span, Ident::from_str_and_span("__rg_oom", span), attrs, kind);
+    cx.stmt_item(sig_span, item)
+}
diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs
new file mode 100644
index 00000000000..64238e81b26
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/asm.rs
@@ -0,0 +1,808 @@
+use crate::errors;
+use crate::util::expr_to_spanned_string;
+use ast::token::IdentIsRaw;
+use lint::BuiltinLintDiag;
+use rustc_ast as ast;
+use rustc_ast::ptr::P;
+use rustc_ast::token::{self, Delimiter};
+use rustc_ast::tokenstream::TokenStream;
+use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
+use rustc_errors::PResult;
+use rustc_expand::base::*;
+use rustc_index::bit_set::GrowableBitSet;
+use rustc_parse::parser::Parser;
+use rustc_parse_format as parse;
+use rustc_session::lint;
+use rustc_span::symbol::Ident;
+use rustc_span::symbol::{kw, sym, Symbol};
+use rustc_span::{ErrorGuaranteed, InnerSpan, Span};
+use rustc_target::asm::InlineAsmArch;
+use smallvec::smallvec;
+
+pub struct AsmArgs {
+    pub templates: Vec<P<ast::Expr>>,
+    pub operands: Vec<(ast::InlineAsmOperand, Span)>,
+    named_args: FxIndexMap<Symbol, usize>,
+    reg_args: GrowableBitSet<usize>,
+    pub clobber_abis: Vec<(Symbol, Span)>,
+    options: ast::InlineAsmOptions,
+    pub options_spans: Vec<Span>,
+}
+
+fn parse_args<'a>(
+    ecx: &ExtCtxt<'a>,
+    sp: Span,
+    tts: TokenStream,
+    is_global_asm: bool,
+) -> PResult<'a, AsmArgs> {
+    let mut p = ecx.new_parser_from_tts(tts);
+    parse_asm_args(&mut p, sp, is_global_asm)
+}
+
+// Primarily public for rustfmt consumption.
+// Internal consumers should continue to leverage `expand_asm`/`expand__global_asm`
+pub fn parse_asm_args<'a>(
+    p: &mut Parser<'a>,
+    sp: Span,
+    is_global_asm: bool,
+) -> PResult<'a, AsmArgs> {
+    let dcx = p.dcx();
+
+    if p.token == token::Eof {
+        return Err(dcx.create_err(errors::AsmRequiresTemplate { span: sp }));
+    }
+
+    let first_template = p.parse_expr()?;
+    let mut args = AsmArgs {
+        templates: vec![first_template],
+        operands: vec![],
+        named_args: Default::default(),
+        reg_args: Default::default(),
+        clobber_abis: Vec::new(),
+        options: ast::InlineAsmOptions::empty(),
+        options_spans: vec![],
+    };
+
+    let mut allow_templates = true;
+    while p.token != token::Eof {
+        if !p.eat(&token::Comma) {
+            if allow_templates {
+                // After a template string, we always expect *only* a comma...
+                return Err(dcx.create_err(errors::AsmExpectedComma { span: p.token.span }));
+            } else {
+                // ...after that delegate to `expect` to also include the other expected tokens.
+                return Err(p.expect(&token::Comma).err().unwrap());
+            }
+        }
+        if p.token == token::Eof {
+            break;
+        } // accept trailing commas
+
+        // Parse clobber_abi
+        if p.eat_keyword(sym::clobber_abi) {
+            parse_clobber_abi(p, &mut args)?;
+            allow_templates = false;
+            continue;
+        }
+
+        // Parse options
+        if p.eat_keyword(sym::options) {
+            parse_options(p, &mut args, is_global_asm)?;
+            allow_templates = false;
+            continue;
+        }
+
+        let span_start = p.token.span;
+
+        // Parse operand names
+        let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) {
+            let (ident, _) = p.token.ident().unwrap();
+            p.bump();
+            p.expect(&token::Eq)?;
+            allow_templates = false;
+            Some(ident.name)
+        } else {
+            None
+        };
+
+        let mut explicit_reg = false;
+        let op = if !is_global_asm && p.eat_keyword(kw::In) {
+            let reg = parse_reg(p, &mut explicit_reg)?;
+            if p.eat_keyword(kw::Underscore) {
+                let err = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span });
+                return Err(err);
+            }
+            let expr = p.parse_expr()?;
+            ast::InlineAsmOperand::In { reg, expr }
+        } else if !is_global_asm && p.eat_keyword(sym::out) {
+            let reg = parse_reg(p, &mut explicit_reg)?;
+            let expr = if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
+            ast::InlineAsmOperand::Out { reg, expr, late: false }
+        } else if !is_global_asm && p.eat_keyword(sym::lateout) {
+            let reg = parse_reg(p, &mut explicit_reg)?;
+            let expr = if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
+            ast::InlineAsmOperand::Out { reg, expr, late: true }
+        } else if !is_global_asm && p.eat_keyword(sym::inout) {
+            let reg = parse_reg(p, &mut explicit_reg)?;
+            if p.eat_keyword(kw::Underscore) {
+                let err = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span });
+                return Err(err);
+            }
+            let expr = p.parse_expr()?;
+            if p.eat(&token::FatArrow) {
+                let out_expr =
+                    if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
+                ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: false }
+            } else {
+                ast::InlineAsmOperand::InOut { reg, expr, late: false }
+            }
+        } else if !is_global_asm && p.eat_keyword(sym::inlateout) {
+            let reg = parse_reg(p, &mut explicit_reg)?;
+            if p.eat_keyword(kw::Underscore) {
+                let err = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span });
+                return Err(err);
+            }
+            let expr = p.parse_expr()?;
+            if p.eat(&token::FatArrow) {
+                let out_expr =
+                    if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) };
+                ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: true }
+            } else {
+                ast::InlineAsmOperand::InOut { reg, expr, late: true }
+            }
+        } else if p.eat_keyword(kw::Const) {
+            let anon_const = p.parse_expr_anon_const()?;
+            ast::InlineAsmOperand::Const { anon_const }
+        } else if p.eat_keyword(sym::sym) {
+            let expr = p.parse_expr()?;
+            let ast::ExprKind::Path(qself, path) = &expr.kind else {
+                let err = dcx.create_err(errors::AsmSymNoPath { span: expr.span });
+                return Err(err);
+            };
+            let sym = ast::InlineAsmSym {
+                id: ast::DUMMY_NODE_ID,
+                qself: qself.clone(),
+                path: path.clone(),
+            };
+            ast::InlineAsmOperand::Sym { sym }
+        } else if !is_global_asm && p.eat_keyword(sym::label) {
+            let block = p.parse_block()?;
+            ast::InlineAsmOperand::Label { block }
+        } else if allow_templates {
+            let template = p.parse_expr()?;
+            // If it can't possibly expand to a string, provide diagnostics here to include other
+            // things it could have been.
+            match template.kind {
+                ast::ExprKind::Lit(token_lit)
+                    if matches!(
+                        token_lit.kind,
+                        token::LitKind::Str | token::LitKind::StrRaw(_)
+                    ) => {}
+                ast::ExprKind::MacCall(..) => {}
+                _ => {
+                    let err = dcx.create_err(errors::AsmExpectedOther {
+                        span: template.span,
+                        is_global_asm,
+                    });
+                    return Err(err);
+                }
+            }
+            args.templates.push(template);
+            continue;
+        } else {
+            p.unexpected_any()?
+        };
+
+        allow_templates = false;
+        let span = span_start.to(p.prev_token.span);
+        let slot = args.operands.len();
+        args.operands.push((op, span));
+
+        // Validate the order of named, positional & explicit register operands and
+        // clobber_abi/options. We do this at the end once we have the full span
+        // of the argument available.
+        if explicit_reg {
+            if name.is_some() {
+                dcx.emit_err(errors::AsmExplicitRegisterName { span });
+            }
+            args.reg_args.insert(slot);
+        } else if let Some(name) = name {
+            if let Some(&prev) = args.named_args.get(&name) {
+                dcx.emit_err(errors::AsmDuplicateArg { span, name, prev: args.operands[prev].1 });
+                continue;
+            }
+            args.named_args.insert(name, slot);
+        } else {
+            if !args.named_args.is_empty() || !args.reg_args.is_empty() {
+                let named = args.named_args.values().map(|p| args.operands[*p].1).collect();
+                let explicit = args.reg_args.iter().map(|p| args.operands[p].1).collect();
+
+                dcx.emit_err(errors::AsmPositionalAfter { span, named, explicit });
+            }
+        }
+    }
+
+    if args.options.contains(ast::InlineAsmOptions::NOMEM)
+        && args.options.contains(ast::InlineAsmOptions::READONLY)
+    {
+        let spans = args.options_spans.clone();
+        dcx.emit_err(errors::AsmMutuallyExclusive { spans, opt1: "nomem", opt2: "readonly" });
+    }
+    if args.options.contains(ast::InlineAsmOptions::PURE)
+        && args.options.contains(ast::InlineAsmOptions::NORETURN)
+    {
+        let spans = args.options_spans.clone();
+        dcx.emit_err(errors::AsmMutuallyExclusive { spans, opt1: "pure", opt2: "noreturn" });
+    }
+    if args.options.contains(ast::InlineAsmOptions::PURE)
+        && !args.options.intersects(ast::InlineAsmOptions::NOMEM | ast::InlineAsmOptions::READONLY)
+    {
+        let spans = args.options_spans.clone();
+        dcx.emit_err(errors::AsmPureCombine { spans });
+    }
+
+    let mut have_real_output = false;
+    let mut outputs_sp = vec![];
+    let mut regclass_outputs = vec![];
+    let mut labels_sp = vec![];
+    for (op, op_sp) in &args.operands {
+        match op {
+            ast::InlineAsmOperand::Out { reg, expr, .. }
+            | ast::InlineAsmOperand::SplitInOut { reg, out_expr: expr, .. } => {
+                outputs_sp.push(*op_sp);
+                have_real_output |= expr.is_some();
+                if let ast::InlineAsmRegOrRegClass::RegClass(_) = reg {
+                    regclass_outputs.push(*op_sp);
+                }
+            }
+            ast::InlineAsmOperand::InOut { reg, .. } => {
+                outputs_sp.push(*op_sp);
+                have_real_output = true;
+                if let ast::InlineAsmRegOrRegClass::RegClass(_) = reg {
+                    regclass_outputs.push(*op_sp);
+                }
+            }
+            ast::InlineAsmOperand::Label { .. } => {
+                labels_sp.push(*op_sp);
+            }
+            _ => {}
+        }
+    }
+    if args.options.contains(ast::InlineAsmOptions::PURE) && !have_real_output {
+        dcx.emit_err(errors::AsmPureNoOutput { spans: args.options_spans.clone() });
+    }
+    if args.options.contains(ast::InlineAsmOptions::NORETURN) && !outputs_sp.is_empty() {
+        let err = dcx.create_err(errors::AsmNoReturn { outputs_sp });
+        // Bail out now since this is likely to confuse MIR
+        return Err(err);
+    }
+    if args.options.contains(ast::InlineAsmOptions::MAY_UNWIND) && !labels_sp.is_empty() {
+        dcx.emit_err(errors::AsmMayUnwind { labels_sp });
+    }
+
+    if args.clobber_abis.len() > 0 {
+        if is_global_asm {
+            let err = dcx.create_err(errors::GlobalAsmClobberAbi {
+                spans: args.clobber_abis.iter().map(|(_, span)| *span).collect(),
+            });
+
+            // Bail out now since this is likely to confuse later stages
+            return Err(err);
+        }
+        if !regclass_outputs.is_empty() {
+            dcx.emit_err(errors::AsmClobberNoReg {
+                spans: regclass_outputs,
+                clobbers: args.clobber_abis.iter().map(|(_, span)| *span).collect(),
+            });
+        }
+    }
+
+    Ok(args)
+}
+
+/// Report a duplicate option error.
+///
+/// This function must be called immediately after the option token is parsed.
+/// Otherwise, the suggestion will be incorrect.
+fn err_duplicate_option(p: &Parser<'_>, symbol: Symbol, span: Span) {
+    // Tool-only output
+    let full_span = if p.token.kind == token::Comma { span.to(p.token.span) } else { span };
+    p.dcx().emit_err(errors::AsmOptAlreadyprovided { span, symbol, full_span });
+}
+
+/// Try to set the provided option in the provided `AsmArgs`.
+/// If it is already set, report a duplicate option error.
+///
+/// This function must be called immediately after the option token is parsed.
+/// Otherwise, the error will not point to the correct spot.
+fn try_set_option<'a>(
+    p: &Parser<'a>,
+    args: &mut AsmArgs,
+    symbol: Symbol,
+    option: ast::InlineAsmOptions,
+) {
+    if !args.options.contains(option) {
+        args.options |= option;
+    } else {
+        err_duplicate_option(p, symbol, p.prev_token.span);
+    }
+}
+
+fn parse_options<'a>(
+    p: &mut Parser<'a>,
+    args: &mut AsmArgs,
+    is_global_asm: bool,
+) -> PResult<'a, ()> {
+    let span_start = p.prev_token.span;
+
+    p.expect(&token::OpenDelim(Delimiter::Parenthesis))?;
+
+    while !p.eat(&token::CloseDelim(Delimiter::Parenthesis)) {
+        if !is_global_asm && p.eat_keyword(sym::pure) {
+            try_set_option(p, args, sym::pure, ast::InlineAsmOptions::PURE);
+        } else if !is_global_asm && p.eat_keyword(sym::nomem) {
+            try_set_option(p, args, sym::nomem, ast::InlineAsmOptions::NOMEM);
+        } else if !is_global_asm && p.eat_keyword(sym::readonly) {
+            try_set_option(p, args, sym::readonly, ast::InlineAsmOptions::READONLY);
+        } else if !is_global_asm && p.eat_keyword(sym::preserves_flags) {
+            try_set_option(p, args, sym::preserves_flags, ast::InlineAsmOptions::PRESERVES_FLAGS);
+        } else if !is_global_asm && p.eat_keyword(sym::noreturn) {
+            try_set_option(p, args, sym::noreturn, ast::InlineAsmOptions::NORETURN);
+        } else if !is_global_asm && p.eat_keyword(sym::nostack) {
+            try_set_option(p, args, sym::nostack, ast::InlineAsmOptions::NOSTACK);
+        } else if !is_global_asm && p.eat_keyword(sym::may_unwind) {
+            try_set_option(p, args, kw::Raw, ast::InlineAsmOptions::MAY_UNWIND);
+        } else if p.eat_keyword(sym::att_syntax) {
+            try_set_option(p, args, sym::att_syntax, ast::InlineAsmOptions::ATT_SYNTAX);
+        } else if p.eat_keyword(kw::Raw) {
+            try_set_option(p, args, kw::Raw, ast::InlineAsmOptions::RAW);
+        } else {
+            return p.unexpected();
+        }
+
+        // Allow trailing commas
+        if p.eat(&token::CloseDelim(Delimiter::Parenthesis)) {
+            break;
+        }
+        p.expect(&token::Comma)?;
+    }
+
+    let new_span = span_start.to(p.prev_token.span);
+    args.options_spans.push(new_span);
+
+    Ok(())
+}
+
+fn parse_clobber_abi<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> PResult<'a, ()> {
+    let span_start = p.prev_token.span;
+
+    p.expect(&token::OpenDelim(Delimiter::Parenthesis))?;
+
+    if p.eat(&token::CloseDelim(Delimiter::Parenthesis)) {
+        return Err(p.dcx().create_err(errors::NonABI { span: p.token.span }));
+    }
+
+    let mut new_abis = Vec::new();
+    while !p.eat(&token::CloseDelim(Delimiter::Parenthesis)) {
+        match p.parse_str_lit() {
+            Ok(str_lit) => {
+                new_abis.push((str_lit.symbol_unescaped, str_lit.span));
+            }
+            Err(opt_lit) => {
+                let span = opt_lit.map_or(p.token.span, |lit| lit.span);
+                let mut err = p.dcx().struct_span_err(span, "expected string literal");
+                err.span_label(span, "not a string literal");
+                return Err(err);
+            }
+        };
+
+        // Allow trailing commas
+        if p.eat(&token::CloseDelim(Delimiter::Parenthesis)) {
+            break;
+        }
+        p.expect(&token::Comma)?;
+    }
+
+    let full_span = span_start.to(p.prev_token.span);
+
+    match &new_abis[..] {
+        // should have errored above during parsing
+        [] => unreachable!(),
+        [(abi, _span)] => args.clobber_abis.push((*abi, full_span)),
+        abis => {
+            for (abi, span) in abis {
+                args.clobber_abis.push((*abi, *span));
+            }
+        }
+    }
+
+    Ok(())
+}
+
+fn parse_reg<'a>(
+    p: &mut Parser<'a>,
+    explicit_reg: &mut bool,
+) -> PResult<'a, ast::InlineAsmRegOrRegClass> {
+    p.expect(&token::OpenDelim(Delimiter::Parenthesis))?;
+    let result = match p.token.uninterpolate().kind {
+        token::Ident(name, IdentIsRaw::No) => ast::InlineAsmRegOrRegClass::RegClass(name),
+        token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => {
+            *explicit_reg = true;
+            ast::InlineAsmRegOrRegClass::Reg(symbol)
+        }
+        _ => {
+            return Err(p.dcx().create_err(errors::ExpectedRegisterClassOrExplicitRegister {
+                span: p.token.span,
+            }));
+        }
+    };
+    p.bump();
+    p.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
+    Ok(result)
+}
+
+fn expand_preparsed_asm(
+    ecx: &mut ExtCtxt<'_>,
+    args: AsmArgs,
+) -> ExpandResult<Result<ast::InlineAsm, ErrorGuaranteed>, ()> {
+    let mut template = vec![];
+    // Register operands are implicitly used since they are not allowed to be
+    // referenced in the template string.
+    let mut used = vec![false; args.operands.len()];
+    for pos in args.reg_args.iter() {
+        used[pos] = true;
+    }
+    let named_pos: FxHashMap<usize, Symbol> =
+        args.named_args.iter().map(|(&sym, &idx)| (idx, sym)).collect();
+    let mut line_spans = Vec::with_capacity(args.templates.len());
+    let mut curarg = 0;
+
+    let mut template_strs = Vec::with_capacity(args.templates.len());
+
+    for (i, template_expr) in args.templates.into_iter().enumerate() {
+        if i != 0 {
+            template.push(ast::InlineAsmTemplatePiece::String("\n".to_string()));
+        }
+
+        let msg = "asm template must be a string literal";
+        let template_sp = template_expr.span;
+        let (template_str, template_style, template_span) = {
+            let ExpandResult::Ready(mac) = expr_to_spanned_string(ecx, template_expr, msg) else {
+                return ExpandResult::Retry(());
+            };
+            match mac {
+                Ok(template_part) => template_part,
+                Err(err) => {
+                    return ExpandResult::Ready(Err(match err {
+                        Ok((err, _)) => err.emit(),
+                        Err(guar) => guar,
+                    }));
+                }
+            }
+        };
+
+        let str_style = match template_style {
+            ast::StrStyle::Cooked => None,
+            ast::StrStyle::Raw(raw) => Some(raw as usize),
+        };
+
+        let template_snippet = ecx.source_map().span_to_snippet(template_sp).ok();
+        template_strs.push((
+            template_str,
+            template_snippet.as_deref().map(Symbol::intern),
+            template_sp,
+        ));
+        let template_str = template_str.as_str();
+
+        if let Some(InlineAsmArch::X86 | InlineAsmArch::X86_64) = ecx.sess.asm_arch {
+            let find_span = |needle: &str| -> Span {
+                if let Some(snippet) = &template_snippet {
+                    if let Some(pos) = snippet.find(needle) {
+                        let end = pos
+                            + snippet[pos..]
+                                .find(|c| matches!(c, '\n' | ';' | '\\' | '"'))
+                                .unwrap_or(snippet[pos..].len() - 1);
+                        let inner = InnerSpan::new(pos, end);
+                        return template_sp.from_inner(inner);
+                    }
+                }
+                template_sp
+            };
+
+            if template_str.contains(".intel_syntax") {
+                ecx.psess().buffer_lint(
+                    lint::builtin::BAD_ASM_STYLE,
+                    find_span(".intel_syntax"),
+                    ecx.current_expansion.lint_node_id,
+                    BuiltinLintDiag::AvoidUsingIntelSyntax,
+                );
+            }
+            if template_str.contains(".att_syntax") {
+                ecx.psess().buffer_lint(
+                    lint::builtin::BAD_ASM_STYLE,
+                    find_span(".att_syntax"),
+                    ecx.current_expansion.lint_node_id,
+                    BuiltinLintDiag::AvoidUsingAttSyntax,
+                );
+            }
+        }
+
+        // Don't treat raw asm as a format string.
+        if args.options.contains(ast::InlineAsmOptions::RAW) {
+            template.push(ast::InlineAsmTemplatePiece::String(template_str.to_string()));
+            let template_num_lines = 1 + template_str.matches('\n').count();
+            line_spans.extend(std::iter::repeat(template_sp).take(template_num_lines));
+            continue;
+        }
+
+        let mut parser = parse::Parser::new(
+            template_str,
+            str_style,
+            template_snippet,
+            false,
+            parse::ParseMode::InlineAsm,
+        );
+        parser.curarg = curarg;
+
+        let mut unverified_pieces = Vec::new();
+        while let Some(piece) = parser.next() {
+            if !parser.errors.is_empty() {
+                break;
+            } else {
+                unverified_pieces.push(piece);
+            }
+        }
+
+        if !parser.errors.is_empty() {
+            let err = parser.errors.remove(0);
+            let err_sp = template_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
+            let msg = format!("invalid asm template string: {}", err.description);
+            let mut e = ecx.dcx().struct_span_err(err_sp, msg);
+            e.span_label(err_sp, err.label + " in asm template string");
+            if let Some(note) = err.note {
+                e.note(note);
+            }
+            if let Some((label, span)) = err.secondary_label {
+                let err_sp = template_span.from_inner(InnerSpan::new(span.start, span.end));
+                e.span_label(err_sp, label);
+            }
+            let guar = e.emit();
+            return ExpandResult::Ready(Err(guar));
+        }
+
+        curarg = parser.curarg;
+
+        let mut arg_spans = parser
+            .arg_places
+            .iter()
+            .map(|span| template_span.from_inner(InnerSpan::new(span.start, span.end)));
+        for piece in unverified_pieces {
+            match piece {
+                parse::Piece::String(s) => {
+                    template.push(ast::InlineAsmTemplatePiece::String(s.to_string()))
+                }
+                parse::Piece::NextArgument(arg) => {
+                    let span = arg_spans.next().unwrap_or(template_sp);
+
+                    let operand_idx = match arg.position {
+                        parse::ArgumentIs(idx) | parse::ArgumentImplicitlyIs(idx) => {
+                            if idx >= args.operands.len()
+                                || named_pos.contains_key(&idx)
+                                || args.reg_args.contains(idx)
+                            {
+                                let msg = format!("invalid reference to argument at index {idx}");
+                                let mut err = ecx.dcx().struct_span_err(span, msg);
+                                err.span_label(span, "from here");
+
+                                let positional_args = args.operands.len()
+                                    - args.named_args.len()
+                                    - args.reg_args.len();
+                                let positional = if positional_args != args.operands.len() {
+                                    "positional "
+                                } else {
+                                    ""
+                                };
+                                let msg = match positional_args {
+                                    0 => format!("no {positional}arguments were given"),
+                                    1 => format!("there is 1 {positional}argument"),
+                                    x => format!("there are {x} {positional}arguments"),
+                                };
+                                err.note(msg);
+
+                                if named_pos.contains_key(&idx) {
+                                    err.span_label(args.operands[idx].1, "named argument");
+                                    err.span_note(
+                                        args.operands[idx].1,
+                                        "named arguments cannot be referenced by position",
+                                    );
+                                } else if args.reg_args.contains(idx) {
+                                    err.span_label(
+                                        args.operands[idx].1,
+                                        "explicit register argument",
+                                    );
+                                    err.span_note(
+                                        args.operands[idx].1,
+                                        "explicit register arguments cannot be used in the asm template",
+                                    );
+                                    err.span_help(
+                                        args.operands[idx].1,
+                                        "use the register name directly in the assembly code",
+                                    );
+                                }
+                                err.emit();
+                                None
+                            } else {
+                                Some(idx)
+                            }
+                        }
+                        parse::ArgumentNamed(name) => {
+                            match args.named_args.get(&Symbol::intern(name)) {
+                                Some(&idx) => Some(idx),
+                                None => {
+                                    let msg = format!("there is no argument named `{name}`");
+                                    let span = arg.position_span;
+                                    ecx.dcx()
+                                        .struct_span_err(
+                                            template_span
+                                                .from_inner(InnerSpan::new(span.start, span.end)),
+                                            msg,
+                                        )
+                                        .emit();
+                                    None
+                                }
+                            }
+                        }
+                    };
+
+                    let mut chars = arg.format.ty.chars();
+                    let mut modifier = chars.next();
+                    if chars.next().is_some() {
+                        let span = arg
+                            .format
+                            .ty_span
+                            .map(|sp| template_sp.from_inner(InnerSpan::new(sp.start, sp.end)))
+                            .unwrap_or(template_sp);
+                        ecx.dcx().emit_err(errors::AsmModifierInvalid { span });
+                        modifier = None;
+                    }
+
+                    if let Some(operand_idx) = operand_idx {
+                        used[operand_idx] = true;
+                        template.push(ast::InlineAsmTemplatePiece::Placeholder {
+                            operand_idx,
+                            modifier,
+                            span,
+                        });
+                    }
+                }
+            }
+        }
+
+        if parser.line_spans.is_empty() {
+            let template_num_lines = 1 + template_str.matches('\n').count();
+            line_spans.extend(std::iter::repeat(template_sp).take(template_num_lines));
+        } else {
+            line_spans.extend(
+                parser
+                    .line_spans
+                    .iter()
+                    .map(|span| template_span.from_inner(InnerSpan::new(span.start, span.end))),
+            );
+        };
+    }
+
+    let mut unused_operands = vec![];
+    let mut help_str = String::new();
+    for (idx, used) in used.into_iter().enumerate() {
+        if !used {
+            let msg = if let Some(sym) = named_pos.get(&idx) {
+                help_str.push_str(&format!(" {{{}}}", sym));
+                "named argument never used"
+            } else {
+                help_str.push_str(&format!(" {{{}}}", idx));
+                "argument never used"
+            };
+            unused_operands.push((args.operands[idx].1, msg));
+        }
+    }
+    match unused_operands.len() {
+        0 => {}
+        1 => {
+            let (sp, msg) = unused_operands.into_iter().next().unwrap();
+            ecx.dcx()
+                .struct_span_err(sp, msg)
+                .with_span_label(sp, msg)
+                .with_help(format!(
+                    "if this argument is intentionally unused, \
+                     consider using it in an asm comment: `\"/*{help_str} */\"`"
+                ))
+                .emit();
+        }
+        _ => {
+            let mut err = ecx.dcx().struct_span_err(
+                unused_operands.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
+                "multiple unused asm arguments",
+            );
+            for (sp, msg) in unused_operands {
+                err.span_label(sp, msg);
+            }
+            err.help(format!(
+                "if these arguments are intentionally unused, \
+                 consider using them in an asm comment: `\"/*{help_str} */\"`"
+            ));
+            err.emit();
+        }
+    }
+
+    ExpandResult::Ready(Ok(ast::InlineAsm {
+        template,
+        template_strs: template_strs.into_boxed_slice(),
+        operands: args.operands,
+        clobber_abis: args.clobber_abis,
+        options: args.options,
+        line_spans,
+    }))
+}
+
+pub(super) fn expand_asm<'cx>(
+    ecx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    ExpandResult::Ready(match parse_args(ecx, sp, tts, false) {
+        Ok(args) => {
+            let ExpandResult::Ready(mac) = expand_preparsed_asm(ecx, args) else {
+                return ExpandResult::Retry(());
+            };
+            let expr = match mac {
+                Ok(inline_asm) => P(ast::Expr {
+                    id: ast::DUMMY_NODE_ID,
+                    kind: ast::ExprKind::InlineAsm(P(inline_asm)),
+                    span: sp,
+                    attrs: ast::AttrVec::new(),
+                    tokens: None,
+                }),
+                Err(guar) => DummyResult::raw_expr(sp, Some(guar)),
+            };
+            MacEager::expr(expr)
+        }
+        Err(err) => {
+            let guar = err.emit();
+            DummyResult::any(sp, guar)
+        }
+    })
+}
+
+pub(super) fn expand_global_asm<'cx>(
+    ecx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    ExpandResult::Ready(match parse_args(ecx, sp, tts, true) {
+        Ok(args) => {
+            let ExpandResult::Ready(mac) = expand_preparsed_asm(ecx, args) else {
+                return ExpandResult::Retry(());
+            };
+            match mac {
+                Ok(inline_asm) => MacEager::items(smallvec![P(ast::Item {
+                    ident: Ident::empty(),
+                    attrs: ast::AttrVec::new(),
+                    id: ast::DUMMY_NODE_ID,
+                    kind: ast::ItemKind::GlobalAsm(Box::new(inline_asm)),
+                    vis: ast::Visibility {
+                        span: sp.shrink_to_lo(),
+                        kind: ast::VisibilityKind::Inherited,
+                        tokens: None,
+                    },
+                    span: sp,
+                    tokens: None,
+                })]),
+                Err(guar) => DummyResult::any(sp, guar),
+            }
+        }
+        Err(err) => {
+            let guar = err.emit();
+            DummyResult::any(sp, guar)
+        }
+    })
+}
diff --git a/compiler/rustc_builtin_macros/src/assert.rs b/compiler/rustc_builtin_macros/src/assert.rs
new file mode 100644
index 00000000000..c75050f2701
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/assert.rs
@@ -0,0 +1,163 @@
+mod context;
+
+use crate::edition_panic::use_panic_2021;
+use crate::errors;
+use rustc_ast::ptr::P;
+use rustc_ast::token;
+use rustc_ast::token::Delimiter;
+use rustc_ast::tokenstream::{DelimSpan, TokenStream};
+use rustc_ast::{DelimArgs, Expr, ExprKind, MacCall, Path, PathSegment, UnOp};
+use rustc_ast_pretty::pprust;
+use rustc_errors::PResult;
+use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
+use rustc_parse::parser::Parser;
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use thin_vec::thin_vec;
+
+pub(crate) fn expand_assert<'cx>(
+    cx: &'cx mut ExtCtxt<'_>,
+    span: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) {
+        Ok(assert) => assert,
+        Err(err) => {
+            let guar = err.emit();
+            return ExpandResult::Ready(DummyResult::any(span, guar));
+        }
+    };
+
+    // `core::panic` and `std::panic` are different macros, so we use call-site
+    // context to pick up whichever is currently in scope.
+    let call_site_span = cx.with_call_site_ctxt(span);
+
+    let panic_path = || {
+        if use_panic_2021(span) {
+            // On edition 2021, we always call `$crate::panic::panic_2021!()`.
+            Path {
+                span: call_site_span,
+                segments: cx
+                    .std_path(&[sym::panic, sym::panic_2021])
+                    .into_iter()
+                    .map(|ident| PathSegment::from_ident(ident))
+                    .collect(),
+                tokens: None,
+            }
+        } else {
+            // Before edition 2021, we call `panic!()` unqualified,
+            // such that it calls either `std::panic!()` or `core::panic!()`.
+            Path::from_ident(Ident::new(sym::panic, call_site_span))
+        }
+    };
+
+    // Simply uses the user provided message instead of generating custom outputs
+    let expr = if let Some(tokens) = custom_message {
+        let then = cx.expr(
+            call_site_span,
+            ExprKind::MacCall(P(MacCall {
+                path: panic_path(),
+                args: P(DelimArgs {
+                    dspan: DelimSpan::from_single(call_site_span),
+                    delim: Delimiter::Parenthesis,
+                    tokens,
+                }),
+            })),
+        );
+        expr_if_not(cx, call_site_span, cond_expr, then, None)
+    }
+    // If `generic_assert` is enabled, generates rich captured outputs
+    //
+    // FIXME(c410-f3r) See https://github.com/rust-lang/rust/issues/96949
+    else if cx.ecfg.features.generic_assert {
+        context::Context::new(cx, call_site_span).build(cond_expr, panic_path())
+    }
+    // If `generic_assert` is not enabled, only outputs a literal "assertion failed: ..."
+    // string
+    else {
+        // Pass our own message directly to $crate::panicking::panic(),
+        // because it might contain `{` and `}` that should always be
+        // passed literally.
+        let then = cx.expr_call_global(
+            call_site_span,
+            cx.std_path(&[sym::panicking, sym::panic]),
+            thin_vec![cx.expr_str(
+                DUMMY_SP,
+                Symbol::intern(&format!(
+                    "assertion failed: {}",
+                    pprust::expr_to_string(&cond_expr)
+                )),
+            )],
+        );
+        expr_if_not(cx, call_site_span, cond_expr, then, None)
+    };
+
+    ExpandResult::Ready(MacEager::expr(expr))
+}
+
+struct Assert {
+    cond_expr: P<Expr>,
+    custom_message: Option<TokenStream>,
+}
+
+// if !{ ... } { ... } else { ... }
+fn expr_if_not(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    cond: P<Expr>,
+    then: P<Expr>,
+    els: Option<P<Expr>>,
+) -> P<Expr> {
+    cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els)
+}
+
+fn parse_assert<'a>(cx: &ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
+    let mut parser = cx.new_parser_from_tts(stream);
+
+    if parser.token == token::Eof {
+        return Err(cx.dcx().create_err(errors::AssertRequiresBoolean { span: sp }));
+    }
+
+    let cond_expr = parser.parse_expr()?;
+
+    // Some crates use the `assert!` macro in the following form (note extra semicolon):
+    //
+    // assert!(
+    //     my_function();
+    // );
+    //
+    // Emit an error about semicolon and suggest removing it.
+    if parser.token == token::Semi {
+        cx.dcx().emit_err(errors::AssertRequiresExpression { span: sp, token: parser.token.span });
+        parser.bump();
+    }
+
+    // Some crates use the `assert!` macro in the following form (note missing comma before
+    // message):
+    //
+    // assert!(true "error message");
+    //
+    // Emit an error and suggest inserting a comma.
+    let custom_message =
+        if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
+            let comma = parser.prev_token.span.shrink_to_hi();
+            cx.dcx().emit_err(errors::AssertMissingComma { span: parser.token.span, comma });
+
+            parse_custom_message(&mut parser)
+        } else if parser.eat(&token::Comma) {
+            parse_custom_message(&mut parser)
+        } else {
+            None
+        };
+
+    if parser.token != token::Eof {
+        parser.unexpected()?;
+    }
+
+    Ok(Assert { cond_expr, custom_message })
+}
+
+fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
+    let ts = parser.parse_tokens();
+    if !ts.is_empty() { Some(ts) } else { None }
+}
diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs
new file mode 100644
index 00000000000..a98cb6f0f76
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/assert/context.rs
@@ -0,0 +1,462 @@
+use rustc_ast::{
+    ptr::P,
+    token::{self, Delimiter, IdentIsRaw},
+    tokenstream::{DelimSpan, TokenStream, TokenTree},
+    BinOpKind, BorrowKind, DelimArgs, Expr, ExprKind, ItemKind, MacCall, MethodCall, Mutability,
+    Path, PathSegment, Stmt, StructRest, UnOp, UseTree, UseTreeKind, DUMMY_NODE_ID,
+};
+use rustc_ast_pretty::pprust;
+use rustc_data_structures::fx::FxHashSet;
+use rustc_expand::base::ExtCtxt;
+use rustc_span::{
+    symbol::{sym, Ident, Symbol},
+    Span,
+};
+use thin_vec::{thin_vec, ThinVec};
+
+pub(super) struct Context<'cx, 'a> {
+    // An optimization.
+    //
+    // Elements that aren't consumed (PartialEq, PartialOrd, ...) can be copied **after** the
+    // `assert!` expression fails rather than copied on-the-fly.
+    best_case_captures: Vec<Stmt>,
+    // Top-level `let captureN = Capture::new()` statements
+    capture_decls: Vec<Capture>,
+    cx: &'cx ExtCtxt<'a>,
+    // Formatting string used for debugging
+    fmt_string: String,
+    // If the current expression being visited consumes itself. Used to construct
+    // `best_case_captures`.
+    is_consumed: bool,
+    // Top-level `let __local_bindN = &expr` statements
+    local_bind_decls: Vec<Stmt>,
+    // Used to avoid capturing duplicated paths
+    //
+    // ```rust
+    // let a = 1i32;
+    // assert!(add(a, a) == 3);
+    // ```
+    paths: FxHashSet<Ident>,
+    span: Span,
+}
+
+impl<'cx, 'a> Context<'cx, 'a> {
+    pub(super) fn new(cx: &'cx ExtCtxt<'a>, span: Span) -> Self {
+        Self {
+            best_case_captures: <_>::default(),
+            capture_decls: <_>::default(),
+            cx,
+            fmt_string: <_>::default(),
+            is_consumed: true,
+            local_bind_decls: <_>::default(),
+            paths: <_>::default(),
+            span,
+        }
+    }
+
+    /// Builds the whole `assert!` expression. For example, `let elem = 1; assert!(elem == 1);` expands to:
+    ///
+    /// ```rust
+    /// let elem = 1;
+    /// {
+    ///   #[allow(unused_imports)]
+    ///   use ::core::asserting::{TryCaptureGeneric, TryCapturePrintable};
+    ///   let mut __capture0 = ::core::asserting::Capture::new();
+    ///   let __local_bind0 = &elem;
+    ///   if !(
+    ///     *{
+    ///       (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0);
+    ///       __local_bind0
+    ///     } == 1
+    ///   ) {
+    ///     panic!("Assertion failed: elem == 1\nWith captures:\n  elem = {:?}", __capture0)
+    ///   }
+    /// }
+    /// ```
+    pub(super) fn build(mut self, mut cond_expr: P<Expr>, panic_path: Path) -> P<Expr> {
+        let expr_str = pprust::expr_to_string(&cond_expr);
+        self.manage_cond_expr(&mut cond_expr);
+        let initial_imports = self.build_initial_imports();
+        let panic = self.build_panic(&expr_str, panic_path);
+        let cond_expr_with_unlikely = self.build_unlikely(cond_expr);
+
+        let Self { best_case_captures, capture_decls, cx, local_bind_decls, span, .. } = self;
+
+        let mut assert_then_stmts = ThinVec::with_capacity(2);
+        assert_then_stmts.extend(best_case_captures);
+        assert_then_stmts.push(self.cx.stmt_expr(panic));
+        let assert_then = self.cx.block(span, assert_then_stmts);
+
+        let mut stmts = ThinVec::with_capacity(4);
+        stmts.push(initial_imports);
+        stmts.extend(capture_decls.into_iter().map(|c| c.decl));
+        stmts.extend(local_bind_decls);
+        stmts.push(
+            cx.stmt_expr(cx.expr(span, ExprKind::If(cond_expr_with_unlikely, assert_then, None))),
+        );
+        cx.expr_block(cx.block(span, stmts))
+    }
+
+    /// Initial **trait** imports
+    ///
+    /// use ::core::asserting::{ ... };
+    fn build_initial_imports(&self) -> Stmt {
+        let nested_tree = |this: &Self, sym| {
+            (
+                UseTree {
+                    prefix: this.cx.path(this.span, vec![Ident::with_dummy_span(sym)]),
+                    kind: UseTreeKind::Simple(None),
+                    span: this.span,
+                },
+                DUMMY_NODE_ID,
+            )
+        };
+        self.cx.stmt_item(
+            self.span,
+            self.cx.item(
+                self.span,
+                Ident::empty(),
+                thin_vec![self.cx.attr_nested_word(sym::allow, sym::unused_imports, self.span)],
+                ItemKind::Use(UseTree {
+                    prefix: self.cx.path(self.span, self.cx.std_path(&[sym::asserting])),
+                    kind: UseTreeKind::Nested {
+                        items: thin_vec![
+                            nested_tree(self, sym::TryCaptureGeneric),
+                            nested_tree(self, sym::TryCapturePrintable),
+                        ],
+                        span: self.span,
+                    },
+                    span: self.span,
+                }),
+            ),
+        )
+    }
+
+    /// Takes the conditional expression of `assert!` and then wraps it inside `unlikely`
+    fn build_unlikely(&self, cond_expr: P<Expr>) -> P<Expr> {
+        let unlikely_path = self.cx.std_path(&[sym::intrinsics, sym::unlikely]);
+        self.cx.expr_call(
+            self.span,
+            self.cx.expr_path(self.cx.path(self.span, unlikely_path)),
+            thin_vec![self.cx.expr(self.span, ExprKind::Unary(UnOp::Not, cond_expr))],
+        )
+    }
+
+    /// The necessary custom `panic!(...)` expression.
+    ///
+    /// panic!(
+    ///     "Assertion failed: ... \n With expansion: ...",
+    ///     __capture0,
+    ///     ...
+    /// );
+    fn build_panic(&self, expr_str: &str, panic_path: Path) -> P<Expr> {
+        let escaped_expr_str = escape_to_fmt(expr_str);
+        let initial = [
+            TokenTree::token_joint(
+                token::Literal(token::Lit {
+                    kind: token::LitKind::Str,
+                    symbol: Symbol::intern(&if self.fmt_string.is_empty() {
+                        format!("Assertion failed: {escaped_expr_str}")
+                    } else {
+                        format!(
+                            "Assertion failed: {escaped_expr_str}\nWith captures:\n{}",
+                            &self.fmt_string
+                        )
+                    }),
+                    suffix: None,
+                }),
+                self.span,
+            ),
+            TokenTree::token_alone(token::Comma, self.span),
+        ];
+        let captures = self.capture_decls.iter().flat_map(|cap| {
+            [
+                TokenTree::token_joint(
+                    token::Ident(cap.ident.name, IdentIsRaw::No),
+                    cap.ident.span,
+                ),
+                TokenTree::token_alone(token::Comma, self.span),
+            ]
+        });
+        self.cx.expr(
+            self.span,
+            ExprKind::MacCall(P(MacCall {
+                path: panic_path,
+                args: P(DelimArgs {
+                    dspan: DelimSpan::from_single(self.span),
+                    delim: Delimiter::Parenthesis,
+                    tokens: initial.into_iter().chain(captures).collect::<TokenStream>(),
+                }),
+            })),
+        )
+    }
+
+    /// Recursive function called until `cond_expr` and `fmt_str` are fully modified.
+    ///
+    /// See [Self::manage_initial_capture] and [Self::manage_try_capture]
+    fn manage_cond_expr(&mut self, expr: &mut P<Expr>) {
+        match &mut expr.kind {
+            ExprKind::AddrOf(_, mutability, local_expr) => {
+                self.with_is_consumed_management(matches!(mutability, Mutability::Mut), |this| {
+                    this.manage_cond_expr(local_expr)
+                });
+            }
+            ExprKind::Array(local_exprs) => {
+                for local_expr in local_exprs {
+                    self.manage_cond_expr(local_expr);
+                }
+            }
+            ExprKind::Binary(op, lhs, rhs) => {
+                self.with_is_consumed_management(
+                    matches!(
+                        op.node,
+                        BinOpKind::Add
+                            | BinOpKind::And
+                            | BinOpKind::BitAnd
+                            | BinOpKind::BitOr
+                            | BinOpKind::BitXor
+                            | BinOpKind::Div
+                            | BinOpKind::Mul
+                            | BinOpKind::Or
+                            | BinOpKind::Rem
+                            | BinOpKind::Shl
+                            | BinOpKind::Shr
+                            | BinOpKind::Sub
+                    ),
+                    |this| {
+                        this.manage_cond_expr(lhs);
+                        this.manage_cond_expr(rhs);
+                    },
+                );
+            }
+            ExprKind::Call(_, local_exprs) => {
+                for local_expr in local_exprs {
+                    self.manage_cond_expr(local_expr);
+                }
+            }
+            ExprKind::Cast(local_expr, _) => {
+                self.manage_cond_expr(local_expr);
+            }
+            ExprKind::If(local_expr, _, _) => {
+                self.manage_cond_expr(local_expr);
+            }
+            ExprKind::Index(prefix, suffix, _) => {
+                self.manage_cond_expr(prefix);
+                self.manage_cond_expr(suffix);
+            }
+            ExprKind::Let(_, local_expr, _, _) => {
+                self.manage_cond_expr(local_expr);
+            }
+            ExprKind::Match(local_expr, ..) => {
+                self.manage_cond_expr(local_expr);
+            }
+            ExprKind::MethodCall(call) => {
+                for arg in &mut call.args {
+                    self.manage_cond_expr(arg);
+                }
+            }
+            ExprKind::Path(_, Path { segments, .. }) if let [path_segment] = &segments[..] => {
+                let path_ident = path_segment.ident;
+                self.manage_initial_capture(expr, path_ident);
+            }
+            ExprKind::Paren(local_expr) => {
+                self.manage_cond_expr(local_expr);
+            }
+            ExprKind::Range(prefix, suffix, _) => {
+                if let Some(elem) = prefix {
+                    self.manage_cond_expr(elem);
+                }
+                if let Some(elem) = suffix {
+                    self.manage_cond_expr(elem);
+                }
+            }
+            ExprKind::Repeat(local_expr, elem) => {
+                self.manage_cond_expr(local_expr);
+                self.manage_cond_expr(&mut elem.value);
+            }
+            ExprKind::Struct(elem) => {
+                for field in &mut elem.fields {
+                    self.manage_cond_expr(&mut field.expr);
+                }
+                if let StructRest::Base(local_expr) = &mut elem.rest {
+                    self.manage_cond_expr(local_expr);
+                }
+            }
+            ExprKind::Tup(local_exprs) => {
+                for local_expr in local_exprs {
+                    self.manage_cond_expr(local_expr);
+                }
+            }
+            ExprKind::Unary(un_op, local_expr) => {
+                self.with_is_consumed_management(matches!(un_op, UnOp::Neg | UnOp::Not), |this| {
+                    this.manage_cond_expr(local_expr)
+                });
+            }
+            // Expressions that are not worth or can not be captured.
+            //
+            // Full list instead of `_` to catch possible future inclusions and to
+            // sync with the `rfc-2011-nicer-assert-messages/all-expr-kinds.rs` test.
+            ExprKind::Assign(_, _, _)
+            | ExprKind::AssignOp(_, _, _)
+            | ExprKind::Gen(_, _, _)
+            | ExprKind::Await(_, _)
+            | ExprKind::Block(_, _)
+            | ExprKind::Break(_, _)
+            | ExprKind::Closure(_)
+            | ExprKind::ConstBlock(_)
+            | ExprKind::Continue(_)
+            | ExprKind::Dummy
+            | ExprKind::Err(_)
+            | ExprKind::Field(_, _)
+            | ExprKind::ForLoop { .. }
+            | ExprKind::FormatArgs(_)
+            | ExprKind::IncludedBytes(..)
+            | ExprKind::InlineAsm(_)
+            | ExprKind::Lit(_)
+            | ExprKind::Loop(_, _, _)
+            | ExprKind::MacCall(_)
+            | ExprKind::OffsetOf(_, _)
+            | ExprKind::Path(_, _)
+            | ExprKind::Ret(_)
+            | ExprKind::Try(_)
+            | ExprKind::TryBlock(_)
+            | ExprKind::Type(_, _)
+            | ExprKind::Underscore
+            | ExprKind::While(_, _, _)
+            | ExprKind::Yeet(_)
+            | ExprKind::Become(_)
+            | ExprKind::Yield(_) => {}
+        }
+    }
+
+    /// Pushes the top-level declarations and modifies `expr` to try capturing variables.
+    ///
+    /// `fmt_str`, the formatting string used for debugging, is constructed to show possible
+    /// captured variables.
+    fn manage_initial_capture(&mut self, expr: &mut P<Expr>, path_ident: Ident) {
+        if self.paths.contains(&path_ident) {
+            return;
+        } else {
+            self.fmt_string.push_str("  ");
+            self.fmt_string.push_str(path_ident.as_str());
+            self.fmt_string.push_str(" = {:?}\n");
+            let _ = self.paths.insert(path_ident);
+        }
+        let curr_capture_idx = self.capture_decls.len();
+        let capture_string = format!("__capture{curr_capture_idx}");
+        let ident = Ident::new(Symbol::intern(&capture_string), self.span);
+        let init_std_path = self.cx.std_path(&[sym::asserting, sym::Capture, sym::new]);
+        let init = self.cx.expr_call(
+            self.span,
+            self.cx.expr_path(self.cx.path(self.span, init_std_path)),
+            ThinVec::new(),
+        );
+        let capture = Capture { decl: self.cx.stmt_let(self.span, true, ident, init), ident };
+        self.capture_decls.push(capture);
+        self.manage_try_capture(ident, curr_capture_idx, expr);
+    }
+
+    /// Tries to copy `__local_bindN` into `__captureN`.
+    ///
+    /// *{
+    ///    (&Wrapper(__local_bindN)).try_capture(&mut __captureN);
+    ///    __local_bindN
+    /// }
+    fn manage_try_capture(&mut self, capture: Ident, curr_capture_idx: usize, expr: &mut P<Expr>) {
+        let local_bind_string = format!("__local_bind{curr_capture_idx}");
+        let local_bind = Ident::new(Symbol::intern(&local_bind_string), self.span);
+        self.local_bind_decls.push(self.cx.stmt_let(
+            self.span,
+            false,
+            local_bind,
+            self.cx.expr_addr_of(self.span, expr.clone()),
+        ));
+        let wrapper = self.cx.expr_call(
+            self.span,
+            self.cx.expr_path(
+                self.cx.path(self.span, self.cx.std_path(&[sym::asserting, sym::Wrapper])),
+            ),
+            thin_vec![self.cx.expr_path(Path::from_ident(local_bind))],
+        );
+        let try_capture_call = self
+            .cx
+            .stmt_expr(expr_method_call(
+                self.cx,
+                PathSegment {
+                    args: None,
+                    id: DUMMY_NODE_ID,
+                    ident: Ident::new(sym::try_capture, self.span),
+                },
+                expr_paren(self.cx, self.span, self.cx.expr_addr_of(self.span, wrapper)),
+                thin_vec![expr_addr_of_mut(
+                    self.cx,
+                    self.span,
+                    self.cx.expr_path(Path::from_ident(capture)),
+                )],
+                self.span,
+            ))
+            .add_trailing_semicolon();
+        let local_bind_path = self.cx.expr_path(Path::from_ident(local_bind));
+        let rslt = if self.is_consumed {
+            let ret = self.cx.stmt_expr(local_bind_path);
+            self.cx.expr_block(self.cx.block(self.span, thin_vec![try_capture_call, ret]))
+        } else {
+            self.best_case_captures.push(try_capture_call);
+            local_bind_path
+        };
+        *expr = self.cx.expr_deref(self.span, rslt);
+    }
+
+    // Calls `f` with the internal `is_consumed` set to `curr_is_consumed` and then
+    // sets the internal `is_consumed` back to its original value.
+    fn with_is_consumed_management(&mut self, curr_is_consumed: bool, f: impl FnOnce(&mut Self)) {
+        let prev_is_consumed = self.is_consumed;
+        self.is_consumed = curr_is_consumed;
+        f(self);
+        self.is_consumed = prev_is_consumed;
+    }
+}
+
+/// Information about a captured element.
+#[derive(Debug)]
+struct Capture {
+    // Generated indexed `Capture` statement.
+    //
+    // `let __capture{} = Capture::new();`
+    decl: Stmt,
+    // The name of the generated indexed `Capture` variable.
+    //
+    // `__capture{}`
+    ident: Ident,
+}
+
+/// Escapes to use as a formatting string.
+fn escape_to_fmt(s: &str) -> String {
+    let mut rslt = String::with_capacity(s.len());
+    for c in s.chars() {
+        rslt.extend(c.escape_debug());
+        match c {
+            '{' | '}' => rslt.push(c),
+            _ => {}
+        }
+    }
+    rslt
+}
+
+fn expr_addr_of_mut(cx: &ExtCtxt<'_>, sp: Span, e: P<Expr>) -> P<Expr> {
+    cx.expr(sp, ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e))
+}
+
+fn expr_method_call(
+    cx: &ExtCtxt<'_>,
+    seg: PathSegment,
+    receiver: P<Expr>,
+    args: ThinVec<P<Expr>>,
+    span: Span,
+) -> P<Expr> {
+    cx.expr(span, ExprKind::MethodCall(Box::new(MethodCall { seg, receiver, args, span })))
+}
+
+fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: P<Expr>) -> P<Expr> {
+    cx.expr(sp, ExprKind::Paren(e))
+}
diff --git a/compiler/rustc_builtin_macros/src/cfg.rs b/compiler/rustc_builtin_macros/src/cfg.rs
new file mode 100644
index 00000000000..827719d7944
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/cfg.rs
@@ -0,0 +1,54 @@
+//! The compiler code necessary to support the cfg! extension, which expands to
+//! a literal `true` or `false` based on whether the given cfg matches the
+//! current compilation environment.
+
+use crate::errors;
+use rustc_ast as ast;
+use rustc_ast::token;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_attr as attr;
+use rustc_errors::PResult;
+use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
+use rustc_span::Span;
+
+pub(crate) fn expand_cfg(
+    cx: &mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'static> {
+    let sp = cx.with_def_site_ctxt(sp);
+
+    ExpandResult::Ready(match parse_cfg(cx, sp, tts) {
+        Ok(cfg) => {
+            let matches_cfg = attr::cfg_matches(
+                &cfg,
+                &cx.sess,
+                cx.current_expansion.lint_node_id,
+                Some(cx.ecfg.features),
+            );
+            MacEager::expr(cx.expr_bool(sp, matches_cfg))
+        }
+        Err(err) => {
+            let guar = err.emit();
+            DummyResult::any(sp, guar)
+        }
+    })
+}
+
+fn parse_cfg<'a>(cx: &ExtCtxt<'a>, span: Span, tts: TokenStream) -> PResult<'a, ast::MetaItem> {
+    let mut p = cx.new_parser_from_tts(tts);
+
+    if p.token == token::Eof {
+        return Err(cx.dcx().create_err(errors::RequiresCfgPattern { span }));
+    }
+
+    let cfg = p.parse_meta_item()?;
+
+    let _ = p.eat(&token::Comma);
+
+    if !p.eat(&token::Eof) {
+        return Err(cx.dcx().create_err(errors::OneCfgPattern { span }));
+    }
+
+    Ok(cfg)
+}
diff --git a/compiler/rustc_builtin_macros/src/cfg_accessible.rs b/compiler/rustc_builtin_macros/src/cfg_accessible.rs
new file mode 100644
index 00000000000..98c0ca3a526
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/cfg_accessible.rs
@@ -0,0 +1,70 @@
+//! Implementation of the `#[cfg_accessible(path)]` attribute macro.
+
+use crate::errors;
+use rustc_ast as ast;
+use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier};
+use rustc_feature::AttributeTemplate;
+use rustc_parse::validate_attr;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+pub(crate) struct Expander;
+
+fn validate_input<'a>(ecx: &ExtCtxt<'_>, mi: &'a ast::MetaItem) -> Option<&'a ast::Path> {
+    use errors::CfgAccessibleInvalid::*;
+    match mi.meta_item_list() {
+        None => {}
+        Some([]) => {
+            ecx.dcx().emit_err(UnspecifiedPath(mi.span));
+        }
+        Some([_, .., l]) => {
+            ecx.dcx().emit_err(MultiplePaths(l.span()));
+        }
+        Some([nmi]) => match nmi.meta_item() {
+            None => {
+                ecx.dcx().emit_err(LiteralPath(nmi.span()));
+            }
+            Some(mi) => {
+                if !mi.is_word() {
+                    ecx.dcx().emit_err(HasArguments(mi.span));
+                }
+                return Some(&mi.path);
+            }
+        },
+    }
+    None
+}
+
+impl MultiItemModifier for Expander {
+    fn expand(
+        &self,
+        ecx: &mut ExtCtxt<'_>,
+        span: Span,
+        meta_item: &ast::MetaItem,
+        item: Annotatable,
+        _is_derive_const: bool,
+    ) -> ExpandResult<Vec<Annotatable>, Annotatable> {
+        let template = AttributeTemplate { list: Some("path"), ..Default::default() };
+        validate_attr::check_builtin_meta_item(
+            &ecx.sess.psess,
+            meta_item,
+            ast::AttrStyle::Outer,
+            sym::cfg_accessible,
+            template,
+        );
+
+        let Some(path) = validate_input(ecx, meta_item) else {
+            return ExpandResult::Ready(Vec::new());
+        };
+
+        match ecx.resolver.cfg_accessible(ecx.current_expansion.id, path) {
+            Ok(true) => ExpandResult::Ready(vec![item]),
+            Ok(false) => ExpandResult::Ready(Vec::new()),
+            Err(Indeterminate) if ecx.force_mode => {
+                ecx.dcx().emit_err(errors::CfgAccessibleIndeterminate { span });
+                ExpandResult::Ready(vec![item])
+            }
+            Err(Indeterminate) => ExpandResult::Retry(item),
+        }
+    }
+}
diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs
new file mode 100644
index 00000000000..03aff6f9633
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs
@@ -0,0 +1,287 @@
+use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute};
+
+use core::ops::ControlFlow;
+use rustc_ast as ast;
+use rustc_ast::mut_visit::MutVisitor;
+use rustc_ast::ptr::P;
+use rustc_ast::visit::Visitor;
+use rustc_ast::NodeId;
+use rustc_ast::{mut_visit, visit};
+use rustc_ast::{Attribute, HasAttrs, HasTokens};
+use rustc_errors::PResult;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_expand::config::StripUnconfigured;
+use rustc_expand::configure;
+use rustc_feature::Features;
+use rustc_parse::parser::{ForceCollect, Parser};
+use rustc_session::Session;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use smallvec::SmallVec;
+use tracing::instrument;
+
+pub(crate) fn expand(
+    ecx: &mut ExtCtxt<'_>,
+    _span: Span,
+    meta_item: &ast::MetaItem,
+    annotatable: Annotatable,
+) -> Vec<Annotatable> {
+    check_builtin_macro_attribute(ecx, meta_item, sym::cfg_eval);
+    warn_on_duplicate_attribute(ecx, &annotatable, sym::cfg_eval);
+    vec![cfg_eval(ecx.sess, ecx.ecfg.features, annotatable, ecx.current_expansion.lint_node_id)]
+}
+
+pub(crate) fn cfg_eval(
+    sess: &Session,
+    features: &Features,
+    annotatable: Annotatable,
+    lint_node_id: NodeId,
+) -> Annotatable {
+    let features = Some(features);
+    CfgEval { cfg: &mut StripUnconfigured { sess, features, config_tokens: true, lint_node_id } }
+        .configure_annotatable(annotatable)
+        // Since the item itself has already been configured by the `InvocationCollector`,
+        // we know that fold result vector will contain exactly one element.
+        .unwrap()
+}
+
+struct CfgEval<'a, 'b> {
+    cfg: &'a mut StripUnconfigured<'b>,
+}
+
+fn flat_map_annotatable(
+    vis: &mut impl MutVisitor,
+    annotatable: Annotatable,
+) -> Option<Annotatable> {
+    match annotatable {
+        Annotatable::Item(item) => vis.flat_map_item(item).pop().map(Annotatable::Item),
+        Annotatable::TraitItem(item) => {
+            vis.flat_map_trait_item(item).pop().map(Annotatable::TraitItem)
+        }
+        Annotatable::ImplItem(item) => {
+            vis.flat_map_impl_item(item).pop().map(Annotatable::ImplItem)
+        }
+        Annotatable::ForeignItem(item) => {
+            vis.flat_map_foreign_item(item).pop().map(Annotatable::ForeignItem)
+        }
+        Annotatable::Stmt(stmt) => {
+            vis.flat_map_stmt(stmt.into_inner()).pop().map(P).map(Annotatable::Stmt)
+        }
+        Annotatable::Expr(mut expr) => {
+            vis.visit_expr(&mut expr);
+            Some(Annotatable::Expr(expr))
+        }
+        Annotatable::Arm(arm) => vis.flat_map_arm(arm).pop().map(Annotatable::Arm),
+        Annotatable::ExprField(field) => {
+            vis.flat_map_expr_field(field).pop().map(Annotatable::ExprField)
+        }
+        Annotatable::PatField(fp) => vis.flat_map_pat_field(fp).pop().map(Annotatable::PatField),
+        Annotatable::GenericParam(param) => {
+            vis.flat_map_generic_param(param).pop().map(Annotatable::GenericParam)
+        }
+        Annotatable::Param(param) => vis.flat_map_param(param).pop().map(Annotatable::Param),
+        Annotatable::FieldDef(sf) => vis.flat_map_field_def(sf).pop().map(Annotatable::FieldDef),
+        Annotatable::Variant(v) => vis.flat_map_variant(v).pop().map(Annotatable::Variant),
+        Annotatable::Crate(mut krate) => {
+            vis.visit_crate(&mut krate);
+            Some(Annotatable::Crate(krate))
+        }
+    }
+}
+
+fn has_cfg_or_cfg_attr(annotatable: &Annotatable) -> bool {
+    struct CfgFinder;
+
+    impl<'ast> visit::Visitor<'ast> for CfgFinder {
+        type Result = ControlFlow<()>;
+        fn visit_attribute(&mut self, attr: &'ast Attribute) -> ControlFlow<()> {
+            if attr
+                .ident()
+                .is_some_and(|ident| ident.name == sym::cfg || ident.name == sym::cfg_attr)
+            {
+                ControlFlow::Break(())
+            } else {
+                ControlFlow::Continue(())
+            }
+        }
+    }
+
+    let res = match annotatable {
+        Annotatable::Item(item) => CfgFinder.visit_item(item),
+        Annotatable::TraitItem(item) => CfgFinder.visit_assoc_item(item, visit::AssocCtxt::Trait),
+        Annotatable::ImplItem(item) => CfgFinder.visit_assoc_item(item, visit::AssocCtxt::Impl),
+        Annotatable::ForeignItem(item) => CfgFinder.visit_foreign_item(item),
+        Annotatable::Stmt(stmt) => CfgFinder.visit_stmt(stmt),
+        Annotatable::Expr(expr) => CfgFinder.visit_expr(expr),
+        Annotatable::Arm(arm) => CfgFinder.visit_arm(arm),
+        Annotatable::ExprField(field) => CfgFinder.visit_expr_field(field),
+        Annotatable::PatField(field) => CfgFinder.visit_pat_field(field),
+        Annotatable::GenericParam(param) => CfgFinder.visit_generic_param(param),
+        Annotatable::Param(param) => CfgFinder.visit_param(param),
+        Annotatable::FieldDef(field) => CfgFinder.visit_field_def(field),
+        Annotatable::Variant(variant) => CfgFinder.visit_variant(variant),
+        Annotatable::Crate(krate) => CfgFinder.visit_crate(krate),
+    };
+    res.is_break()
+}
+
+impl CfgEval<'_, '_> {
+    fn configure<T: HasAttrs + HasTokens>(&mut self, node: T) -> Option<T> {
+        self.cfg.configure(node)
+    }
+
+    fn configure_annotatable(&mut self, mut annotatable: Annotatable) -> Option<Annotatable> {
+        // Tokenizing and re-parsing the `Annotatable` can have a significant
+        // performance impact, so try to avoid it if possible
+        if !has_cfg_or_cfg_attr(&annotatable) {
+            return Some(annotatable);
+        }
+
+        // The majority of parsed attribute targets will never need to have early cfg-expansion
+        // run (e.g. they are not part of a `#[derive]` or `#[cfg_eval]` macro input).
+        // Therefore, we normally do not capture the necessary information about `#[cfg]`
+        // and `#[cfg_attr]` attributes during parsing.
+        //
+        // Therefore, when we actually *do* run early cfg-expansion, we need to tokenize
+        // and re-parse the attribute target, this time capturing information about
+        // the location of `#[cfg]` and `#[cfg_attr]` in the token stream. The tokenization
+        // process is lossless, so this process is invisible to proc-macros.
+
+        let parse_annotatable_with: for<'a> fn(&mut Parser<'a>) -> PResult<'a, _> =
+            match annotatable {
+                Annotatable::Item(_) => {
+                    |parser| Ok(Annotatable::Item(parser.parse_item(ForceCollect::Yes)?.unwrap()))
+                }
+                Annotatable::TraitItem(_) => |parser| {
+                    Ok(Annotatable::TraitItem(
+                        parser.parse_trait_item(ForceCollect::Yes)?.unwrap().unwrap(),
+                    ))
+                },
+                Annotatable::ImplItem(_) => |parser| {
+                    Ok(Annotatable::ImplItem(
+                        parser.parse_impl_item(ForceCollect::Yes)?.unwrap().unwrap(),
+                    ))
+                },
+                Annotatable::ForeignItem(_) => |parser| {
+                    Ok(Annotatable::ForeignItem(
+                        parser.parse_foreign_item(ForceCollect::Yes)?.unwrap().unwrap(),
+                    ))
+                },
+                Annotatable::Stmt(_) => |parser| {
+                    Ok(Annotatable::Stmt(P(parser
+                        .parse_stmt_without_recovery(false, ForceCollect::Yes)?
+                        .unwrap())))
+                },
+                Annotatable::Expr(_) => {
+                    |parser| Ok(Annotatable::Expr(parser.parse_expr_force_collect()?))
+                }
+                _ => unreachable!(),
+            };
+
+        // 'Flatten' all nonterminals (i.e. `TokenKind::Interpolated`)
+        // to `None`-delimited groups containing the corresponding tokens. This
+        // is normally delayed until the proc-macro server actually needs to
+        // provide a `TokenKind::Interpolated` to a proc-macro. We do this earlier,
+        // so that we can handle cases like:
+        //
+        // ```rust
+        // #[cfg_eval] #[cfg] $item
+        //```
+        //
+        // where `$item` is `#[cfg_attr] struct Foo {}`. We want to make
+        // sure to evaluate *all* `#[cfg]` and `#[cfg_attr]` attributes - the simplest
+        // way to do this is to do a single parse of a stream without any nonterminals.
+        let orig_tokens = annotatable.to_tokens().flattened();
+
+        // Re-parse the tokens, setting the `capture_cfg` flag to save extra information
+        // to the captured `AttrTokenStream` (specifically, we capture
+        // `AttrTokenTree::AttributesData` for all occurrences of `#[cfg]` and `#[cfg_attr]`)
+        let mut parser = Parser::new(&self.cfg.sess.psess, orig_tokens, None);
+        parser.capture_cfg = true;
+        match parse_annotatable_with(&mut parser) {
+            Ok(a) => annotatable = a,
+            Err(err) => {
+                err.emit();
+                return Some(annotatable);
+            }
+        }
+
+        // Now that we have our re-parsed `AttrTokenStream`, recursively configuring
+        // our attribute target will correctly the tokens as well.
+        flat_map_annotatable(self, annotatable)
+    }
+}
+
+impl MutVisitor for CfgEval<'_, '_> {
+    #[instrument(level = "trace", skip(self))]
+    fn visit_expr(&mut self, expr: &mut P<ast::Expr>) {
+        self.cfg.configure_expr(expr, false);
+        mut_visit::noop_visit_expr(expr, self);
+    }
+
+    #[instrument(level = "trace", skip(self))]
+    fn visit_method_receiver_expr(&mut self, expr: &mut P<ast::Expr>) {
+        self.cfg.configure_expr(expr, true);
+        mut_visit::noop_visit_expr(expr, self);
+    }
+
+    fn filter_map_expr(&mut self, expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
+        let mut expr = configure!(self, expr);
+        mut_visit::noop_visit_expr(&mut expr, self);
+        Some(expr)
+    }
+
+    fn flat_map_generic_param(
+        &mut self,
+        param: ast::GenericParam,
+    ) -> SmallVec<[ast::GenericParam; 1]> {
+        mut_visit::noop_flat_map_generic_param(configure!(self, param), self)
+    }
+
+    fn flat_map_stmt(&mut self, stmt: ast::Stmt) -> SmallVec<[ast::Stmt; 1]> {
+        mut_visit::noop_flat_map_stmt(configure!(self, stmt), self)
+    }
+
+    fn flat_map_item(&mut self, item: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
+        mut_visit::noop_flat_map_item(configure!(self, item), self)
+    }
+
+    fn flat_map_impl_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
+        mut_visit::noop_flat_map_item(configure!(self, item), self)
+    }
+
+    fn flat_map_trait_item(&mut self, item: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
+        mut_visit::noop_flat_map_item(configure!(self, item), self)
+    }
+
+    fn flat_map_foreign_item(
+        &mut self,
+        foreign_item: P<ast::ForeignItem>,
+    ) -> SmallVec<[P<ast::ForeignItem>; 1]> {
+        mut_visit::noop_flat_map_item(configure!(self, foreign_item), self)
+    }
+
+    fn flat_map_arm(&mut self, arm: ast::Arm) -> SmallVec<[ast::Arm; 1]> {
+        mut_visit::noop_flat_map_arm(configure!(self, arm), self)
+    }
+
+    fn flat_map_expr_field(&mut self, field: ast::ExprField) -> SmallVec<[ast::ExprField; 1]> {
+        mut_visit::noop_flat_map_expr_field(configure!(self, field), self)
+    }
+
+    fn flat_map_pat_field(&mut self, fp: ast::PatField) -> SmallVec<[ast::PatField; 1]> {
+        mut_visit::noop_flat_map_pat_field(configure!(self, fp), self)
+    }
+
+    fn flat_map_param(&mut self, p: ast::Param) -> SmallVec<[ast::Param; 1]> {
+        mut_visit::noop_flat_map_param(configure!(self, p), self)
+    }
+
+    fn flat_map_field_def(&mut self, sf: ast::FieldDef) -> SmallVec<[ast::FieldDef; 1]> {
+        mut_visit::noop_flat_map_field_def(configure!(self, sf), self)
+    }
+
+    fn flat_map_variant(&mut self, variant: ast::Variant) -> SmallVec<[ast::Variant; 1]> {
+        mut_visit::noop_flat_map_variant(configure!(self, variant), self)
+    }
+}
diff --git a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs
new file mode 100644
index 00000000000..58928815e89
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs
@@ -0,0 +1,42 @@
+//! Attributes injected into the crate root from command line using `-Z crate-attr`.
+
+use crate::errors;
+use rustc_ast::attr::mk_attr;
+use rustc_ast::token;
+use rustc_ast::{self as ast, AttrItem, AttrStyle};
+use rustc_parse::{new_parser_from_source_str, unwrap_or_emit_fatal};
+use rustc_session::parse::ParseSess;
+use rustc_span::FileName;
+
+pub fn inject(krate: &mut ast::Crate, psess: &ParseSess, attrs: &[String]) {
+    for raw_attr in attrs {
+        let mut parser = unwrap_or_emit_fatal(new_parser_from_source_str(
+            psess,
+            FileName::cli_crate_attr_source_code(raw_attr),
+            raw_attr.clone(),
+        ));
+
+        let start_span = parser.token.span;
+        let AttrItem { unsafety, path, args, tokens: _ } = match parser.parse_attr_item(false) {
+            Ok(ai) => ai,
+            Err(err) => {
+                err.emit();
+                continue;
+            }
+        };
+        let end_span = parser.token.span;
+        if parser.token != token::Eof {
+            psess.dcx().emit_err(errors::InvalidCrateAttr { span: start_span.to(end_span) });
+            continue;
+        }
+
+        krate.attrs.push(mk_attr(
+            &psess.attr_id_generator,
+            AttrStyle::Inner,
+            unsafety,
+            path,
+            args,
+            start_span.to(end_span),
+        ));
+    }
+}
diff --git a/compiler/rustc_builtin_macros/src/compile_error.rs b/compiler/rustc_builtin_macros/src/compile_error.rs
new file mode 100644
index 00000000000..a08e8b2819b
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/compile_error.rs
@@ -0,0 +1,26 @@
+// The compiler code necessary to support the compile_error! extension.
+
+use crate::util::get_single_str_from_tts;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
+use rustc_span::Span;
+
+pub(crate) fn expand_compile_error<'cx>(
+    cx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "compile_error!") else {
+        return ExpandResult::Retry(());
+    };
+    let var = match mac {
+        Ok(var) => var,
+        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
+    };
+
+    #[expect(rustc::diagnostic_outside_of_impl, reason = "diagnostic message is specified by user")]
+    #[expect(rustc::untranslatable_diagnostic, reason = "diagnostic message is specified by user")]
+    let guar = cx.dcx().span_err(sp, var.to_string());
+
+    ExpandResult::Ready(DummyResult::any(sp, guar))
+}
diff --git a/compiler/rustc_builtin_macros/src/concat.rs b/compiler/rustc_builtin_macros/src/concat.rs
new file mode 100644
index 00000000000..15af79ef67d
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/concat.rs
@@ -0,0 +1,85 @@
+use crate::errors;
+use crate::util::get_exprs_from_tts;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast::{ExprKind, LitKind, UnOp};
+use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
+use rustc_session::errors::report_lit_error;
+use rustc_span::symbol::Symbol;
+
+pub(crate) fn expand_concat(
+    cx: &mut ExtCtxt<'_>,
+    sp: rustc_span::Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'static> {
+    let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else {
+        return ExpandResult::Retry(());
+    };
+    let es = match mac {
+        Ok(es) => es,
+        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
+    };
+    let mut accumulator = String::new();
+    let mut missing_literal = vec![];
+    let mut guar = None;
+    for e in es {
+        match e.kind {
+            ExprKind::Lit(token_lit) => match LitKind::from_token_lit(token_lit) {
+                Ok(LitKind::Str(s, _) | LitKind::Float(s, _)) => {
+                    accumulator.push_str(s.as_str());
+                }
+                Ok(LitKind::Char(c)) => {
+                    accumulator.push(c);
+                }
+                Ok(LitKind::Int(i, _)) => {
+                    accumulator.push_str(&i.to_string());
+                }
+                Ok(LitKind::Bool(b)) => {
+                    accumulator.push_str(&b.to_string());
+                }
+                Ok(LitKind::CStr(..)) => {
+                    guar = Some(cx.dcx().emit_err(errors::ConcatCStrLit { span: e.span }));
+                }
+                Ok(LitKind::Byte(..) | LitKind::ByteStr(..)) => {
+                    guar = Some(cx.dcx().emit_err(errors::ConcatBytestr { span: e.span }));
+                }
+                Ok(LitKind::Err(guarantee)) => {
+                    guar = Some(guarantee);
+                }
+                Err(err) => {
+                    guar = Some(report_lit_error(&cx.sess.psess, err, token_lit, e.span));
+                }
+            },
+            // We also want to allow negative numeric literals.
+            ExprKind::Unary(UnOp::Neg, ref expr) if let ExprKind::Lit(token_lit) = expr.kind => {
+                match LitKind::from_token_lit(token_lit) {
+                    Ok(LitKind::Int(i, _)) => accumulator.push_str(&format!("-{i}")),
+                    Ok(LitKind::Float(f, _)) => accumulator.push_str(&format!("-{f}")),
+                    Err(err) => {
+                        guar = Some(report_lit_error(&cx.sess.psess, err, token_lit, e.span));
+                    }
+                    _ => missing_literal.push(e.span),
+                }
+            }
+            ExprKind::IncludedBytes(..) => {
+                cx.dcx().emit_err(errors::ConcatBytestr { span: e.span });
+            }
+            ExprKind::Err(guarantee) => {
+                guar = Some(guarantee);
+            }
+            ExprKind::Dummy => cx.dcx().span_bug(e.span, "concatenating `ExprKind::Dummy`"),
+            _ => {
+                missing_literal.push(e.span);
+            }
+        }
+    }
+
+    ExpandResult::Ready(if !missing_literal.is_empty() {
+        let guar = cx.dcx().emit_err(errors::ConcatMissingLiteral { spans: missing_literal });
+        DummyResult::any(sp, guar)
+    } else if let Some(guar) = guar {
+        DummyResult::any(sp, guar)
+    } else {
+        let sp = cx.with_def_site_ctxt(sp);
+        MacEager::expr(cx.expr_str(sp, Symbol::intern(&accumulator)))
+    })
+}
diff --git a/compiler/rustc_builtin_macros/src/concat_bytes.rs b/compiler/rustc_builtin_macros/src/concat_bytes.rs
new file mode 100644
index 00000000000..3130870df41
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/concat_bytes.rs
@@ -0,0 +1,185 @@
+use crate::errors;
+use crate::util::get_exprs_from_tts;
+use rustc_ast::{ptr::P, token, tokenstream::TokenStream, ExprKind, LitIntType, LitKind, UintTy};
+use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
+use rustc_session::errors::report_lit_error;
+use rustc_span::{ErrorGuaranteed, Span};
+
+/// Emits errors for literal expressions that are invalid inside and outside of an array.
+fn invalid_type_err(
+    cx: &ExtCtxt<'_>,
+    token_lit: token::Lit,
+    span: Span,
+    is_nested: bool,
+) -> ErrorGuaranteed {
+    use errors::{
+        ConcatBytesInvalid, ConcatBytesInvalidSuggestion, ConcatBytesNonU8, ConcatBytesOob,
+    };
+    let snippet = cx.sess.source_map().span_to_snippet(span).ok();
+    let dcx = cx.dcx();
+    match LitKind::from_token_lit(token_lit) {
+        Ok(LitKind::CStr(_, _)) => {
+            // Avoid ambiguity in handling of terminal `NUL` by refusing to
+            // concatenate C string literals as bytes.
+            dcx.emit_err(errors::ConcatCStrLit { span })
+        }
+        Ok(LitKind::Char(_)) => {
+            let sugg =
+                snippet.map(|snippet| ConcatBytesInvalidSuggestion::CharLit { span, snippet });
+            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "character", sugg })
+        }
+        Ok(LitKind::Str(_, _)) => {
+            // suggestion would be invalid if we are nested
+            let sugg = if !is_nested {
+                snippet.map(|snippet| ConcatBytesInvalidSuggestion::StrLit { span, snippet })
+            } else {
+                None
+            };
+            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "string", sugg })
+        }
+        Ok(LitKind::Float(_, _)) => {
+            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "float", sugg: None })
+        }
+        Ok(LitKind::Bool(_)) => {
+            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "boolean", sugg: None })
+        }
+        Ok(LitKind::Int(_, _)) if !is_nested => {
+            let sugg =
+                snippet.map(|snippet| ConcatBytesInvalidSuggestion::IntLit { span, snippet });
+            dcx.emit_err(ConcatBytesInvalid { span, lit_kind: "numeric", sugg })
+        }
+        Ok(LitKind::Int(val, LitIntType::Unsuffixed | LitIntType::Unsigned(UintTy::U8))) => {
+            assert!(val.get() > u8::MAX.into()); // must be an error
+            dcx.emit_err(ConcatBytesOob { span })
+        }
+        Ok(LitKind::Int(_, _)) => dcx.emit_err(ConcatBytesNonU8 { span }),
+        Ok(LitKind::ByteStr(..) | LitKind::Byte(_)) => unreachable!(),
+        Ok(LitKind::Err(guar)) => guar,
+        Err(err) => report_lit_error(&cx.sess.psess, err, token_lit, span),
+    }
+}
+
+/// Returns `expr` as a *single* byte literal if applicable.
+///
+/// Otherwise, returns `None`, and either pushes the `expr`'s span to `missing_literals` or
+/// updates `guar` accordingly.
+fn handle_array_element(
+    cx: &ExtCtxt<'_>,
+    guar: &mut Option<ErrorGuaranteed>,
+    missing_literals: &mut Vec<rustc_span::Span>,
+    expr: &P<rustc_ast::Expr>,
+) -> Option<u8> {
+    let dcx = cx.dcx();
+
+    match expr.kind {
+        ExprKind::Lit(token_lit) => {
+            match LitKind::from_token_lit(token_lit) {
+                Ok(LitKind::Int(
+                    val,
+                    LitIntType::Unsuffixed | LitIntType::Unsigned(UintTy::U8),
+                )) if let Ok(val) = u8::try_from(val.get()) => {
+                    return Some(val);
+                }
+                Ok(LitKind::Byte(val)) => return Some(val),
+                Ok(LitKind::ByteStr(..)) => {
+                    guar.get_or_insert_with(|| {
+                        dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: true })
+                    });
+                }
+                _ => {
+                    guar.get_or_insert_with(|| invalid_type_err(cx, token_lit, expr.span, true));
+                }
+            };
+        }
+        ExprKind::Array(_) | ExprKind::Repeat(_, _) => {
+            guar.get_or_insert_with(|| {
+                dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false })
+            });
+        }
+        ExprKind::IncludedBytes(..) => {
+            guar.get_or_insert_with(|| {
+                dcx.emit_err(errors::ConcatBytesArray { span: expr.span, bytestr: false })
+            });
+        }
+        _ => missing_literals.push(expr.span),
+    }
+
+    None
+}
+
+pub(crate) fn expand_concat_bytes(
+    cx: &mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'static> {
+    let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else {
+        return ExpandResult::Retry(());
+    };
+    let es = match mac {
+        Ok(es) => es,
+        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
+    };
+    let mut accumulator = Vec::new();
+    let mut missing_literals = vec![];
+    let mut guar = None;
+    for e in es {
+        match &e.kind {
+            ExprKind::Array(exprs) => {
+                for expr in exprs {
+                    if let Some(elem) =
+                        handle_array_element(cx, &mut guar, &mut missing_literals, expr)
+                    {
+                        accumulator.push(elem);
+                    }
+                }
+            }
+            ExprKind::Repeat(expr, count) => {
+                if let ExprKind::Lit(token_lit) = count.value.kind
+                    && let Ok(LitKind::Int(count_val, _)) = LitKind::from_token_lit(token_lit)
+                {
+                    if let Some(elem) =
+                        handle_array_element(cx, &mut guar, &mut missing_literals, expr)
+                    {
+                        for _ in 0..count_val.get() {
+                            accumulator.push(elem);
+                        }
+                    }
+                } else {
+                    guar = Some(
+                        cx.dcx().emit_err(errors::ConcatBytesBadRepeat { span: count.value.span }),
+                    );
+                }
+            }
+            &ExprKind::Lit(token_lit) => match LitKind::from_token_lit(token_lit) {
+                Ok(LitKind::Byte(val)) => {
+                    accumulator.push(val);
+                }
+                Ok(LitKind::ByteStr(ref bytes, _)) => {
+                    accumulator.extend_from_slice(bytes);
+                }
+                _ => {
+                    guar.get_or_insert_with(|| invalid_type_err(cx, token_lit, e.span, false));
+                }
+            },
+            ExprKind::IncludedBytes(bytes) => {
+                accumulator.extend_from_slice(bytes);
+            }
+            ExprKind::Err(guarantee) => {
+                guar = Some(*guarantee);
+            }
+            ExprKind::Dummy => cx.dcx().span_bug(e.span, "concatenating `ExprKind::Dummy`"),
+            _ => {
+                missing_literals.push(e.span);
+            }
+        }
+    }
+    ExpandResult::Ready(if !missing_literals.is_empty() {
+        let guar = cx.dcx().emit_err(errors::ConcatBytesMissingLiteral { spans: missing_literals });
+        MacEager::expr(DummyResult::raw_expr(sp, Some(guar)))
+    } else if let Some(guar) = guar {
+        MacEager::expr(DummyResult::raw_expr(sp, Some(guar)))
+    } else {
+        let sp = cx.with_def_site_ctxt(sp);
+        MacEager::expr(cx.expr_byte_str(sp, accumulator))
+    })
+}
diff --git a/compiler/rustc_builtin_macros/src/concat_idents.rs b/compiler/rustc_builtin_macros/src/concat_idents.rs
new file mode 100644
index 00000000000..13729a9d250
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/concat_idents.rs
@@ -0,0 +1,72 @@
+use rustc_ast::ptr::P;
+use rustc_ast::token::{self, Token};
+use rustc_ast::tokenstream::{TokenStream, TokenTree};
+use rustc_ast::{AttrVec, Expr, ExprKind, Path, Ty, TyKind, DUMMY_NODE_ID};
+use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacResult, MacroExpanderResult};
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::Span;
+
+use crate::errors;
+
+pub(crate) fn expand_concat_idents<'cx>(
+    cx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    if tts.is_empty() {
+        let guar = cx.dcx().emit_err(errors::ConcatIdentsMissingArgs { span: sp });
+        return ExpandResult::Ready(DummyResult::any(sp, guar));
+    }
+
+    let mut res_str = String::new();
+    for (i, e) in tts.trees().enumerate() {
+        if i & 1 == 1 {
+            match e {
+                TokenTree::Token(Token { kind: token::Comma, .. }, _) => {}
+                _ => {
+                    let guar = cx.dcx().emit_err(errors::ConcatIdentsMissingComma { span: sp });
+                    return ExpandResult::Ready(DummyResult::any(sp, guar));
+                }
+            }
+        } else {
+            if let TokenTree::Token(token, _) = e {
+                if let Some((ident, _)) = token.ident() {
+                    res_str.push_str(ident.name.as_str());
+                    continue;
+                }
+            }
+
+            let guar = cx.dcx().emit_err(errors::ConcatIdentsIdentArgs { span: sp });
+            return ExpandResult::Ready(DummyResult::any(sp, guar));
+        }
+    }
+
+    let ident = Ident::new(Symbol::intern(&res_str), cx.with_call_site_ctxt(sp));
+
+    struct ConcatIdentsResult {
+        ident: Ident,
+    }
+
+    impl MacResult for ConcatIdentsResult {
+        fn make_expr(self: Box<Self>) -> Option<P<Expr>> {
+            Some(P(Expr {
+                id: DUMMY_NODE_ID,
+                kind: ExprKind::Path(None, Path::from_ident(self.ident)),
+                span: self.ident.span,
+                attrs: AttrVec::new(),
+                tokens: None,
+            }))
+        }
+
+        fn make_ty(self: Box<Self>) -> Option<P<Ty>> {
+            Some(P(Ty {
+                id: DUMMY_NODE_ID,
+                kind: TyKind::Path(None, Path::from_ident(self.ident)),
+                span: self.ident.span,
+                tokens: None,
+            }))
+        }
+    }
+
+    ExpandResult::Ready(Box::new(ConcatIdentsResult { ident }))
+}
diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs
new file mode 100644
index 00000000000..b5cbfdf0ec6
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/derive.rs
@@ -0,0 +1,172 @@
+use crate::cfg_eval::cfg_eval;
+use crate::errors;
+
+use rustc_ast as ast;
+use rustc_ast::{GenericParamKind, ItemKind, MetaItemKind, NestedMetaItem, Safety, StmtKind};
+use rustc_expand::base::{
+    Annotatable, DeriveResolution, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier,
+};
+use rustc_feature::AttributeTemplate;
+use rustc_parse::validate_attr;
+use rustc_session::Session;
+use rustc_span::symbol::{sym, Ident};
+use rustc_span::{ErrorGuaranteed, Span};
+
+pub(crate) struct Expander {
+    pub is_const: bool,
+}
+
+impl MultiItemModifier for Expander {
+    fn expand(
+        &self,
+        ecx: &mut ExtCtxt<'_>,
+        span: Span,
+        meta_item: &ast::MetaItem,
+        item: Annotatable,
+        _: bool,
+    ) -> ExpandResult<Vec<Annotatable>, Annotatable> {
+        let sess = ecx.sess;
+        if report_bad_target(sess, &item, span).is_err() {
+            // We don't want to pass inappropriate targets to derive macros to avoid
+            // follow up errors, all other errors below are recoverable.
+            return ExpandResult::Ready(vec![item]);
+        }
+
+        let (sess, features) = (ecx.sess, ecx.ecfg.features);
+        let result =
+            ecx.resolver.resolve_derives(ecx.current_expansion.id, ecx.force_mode, &|| {
+                let template =
+                    AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
+                validate_attr::check_builtin_meta_item(
+                    &sess.psess,
+                    meta_item,
+                    ast::AttrStyle::Outer,
+                    sym::derive,
+                    template,
+                );
+
+                let mut resolutions = match &meta_item.kind {
+                    MetaItemKind::List(list) => {
+                        list.iter()
+                            .filter_map(|nested_meta| match nested_meta {
+                                NestedMetaItem::MetaItem(meta) => Some(meta),
+                                NestedMetaItem::Lit(lit) => {
+                                    // Reject `#[derive("Debug")]`.
+                                    report_unexpected_meta_item_lit(sess, lit);
+                                    None
+                                }
+                            })
+                            .map(|meta| {
+                                // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the
+                                // paths.
+                                report_path_args(sess, meta);
+                                report_unsafe_args(sess, meta);
+                                meta.path.clone()
+                            })
+                            .map(|path| DeriveResolution {
+                                path,
+                                item: dummy_annotatable(),
+                                exts: None,
+                                is_const: self.is_const,
+                            })
+                            .collect()
+                    }
+                    _ => vec![],
+                };
+
+                // Do not configure or clone items unless necessary.
+                match &mut resolutions[..] {
+                    [] => {}
+                    [first, others @ ..] => {
+                        first.item = cfg_eval(
+                            sess,
+                            features,
+                            item.clone(),
+                            ecx.current_expansion.lint_node_id,
+                        );
+                        for other in others {
+                            other.item = first.item.clone();
+                        }
+                    }
+                }
+
+                resolutions
+            });
+
+        match result {
+            Ok(()) => ExpandResult::Ready(vec![item]),
+            Err(Indeterminate) => ExpandResult::Retry(item),
+        }
+    }
+}
+
+// The cheapest `Annotatable` to construct.
+fn dummy_annotatable() -> Annotatable {
+    Annotatable::GenericParam(ast::GenericParam {
+        id: ast::DUMMY_NODE_ID,
+        ident: Ident::empty(),
+        attrs: Default::default(),
+        bounds: Default::default(),
+        is_placeholder: false,
+        kind: GenericParamKind::Lifetime,
+        colon_span: None,
+    })
+}
+
+fn report_bad_target(
+    sess: &Session,
+    item: &Annotatable,
+    span: Span,
+) -> Result<(), ErrorGuaranteed> {
+    let item_kind = match item {
+        Annotatable::Item(item) => Some(&item.kind),
+        Annotatable::Stmt(stmt) => match &stmt.kind {
+            StmtKind::Item(item) => Some(&item.kind),
+            _ => None,
+        },
+        _ => None,
+    };
+
+    let bad_target =
+        !matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)));
+    if bad_target {
+        return Err(sess.dcx().emit_err(errors::BadDeriveTarget { span, item: item.span() }));
+    }
+    Ok(())
+}
+
+fn report_unexpected_meta_item_lit(sess: &Session, lit: &ast::MetaItemLit) {
+    let help = match lit.kind {
+        ast::LitKind::Str(_, ast::StrStyle::Cooked)
+            if rustc_lexer::is_ident(lit.symbol.as_str()) =>
+        {
+            errors::BadDeriveLitHelp::StrLit { sym: lit.symbol }
+        }
+        _ => errors::BadDeriveLitHelp::Other,
+    };
+    sess.dcx().emit_err(errors::BadDeriveLit { span: lit.span, help });
+}
+
+fn report_path_args(sess: &Session, meta: &ast::MetaItem) {
+    let span = meta.span.with_lo(meta.path.span.hi());
+
+    match meta.kind {
+        MetaItemKind::Word => {}
+        MetaItemKind::List(..) => {
+            sess.dcx().emit_err(errors::DerivePathArgsList { span });
+        }
+        MetaItemKind::NameValue(..) => {
+            sess.dcx().emit_err(errors::DerivePathArgsValue { span });
+        }
+    }
+}
+
+fn report_unsafe_args(sess: &Session, meta: &ast::MetaItem) {
+    match meta.unsafety {
+        Safety::Unsafe(span) => {
+            sess.dcx().emit_err(errors::DeriveUnsafePath { span });
+        }
+        Safety::Default => {}
+        Safety::Safe(_) => unreachable!(),
+    }
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/bounds.rs b/compiler/rustc_builtin_macros/src/deriving/bounds.rs
new file mode 100644
index 00000000000..97e2344ff30
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/bounds.rs
@@ -0,0 +1,52 @@
+use crate::deriving::generic::*;
+use crate::deriving::path_std;
+
+use rustc_ast::MetaItem;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::Span;
+
+pub(crate) fn expand_deriving_copy(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    let trait_def = TraitDef {
+        span,
+        path: path_std!(marker::Copy),
+        skip_path_as_bound: false,
+        needs_copy_as_bound_if_packed: false,
+        additional_bounds: Vec::new(),
+        supports_unions: true,
+        methods: Vec::new(),
+        associated_types: Vec::new(),
+        is_const,
+    };
+
+    trait_def.expand(cx, mitem, item, push);
+}
+
+pub(crate) fn expand_deriving_const_param_ty(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    let trait_def = TraitDef {
+        span,
+        path: path_std!(marker::ConstParamTy),
+        skip_path_as_bound: false,
+        needs_copy_as_bound_if_packed: false,
+        additional_bounds: vec![ty::Ty::Path(path_std!(cmp::Eq))],
+        supports_unions: false,
+        methods: Vec::new(),
+        associated_types: Vec::new(),
+        is_const,
+    };
+
+    trait_def.expand(cx, mitem, item, push);
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/clone.rs b/compiler/rustc_builtin_macros/src/deriving/clone.rs
new file mode 100644
index 00000000000..abcb402a46f
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/clone.rs
@@ -0,0 +1,218 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::path_std;
+use rustc_ast::{self as ast, Generics, ItemKind, MetaItem, VariantData};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{kw, sym, Ident};
+use rustc_span::Span;
+use thin_vec::{thin_vec, ThinVec};
+
+pub(crate) fn expand_deriving_clone(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    // The simple form is `fn clone(&self) -> Self { *self }`, possibly with
+    // some additional `AssertParamIsClone` assertions.
+    //
+    // We can use the simple form if either of the following are true.
+    // - The type derives Copy and there are no generic parameters. (If we
+    //   used the simple form with generics, we'd have to bound the generics
+    //   with Clone + Copy, and then there'd be no Clone impl at all if the
+    //   user fills in something that is Clone but not Copy. After
+    //   specialization we can remove this no-generics limitation.)
+    // - The item is a union. (Unions with generic parameters still can derive
+    //   Clone because they require Copy for deriving, Clone alone is not
+    //   enough. Whether Clone is implemented for fields is irrelevant so we
+    //   don't assert it.)
+    let bounds;
+    let substructure;
+    let is_simple;
+    match item {
+        Annotatable::Item(annitem) => match &annitem.kind {
+            ItemKind::Struct(_, Generics { params, .. })
+            | ItemKind::Enum(_, Generics { params, .. }) => {
+                let container_id = cx.current_expansion.id.expn_data().parent.expect_local();
+                let has_derive_copy = cx.resolver.has_derive_copy(container_id);
+                if has_derive_copy
+                    && !params
+                        .iter()
+                        .any(|param| matches!(param.kind, ast::GenericParamKind::Type { .. }))
+                {
+                    bounds = vec![];
+                    is_simple = true;
+                    substructure = combine_substructure(Box::new(|c, s, sub| {
+                        cs_clone_simple("Clone", c, s, sub, false)
+                    }));
+                } else {
+                    bounds = vec![];
+                    is_simple = false;
+                    substructure =
+                        combine_substructure(Box::new(|c, s, sub| cs_clone("Clone", c, s, sub)));
+                }
+            }
+            ItemKind::Union(..) => {
+                bounds = vec![Path(path_std!(marker::Copy))];
+                is_simple = true;
+                substructure = combine_substructure(Box::new(|c, s, sub| {
+                    cs_clone_simple("Clone", c, s, sub, true)
+                }));
+            }
+            _ => cx.dcx().span_bug(span, "`#[derive(Clone)]` on wrong item kind"),
+        },
+
+        _ => cx.dcx().span_bug(span, "`#[derive(Clone)]` on trait item or impl item"),
+    }
+
+    let trait_def = TraitDef {
+        span,
+        path: path_std!(clone::Clone),
+        skip_path_as_bound: false,
+        needs_copy_as_bound_if_packed: true,
+        additional_bounds: bounds,
+        supports_unions: true,
+        methods: vec![MethodDef {
+            name: sym::clone,
+            generics: Bounds::empty(),
+            explicit_self: true,
+            nonself_args: Vec::new(),
+            ret_ty: Self_,
+            attributes: thin_vec![cx.attr_word(sym::inline, span)],
+            fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
+            combine_substructure: substructure,
+        }],
+        associated_types: Vec::new(),
+        is_const,
+    };
+
+    trait_def.expand_ext(cx, mitem, item, push, is_simple)
+}
+
+fn cs_clone_simple(
+    name: &str,
+    cx: &ExtCtxt<'_>,
+    trait_span: Span,
+    substr: &Substructure<'_>,
+    is_union: bool,
+) -> BlockOrExpr {
+    let mut stmts = ThinVec::new();
+    let mut seen_type_names = FxHashSet::default();
+    let mut process_variant = |variant: &VariantData| {
+        for field in variant.fields() {
+            // This basic redundancy checking only prevents duplication of
+            // assertions like `AssertParamIsClone<Foo>` where the type is a
+            // simple name. That's enough to get a lot of cases, though.
+            if let Some(name) = field.ty.kind.is_simple_path()
+                && !seen_type_names.insert(name)
+            {
+                // Already produced an assertion for this type.
+                // Anonymous structs or unions must be eliminated as they cannot be
+                // type parameters.
+            } else if !field.ty.kind.is_anon_adt() {
+                // let _: AssertParamIsClone<FieldTy>;
+                super::assert_ty_bounds(
+                    cx,
+                    &mut stmts,
+                    field.ty.clone(),
+                    field.span,
+                    &[sym::clone, sym::AssertParamIsClone],
+                );
+            }
+        }
+    };
+
+    if is_union {
+        // Just a single assertion for unions, that the union impls `Copy`.
+        // let _: AssertParamIsCopy<Self>;
+        let self_ty = cx.ty_path(cx.path_ident(trait_span, Ident::with_dummy_span(kw::SelfUpper)));
+        super::assert_ty_bounds(
+            cx,
+            &mut stmts,
+            self_ty,
+            trait_span,
+            &[sym::clone, sym::AssertParamIsCopy],
+        );
+    } else {
+        match *substr.fields {
+            StaticStruct(vdata, ..) => {
+                process_variant(vdata);
+            }
+            StaticEnum(enum_def, ..) => {
+                for variant in &enum_def.variants {
+                    process_variant(&variant.data);
+                }
+            }
+            _ => cx.dcx().span_bug(
+                trait_span,
+                format!("unexpected substructure in simple `derive({name})`"),
+            ),
+        }
+    }
+    BlockOrExpr::new_mixed(stmts, Some(cx.expr_deref(trait_span, cx.expr_self(trait_span))))
+}
+
+fn cs_clone(
+    name: &str,
+    cx: &ExtCtxt<'_>,
+    trait_span: Span,
+    substr: &Substructure<'_>,
+) -> BlockOrExpr {
+    let ctor_path;
+    let all_fields;
+    let fn_path = cx.std_path(&[sym::clone, sym::Clone, sym::clone]);
+    let subcall = |cx: &ExtCtxt<'_>, field: &FieldInfo| {
+        let args = thin_vec![field.self_expr.clone()];
+        cx.expr_call_global(field.span, fn_path.clone(), args)
+    };
+
+    let vdata;
+    match substr.fields {
+        Struct(vdata_, af) => {
+            ctor_path = cx.path(trait_span, vec![substr.type_ident]);
+            all_fields = af;
+            vdata = *vdata_;
+        }
+        EnumMatching(.., variant, af) => {
+            ctor_path = cx.path(trait_span, vec![substr.type_ident, variant.ident]);
+            all_fields = af;
+            vdata = &variant.data;
+        }
+        EnumDiscr(..) | AllFieldlessEnum(..) => {
+            cx.dcx().span_bug(trait_span, format!("enum discriminants in `derive({name})`",))
+        }
+        StaticEnum(..) | StaticStruct(..) => {
+            cx.dcx().span_bug(trait_span, format!("associated function in `derive({name})`"))
+        }
+    }
+
+    let expr = match *vdata {
+        VariantData::Struct { .. } => {
+            let fields = all_fields
+                .iter()
+                .map(|field| {
+                    let Some(ident) = field.name else {
+                        cx.dcx().span_bug(
+                            trait_span,
+                            format!("unnamed field in normal struct in `derive({name})`",),
+                        );
+                    };
+                    let call = subcall(cx, field);
+                    cx.field_imm(field.span, ident, call)
+                })
+                .collect::<ThinVec<_>>();
+
+            cx.expr_struct(trait_span, ctor_path, fields)
+        }
+        VariantData::Tuple(..) => {
+            let subcalls = all_fields.iter().map(|f| subcall(cx, f)).collect();
+            let path = cx.expr_path(ctor_path);
+            cx.expr_call(trait_span, path, subcalls)
+        }
+        VariantData::Unit(..) => cx.expr_path(ctor_path),
+    };
+    BlockOrExpr::new_expr(expr)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
new file mode 100644
index 00000000000..53a15131605
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
@@ -0,0 +1,92 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::path_std;
+
+use rustc_ast::{self as ast, MetaItem};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use thin_vec::{thin_vec, ThinVec};
+
+pub(crate) fn expand_deriving_eq(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    let span = cx.with_def_site_ctxt(span);
+
+    let trait_def = TraitDef {
+        span,
+        path: path_std!(cmp::Eq),
+        skip_path_as_bound: false,
+        needs_copy_as_bound_if_packed: true,
+        additional_bounds: Vec::new(),
+        supports_unions: true,
+        methods: vec![MethodDef {
+            name: sym::assert_receiver_is_total_eq,
+            generics: Bounds::empty(),
+            explicit_self: true,
+            nonself_args: vec![],
+            ret_ty: Unit,
+            attributes: thin_vec![
+                cx.attr_word(sym::inline, span),
+                cx.attr_nested_word(sym::doc, sym::hidden, span),
+                cx.attr_nested_word(sym::coverage, sym::off, span)
+            ],
+            fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
+            combine_substructure: combine_substructure(Box::new(|a, b, c| {
+                cs_total_eq_assert(a, b, c)
+            })),
+        }],
+        associated_types: Vec::new(),
+        is_const,
+    };
+    trait_def.expand_ext(cx, mitem, item, push, true)
+}
+
+fn cs_total_eq_assert(
+    cx: &ExtCtxt<'_>,
+    trait_span: Span,
+    substr: &Substructure<'_>,
+) -> BlockOrExpr {
+    let mut stmts = ThinVec::new();
+    let mut seen_type_names = FxHashSet::default();
+    let mut process_variant = |variant: &ast::VariantData| {
+        for field in variant.fields() {
+            // This basic redundancy checking only prevents duplication of
+            // assertions like `AssertParamIsEq<Foo>` where the type is a
+            // simple name. That's enough to get a lot of cases, though.
+            if let Some(name) = field.ty.kind.is_simple_path()
+                && !seen_type_names.insert(name)
+            {
+                // Already produced an assertion for this type.
+            } else {
+                // let _: AssertParamIsEq<FieldTy>;
+                super::assert_ty_bounds(
+                    cx,
+                    &mut stmts,
+                    field.ty.clone(),
+                    field.span,
+                    &[sym::cmp, sym::AssertParamIsEq],
+                );
+            }
+        }
+    };
+
+    match *substr.fields {
+        StaticStruct(vdata, ..) => {
+            process_variant(vdata);
+        }
+        StaticEnum(enum_def, ..) => {
+            for variant in &enum_def.variants {
+                process_variant(&variant.data);
+            }
+        }
+        _ => cx.dcx().span_bug(trait_span, "unexpected substructure in `derive(Eq)`"),
+    }
+    BlockOrExpr::new_stmts(stmts)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs
new file mode 100644
index 00000000000..8470d466a23
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs
@@ -0,0 +1,79 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::path_std;
+use rustc_ast::MetaItem;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident};
+use rustc_span::Span;
+use thin_vec::thin_vec;
+
+pub(crate) fn expand_deriving_ord(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    let trait_def = TraitDef {
+        span,
+        path: path_std!(cmp::Ord),
+        skip_path_as_bound: false,
+        needs_copy_as_bound_if_packed: true,
+        additional_bounds: Vec::new(),
+        supports_unions: false,
+        methods: vec![MethodDef {
+            name: sym::cmp,
+            generics: Bounds::empty(),
+            explicit_self: true,
+            nonself_args: vec![(self_ref(), sym::other)],
+            ret_ty: Path(path_std!(cmp::Ordering)),
+            attributes: thin_vec![cx.attr_word(sym::inline, span)],
+            fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
+            combine_substructure: combine_substructure(Box::new(|a, b, c| cs_cmp(a, b, c))),
+        }],
+        associated_types: Vec::new(),
+        is_const,
+    };
+
+    trait_def.expand(cx, mitem, item, push)
+}
+
+pub(crate) fn cs_cmp(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
+    let test_id = Ident::new(sym::cmp, span);
+    let equal_path = cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::Equal]));
+    let cmp_path = cx.std_path(&[sym::cmp, sym::Ord, sym::cmp]);
+
+    // Builds:
+    //
+    // match ::core::cmp::Ord::cmp(&self.x, &other.x) {
+    //     ::std::cmp::Ordering::Equal =>
+    //         ::core::cmp::Ord::cmp(&self.y, &other.y),
+    //     cmp => cmp,
+    // }
+    let expr = cs_fold(
+        // foldr nests the if-elses correctly, leaving the first field
+        // as the outermost one, and the last as the innermost.
+        false,
+        cx,
+        span,
+        substr,
+        |cx, fold| match fold {
+            CsFold::Single(field) => {
+                let [other_expr] = &field.other_selflike_exprs[..] else {
+                    cx.dcx().span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`");
+                };
+                let args = thin_vec![field.self_expr.clone(), other_expr.clone()];
+                cx.expr_call_global(field.span, cmp_path.clone(), args)
+            }
+            CsFold::Combine(span, expr1, expr2) => {
+                let eq_arm = cx.arm(span, cx.pat_path(span, equal_path.clone()), expr1);
+                let neq_arm =
+                    cx.arm(span, cx.pat_ident(span, test_id), cx.expr_ident(span, test_id));
+                cx.expr_match(span, expr2, thin_vec![eq_arm, neq_arm])
+            }
+            CsFold::Fieldless => cx.expr_path(equal_path.clone()),
+        },
+    );
+    BlockOrExpr::new_expr(expr)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs
new file mode 100644
index 00000000000..a6457f4a433
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs
@@ -0,0 +1,115 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::{path_local, path_std};
+use rustc_ast::ptr::P;
+use rustc_ast::{BinOpKind, BorrowKind, Expr, ExprKind, MetaItem, Mutability};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use thin_vec::thin_vec;
+
+pub(crate) fn expand_deriving_partial_eq(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    fn cs_eq(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
+        let base = true;
+        let expr = cs_fold(
+            true, // use foldl
+            cx,
+            span,
+            substr,
+            |cx, fold| match fold {
+                CsFold::Single(field) => {
+                    let [other_expr] = &field.other_selflike_exprs[..] else {
+                        cx.dcx()
+                            .span_bug(field.span, "not exactly 2 arguments in `derive(PartialEq)`");
+                    };
+
+                    // We received arguments of type `&T`. Convert them to type `T` by stripping
+                    // any leading `&`. This isn't necessary for type checking, but
+                    // it results in better error messages if something goes wrong.
+                    //
+                    // Note: for arguments that look like `&{ x }`, which occur with packed
+                    // structs, this would cause expressions like `{ self.x } == { other.x }`,
+                    // which isn't valid Rust syntax. This wouldn't break compilation because these
+                    // AST nodes are constructed within the compiler. But it would mean that code
+                    // printed by `-Zunpretty=expanded` (or `cargo expand`) would have invalid
+                    // syntax, which would be suboptimal. So we wrap these in parens, giving
+                    // `({ self.x }) == ({ other.x })`, which is valid syntax.
+                    let convert = |expr: &P<Expr>| {
+                        if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) =
+                            &expr.kind
+                        {
+                            if let ExprKind::Block(..) = &inner.kind {
+                                // `&{ x }` form: remove the `&`, add parens.
+                                cx.expr_paren(field.span, inner.clone())
+                            } else {
+                                // `&x` form: remove the `&`.
+                                inner.clone()
+                            }
+                        } else {
+                            expr.clone()
+                        }
+                    };
+                    cx.expr_binary(
+                        field.span,
+                        BinOpKind::Eq,
+                        convert(&field.self_expr),
+                        convert(other_expr),
+                    )
+                }
+                CsFold::Combine(span, expr1, expr2) => {
+                    cx.expr_binary(span, BinOpKind::And, expr1, expr2)
+                }
+                CsFold::Fieldless => cx.expr_bool(span, base),
+            },
+        );
+        BlockOrExpr::new_expr(expr)
+    }
+
+    let structural_trait_def = TraitDef {
+        span,
+        path: path_std!(marker::StructuralPartialEq),
+        skip_path_as_bound: true, // crucial!
+        needs_copy_as_bound_if_packed: false,
+        additional_bounds: Vec::new(),
+        // We really don't support unions, but that's already checked by the impl generated below;
+        // a second check here would lead to redundant error messages.
+        supports_unions: true,
+        methods: Vec::new(),
+        associated_types: Vec::new(),
+        is_const: false,
+    };
+    structural_trait_def.expand(cx, mitem, item, push);
+
+    // No need to generate `ne`, the default suffices, and not generating it is
+    // faster.
+    let methods = vec![MethodDef {
+        name: sym::eq,
+        generics: Bounds::empty(),
+        explicit_self: true,
+        nonself_args: vec![(self_ref(), sym::other)],
+        ret_ty: Path(path_local!(bool)),
+        attributes: thin_vec![cx.attr_word(sym::inline, span)],
+        fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
+        combine_substructure: combine_substructure(Box::new(|a, b, c| cs_eq(a, b, c))),
+    }];
+
+    let trait_def = TraitDef {
+        span,
+        path: path_std!(cmp::PartialEq),
+        skip_path_as_bound: false,
+        needs_copy_as_bound_if_packed: true,
+        additional_bounds: Vec::new(),
+        supports_unions: false,
+        methods,
+        associated_types: Vec::new(),
+        is_const,
+    };
+    trait_def.expand(cx, mitem, item, push)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs
new file mode 100644
index 00000000000..006e5a3d268
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/partial_ord.rs
@@ -0,0 +1,156 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::{path_std, pathvec_std};
+use rustc_ast::{ExprKind, ItemKind, MetaItem, PatKind};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident};
+use rustc_span::Span;
+use thin_vec::thin_vec;
+
+pub(crate) fn expand_deriving_partial_ord(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    let ordering_ty = Path(path_std!(cmp::Ordering));
+    let ret_ty =
+        Path(Path::new_(pathvec_std!(option::Option), vec![Box::new(ordering_ty)], PathKind::Std));
+
+    // Order in which to perform matching
+    let discr_then_data = if let Annotatable::Item(item) = item
+        && let ItemKind::Enum(def, _) = &item.kind
+    {
+        let dataful: Vec<bool> = def.variants.iter().map(|v| !v.data.fields().is_empty()).collect();
+        match dataful.iter().filter(|&&b| b).count() {
+            // No data, placing the discriminant check first makes codegen simpler
+            0 => true,
+            1..=2 => false,
+            _ => (0..dataful.len() - 1).any(|i| {
+                if dataful[i]
+                    && let Some(idx) = dataful[i + 1..].iter().position(|v| *v)
+                {
+                    idx >= 2
+                } else {
+                    false
+                }
+            }),
+        }
+    } else {
+        true
+    };
+    let partial_cmp_def = MethodDef {
+        name: sym::partial_cmp,
+        generics: Bounds::empty(),
+        explicit_self: true,
+        nonself_args: vec![(self_ref(), sym::other)],
+        ret_ty,
+        attributes: thin_vec![cx.attr_word(sym::inline, span)],
+        fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
+        combine_substructure: combine_substructure(Box::new(|cx, span, substr| {
+            cs_partial_cmp(cx, span, substr, discr_then_data)
+        })),
+    };
+
+    let trait_def = TraitDef {
+        span,
+        path: path_std!(cmp::PartialOrd),
+        skip_path_as_bound: false,
+        needs_copy_as_bound_if_packed: true,
+        additional_bounds: vec![],
+        supports_unions: false,
+        methods: vec![partial_cmp_def],
+        associated_types: Vec::new(),
+        is_const,
+    };
+    trait_def.expand(cx, mitem, item, push)
+}
+
+fn cs_partial_cmp(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    substr: &Substructure<'_>,
+    discr_then_data: bool,
+) -> BlockOrExpr {
+    let test_id = Ident::new(sym::cmp, span);
+    let equal_path = cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::Equal]));
+    let partial_cmp_path = cx.std_path(&[sym::cmp, sym::PartialOrd, sym::partial_cmp]);
+
+    // Builds:
+    //
+    // match ::core::cmp::PartialOrd::partial_cmp(&self.x, &other.x) {
+    //     ::core::option::Option::Some(::core::cmp::Ordering::Equal) =>
+    //         ::core::cmp::PartialOrd::partial_cmp(&self.y, &other.y),
+    //     cmp => cmp,
+    // }
+    let expr = cs_fold(
+        // foldr nests the if-elses correctly, leaving the first field
+        // as the outermost one, and the last as the innermost.
+        false,
+        cx,
+        span,
+        substr,
+        |cx, fold| match fold {
+            CsFold::Single(field) => {
+                let [other_expr] = &field.other_selflike_exprs[..] else {
+                    cx.dcx().span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`");
+                };
+                let args = thin_vec![field.self_expr.clone(), other_expr.clone()];
+                cx.expr_call_global(field.span, partial_cmp_path.clone(), args)
+            }
+            CsFold::Combine(span, mut expr1, expr2) => {
+                // When the item is an enum, this expands to
+                // ```
+                // match (expr2) {
+                //     Some(Ordering::Equal) => expr1,
+                //     cmp => cmp
+                // }
+                // ```
+                // where `expr2` is `partial_cmp(self_discr, other_discr)`, and `expr1` is a `match`
+                // against the enum variants. This means that we begin by comparing the enum discriminants,
+                // before either inspecting their contents (if they match), or returning
+                // the `cmp::Ordering` of comparing the enum discriminants.
+                // ```
+                // match partial_cmp(self_discr, other_discr) {
+                //     Some(Ordering::Equal) => match (self, other)  {
+                //         (Self::A(self_0), Self::A(other_0)) => partial_cmp(self_0, other_0),
+                //         (Self::B(self_0), Self::B(other_0)) => partial_cmp(self_0, other_0),
+                //         _ => Some(Ordering::Equal)
+                //     }
+                //     cmp => cmp
+                // }
+                // ```
+                // If we have any certain enum layouts, flipping this results in better codegen
+                // ```
+                // match (self, other) {
+                //     (Self::A(self_0), Self::A(other_0)) => partial_cmp(self_0, other_0),
+                //     _ => partial_cmp(self_discr, other_discr)
+                // }
+                // ```
+                // Reference: https://github.com/rust-lang/rust/pull/103659#issuecomment-1328126354
+
+                if !discr_then_data
+                    && let ExprKind::Match(_, arms, _) = &mut expr1.kind
+                    && let Some(last) = arms.last_mut()
+                    && let PatKind::Wild = last.pat.kind
+                {
+                    last.body = Some(expr2);
+                    expr1
+                } else {
+                    let eq_arm = cx.arm(
+                        span,
+                        cx.pat_some(span, cx.pat_path(span, equal_path.clone())),
+                        expr1,
+                    );
+                    let neq_arm =
+                        cx.arm(span, cx.pat_ident(span, test_id), cx.expr_ident(span, test_id));
+                    cx.expr_match(span, expr2, thin_vec![eq_arm, neq_arm])
+                }
+            }
+            CsFold::Fieldless => cx.expr_some(span, cx.expr_path(equal_path.clone())),
+        },
+    );
+    BlockOrExpr::new_expr(expr)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs
new file mode 100644
index 00000000000..57ec0435e3e
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs
@@ -0,0 +1,240 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::path_std;
+
+use rustc_ast::{self as ast, EnumDef, MetaItem};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::Span;
+use thin_vec::{thin_vec, ThinVec};
+
+pub(crate) fn expand_deriving_debug(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    // &mut ::std::fmt::Formatter
+    let fmtr = Ref(Box::new(Path(path_std!(fmt::Formatter))), ast::Mutability::Mut);
+
+    let trait_def = TraitDef {
+        span,
+        path: path_std!(fmt::Debug),
+        skip_path_as_bound: false,
+        needs_copy_as_bound_if_packed: true,
+        additional_bounds: Vec::new(),
+        supports_unions: false,
+        methods: vec![MethodDef {
+            name: sym::fmt,
+            generics: Bounds::empty(),
+            explicit_self: true,
+            nonself_args: vec![(fmtr, sym::f)],
+            ret_ty: Path(path_std!(fmt::Result)),
+            attributes: thin_vec![cx.attr_word(sym::inline, span)],
+            fieldless_variants_strategy:
+                FieldlessVariantsStrategy::SpecializeIfAllVariantsFieldless,
+            combine_substructure: combine_substructure(Box::new(|a, b, c| {
+                show_substructure(a, b, c)
+            })),
+        }],
+        associated_types: Vec::new(),
+        is_const,
+    };
+    trait_def.expand(cx, mitem, item, push)
+}
+
+fn show_substructure(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
+    // We want to make sure we have the ctxt set so that we can use unstable methods
+    let span = cx.with_def_site_ctxt(span);
+
+    let (ident, vdata, fields) = match substr.fields {
+        Struct(vdata, fields) => (substr.type_ident, *vdata, fields),
+        EnumMatching(_, v, fields) => (v.ident, &v.data, fields),
+        AllFieldlessEnum(enum_def) => return show_fieldless_enum(cx, span, enum_def, substr),
+        EnumDiscr(..) | StaticStruct(..) | StaticEnum(..) => {
+            cx.dcx().span_bug(span, "nonsensical .fields in `#[derive(Debug)]`")
+        }
+    };
+
+    let name = cx.expr_str(span, ident.name);
+    let fmt = substr.nonselflike_args[0].clone();
+
+    // Struct and tuples are similar enough that we use the same code for both,
+    // with some extra pieces for structs due to the field names.
+    let (is_struct, args_per_field) = match vdata {
+        ast::VariantData::Unit(..) => {
+            // Special fast path for unit variants.
+            assert!(fields.is_empty());
+            (false, 0)
+        }
+        ast::VariantData::Tuple(..) => (false, 1),
+        ast::VariantData::Struct { .. } => (true, 2),
+    };
+
+    // The number of fields that can be handled without an array.
+    const CUTOFF: usize = 5;
+
+    fn expr_for_field(
+        cx: &ExtCtxt<'_>,
+        field: &FieldInfo,
+        index: usize,
+        len: usize,
+    ) -> ast::ptr::P<ast::Expr> {
+        if index < len - 1 {
+            field.self_expr.clone()
+        } else {
+            // Unsized types need an extra indirection, but only the last field
+            // may be unsized.
+            cx.expr_addr_of(field.span, field.self_expr.clone())
+        }
+    }
+
+    if fields.is_empty() {
+        // Special case for no fields.
+        let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
+        let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name]);
+        BlockOrExpr::new_expr(expr)
+    } else if fields.len() <= CUTOFF {
+        // Few enough fields that we can use a specific-length method.
+        let debug = if is_struct {
+            format!("debug_struct_field{}_finish", fields.len())
+        } else {
+            format!("debug_tuple_field{}_finish", fields.len())
+        };
+        let fn_path_debug = cx.std_path(&[sym::fmt, sym::Formatter, Symbol::intern(&debug)]);
+
+        let mut args = ThinVec::with_capacity(2 + fields.len() * args_per_field);
+        args.extend([fmt, name]);
+        for i in 0..fields.len() {
+            let field = &fields[i];
+            if is_struct {
+                let name = cx.expr_str(field.span, field.name.unwrap().name);
+                args.push(name);
+            }
+
+            let field = expr_for_field(cx, field, i, fields.len());
+            args.push(field);
+        }
+        let expr = cx.expr_call_global(span, fn_path_debug, args);
+        BlockOrExpr::new_expr(expr)
+    } else {
+        // Enough fields that we must use the any-length method.
+        let mut name_exprs = ThinVec::with_capacity(fields.len());
+        let mut value_exprs = ThinVec::with_capacity(fields.len());
+
+        for i in 0..fields.len() {
+            let field = &fields[i];
+            if is_struct {
+                name_exprs.push(cx.expr_str(field.span, field.name.unwrap().name));
+            }
+
+            let field = expr_for_field(cx, field, i, fields.len());
+            value_exprs.push(field);
+        }
+
+        // `let names: &'static _ = &["field1", "field2"];`
+        let names_let = is_struct.then(|| {
+            let lt_static = Some(cx.lifetime_static(span));
+            let ty_static_ref = cx.ty_ref(span, cx.ty_infer(span), lt_static, ast::Mutability::Not);
+            cx.stmt_let_ty(
+                span,
+                false,
+                Ident::new(sym::names, span),
+                Some(ty_static_ref),
+                cx.expr_array_ref(span, name_exprs),
+            )
+        });
+
+        // `let values: &[&dyn Debug] = &[&&self.field1, &&self.field2];`
+        let path_debug = cx.path_global(span, cx.std_path(&[sym::fmt, sym::Debug]));
+        let ty_dyn_debug = cx.ty(
+            span,
+            ast::TyKind::TraitObject(
+                vec![cx.trait_bound(path_debug, false)],
+                ast::TraitObjectSyntax::Dyn,
+            ),
+        );
+        let ty_slice = cx.ty(
+            span,
+            ast::TyKind::Slice(cx.ty_ref(span, ty_dyn_debug, None, ast::Mutability::Not)),
+        );
+        let values_let = cx.stmt_let_ty(
+            span,
+            false,
+            Ident::new(sym::values, span),
+            Some(cx.ty_ref(span, ty_slice, None, ast::Mutability::Not)),
+            cx.expr_array_ref(span, value_exprs),
+        );
+
+        // `fmt::Formatter::debug_struct_fields_finish(fmt, name, names, values)` or
+        // `fmt::Formatter::debug_tuple_fields_finish(fmt, name, values)`
+        let sym_debug = if is_struct {
+            sym::debug_struct_fields_finish
+        } else {
+            sym::debug_tuple_fields_finish
+        };
+        let fn_path_debug_internal = cx.std_path(&[sym::fmt, sym::Formatter, sym_debug]);
+
+        let mut args = ThinVec::with_capacity(4);
+        args.push(fmt);
+        args.push(name);
+        if is_struct {
+            args.push(cx.expr_ident(span, Ident::new(sym::names, span)));
+        }
+        args.push(cx.expr_ident(span, Ident::new(sym::values, span)));
+        let expr = cx.expr_call_global(span, fn_path_debug_internal, args);
+
+        let mut stmts = ThinVec::with_capacity(2);
+        if is_struct {
+            stmts.push(names_let.unwrap());
+        }
+        stmts.push(values_let);
+        BlockOrExpr::new_mixed(stmts, Some(expr))
+    }
+}
+
+/// Special case for enums with no fields. Builds:
+/// ```text
+/// impl ::core::fmt::Debug for A {
+///     fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+///          ::core::fmt::Formatter::write_str(f,
+///             match self {
+///                 A::A => "A",
+///                 A::B() => "B",
+///                 A::C {} => "C",
+///             })
+///     }
+/// }
+/// ```
+fn show_fieldless_enum(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    def: &EnumDef,
+    substr: &Substructure<'_>,
+) -> BlockOrExpr {
+    let fmt = substr.nonselflike_args[0].clone();
+    let arms = def
+        .variants
+        .iter()
+        .map(|v| {
+            let variant_path = cx.path(span, vec![substr.type_ident, v.ident]);
+            let pat = match &v.data {
+                ast::VariantData::Tuple(fields, _) => {
+                    debug_assert!(fields.is_empty());
+                    cx.pat_tuple_struct(span, variant_path, ThinVec::new())
+                }
+                ast::VariantData::Struct { fields, .. } => {
+                    debug_assert!(fields.is_empty());
+                    cx.pat_struct(span, variant_path, ThinVec::new())
+                }
+                ast::VariantData::Unit(_) => cx.pat_path(span, variant_path),
+            };
+            cx.arm(span, pat, cx.expr_str(span, v.ident.name))
+        })
+        .collect::<ThinVec<_>>();
+    let name = cx.expr_match(span, cx.expr_self(span), arms);
+    let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
+    BlockOrExpr::new_expr(cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name]))
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/decodable.rs b/compiler/rustc_builtin_macros/src/deriving/decodable.rs
new file mode 100644
index 00000000000..e9851c87aea
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/decodable.rs
@@ -0,0 +1,226 @@
+//! The compiler code necessary for `#[derive(RustcDecodable)]`. See encodable.rs for more.
+
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::pathvec_std;
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, Expr, MetaItem, Mutability};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::Span;
+use thin_vec::{thin_vec, ThinVec};
+
+pub(crate) fn expand_deriving_rustc_decodable(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    let krate = sym::rustc_serialize;
+    let typaram = sym::__D;
+
+    let trait_def = TraitDef {
+        span,
+        path: Path::new_(vec![krate, sym::Decodable], vec![], PathKind::Global),
+        skip_path_as_bound: false,
+        needs_copy_as_bound_if_packed: true,
+        additional_bounds: Vec::new(),
+        supports_unions: false,
+        methods: vec![MethodDef {
+            name: sym::decode,
+            generics: Bounds {
+                bounds: vec![(
+                    typaram,
+                    vec![Path::new_(vec![krate, sym::Decoder], vec![], PathKind::Global)],
+                )],
+            },
+            explicit_self: false,
+            nonself_args: vec![(
+                Ref(Box::new(Path(Path::new_local(typaram))), Mutability::Mut),
+                sym::d,
+            )],
+            ret_ty: Path(Path::new_(
+                pathvec_std!(result::Result),
+                vec![
+                    Box::new(Self_),
+                    Box::new(Path(Path::new_(vec![typaram, sym::Error], vec![], PathKind::Local))),
+                ],
+                PathKind::Std,
+            )),
+            attributes: ast::AttrVec::new(),
+            fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
+            combine_substructure: combine_substructure(Box::new(|a, b, c| {
+                decodable_substructure(a, b, c, krate)
+            })),
+        }],
+        associated_types: Vec::new(),
+        is_const,
+    };
+
+    trait_def.expand(cx, mitem, item, push)
+}
+
+fn decodable_substructure(
+    cx: &ExtCtxt<'_>,
+    trait_span: Span,
+    substr: &Substructure<'_>,
+    krate: Symbol,
+) -> BlockOrExpr {
+    let decoder = substr.nonselflike_args[0].clone();
+    let recurse = vec![
+        Ident::new(krate, trait_span),
+        Ident::new(sym::Decodable, trait_span),
+        Ident::new(sym::decode, trait_span),
+    ];
+    let exprdecode = cx.expr_path(cx.path_global(trait_span, recurse));
+    // throw an underscore in front to suppress unused variable warnings
+    let blkarg = Ident::new(sym::_d, trait_span);
+    let blkdecoder = cx.expr_ident(trait_span, blkarg);
+
+    let expr = match substr.fields {
+        StaticStruct(_, summary) => {
+            let nfields = match summary {
+                Unnamed(fields, _) => fields.len(),
+                Named(fields) => fields.len(),
+            };
+            let fn_read_struct_field_path: Vec<_> =
+                cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_struct_field]);
+
+            let path = cx.path_ident(trait_span, substr.type_ident);
+            let result =
+                decode_static_fields(cx, trait_span, path, summary, |cx, span, name, field| {
+                    cx.expr_try(
+                        span,
+                        cx.expr_call_global(
+                            span,
+                            fn_read_struct_field_path.clone(),
+                            thin_vec![
+                                blkdecoder.clone(),
+                                cx.expr_str(span, name),
+                                cx.expr_usize(span, field),
+                                exprdecode.clone(),
+                            ],
+                        ),
+                    )
+                });
+            let result = cx.expr_ok(trait_span, result);
+            let fn_read_struct_path: Vec<_> =
+                cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_struct]);
+
+            cx.expr_call_global(
+                trait_span,
+                fn_read_struct_path,
+                thin_vec![
+                    decoder,
+                    cx.expr_str(trait_span, substr.type_ident.name),
+                    cx.expr_usize(trait_span, nfields),
+                    cx.lambda1(trait_span, result, blkarg),
+                ],
+            )
+        }
+        StaticEnum(_, fields) => {
+            let variant = Ident::new(sym::i, trait_span);
+
+            let mut arms = ThinVec::with_capacity(fields.len() + 1);
+            let mut variants = ThinVec::with_capacity(fields.len());
+
+            let fn_read_enum_variant_arg_path: Vec<_> =
+                cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_enum_variant_arg]);
+
+            for (i, &(ident, v_span, ref parts)) in fields.iter().enumerate() {
+                variants.push(cx.expr_str(v_span, ident.name));
+
+                let path = cx.path(trait_span, vec![substr.type_ident, ident]);
+                let decoded =
+                    decode_static_fields(cx, v_span, path, parts, |cx, span, _, field| {
+                        let idx = cx.expr_usize(span, field);
+                        cx.expr_try(
+                            span,
+                            cx.expr_call_global(
+                                span,
+                                fn_read_enum_variant_arg_path.clone(),
+                                thin_vec![blkdecoder.clone(), idx, exprdecode.clone()],
+                            ),
+                        )
+                    });
+
+                arms.push(cx.arm(v_span, cx.pat_lit(v_span, cx.expr_usize(v_span, i)), decoded));
+            }
+
+            arms.push(cx.arm_unreachable(trait_span));
+
+            let result = cx.expr_ok(
+                trait_span,
+                cx.expr_match(trait_span, cx.expr_ident(trait_span, variant), arms),
+            );
+            let lambda = cx.lambda(trait_span, vec![blkarg, variant], result);
+            let variant_array_ref = cx.expr_array_ref(trait_span, variants);
+            let fn_read_enum_variant_path: Vec<_> =
+                cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_enum_variant]);
+            let result = cx.expr_call_global(
+                trait_span,
+                fn_read_enum_variant_path,
+                thin_vec![blkdecoder, variant_array_ref, lambda],
+            );
+            let fn_read_enum_path: Vec<_> =
+                cx.def_site_path(&[sym::rustc_serialize, sym::Decoder, sym::read_enum]);
+
+            cx.expr_call_global(
+                trait_span,
+                fn_read_enum_path,
+                thin_vec![
+                    decoder,
+                    cx.expr_str(trait_span, substr.type_ident.name),
+                    cx.lambda1(trait_span, result, blkarg),
+                ],
+            )
+        }
+        _ => cx.dcx().bug("expected StaticEnum or StaticStruct in derive(Decodable)"),
+    };
+    BlockOrExpr::new_expr(expr)
+}
+
+/// Creates a decoder for a single enum variant/struct:
+/// - `outer_pat_path` is the path to this enum variant/struct
+/// - `getarg` should retrieve the `usize`-th field with name `@str`.
+fn decode_static_fields<F>(
+    cx: &ExtCtxt<'_>,
+    trait_span: Span,
+    outer_pat_path: ast::Path,
+    fields: &StaticFields,
+    mut getarg: F,
+) -> P<Expr>
+where
+    F: FnMut(&ExtCtxt<'_>, Span, Symbol, usize) -> P<Expr>,
+{
+    match fields {
+        Unnamed(fields, is_tuple) => {
+            let path_expr = cx.expr_path(outer_pat_path);
+            if matches!(is_tuple, IsTuple::No) {
+                path_expr
+            } else {
+                let fields = fields
+                    .iter()
+                    .enumerate()
+                    .map(|(i, &span)| getarg(cx, span, Symbol::intern(&format!("_field{i}")), i))
+                    .collect();
+
+                cx.expr_call(trait_span, path_expr, fields)
+            }
+        }
+        Named(fields) => {
+            // use the field's span to get nicer error messages.
+            let fields = fields
+                .iter()
+                .enumerate()
+                .map(|(i, &(ident, span))| {
+                    let arg = getarg(cx, span, ident.name, i);
+                    cx.field_imm(span, ident, arg)
+                })
+                .collect();
+            cx.expr_struct(trait_span, outer_pat_path, fields)
+        }
+    }
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/default.rs b/compiler/rustc_builtin_macros/src/deriving/default.rs
new file mode 100644
index 00000000000..577523a1d5a
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/default.rs
@@ -0,0 +1,250 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::errors;
+use core::ops::ControlFlow;
+use rustc_ast as ast;
+use rustc_ast::visit::visit_opt;
+use rustc_ast::{attr, EnumDef, VariantData};
+use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt};
+use rustc_span::symbol::Ident;
+use rustc_span::symbol::{kw, sym};
+use rustc_span::{ErrorGuaranteed, Span};
+use smallvec::SmallVec;
+use thin_vec::{thin_vec, ThinVec};
+
+pub(crate) fn expand_deriving_default(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &ast::MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    item.visit_with(&mut DetectNonVariantDefaultAttr { cx });
+
+    let trait_def = TraitDef {
+        span,
+        path: Path::new(vec![kw::Default, sym::Default]),
+        skip_path_as_bound: has_a_default_variant(item),
+        needs_copy_as_bound_if_packed: false,
+        additional_bounds: Vec::new(),
+        supports_unions: false,
+        methods: vec![MethodDef {
+            name: kw::Default,
+            generics: Bounds::empty(),
+            explicit_self: false,
+            nonself_args: Vec::new(),
+            ret_ty: Self_,
+            attributes: thin_vec![cx.attr_word(sym::inline, span)],
+            fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
+            combine_substructure: combine_substructure(Box::new(|cx, trait_span, substr| {
+                match substr.fields {
+                    StaticStruct(_, fields) => {
+                        default_struct_substructure(cx, trait_span, substr, fields)
+                    }
+                    StaticEnum(enum_def, _) => default_enum_substructure(cx, trait_span, enum_def),
+                    _ => cx.dcx().span_bug(trait_span, "method in `derive(Default)`"),
+                }
+            })),
+        }],
+        associated_types: Vec::new(),
+        is_const,
+    };
+    trait_def.expand(cx, mitem, item, push)
+}
+
+fn default_struct_substructure(
+    cx: &ExtCtxt<'_>,
+    trait_span: Span,
+    substr: &Substructure<'_>,
+    summary: &StaticFields,
+) -> BlockOrExpr {
+    // Note that `kw::Default` is "default" and `sym::Default` is "Default"!
+    let default_ident = cx.std_path(&[kw::Default, sym::Default, kw::Default]);
+    let default_call = |span| cx.expr_call_global(span, default_ident.clone(), ThinVec::new());
+
+    let expr = match summary {
+        Unnamed(_, IsTuple::No) => cx.expr_ident(trait_span, substr.type_ident),
+        Unnamed(fields, IsTuple::Yes) => {
+            let exprs = fields.iter().map(|sp| default_call(*sp)).collect();
+            cx.expr_call_ident(trait_span, substr.type_ident, exprs)
+        }
+        Named(fields) => {
+            let default_fields = fields
+                .iter()
+                .map(|&(ident, span)| cx.field_imm(span, ident, default_call(span)))
+                .collect();
+            cx.expr_struct_ident(trait_span, substr.type_ident, default_fields)
+        }
+    };
+    BlockOrExpr::new_expr(expr)
+}
+
+fn default_enum_substructure(
+    cx: &ExtCtxt<'_>,
+    trait_span: Span,
+    enum_def: &EnumDef,
+) -> BlockOrExpr {
+    let expr = match try {
+        let default_variant = extract_default_variant(cx, enum_def, trait_span)?;
+        validate_default_attribute(cx, default_variant)?;
+        default_variant
+    } {
+        Ok(default_variant) => {
+            // We now know there is exactly one unit variant with exactly one `#[default]` attribute.
+            cx.expr_path(cx.path(
+                default_variant.span,
+                vec![Ident::new(kw::SelfUpper, default_variant.span), default_variant.ident],
+            ))
+        }
+        Err(guar) => DummyResult::raw_expr(trait_span, Some(guar)),
+    };
+    BlockOrExpr::new_expr(expr)
+}
+
+fn extract_default_variant<'a>(
+    cx: &ExtCtxt<'_>,
+    enum_def: &'a EnumDef,
+    trait_span: Span,
+) -> Result<&'a rustc_ast::Variant, ErrorGuaranteed> {
+    let default_variants: SmallVec<[_; 1]> = enum_def
+        .variants
+        .iter()
+        .filter(|variant| attr::contains_name(&variant.attrs, kw::Default))
+        .collect();
+
+    let variant = match default_variants.as_slice() {
+        [variant] => variant,
+        [] => {
+            let possible_defaults = enum_def
+                .variants
+                .iter()
+                .filter(|variant| matches!(variant.data, VariantData::Unit(..)))
+                .filter(|variant| !attr::contains_name(&variant.attrs, sym::non_exhaustive));
+
+            let suggs = possible_defaults
+                .map(|v| errors::NoDefaultVariantSugg { span: v.span, ident: v.ident })
+                .collect();
+            let guar = cx.dcx().emit_err(errors::NoDefaultVariant { span: trait_span, suggs });
+
+            return Err(guar);
+        }
+        [first, rest @ ..] => {
+            let suggs = default_variants
+                .iter()
+                .filter_map(|variant| {
+                    let keep = attr::find_by_name(&variant.attrs, kw::Default)?.span;
+                    let spans: Vec<Span> = default_variants
+                        .iter()
+                        .flat_map(|v| {
+                            attr::filter_by_name(&v.attrs, kw::Default)
+                                .filter_map(|attr| (attr.span != keep).then_some(attr.span))
+                        })
+                        .collect();
+                    (!spans.is_empty())
+                        .then_some(errors::MultipleDefaultsSugg { spans, ident: variant.ident })
+                })
+                .collect();
+            let guar = cx.dcx().emit_err(errors::MultipleDefaults {
+                span: trait_span,
+                first: first.span,
+                additional: rest.iter().map(|v| v.span).collect(),
+                suggs,
+            });
+            return Err(guar);
+        }
+    };
+
+    if !matches!(variant.data, VariantData::Unit(..)) {
+        let guar = cx.dcx().emit_err(errors::NonUnitDefault { span: variant.ident.span });
+        return Err(guar);
+    }
+
+    if let Some(non_exhaustive_attr) = attr::find_by_name(&variant.attrs, sym::non_exhaustive) {
+        let guar = cx.dcx().emit_err(errors::NonExhaustiveDefault {
+            span: variant.ident.span,
+            non_exhaustive: non_exhaustive_attr.span,
+        });
+
+        return Err(guar);
+    }
+
+    Ok(variant)
+}
+
+fn validate_default_attribute(
+    cx: &ExtCtxt<'_>,
+    default_variant: &rustc_ast::Variant,
+) -> Result<(), ErrorGuaranteed> {
+    let attrs: SmallVec<[_; 1]> =
+        attr::filter_by_name(&default_variant.attrs, kw::Default).collect();
+
+    let attr = match attrs.as_slice() {
+        [attr] => attr,
+        [] => cx.dcx().bug(
+            "this method must only be called with a variant that has a `#[default]` attribute",
+        ),
+        [first, rest @ ..] => {
+            let sugg = errors::MultipleDefaultAttrsSugg {
+                spans: rest.iter().map(|attr| attr.span).collect(),
+            };
+            let guar = cx.dcx().emit_err(errors::MultipleDefaultAttrs {
+                span: default_variant.ident.span,
+                first: first.span,
+                first_rest: rest[0].span,
+                rest: rest.iter().map(|attr| attr.span).collect::<Vec<_>>().into(),
+                only_one: rest.len() == 1,
+                sugg,
+            });
+
+            return Err(guar);
+        }
+    };
+    if !attr.is_word() {
+        let guar = cx.dcx().emit_err(errors::DefaultHasArg { span: attr.span });
+
+        return Err(guar);
+    }
+    Ok(())
+}
+
+struct DetectNonVariantDefaultAttr<'a, 'b> {
+    cx: &'a ExtCtxt<'b>,
+}
+
+impl<'a, 'b> rustc_ast::visit::Visitor<'a> for DetectNonVariantDefaultAttr<'a, 'b> {
+    fn visit_attribute(&mut self, attr: &'a rustc_ast::Attribute) {
+        if attr.has_name(kw::Default) {
+            self.cx.dcx().emit_err(errors::NonUnitDefault { span: attr.span });
+        }
+
+        rustc_ast::visit::walk_attribute(self, attr);
+    }
+    fn visit_variant(&mut self, v: &'a rustc_ast::Variant) {
+        self.visit_ident(v.ident);
+        self.visit_vis(&v.vis);
+        self.visit_variant_data(&v.data);
+        visit_opt!(self, visit_anon_const, &v.disr_expr);
+        for attr in &v.attrs {
+            rustc_ast::visit::walk_attribute(self, attr);
+        }
+    }
+}
+
+fn has_a_default_variant(item: &Annotatable) -> bool {
+    struct HasDefaultAttrOnVariant;
+
+    impl<'ast> rustc_ast::visit::Visitor<'ast> for HasDefaultAttrOnVariant {
+        type Result = ControlFlow<()>;
+        fn visit_variant(&mut self, v: &'ast rustc_ast::Variant) -> ControlFlow<()> {
+            if v.attrs.iter().any(|attr| attr.has_name(kw::Default)) {
+                ControlFlow::Break(())
+            } else {
+                // no need to subrecurse.
+                ControlFlow::Continue(())
+            }
+        }
+    }
+
+    item.visit_with(&mut HasDefaultAttrOnVariant).is_break()
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/encodable.rs b/compiler/rustc_builtin_macros/src/deriving/encodable.rs
new file mode 100644
index 00000000000..3bd74d8d019
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/encodable.rs
@@ -0,0 +1,301 @@
+//! The compiler code necessary to implement the `#[derive(RustcEncodable)]`
+//! (and `RustcDecodable`, in `decodable.rs`) extension. The idea here is that
+//! type-defining items may be tagged with
+//! `#[derive(RustcEncodable, RustcDecodable)]`.
+//!
+//! For example, a type like:
+//!
+//! ```ignore (old code)
+//! #[derive(RustcEncodable, RustcDecodable)]
+//! struct Node { id: usize }
+//! ```
+//!
+//! would generate two implementations like:
+//!
+//! ```ignore (old code)
+//! # struct Node { id: usize }
+//! impl<S: Encoder<E>, E> Encodable<S, E> for Node {
+//!     fn encode(&self, s: &mut S) -> Result<(), E> {
+//!         s.emit_struct("Node", 1, |this| {
+//!             this.emit_struct_field("id", 0, |this| {
+//!                 Encodable::encode(&self.id, this)
+//!                 /* this.emit_usize(self.id) can also be used */
+//!             })
+//!         })
+//!     }
+//! }
+//!
+//! impl<D: Decoder<E>, E> Decodable<D, E> for Node {
+//!     fn decode(d: &mut D) -> Result<Node, E> {
+//!         d.read_struct("Node", 1, |this| {
+//!             match this.read_struct_field("id", 0, |this| Decodable::decode(this)) {
+//!                 Ok(id) => Ok(Node { id: id }),
+//!                 Err(e) => Err(e),
+//!             }
+//!         })
+//!     }
+//! }
+//! ```
+//!
+//! Other interesting scenarios are when the item has type parameters or
+//! references other non-built-in types. A type definition like:
+//!
+//! ```ignore (old code)
+//! # #[derive(RustcEncodable, RustcDecodable)]
+//! # struct Span;
+//! #[derive(RustcEncodable, RustcDecodable)]
+//! struct Spanned<T> { node: T, span: Span }
+//! ```
+//!
+//! would yield functions like:
+//!
+//! ```ignore (old code)
+//! # #[derive(RustcEncodable, RustcDecodable)]
+//! # struct Span;
+//! # struct Spanned<T> { node: T, span: Span }
+//! impl<
+//!     S: Encoder<E>,
+//!     E,
+//!     T: Encodable<S, E>
+//! > Encodable<S, E> for Spanned<T> {
+//!     fn encode(&self, s: &mut S) -> Result<(), E> {
+//!         s.emit_struct("Spanned", 2, |this| {
+//!             this.emit_struct_field("node", 0, |this| self.node.encode(this))
+//!                 .unwrap();
+//!             this.emit_struct_field("span", 1, |this| self.span.encode(this))
+//!         })
+//!     }
+//! }
+//!
+//! impl<
+//!     D: Decoder<E>,
+//!     E,
+//!     T: Decodable<D, E>
+//! > Decodable<D, E> for Spanned<T> {
+//!     fn decode(d: &mut D) -> Result<Spanned<T>, E> {
+//!         d.read_struct("Spanned", 2, |this| {
+//!             Ok(Spanned {
+//!                 node: this.read_struct_field("node", 0, |this| Decodable::decode(this))
+//!                     .unwrap(),
+//!                 span: this.read_struct_field("span", 1, |this| Decodable::decode(this))
+//!                     .unwrap(),
+//!             })
+//!         })
+//!     }
+//! }
+//! ```
+
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::pathvec_std;
+use rustc_ast::{AttrVec, ExprKind, MetaItem, Mutability};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::Span;
+use thin_vec::{thin_vec, ThinVec};
+
+pub(crate) fn expand_deriving_rustc_encodable(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    let krate = sym::rustc_serialize;
+    let typaram = sym::__S;
+
+    let trait_def = TraitDef {
+        span,
+        path: Path::new_(vec![krate, sym::Encodable], vec![], PathKind::Global),
+        skip_path_as_bound: false,
+        needs_copy_as_bound_if_packed: true,
+        additional_bounds: Vec::new(),
+        supports_unions: false,
+        methods: vec![MethodDef {
+            name: sym::encode,
+            generics: Bounds {
+                bounds: vec![(
+                    typaram,
+                    vec![Path::new_(vec![krate, sym::Encoder], vec![], PathKind::Global)],
+                )],
+            },
+            explicit_self: true,
+            nonself_args: vec![(
+                Ref(Box::new(Path(Path::new_local(typaram))), Mutability::Mut),
+                sym::s,
+            )],
+            ret_ty: Path(Path::new_(
+                pathvec_std!(result::Result),
+                vec![
+                    Box::new(Unit),
+                    Box::new(Path(Path::new_(vec![typaram, sym::Error], vec![], PathKind::Local))),
+                ],
+                PathKind::Std,
+            )),
+            attributes: AttrVec::new(),
+            fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
+            combine_substructure: combine_substructure(Box::new(|a, b, c| {
+                encodable_substructure(a, b, c, krate)
+            })),
+        }],
+        associated_types: Vec::new(),
+        is_const,
+    };
+
+    trait_def.expand(cx, mitem, item, push)
+}
+
+fn encodable_substructure(
+    cx: &ExtCtxt<'_>,
+    trait_span: Span,
+    substr: &Substructure<'_>,
+    krate: Symbol,
+) -> BlockOrExpr {
+    let encoder = substr.nonselflike_args[0].clone();
+    // throw an underscore in front to suppress unused variable warnings
+    let blkarg = Ident::new(sym::_e, trait_span);
+    let blkencoder = cx.expr_ident(trait_span, blkarg);
+    let fn_path = cx.expr_path(cx.path_global(
+        trait_span,
+        vec![
+            Ident::new(krate, trait_span),
+            Ident::new(sym::Encodable, trait_span),
+            Ident::new(sym::encode, trait_span),
+        ],
+    ));
+
+    match substr.fields {
+        Struct(_, fields) => {
+            let fn_emit_struct_field_path =
+                cx.def_site_path(&[sym::rustc_serialize, sym::Encoder, sym::emit_struct_field]);
+            let mut stmts = ThinVec::new();
+            for (i, &FieldInfo { name, ref self_expr, span, .. }) in fields.iter().enumerate() {
+                let name = match name {
+                    Some(id) => id.name,
+                    None => Symbol::intern(&format!("_field{i}")),
+                };
+                let self_ref = cx.expr_addr_of(span, self_expr.clone());
+                let enc =
+                    cx.expr_call(span, fn_path.clone(), thin_vec![self_ref, blkencoder.clone()]);
+                let lambda = cx.lambda1(span, enc, blkarg);
+                let call = cx.expr_call_global(
+                    span,
+                    fn_emit_struct_field_path.clone(),
+                    thin_vec![
+                        blkencoder.clone(),
+                        cx.expr_str(span, name),
+                        cx.expr_usize(span, i),
+                        lambda,
+                    ],
+                );
+
+                // last call doesn't need a try!
+                let last = fields.len() - 1;
+                let call = if i != last {
+                    cx.expr_try(span, call)
+                } else {
+                    cx.expr(span, ExprKind::Ret(Some(call)))
+                };
+
+                let stmt = cx.stmt_expr(call);
+                stmts.push(stmt);
+            }
+
+            // unit structs have no fields and need to return Ok()
+            let blk = if stmts.is_empty() {
+                let ok = cx.expr_ok(trait_span, cx.expr_tuple(trait_span, ThinVec::new()));
+                cx.lambda1(trait_span, ok, blkarg)
+            } else {
+                cx.lambda_stmts_1(trait_span, stmts, blkarg)
+            };
+
+            let fn_emit_struct_path =
+                cx.def_site_path(&[sym::rustc_serialize, sym::Encoder, sym::emit_struct]);
+
+            let expr = cx.expr_call_global(
+                trait_span,
+                fn_emit_struct_path,
+                thin_vec![
+                    encoder,
+                    cx.expr_str(trait_span, substr.type_ident.name),
+                    cx.expr_usize(trait_span, fields.len()),
+                    blk,
+                ],
+            );
+            BlockOrExpr::new_expr(expr)
+        }
+
+        EnumMatching(idx, variant, fields) => {
+            // We're not generating an AST that the borrow checker is expecting,
+            // so we need to generate a unique local variable to take the
+            // mutable loan out on, otherwise we get conflicts which don't
+            // actually exist.
+            let me = cx.stmt_let(trait_span, false, blkarg, encoder);
+            let encoder = cx.expr_ident(trait_span, blkarg);
+
+            let fn_emit_enum_variant_arg_path: Vec<_> =
+                cx.def_site_path(&[sym::rustc_serialize, sym::Encoder, sym::emit_enum_variant_arg]);
+
+            let mut stmts = ThinVec::new();
+            if !fields.is_empty() {
+                let last = fields.len() - 1;
+                for (i, &FieldInfo { ref self_expr, span, .. }) in fields.iter().enumerate() {
+                    let self_ref = cx.expr_addr_of(span, self_expr.clone());
+                    let enc = cx.expr_call(
+                        span,
+                        fn_path.clone(),
+                        thin_vec![self_ref, blkencoder.clone()],
+                    );
+                    let lambda = cx.lambda1(span, enc, blkarg);
+
+                    let call = cx.expr_call_global(
+                        span,
+                        fn_emit_enum_variant_arg_path.clone(),
+                        thin_vec![blkencoder.clone(), cx.expr_usize(span, i), lambda],
+                    );
+                    let call = if i != last {
+                        cx.expr_try(span, call)
+                    } else {
+                        cx.expr(span, ExprKind::Ret(Some(call)))
+                    };
+                    stmts.push(cx.stmt_expr(call));
+                }
+            } else {
+                let ok = cx.expr_ok(trait_span, cx.expr_tuple(trait_span, ThinVec::new()));
+                let ret_ok = cx.expr(trait_span, ExprKind::Ret(Some(ok)));
+                stmts.push(cx.stmt_expr(ret_ok));
+            }
+
+            let blk = cx.lambda_stmts_1(trait_span, stmts, blkarg);
+            let name = cx.expr_str(trait_span, variant.ident.name);
+
+            let fn_emit_enum_variant_path: Vec<_> =
+                cx.def_site_path(&[sym::rustc_serialize, sym::Encoder, sym::emit_enum_variant]);
+
+            let call = cx.expr_call_global(
+                trait_span,
+                fn_emit_enum_variant_path,
+                thin_vec![
+                    blkencoder,
+                    name,
+                    cx.expr_usize(trait_span, *idx),
+                    cx.expr_usize(trait_span, fields.len()),
+                    blk,
+                ],
+            );
+
+            let blk = cx.lambda1(trait_span, call, blkarg);
+            let fn_emit_enum_path: Vec<_> =
+                cx.def_site_path(&[sym::rustc_serialize, sym::Encoder, sym::emit_enum]);
+            let expr = cx.expr_call_global(
+                trait_span,
+                fn_emit_enum_path,
+                thin_vec![encoder, cx.expr_str(trait_span, substr.type_ident.name), blk],
+            );
+            BlockOrExpr::new_mixed(thin_vec![me], Some(expr))
+        }
+
+        _ => cx.dcx().bug("expected Struct or EnumMatching in derive(Encodable)"),
+    }
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
new file mode 100644
index 00000000000..ba289f9552e
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
@@ -0,0 +1,1726 @@
+//! Some code that abstracts away much of the boilerplate of writing
+//! `derive` instances for traits. Among other things it manages getting
+//! access to the fields of the 4 different sorts of structs and enum
+//! variants, as well as creating the method and impl ast instances.
+//!
+//! Supported features (fairly exhaustive):
+//!
+//! - Methods taking any number of parameters of any type, and returning
+//!   any type, other than vectors, bottom and closures.
+//! - Generating `impl`s for types with type parameters and lifetimes
+//!   (e.g., `Option<T>`), the parameters are automatically given the
+//!   current trait as a bound. (This includes separate type parameters
+//!   and lifetimes for methods.)
+//! - Additional bounds on the type parameters (`TraitDef.additional_bounds`)
+//!
+//! The most important thing for implementors is the `Substructure` and
+//! `SubstructureFields` objects. The latter groups 5 possibilities of the
+//! arguments:
+//!
+//! - `Struct`, when `Self` is a struct (including tuple structs, e.g
+//!   `struct T(i32, char)`).
+//! - `EnumMatching`, when `Self` is an enum and all the arguments are the
+//!   same variant of the enum (e.g., `Some(1)`, `Some(3)` and `Some(4)`)
+//! - `EnumDiscr` when `Self` is an enum, for comparing the enum discriminants.
+//! - `StaticEnum` and `StaticStruct` for static methods, where the type
+//!   being derived upon is either an enum or struct respectively. (Any
+//!   argument with type Self is just grouped among the non-self
+//!   arguments.)
+//!
+//! In the first two cases, the values from the corresponding fields in
+//! all the arguments are grouped together.
+//!
+//! The non-static cases have `Option<ident>` in several places associated
+//! with field `expr`s. This represents the name of the field it is
+//! associated with. It is only not `None` when the associated field has
+//! an identifier in the source code. For example, the `x`s in the
+//! following snippet
+//!
+//! ```rust
+//! struct A {
+//!     x: i32,
+//! }
+//!
+//! struct B(i32);
+//!
+//! enum C {
+//!     C0(i32),
+//!     C1 { x: i32 }
+//! }
+//! ```
+//!
+//! The `i32`s in `B` and `C0` don't have an identifier, so the
+//! `Option<ident>`s would be `None` for them.
+//!
+//! In the static cases, the structure is summarized, either into the just
+//! spans of the fields or a list of spans and the field idents (for tuple
+//! structs and record structs, respectively), or a list of these, for
+//! enums (one for each variant). For empty struct and empty enum
+//! variants, it is represented as a count of 0.
+//!
+//! # "`cs`" functions
+//!
+//! The `cs_...` functions ("combine substructure") are designed to
+//! make life easier by providing some pre-made recipes for common
+//! threads; mostly calling the function being derived on all the
+//! arguments and then combining them back together in some way (or
+//! letting the user chose that). They are not meant to be the only
+//! way to handle the structures that this code creates.
+//!
+//! # Examples
+//!
+//! The following simplified `PartialEq` is used for in-code examples:
+//!
+//! ```rust
+//! trait PartialEq {
+//!     fn eq(&self, other: &Self) -> bool;
+//! }
+//!
+//! impl PartialEq for i32 {
+//!     fn eq(&self, other: &i32) -> bool {
+//!         *self == *other
+//!     }
+//! }
+//! ```
+//!
+//! Some examples of the values of `SubstructureFields` follow, using the
+//! above `PartialEq`, `A`, `B` and `C`.
+//!
+//! ## Structs
+//!
+//! When generating the `expr` for the `A` impl, the `SubstructureFields` is
+//!
+//! ```text
+//! Struct(vec![FieldInfo {
+//!     span: <span of x>,
+//!     name: Some(<ident of x>),
+//!     self_: <expr for &self.x>,
+//!     other: vec![<expr for &other.x>],
+//! }])
+//! ```
+//!
+//! For the `B` impl, called with `B(a)` and `B(b)`,
+//!
+//! ```text
+//! Struct(vec![FieldInfo {
+//!     span: <span of i32>,
+//!     name: None,
+//!     self_: <expr for &a>,
+//!     other: vec![<expr for &b>],
+//! }])
+//! ```
+//!
+//! ## Enums
+//!
+//! When generating the `expr` for a call with `self == C0(a)` and `other
+//! == C0(b)`, the SubstructureFields is
+//!
+//! ```text
+//! EnumMatching(
+//!     0,
+//!     <ast::Variant for C0>,
+//!     vec![FieldInfo {
+//!         span: <span of i32>,
+//!         name: None,
+//!         self_: <expr for &a>,
+//!         other: vec![<expr for &b>],
+//!     }],
+//! )
+//! ```
+//!
+//! For `C1 {x}` and `C1 {x}`,
+//!
+//! ```text
+//! EnumMatching(
+//!     1,
+//!     <ast::Variant for C1>,
+//!     vec![FieldInfo {
+//!         span: <span of x>,
+//!         name: Some(<ident of x>),
+//!         self_: <expr for &self.x>,
+//!         other: vec![<expr for &other.x>],
+//!     }],
+//! )
+//! ```
+//!
+//! For the discriminants,
+//!
+//! ```text
+//! EnumDiscr(
+//!     &[<ident of self discriminant>, <ident of other discriminant>],
+//!     <expr to combine with>,
+//! )
+//! ```
+//!
+//! Note that this setup doesn't allow for the brute-force "match every variant
+//! against every other variant" approach, which is bad because it produces a
+//! quadratic amount of code (see #15375).
+//!
+//! ## Static
+//!
+//! A static method on the types above would result in,
+//!
+//! ```text
+//! StaticStruct(<ast::VariantData of A>, Named(vec![(<ident of x>, <span of x>)]))
+//!
+//! StaticStruct(<ast::VariantData of B>, Unnamed(vec![<span of x>]))
+//!
+//! StaticEnum(
+//!     <ast::EnumDef of C>,
+//!     vec![
+//!         (<ident of C0>, <span of C0>, Unnamed(vec![<span of i32>])),
+//!         (<ident of C1>, <span of C1>, Named(vec![(<ident of x>, <span of x>)])),
+//!     ],
+//! )
+//! ```
+
+pub(crate) use StaticFields::*;
+pub(crate) use SubstructureFields::*;
+
+use crate::{deriving, errors};
+use rustc_ast::ptr::P;
+use rustc_ast::{
+    self as ast, BindingMode, ByRef, EnumDef, Expr, GenericArg, GenericParamKind, Generics,
+    Mutability, PatKind, TyKind, VariantData,
+};
+use rustc_attr as attr;
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_session::lint::builtin::BYTE_SLICE_IN_PACKED_STRUCT_WITH_DERIVE;
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use std::cell::RefCell;
+use std::iter;
+use std::ops::Not;
+use std::vec;
+use thin_vec::{thin_vec, ThinVec};
+use ty::{Bounds, Path, Ref, Self_, Ty};
+
+pub(crate) mod ty;
+
+pub(crate) struct TraitDef<'a> {
+    /// The span for the current #[derive(Foo)] header.
+    pub span: Span,
+
+    /// Path of the trait, including any type parameters
+    pub path: Path,
+
+    /// Whether to skip adding the current trait as a bound to the type parameters of the type.
+    pub skip_path_as_bound: bool,
+
+    /// Whether `Copy` is needed as an additional bound on type parameters in a packed struct.
+    pub needs_copy_as_bound_if_packed: bool,
+
+    /// Additional bounds required of any type parameters of the type,
+    /// other than the current trait
+    pub additional_bounds: Vec<Ty>,
+
+    /// Can this trait be derived for unions?
+    pub supports_unions: bool,
+
+    pub methods: Vec<MethodDef<'a>>,
+
+    pub associated_types: Vec<(Ident, Ty)>,
+
+    pub is_const: bool,
+}
+
+pub(crate) struct MethodDef<'a> {
+    /// name of the method
+    pub name: Symbol,
+    /// List of generics, e.g., `R: rand::Rng`
+    pub generics: Bounds,
+
+    /// Is there is a `&self` argument? If not, it is a static function.
+    pub explicit_self: bool,
+
+    /// Arguments other than the self argument.
+    pub nonself_args: Vec<(Ty, Symbol)>,
+
+    /// Returns type
+    pub ret_ty: Ty,
+
+    pub attributes: ast::AttrVec,
+
+    pub fieldless_variants_strategy: FieldlessVariantsStrategy,
+
+    pub combine_substructure: RefCell<CombineSubstructureFunc<'a>>,
+}
+
+/// How to handle fieldless enum variants.
+#[derive(PartialEq)]
+pub(crate) enum FieldlessVariantsStrategy {
+    /// Combine fieldless variants into a single match arm.
+    /// This assumes that relevant information has been handled
+    /// by looking at the enum's discriminant.
+    Unify,
+    /// Don't do anything special about fieldless variants. They are
+    /// handled like any other variant.
+    Default,
+    /// If all variants of the enum are fieldless, expand the special
+    /// `AllFieldLessEnum` substructure, so that the entire enum can be handled
+    /// at once.
+    SpecializeIfAllVariantsFieldless,
+}
+
+/// All the data about the data structure/method being derived upon.
+pub(crate) struct Substructure<'a> {
+    /// ident of self
+    pub type_ident: Ident,
+    /// Verbatim access to any non-selflike arguments, i.e. arguments that
+    /// don't have type `&Self`.
+    pub nonselflike_args: &'a [P<Expr>],
+    pub fields: &'a SubstructureFields<'a>,
+}
+
+/// Summary of the relevant parts of a struct/enum field.
+pub(crate) struct FieldInfo {
+    pub span: Span,
+    /// None for tuple structs/normal enum variants, Some for normal
+    /// structs/struct enum variants.
+    pub name: Option<Ident>,
+    /// The expression corresponding to this field of `self`
+    /// (specifically, a reference to it).
+    pub self_expr: P<Expr>,
+    /// The expressions corresponding to references to this field in
+    /// the other selflike arguments.
+    pub other_selflike_exprs: Vec<P<Expr>>,
+}
+
+#[derive(Copy, Clone)]
+pub(crate) enum IsTuple {
+    No,
+    Yes,
+}
+
+/// Fields for a static method
+pub(crate) enum StaticFields {
+    /// Tuple and unit structs/enum variants like this.
+    Unnamed(Vec<Span>, IsTuple),
+    /// Normal structs/struct variants.
+    Named(Vec<(Ident, Span)>),
+}
+
+/// A summary of the possible sets of fields.
+pub(crate) enum SubstructureFields<'a> {
+    /// A non-static method where `Self` is a struct.
+    Struct(&'a ast::VariantData, Vec<FieldInfo>),
+
+    /// A non-static method handling the entire enum at once
+    /// (after it has been determined that none of the enum
+    /// variants has any fields).
+    AllFieldlessEnum(&'a ast::EnumDef),
+
+    /// Matching variants of the enum: variant index, ast::Variant,
+    /// fields: the field name is only non-`None` in the case of a struct
+    /// variant.
+    EnumMatching(usize, &'a ast::Variant, Vec<FieldInfo>),
+
+    /// The discriminant of an enum. The first field is a `FieldInfo` for the discriminants, as
+    /// if they were fields. The second field is the expression to combine the
+    /// discriminant expression with; it will be `None` if no match is necessary.
+    EnumDiscr(FieldInfo, Option<P<Expr>>),
+
+    /// A static method where `Self` is a struct.
+    StaticStruct(&'a ast::VariantData, StaticFields),
+
+    /// A static method where `Self` is an enum.
+    StaticEnum(&'a ast::EnumDef, Vec<(Ident, Span, StaticFields)>),
+}
+
+/// Combine the values of all the fields together. The last argument is
+/// all the fields of all the structures.
+pub(crate) type CombineSubstructureFunc<'a> =
+    Box<dyn FnMut(&ExtCtxt<'_>, Span, &Substructure<'_>) -> BlockOrExpr + 'a>;
+
+pub(crate) fn combine_substructure(
+    f: CombineSubstructureFunc<'_>,
+) -> RefCell<CombineSubstructureFunc<'_>> {
+    RefCell::new(f)
+}
+
+struct TypeParameter {
+    bound_generic_params: ThinVec<ast::GenericParam>,
+    ty: P<ast::Ty>,
+}
+
+/// The code snippets built up for derived code are sometimes used as blocks
+/// (e.g. in a function body) and sometimes used as expressions (e.g. in a match
+/// arm). This structure avoids committing to either form until necessary,
+/// avoiding the insertion of any unnecessary blocks.
+///
+/// The statements come before the expression.
+pub(crate) struct BlockOrExpr(ThinVec<ast::Stmt>, Option<P<Expr>>);
+
+impl BlockOrExpr {
+    pub fn new_stmts(stmts: ThinVec<ast::Stmt>) -> BlockOrExpr {
+        BlockOrExpr(stmts, None)
+    }
+
+    pub fn new_expr(expr: P<Expr>) -> BlockOrExpr {
+        BlockOrExpr(ThinVec::new(), Some(expr))
+    }
+
+    pub fn new_mixed(stmts: ThinVec<ast::Stmt>, expr: Option<P<Expr>>) -> BlockOrExpr {
+        BlockOrExpr(stmts, expr)
+    }
+
+    // Converts it into a block.
+    fn into_block(mut self, cx: &ExtCtxt<'_>, span: Span) -> P<ast::Block> {
+        if let Some(expr) = self.1 {
+            self.0.push(cx.stmt_expr(expr));
+        }
+        cx.block(span, self.0)
+    }
+
+    // Converts it into an expression.
+    fn into_expr(self, cx: &ExtCtxt<'_>, span: Span) -> P<Expr> {
+        if self.0.is_empty() {
+            match self.1 {
+                None => cx.expr_block(cx.block(span, ThinVec::new())),
+                Some(expr) => expr,
+            }
+        } else if self.0.len() == 1
+            && let ast::StmtKind::Expr(expr) = &self.0[0].kind
+            && self.1.is_none()
+        {
+            // There's only a single statement expression. Pull it out.
+            expr.clone()
+        } else {
+            // Multiple statements and/or expressions.
+            cx.expr_block(self.into_block(cx, span))
+        }
+    }
+}
+
+/// This method helps to extract all the type parameters referenced from a
+/// type. For a type parameter `<T>`, it looks for either a `TyPath` that
+/// is not global and starts with `T`, or a `TyQPath`.
+/// Also include bound generic params from the input type.
+fn find_type_parameters(
+    ty: &ast::Ty,
+    ty_param_names: &[Symbol],
+    cx: &ExtCtxt<'_>,
+) -> Vec<TypeParameter> {
+    use rustc_ast::visit;
+
+    struct Visitor<'a, 'b> {
+        cx: &'a ExtCtxt<'b>,
+        ty_param_names: &'a [Symbol],
+        bound_generic_params_stack: ThinVec<ast::GenericParam>,
+        type_params: Vec<TypeParameter>,
+    }
+
+    impl<'a, 'b> visit::Visitor<'a> for Visitor<'a, 'b> {
+        fn visit_ty(&mut self, ty: &'a ast::Ty) {
+            let stack_len = self.bound_generic_params_stack.len();
+            if let ast::TyKind::BareFn(bare_fn) = &ty.kind
+                && !bare_fn.generic_params.is_empty()
+            {
+                // Given a field `x: for<'a> fn(T::SomeType<'a>)`, we wan't to account for `'a` so
+                // that we generate `where for<'a> T::SomeType<'a>: ::core::clone::Clone`. #122622
+                self.bound_generic_params_stack.extend(bare_fn.generic_params.iter().cloned());
+            }
+
+            if let ast::TyKind::Path(_, path) = &ty.kind
+                && let Some(segment) = path.segments.first()
+                && self.ty_param_names.contains(&segment.ident.name)
+            {
+                self.type_params.push(TypeParameter {
+                    bound_generic_params: self.bound_generic_params_stack.clone(),
+                    ty: P(ty.clone()),
+                });
+            }
+
+            visit::walk_ty(self, ty);
+            self.bound_generic_params_stack.truncate(stack_len);
+        }
+
+        // Place bound generic params on a stack, to extract them when a type is encountered.
+        fn visit_poly_trait_ref(&mut self, trait_ref: &'a ast::PolyTraitRef) {
+            let stack_len = self.bound_generic_params_stack.len();
+            self.bound_generic_params_stack.extend(trait_ref.bound_generic_params.iter().cloned());
+
+            visit::walk_poly_trait_ref(self, trait_ref);
+
+            self.bound_generic_params_stack.truncate(stack_len);
+        }
+
+        fn visit_mac_call(&mut self, mac: &ast::MacCall) {
+            self.cx.dcx().emit_err(errors::DeriveMacroCall { span: mac.span() });
+        }
+    }
+
+    let mut visitor = Visitor {
+        cx,
+        ty_param_names,
+        bound_generic_params_stack: ThinVec::new(),
+        type_params: Vec::new(),
+    };
+    visit::Visitor::visit_ty(&mut visitor, ty);
+
+    visitor.type_params
+}
+
+impl<'a> TraitDef<'a> {
+    pub fn expand(
+        self,
+        cx: &ExtCtxt<'_>,
+        mitem: &ast::MetaItem,
+        item: &'a Annotatable,
+        push: &mut dyn FnMut(Annotatable),
+    ) {
+        self.expand_ext(cx, mitem, item, push, false);
+    }
+
+    pub fn expand_ext(
+        self,
+        cx: &ExtCtxt<'_>,
+        mitem: &ast::MetaItem,
+        item: &'a Annotatable,
+        push: &mut dyn FnMut(Annotatable),
+        from_scratch: bool,
+    ) {
+        match item {
+            Annotatable::Item(item) => {
+                let is_packed = item.attrs.iter().any(|attr| {
+                    for r in attr::find_repr_attrs(cx.sess, attr) {
+                        if let attr::ReprPacked(_) = r {
+                            return true;
+                        }
+                    }
+                    false
+                });
+
+                let newitem = match &item.kind {
+                    ast::ItemKind::Struct(struct_def, generics) => self.expand_struct_def(
+                        cx,
+                        struct_def,
+                        item.ident,
+                        generics,
+                        from_scratch,
+                        is_packed,
+                    ),
+                    ast::ItemKind::Enum(enum_def, generics) => {
+                        // We ignore `is_packed` here, because `repr(packed)`
+                        // enums cause an error later on.
+                        //
+                        // This can only cause further compilation errors
+                        // downstream in blatantly illegal code, so it is fine.
+                        self.expand_enum_def(cx, enum_def, item.ident, generics, from_scratch)
+                    }
+                    ast::ItemKind::Union(struct_def, generics) => {
+                        if self.supports_unions {
+                            self.expand_struct_def(
+                                cx,
+                                struct_def,
+                                item.ident,
+                                generics,
+                                from_scratch,
+                                is_packed,
+                            )
+                        } else {
+                            cx.dcx().emit_err(errors::DeriveUnion { span: mitem.span });
+                            return;
+                        }
+                    }
+                    _ => unreachable!(),
+                };
+                // Keep the lint attributes of the previous item to control how the
+                // generated implementations are linted
+                let mut attrs = newitem.attrs.clone();
+                attrs.extend(
+                    item.attrs
+                        .iter()
+                        .filter(|a| {
+                            [
+                                sym::allow,
+                                sym::warn,
+                                sym::deny,
+                                sym::forbid,
+                                sym::stable,
+                                sym::unstable,
+                            ]
+                            .contains(&a.name_or_empty())
+                        })
+                        .cloned(),
+                );
+                push(Annotatable::Item(P(ast::Item { attrs, ..(*newitem).clone() })))
+            }
+            _ => unreachable!(),
+        }
+    }
+
+    /// Given that we are deriving a trait `DerivedTrait` for a type like:
+    ///
+    /// ```ignore (only-for-syntax-highlight)
+    /// struct Struct<'a, ..., 'z, A, B: DeclaredTrait, C, ..., Z>
+    /// where
+    ///     C: WhereTrait,
+    /// {
+    ///     a: A,
+    ///     b: B::Item,
+    ///     b1: <B as DeclaredTrait>::Item,
+    ///     c1: <C as WhereTrait>::Item,
+    ///     c2: Option<<C as WhereTrait>::Item>,
+    ///     ...
+    /// }
+    /// ```
+    ///
+    /// create an impl like:
+    ///
+    /// ```ignore (only-for-syntax-highlight)
+    /// impl<'a, ..., 'z, A, B: DeclaredTrait, C, ..., Z>
+    /// where
+    ///     C: WhereTrait,
+    ///     A: DerivedTrait + B1 + ... + BN,
+    ///     B: DerivedTrait + B1 + ... + BN,
+    ///     C: DerivedTrait + B1 + ... + BN,
+    ///     B::Item: DerivedTrait + B1 + ... + BN,
+    ///     <C as WhereTrait>::Item: DerivedTrait + B1 + ... + BN,
+    ///     ...
+    /// {
+    ///     ...
+    /// }
+    /// ```
+    ///
+    /// where B1, ..., BN are the bounds given by `bounds_paths`.'. Z is a phantom type, and
+    /// therefore does not get bound by the derived trait.
+    fn create_derived_impl(
+        &self,
+        cx: &ExtCtxt<'_>,
+        type_ident: Ident,
+        generics: &Generics,
+        field_tys: Vec<P<ast::Ty>>,
+        methods: Vec<P<ast::AssocItem>>,
+        is_packed: bool,
+    ) -> P<ast::Item> {
+        let trait_path = self.path.to_path(cx, self.span, type_ident, generics);
+
+        // Transform associated types from `deriving::ty::Ty` into `ast::AssocItem`
+        let associated_types = self.associated_types.iter().map(|&(ident, ref type_def)| {
+            P(ast::AssocItem {
+                id: ast::DUMMY_NODE_ID,
+                span: self.span,
+                ident,
+                vis: ast::Visibility {
+                    span: self.span.shrink_to_lo(),
+                    kind: ast::VisibilityKind::Inherited,
+                    tokens: None,
+                },
+                attrs: ast::AttrVec::new(),
+                kind: ast::AssocItemKind::Type(Box::new(ast::TyAlias {
+                    defaultness: ast::Defaultness::Final,
+                    generics: Generics::default(),
+                    where_clauses: ast::TyAliasWhereClauses::default(),
+                    bounds: Vec::new(),
+                    ty: Some(type_def.to_ty(cx, self.span, type_ident, generics)),
+                })),
+                tokens: None,
+            })
+        });
+
+        let mut where_clause = ast::WhereClause::default();
+        where_clause.span = generics.where_clause.span;
+        let ctxt = self.span.ctxt();
+        let span = generics.span.with_ctxt(ctxt);
+
+        // Create the generic parameters
+        let params: ThinVec<_> = generics
+            .params
+            .iter()
+            .map(|param| match &param.kind {
+                GenericParamKind::Lifetime { .. } => param.clone(),
+                GenericParamKind::Type { .. } => {
+                    // Extra restrictions on the generics parameters to the
+                    // type being derived upon.
+                    let bounds: Vec<_> = self
+                        .additional_bounds
+                        .iter()
+                        .map(|p| {
+                            cx.trait_bound(
+                                p.to_path(cx, self.span, type_ident, generics),
+                                self.is_const,
+                            )
+                        })
+                        .chain(
+                            // Add a bound for the current trait.
+                            self.skip_path_as_bound
+                                .not()
+                                .then(|| cx.trait_bound(trait_path.clone(), self.is_const)),
+                        )
+                        .chain({
+                            // Add a `Copy` bound if required.
+                            if is_packed && self.needs_copy_as_bound_if_packed {
+                                let p = deriving::path_std!(marker::Copy);
+                                Some(cx.trait_bound(
+                                    p.to_path(cx, self.span, type_ident, generics),
+                                    self.is_const,
+                                ))
+                            } else {
+                                None
+                            }
+                        })
+                        .chain(
+                            // Also add in any bounds from the declaration.
+                            param.bounds.iter().cloned(),
+                        )
+                        .collect();
+
+                    cx.typaram(param.ident.span.with_ctxt(ctxt), param.ident, bounds, None)
+                }
+                GenericParamKind::Const { ty, kw_span, .. } => {
+                    let const_nodefault_kind = GenericParamKind::Const {
+                        ty: ty.clone(),
+                        kw_span: kw_span.with_ctxt(ctxt),
+
+                        // We can't have default values inside impl block
+                        default: None,
+                    };
+                    let mut param_clone = param.clone();
+                    param_clone.kind = const_nodefault_kind;
+                    param_clone
+                }
+            })
+            .collect();
+
+        // and similarly for where clauses
+        where_clause.predicates.extend(generics.where_clause.predicates.iter().map(|clause| {
+            match clause {
+                ast::WherePredicate::BoundPredicate(wb) => {
+                    let span = wb.span.with_ctxt(ctxt);
+                    ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate {
+                        span,
+                        ..wb.clone()
+                    })
+                }
+                ast::WherePredicate::RegionPredicate(wr) => {
+                    let span = wr.span.with_ctxt(ctxt);
+                    ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate {
+                        span,
+                        ..wr.clone()
+                    })
+                }
+                ast::WherePredicate::EqPredicate(we) => {
+                    let span = we.span.with_ctxt(ctxt);
+                    ast::WherePredicate::EqPredicate(ast::WhereEqPredicate { span, ..we.clone() })
+                }
+            }
+        }));
+
+        let ty_param_names: Vec<Symbol> = params
+            .iter()
+            .filter(|param| matches!(param.kind, ast::GenericParamKind::Type { .. }))
+            .map(|ty_param| ty_param.ident.name)
+            .collect();
+
+        if !ty_param_names.is_empty() {
+            for field_ty in field_tys {
+                let field_ty_params = find_type_parameters(&field_ty, &ty_param_names, cx);
+
+                for field_ty_param in field_ty_params {
+                    // if we have already handled this type, skip it
+                    if let ast::TyKind::Path(_, p) = &field_ty_param.ty.kind
+                        && let [sole_segment] = &*p.segments
+                        && ty_param_names.contains(&sole_segment.ident.name)
+                    {
+                        continue;
+                    }
+                    let mut bounds: Vec<_> = self
+                        .additional_bounds
+                        .iter()
+                        .map(|p| {
+                            cx.trait_bound(
+                                p.to_path(cx, self.span, type_ident, generics),
+                                self.is_const,
+                            )
+                        })
+                        .collect();
+
+                    // Require the current trait.
+                    if !self.skip_path_as_bound {
+                        bounds.push(cx.trait_bound(trait_path.clone(), self.is_const));
+                    }
+
+                    // Add a `Copy` bound if required.
+                    if is_packed && self.needs_copy_as_bound_if_packed {
+                        let p = deriving::path_std!(marker::Copy);
+                        bounds.push(cx.trait_bound(
+                            p.to_path(cx, self.span, type_ident, generics),
+                            self.is_const,
+                        ));
+                    }
+
+                    if !bounds.is_empty() {
+                        let predicate = ast::WhereBoundPredicate {
+                            span: self.span,
+                            bound_generic_params: field_ty_param.bound_generic_params,
+                            bounded_ty: field_ty_param.ty,
+                            bounds,
+                        };
+
+                        let predicate = ast::WherePredicate::BoundPredicate(predicate);
+                        where_clause.predicates.push(predicate);
+                    }
+                }
+            }
+        }
+
+        let trait_generics = Generics { params, where_clause, span };
+
+        // Create the reference to the trait.
+        let trait_ref = cx.trait_ref(trait_path);
+
+        let self_params: Vec<_> = generics
+            .params
+            .iter()
+            .map(|param| match param.kind {
+                GenericParamKind::Lifetime { .. } => {
+                    GenericArg::Lifetime(cx.lifetime(param.ident.span.with_ctxt(ctxt), param.ident))
+                }
+                GenericParamKind::Type { .. } => {
+                    GenericArg::Type(cx.ty_ident(param.ident.span.with_ctxt(ctxt), param.ident))
+                }
+                GenericParamKind::Const { .. } => {
+                    GenericArg::Const(cx.const_ident(param.ident.span.with_ctxt(ctxt), param.ident))
+                }
+            })
+            .collect();
+
+        // Create the type of `self`.
+        let path = cx.path_all(self.span, false, vec![type_ident], self_params);
+        let self_type = cx.ty_path(path);
+
+        let attrs = thin_vec![cx.attr_word(sym::automatically_derived, self.span),];
+        let opt_trait_ref = Some(trait_ref);
+
+        cx.item(
+            self.span,
+            Ident::empty(),
+            attrs,
+            ast::ItemKind::Impl(Box::new(ast::Impl {
+                safety: ast::Safety::Default,
+                polarity: ast::ImplPolarity::Positive,
+                defaultness: ast::Defaultness::Final,
+                constness: if self.is_const { ast::Const::Yes(DUMMY_SP) } else { ast::Const::No },
+                generics: trait_generics,
+                of_trait: opt_trait_ref,
+                self_ty: self_type,
+                items: methods.into_iter().chain(associated_types).collect(),
+            })),
+        )
+    }
+
+    fn expand_struct_def(
+        &self,
+        cx: &ExtCtxt<'_>,
+        struct_def: &'a VariantData,
+        type_ident: Ident,
+        generics: &Generics,
+        from_scratch: bool,
+        is_packed: bool,
+    ) -> P<ast::Item> {
+        let field_tys: Vec<P<ast::Ty>> =
+            struct_def.fields().iter().map(|field| field.ty.clone()).collect();
+
+        let methods = self
+            .methods
+            .iter()
+            .map(|method_def| {
+                let (explicit_self, selflike_args, nonselflike_args, nonself_arg_tys) =
+                    method_def.extract_arg_details(cx, self, type_ident, generics);
+
+                let body = if from_scratch || method_def.is_static() {
+                    method_def.expand_static_struct_method_body(
+                        cx,
+                        self,
+                        struct_def,
+                        type_ident,
+                        &nonselflike_args,
+                    )
+                } else {
+                    method_def.expand_struct_method_body(
+                        cx,
+                        self,
+                        struct_def,
+                        type_ident,
+                        &selflike_args,
+                        &nonselflike_args,
+                        is_packed,
+                    )
+                };
+
+                method_def.create_method(
+                    cx,
+                    self,
+                    type_ident,
+                    generics,
+                    explicit_self,
+                    nonself_arg_tys,
+                    body,
+                )
+            })
+            .collect();
+
+        self.create_derived_impl(cx, type_ident, generics, field_tys, methods, is_packed)
+    }
+
+    fn expand_enum_def(
+        &self,
+        cx: &ExtCtxt<'_>,
+        enum_def: &'a EnumDef,
+        type_ident: Ident,
+        generics: &Generics,
+        from_scratch: bool,
+    ) -> P<ast::Item> {
+        let mut field_tys = Vec::new();
+
+        for variant in &enum_def.variants {
+            field_tys.extend(variant.data.fields().iter().map(|field| field.ty.clone()));
+        }
+
+        let methods = self
+            .methods
+            .iter()
+            .map(|method_def| {
+                let (explicit_self, selflike_args, nonselflike_args, nonself_arg_tys) =
+                    method_def.extract_arg_details(cx, self, type_ident, generics);
+
+                let body = if from_scratch || method_def.is_static() {
+                    method_def.expand_static_enum_method_body(
+                        cx,
+                        self,
+                        enum_def,
+                        type_ident,
+                        &nonselflike_args,
+                    )
+                } else {
+                    method_def.expand_enum_method_body(
+                        cx,
+                        self,
+                        enum_def,
+                        type_ident,
+                        selflike_args,
+                        &nonselflike_args,
+                    )
+                };
+
+                method_def.create_method(
+                    cx,
+                    self,
+                    type_ident,
+                    generics,
+                    explicit_self,
+                    nonself_arg_tys,
+                    body,
+                )
+            })
+            .collect();
+
+        let is_packed = false; // enums are never packed
+        self.create_derived_impl(cx, type_ident, generics, field_tys, methods, is_packed)
+    }
+}
+
+impl<'a> MethodDef<'a> {
+    fn call_substructure_method(
+        &self,
+        cx: &ExtCtxt<'_>,
+        trait_: &TraitDef<'_>,
+        type_ident: Ident,
+        nonselflike_args: &[P<Expr>],
+        fields: &SubstructureFields<'_>,
+    ) -> BlockOrExpr {
+        let span = trait_.span;
+        let substructure = Substructure { type_ident, nonselflike_args, fields };
+        let mut f = self.combine_substructure.borrow_mut();
+        let f: &mut CombineSubstructureFunc<'_> = &mut *f;
+        f(cx, span, &substructure)
+    }
+
+    fn get_ret_ty(
+        &self,
+        cx: &ExtCtxt<'_>,
+        trait_: &TraitDef<'_>,
+        generics: &Generics,
+        type_ident: Ident,
+    ) -> P<ast::Ty> {
+        self.ret_ty.to_ty(cx, trait_.span, type_ident, generics)
+    }
+
+    fn is_static(&self) -> bool {
+        !self.explicit_self
+    }
+
+    // The return value includes:
+    // - explicit_self: The `&self` arg, if present.
+    // - selflike_args: Expressions for `&self` (if present) and also any other
+    //   args with the same type (e.g. the `other` arg in `PartialEq::eq`).
+    // - nonselflike_args: Expressions for all the remaining args.
+    // - nonself_arg_tys: Additional information about all the args other than
+    //   `&self`.
+    fn extract_arg_details(
+        &self,
+        cx: &ExtCtxt<'_>,
+        trait_: &TraitDef<'_>,
+        type_ident: Ident,
+        generics: &Generics,
+    ) -> (Option<ast::ExplicitSelf>, ThinVec<P<Expr>>, Vec<P<Expr>>, Vec<(Ident, P<ast::Ty>)>) {
+        let mut selflike_args = ThinVec::new();
+        let mut nonselflike_args = Vec::new();
+        let mut nonself_arg_tys = Vec::new();
+        let span = trait_.span;
+
+        let explicit_self = self.explicit_self.then(|| {
+            let (self_expr, explicit_self) = ty::get_explicit_self(cx, span);
+            selflike_args.push(self_expr);
+            explicit_self
+        });
+
+        for (ty, name) in self.nonself_args.iter() {
+            let ast_ty = ty.to_ty(cx, span, type_ident, generics);
+            let ident = Ident::new(*name, span);
+            nonself_arg_tys.push((ident, ast_ty));
+
+            let arg_expr = cx.expr_ident(span, ident);
+
+            match ty {
+                // Selflike (`&Self`) arguments only occur in non-static methods.
+                Ref(box Self_, _) if !self.is_static() => selflike_args.push(arg_expr),
+                Self_ => cx.dcx().span_bug(span, "`Self` in non-return position"),
+                _ => nonselflike_args.push(arg_expr),
+            }
+        }
+
+        (explicit_self, selflike_args, nonselflike_args, nonself_arg_tys)
+    }
+
+    fn create_method(
+        &self,
+        cx: &ExtCtxt<'_>,
+        trait_: &TraitDef<'_>,
+        type_ident: Ident,
+        generics: &Generics,
+        explicit_self: Option<ast::ExplicitSelf>,
+        nonself_arg_tys: Vec<(Ident, P<ast::Ty>)>,
+        body: BlockOrExpr,
+    ) -> P<ast::AssocItem> {
+        let span = trait_.span;
+        // Create the generics that aren't for `Self`.
+        let fn_generics = self.generics.to_generics(cx, span, type_ident, generics);
+
+        let args = {
+            let self_arg = explicit_self.map(|explicit_self| {
+                let ident = Ident::with_dummy_span(kw::SelfLower).with_span_pos(span);
+                ast::Param::from_self(ast::AttrVec::default(), explicit_self, ident)
+            });
+            let nonself_args =
+                nonself_arg_tys.into_iter().map(|(name, ty)| cx.param(span, name, ty));
+            self_arg.into_iter().chain(nonself_args).collect()
+        };
+
+        let ret_type = self.get_ret_ty(cx, trait_, generics, type_ident);
+
+        let method_ident = Ident::new(self.name, span);
+        let fn_decl = cx.fn_decl(args, ast::FnRetTy::Ty(ret_type));
+        let body_block = body.into_block(cx, span);
+
+        let trait_lo_sp = span.shrink_to_lo();
+
+        let sig = ast::FnSig { header: ast::FnHeader::default(), decl: fn_decl, span };
+        let defaultness = ast::Defaultness::Final;
+
+        // Create the method.
+        P(ast::AssocItem {
+            id: ast::DUMMY_NODE_ID,
+            attrs: self.attributes.clone(),
+            span,
+            vis: ast::Visibility {
+                span: trait_lo_sp,
+                kind: ast::VisibilityKind::Inherited,
+                tokens: None,
+            },
+            ident: method_ident,
+            kind: ast::AssocItemKind::Fn(Box::new(ast::Fn {
+                defaultness,
+                sig,
+                generics: fn_generics,
+                body: Some(body_block),
+            })),
+            tokens: None,
+        })
+    }
+
+    /// The normal case uses field access.
+    ///
+    /// ```
+    /// #[derive(PartialEq)]
+    /// # struct Dummy;
+    /// struct A { x: u8, y: u8 }
+    ///
+    /// // equivalent to:
+    /// impl PartialEq for A {
+    ///     fn eq(&self, other: &A) -> bool {
+    ///         self.x == other.x && self.y == other.y
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// But if the struct is `repr(packed)`, we can't use something like
+    /// `&self.x` because that might cause an unaligned ref. So for any trait
+    /// method that takes a reference, we use a local block to force a copy.
+    /// This requires that the field impl `Copy`.
+    ///
+    /// ```rust,ignore (example)
+    /// # struct A { x: u8, y: u8 }
+    /// impl PartialEq for A {
+    ///     fn eq(&self, other: &A) -> bool {
+    ///         // Desugars to `{ self.x }.eq(&{ other.y }) && ...`
+    ///         { self.x } == { other.y } && { self.y } == { other.y }
+    ///     }
+    /// }
+    /// impl Hash for A {
+    ///     fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
+    ///         ::core::hash::Hash::hash(&{ self.x }, state);
+    ///         ::core::hash::Hash::hash(&{ self.y }, state);
+    ///     }
+    /// }
+    /// ```
+    fn expand_struct_method_body<'b>(
+        &self,
+        cx: &ExtCtxt<'_>,
+        trait_: &TraitDef<'b>,
+        struct_def: &'b VariantData,
+        type_ident: Ident,
+        selflike_args: &[P<Expr>],
+        nonselflike_args: &[P<Expr>],
+        is_packed: bool,
+    ) -> BlockOrExpr {
+        assert!(selflike_args.len() == 1 || selflike_args.len() == 2);
+
+        let selflike_fields =
+            trait_.create_struct_field_access_fields(cx, selflike_args, struct_def, is_packed);
+        self.call_substructure_method(
+            cx,
+            trait_,
+            type_ident,
+            nonselflike_args,
+            &Struct(struct_def, selflike_fields),
+        )
+    }
+
+    fn expand_static_struct_method_body(
+        &self,
+        cx: &ExtCtxt<'_>,
+        trait_: &TraitDef<'_>,
+        struct_def: &VariantData,
+        type_ident: Ident,
+        nonselflike_args: &[P<Expr>],
+    ) -> BlockOrExpr {
+        let summary = trait_.summarise_struct(cx, struct_def);
+
+        self.call_substructure_method(
+            cx,
+            trait_,
+            type_ident,
+            nonselflike_args,
+            &StaticStruct(struct_def, summary),
+        )
+    }
+
+    /// ```
+    /// #[derive(PartialEq)]
+    /// # struct Dummy;
+    /// enum A {
+    ///     A1,
+    ///     A2(i32)
+    /// }
+    /// ```
+    ///
+    /// is equivalent to:
+    ///
+    /// ```
+    /// #![feature(core_intrinsics)]
+    /// enum A {
+    ///     A1,
+    ///     A2(i32)
+    /// }
+    /// impl ::core::cmp::PartialEq for A {
+    ///     #[inline]
+    ///     fn eq(&self, other: &A) -> bool {
+    ///         let __self_discr = ::core::intrinsics::discriminant_value(self);
+    ///         let __arg1_discr = ::core::intrinsics::discriminant_value(other);
+    ///         __self_discr == __arg1_discr
+    ///             && match (self, other) {
+    ///                 (A::A2(__self_0), A::A2(__arg1_0)) => *__self_0 == *__arg1_0,
+    ///                 _ => true,
+    ///             }
+    ///     }
+    /// }
+    /// ```
+    ///
+    /// Creates a discriminant check combined with a match for a tuple of all
+    /// `selflike_args`, with an arm for each variant with fields, possibly an
+    /// arm for each fieldless variant (if `unify_fieldless_variants` is not
+    /// `Unify`), and possibly a default arm.
+    fn expand_enum_method_body<'b>(
+        &self,
+        cx: &ExtCtxt<'_>,
+        trait_: &TraitDef<'b>,
+        enum_def: &'b EnumDef,
+        type_ident: Ident,
+        mut selflike_args: ThinVec<P<Expr>>,
+        nonselflike_args: &[P<Expr>],
+    ) -> BlockOrExpr {
+        assert!(
+            !selflike_args.is_empty(),
+            "static methods must use `expand_static_enum_method_body`",
+        );
+
+        let span = trait_.span;
+        let variants = &enum_def.variants;
+
+        // Traits that unify fieldless variants always use the discriminant(s).
+        let unify_fieldless_variants =
+            self.fieldless_variants_strategy == FieldlessVariantsStrategy::Unify;
+
+        // For zero-variant enum, this function body is unreachable. Generate
+        // `match *self {}`. This produces machine code identical to `unsafe {
+        // core::intrinsics::unreachable() }` while being safe and stable.
+        if variants.is_empty() {
+            selflike_args.truncate(1);
+            let match_arg = cx.expr_deref(span, selflike_args.pop().unwrap());
+            let match_arms = ThinVec::new();
+            let expr = cx.expr_match(span, match_arg, match_arms);
+            return BlockOrExpr(ThinVec::new(), Some(expr));
+        }
+
+        let prefixes = iter::once("__self".to_string())
+            .chain(
+                selflike_args
+                    .iter()
+                    .enumerate()
+                    .skip(1)
+                    .map(|(arg_count, _selflike_arg)| format!("__arg{arg_count}")),
+            )
+            .collect::<Vec<String>>();
+
+        // Build a series of let statements mapping each selflike_arg
+        // to its discriminant value.
+        //
+        // e.g. for `PartialEq::eq` builds two statements:
+        // ```
+        // let __self_discr = ::core::intrinsics::discriminant_value(self);
+        // let __arg1_discr = ::core::intrinsics::discriminant_value(other);
+        // ```
+        let get_discr_pieces = |cx: &ExtCtxt<'_>| {
+            let discr_idents: Vec<_> = prefixes
+                .iter()
+                .map(|name| Ident::from_str_and_span(&format!("{name}_discr"), span))
+                .collect();
+
+            let mut discr_exprs: Vec<_> = discr_idents
+                .iter()
+                .map(|&ident| cx.expr_addr_of(span, cx.expr_ident(span, ident)))
+                .collect();
+
+            let self_expr = discr_exprs.remove(0);
+            let other_selflike_exprs = discr_exprs;
+            let discr_field = FieldInfo { span, name: None, self_expr, other_selflike_exprs };
+
+            let discr_let_stmts: ThinVec<_> = iter::zip(&discr_idents, &selflike_args)
+                .map(|(&ident, selflike_arg)| {
+                    let variant_value = deriving::call_intrinsic(
+                        cx,
+                        span,
+                        sym::discriminant_value,
+                        thin_vec![selflike_arg.clone()],
+                    );
+                    cx.stmt_let(span, false, ident, variant_value)
+                })
+                .collect();
+
+            (discr_field, discr_let_stmts)
+        };
+
+        // There are some special cases involving fieldless enums where no
+        // match is necessary.
+        let all_fieldless = variants.iter().all(|v| v.data.fields().is_empty());
+        if all_fieldless {
+            if variants.len() > 1 {
+                match self.fieldless_variants_strategy {
+                    FieldlessVariantsStrategy::Unify => {
+                        // If the type is fieldless and the trait uses the discriminant and
+                        // there are multiple variants, we need just an operation on
+                        // the discriminant(s).
+                        let (discr_field, mut discr_let_stmts) = get_discr_pieces(cx);
+                        let mut discr_check = self.call_substructure_method(
+                            cx,
+                            trait_,
+                            type_ident,
+                            nonselflike_args,
+                            &EnumDiscr(discr_field, None),
+                        );
+                        discr_let_stmts.append(&mut discr_check.0);
+                        return BlockOrExpr(discr_let_stmts, discr_check.1);
+                    }
+                    FieldlessVariantsStrategy::SpecializeIfAllVariantsFieldless => {
+                        return self.call_substructure_method(
+                            cx,
+                            trait_,
+                            type_ident,
+                            nonselflike_args,
+                            &AllFieldlessEnum(enum_def),
+                        );
+                    }
+                    FieldlessVariantsStrategy::Default => (),
+                }
+            } else if variants.len() == 1 {
+                // If there is a single variant, we don't need an operation on
+                // the discriminant(s). Just use the most degenerate result.
+                return self.call_substructure_method(
+                    cx,
+                    trait_,
+                    type_ident,
+                    nonselflike_args,
+                    &EnumMatching(0, &variants[0], Vec::new()),
+                );
+            }
+        }
+
+        // These arms are of the form:
+        // (Variant1, Variant1, ...) => Body1
+        // (Variant2, Variant2, ...) => Body2
+        // ...
+        // where each tuple has length = selflike_args.len()
+        let mut match_arms: ThinVec<ast::Arm> = variants
+            .iter()
+            .enumerate()
+            .filter(|&(_, v)| !(unify_fieldless_variants && v.data.fields().is_empty()))
+            .map(|(index, variant)| {
+                // A single arm has form (&VariantK, &VariantK, ...) => BodyK
+                // (see "Final wrinkle" note below for why.)
+
+                let fields = trait_.create_struct_pattern_fields(cx, &variant.data, &prefixes);
+
+                let sp = variant.span.with_ctxt(trait_.span.ctxt());
+                let variant_path = cx.path(sp, vec![type_ident, variant.ident]);
+                let by_ref = ByRef::No; // because enums can't be repr(packed)
+                let mut subpats = trait_.create_struct_patterns(
+                    cx,
+                    variant_path,
+                    &variant.data,
+                    &prefixes,
+                    by_ref,
+                );
+
+                // `(VariantK, VariantK, ...)` or just `VariantK`.
+                let single_pat = if subpats.len() == 1 {
+                    subpats.pop().unwrap()
+                } else {
+                    cx.pat_tuple(span, subpats)
+                };
+
+                // For the BodyK, we need to delegate to our caller,
+                // passing it an EnumMatching to indicate which case
+                // we are in.
+                //
+                // Now, for some given VariantK, we have built up
+                // expressions for referencing every field of every
+                // Self arg, assuming all are instances of VariantK.
+                // Build up code associated with such a case.
+                let substructure = EnumMatching(index, variant, fields);
+                let arm_expr = self
+                    .call_substructure_method(
+                        cx,
+                        trait_,
+                        type_ident,
+                        nonselflike_args,
+                        &substructure,
+                    )
+                    .into_expr(cx, span);
+
+                cx.arm(span, single_pat, arm_expr)
+            })
+            .collect();
+
+        // Add a default arm to the match, if necessary.
+        let first_fieldless = variants.iter().find(|v| v.data.fields().is_empty());
+        let default = match first_fieldless {
+            Some(v) if unify_fieldless_variants => {
+                // We need a default case that handles all the fieldless
+                // variants. The index and actual variant aren't meaningful in
+                // this case, so just use dummy values.
+                Some(
+                    self.call_substructure_method(
+                        cx,
+                        trait_,
+                        type_ident,
+                        nonselflike_args,
+                        &EnumMatching(0, v, Vec::new()),
+                    )
+                    .into_expr(cx, span),
+                )
+            }
+            _ if variants.len() > 1 && selflike_args.len() > 1 => {
+                // Because we know that all the arguments will match if we reach
+                // the match expression we add the unreachable intrinsics as the
+                // result of the default which should help llvm in optimizing it.
+                Some(deriving::call_unreachable(cx, span))
+            }
+            _ => None,
+        };
+        if let Some(arm) = default {
+            match_arms.push(cx.arm(span, cx.pat_wild(span), arm));
+        }
+
+        // Create a match expression with one arm per discriminant plus
+        // possibly a default arm, e.g.:
+        //      match (self, other) {
+        //          (Variant1, Variant1, ...) => Body1
+        //          (Variant2, Variant2, ...) => Body2,
+        //          ...
+        //          _ => ::core::intrinsics::unreachable(),
+        //      }
+        let get_match_expr = |mut selflike_args: ThinVec<P<Expr>>| {
+            let match_arg = if selflike_args.len() == 1 {
+                selflike_args.pop().unwrap()
+            } else {
+                cx.expr(span, ast::ExprKind::Tup(selflike_args))
+            };
+            cx.expr_match(span, match_arg, match_arms)
+        };
+
+        // If the trait uses the discriminant and there are multiple variants, we need
+        // to add a discriminant check operation before the match. Otherwise, the match
+        // is enough.
+        if unify_fieldless_variants && variants.len() > 1 {
+            let (discr_field, mut discr_let_stmts) = get_discr_pieces(cx);
+
+            // Combine a discriminant check with the match.
+            let mut discr_check_plus_match = self.call_substructure_method(
+                cx,
+                trait_,
+                type_ident,
+                nonselflike_args,
+                &EnumDiscr(discr_field, Some(get_match_expr(selflike_args))),
+            );
+            discr_let_stmts.append(&mut discr_check_plus_match.0);
+            BlockOrExpr(discr_let_stmts, discr_check_plus_match.1)
+        } else {
+            BlockOrExpr(ThinVec::new(), Some(get_match_expr(selflike_args)))
+        }
+    }
+
+    fn expand_static_enum_method_body(
+        &self,
+        cx: &ExtCtxt<'_>,
+        trait_: &TraitDef<'_>,
+        enum_def: &EnumDef,
+        type_ident: Ident,
+        nonselflike_args: &[P<Expr>],
+    ) -> BlockOrExpr {
+        let summary = enum_def
+            .variants
+            .iter()
+            .map(|v| {
+                let sp = v.span.with_ctxt(trait_.span.ctxt());
+                let summary = trait_.summarise_struct(cx, &v.data);
+                (v.ident, sp, summary)
+            })
+            .collect();
+        self.call_substructure_method(
+            cx,
+            trait_,
+            type_ident,
+            nonselflike_args,
+            &StaticEnum(enum_def, summary),
+        )
+    }
+}
+
+// general helper methods.
+impl<'a> TraitDef<'a> {
+    fn summarise_struct(&self, cx: &ExtCtxt<'_>, struct_def: &VariantData) -> StaticFields {
+        let mut named_idents = Vec::new();
+        let mut just_spans = Vec::new();
+        for field in struct_def.fields() {
+            let sp = field.span.with_ctxt(self.span.ctxt());
+            match field.ident {
+                Some(ident) => named_idents.push((ident, sp)),
+                _ => just_spans.push(sp),
+            }
+        }
+
+        let is_tuple = match struct_def {
+            ast::VariantData::Tuple(..) => IsTuple::Yes,
+            _ => IsTuple::No,
+        };
+        match (just_spans.is_empty(), named_idents.is_empty()) {
+            (false, false) => cx
+                .dcx()
+                .span_bug(self.span, "a struct with named and unnamed fields in generic `derive`"),
+            // named fields
+            (_, false) => Named(named_idents),
+            // unnamed fields
+            (false, _) => Unnamed(just_spans, is_tuple),
+            // empty
+            _ => Named(Vec::new()),
+        }
+    }
+
+    fn create_struct_patterns(
+        &self,
+        cx: &ExtCtxt<'_>,
+        struct_path: ast::Path,
+        struct_def: &'a VariantData,
+        prefixes: &[String],
+        by_ref: ByRef,
+    ) -> ThinVec<P<ast::Pat>> {
+        prefixes
+            .iter()
+            .map(|prefix| {
+                let pieces_iter =
+                    struct_def.fields().iter().enumerate().map(|(i, struct_field)| {
+                        let sp = struct_field.span.with_ctxt(self.span.ctxt());
+                        let ident = self.mk_pattern_ident(prefix, i);
+                        let path = ident.with_span_pos(sp);
+                        (
+                            sp,
+                            struct_field.ident,
+                            cx.pat(
+                                path.span,
+                                PatKind::Ident(BindingMode(by_ref, Mutability::Not), path, None),
+                            ),
+                        )
+                    });
+
+                let struct_path = struct_path.clone();
+                match *struct_def {
+                    VariantData::Struct { .. } => {
+                        let field_pats = pieces_iter
+                            .map(|(sp, ident, pat)| {
+                                if ident.is_none() {
+                                    cx.dcx().span_bug(
+                                        sp,
+                                        "a braced struct with unnamed fields in `derive`",
+                                    );
+                                }
+                                ast::PatField {
+                                    ident: ident.unwrap(),
+                                    is_shorthand: false,
+                                    attrs: ast::AttrVec::new(),
+                                    id: ast::DUMMY_NODE_ID,
+                                    span: pat.span.with_ctxt(self.span.ctxt()),
+                                    pat,
+                                    is_placeholder: false,
+                                }
+                            })
+                            .collect();
+                        cx.pat_struct(self.span, struct_path, field_pats)
+                    }
+                    VariantData::Tuple(..) => {
+                        let subpats = pieces_iter.map(|(_, _, subpat)| subpat).collect();
+                        cx.pat_tuple_struct(self.span, struct_path, subpats)
+                    }
+                    VariantData::Unit(..) => cx.pat_path(self.span, struct_path),
+                }
+            })
+            .collect()
+    }
+
+    fn create_fields<F>(&self, struct_def: &'a VariantData, mk_exprs: F) -> Vec<FieldInfo>
+    where
+        F: Fn(usize, &ast::FieldDef, Span) -> Vec<P<ast::Expr>>,
+    {
+        struct_def
+            .fields()
+            .iter()
+            .enumerate()
+            .map(|(i, struct_field)| {
+                // For this field, get an expr for each selflike_arg. E.g. for
+                // `PartialEq::eq`, one for each of `&self` and `other`.
+                let sp = struct_field.span.with_ctxt(self.span.ctxt());
+                let mut exprs: Vec<_> = mk_exprs(i, struct_field, sp);
+                let self_expr = exprs.remove(0);
+                let other_selflike_exprs = exprs;
+                FieldInfo {
+                    span: sp.with_ctxt(self.span.ctxt()),
+                    name: struct_field.ident,
+                    self_expr,
+                    other_selflike_exprs,
+                }
+            })
+            .collect()
+    }
+
+    fn mk_pattern_ident(&self, prefix: &str, i: usize) -> Ident {
+        Ident::from_str_and_span(&format!("{prefix}_{i}"), self.span)
+    }
+
+    fn create_struct_pattern_fields(
+        &self,
+        cx: &ExtCtxt<'_>,
+        struct_def: &'a VariantData,
+        prefixes: &[String],
+    ) -> Vec<FieldInfo> {
+        self.create_fields(struct_def, |i, _struct_field, sp| {
+            prefixes
+                .iter()
+                .map(|prefix| {
+                    let ident = self.mk_pattern_ident(prefix, i);
+                    cx.expr_path(cx.path_ident(sp, ident))
+                })
+                .collect()
+        })
+    }
+
+    fn create_struct_field_access_fields(
+        &self,
+        cx: &ExtCtxt<'_>,
+        selflike_args: &[P<Expr>],
+        struct_def: &'a VariantData,
+        is_packed: bool,
+    ) -> Vec<FieldInfo> {
+        self.create_fields(struct_def, |i, struct_field, sp| {
+            selflike_args
+                .iter()
+                .map(|selflike_arg| {
+                    // Note: we must use `struct_field.span` rather than `sp` in the
+                    // `unwrap_or_else` case otherwise the hygiene is wrong and we get
+                    // "field `0` of struct `Point` is private" errors on tuple
+                    // structs.
+                    let mut field_expr = cx.expr(
+                        sp,
+                        ast::ExprKind::Field(
+                            selflike_arg.clone(),
+                            struct_field.ident.unwrap_or_else(|| {
+                                Ident::from_str_and_span(&i.to_string(), struct_field.span)
+                            }),
+                        ),
+                    );
+                    if is_packed {
+                        // In general, fields in packed structs are copied via a
+                        // block, e.g. `&{self.0}`. The two exceptions are `[u8]`
+                        // and `str` fields, which cannot be copied and also never
+                        // cause unaligned references. These exceptions are allowed
+                        // to handle the `FlexZeroSlice` type in the `zerovec`
+                        // crate within `icu4x-0.9.0`.
+                        //
+                        // Once use of `icu4x-0.9.0` has dropped sufficiently, this
+                        // exception should be removed.
+                        let is_simple_path = |ty: &P<ast::Ty>, sym| {
+                            if let TyKind::Path(None, ast::Path { segments, .. }) = &ty.kind
+                                && let [seg] = segments.as_slice()
+                                && seg.ident.name == sym
+                                && seg.args.is_none()
+                            {
+                                true
+                            } else {
+                                false
+                            }
+                        };
+
+                        let exception = if let TyKind::Slice(ty) = &struct_field.ty.kind
+                            && is_simple_path(ty, sym::u8)
+                        {
+                            Some("byte")
+                        } else if is_simple_path(&struct_field.ty, sym::str) {
+                            Some("string")
+                        } else {
+                            None
+                        };
+
+                        if let Some(ty) = exception {
+                            cx.sess.psess.buffer_lint(
+                                BYTE_SLICE_IN_PACKED_STRUCT_WITH_DERIVE,
+                                sp,
+                                ast::CRATE_NODE_ID,
+                                rustc_lint_defs::BuiltinLintDiag::ByteSliceInPackedStructWithDerive {
+                                    ty: ty.to_string(),
+                                },
+                            );
+                        } else {
+                            // Wrap the expression in `{...}`, causing a copy.
+                            field_expr = cx.expr_block(
+                                cx.block(struct_field.span, thin_vec![cx.stmt_expr(field_expr)]),
+                            );
+                        }
+                    }
+                    cx.expr_addr_of(sp, field_expr)
+                })
+                .collect()
+        })
+    }
+}
+
+/// The function passed to `cs_fold` is called repeatedly with a value of this
+/// type. It describes one part of the code generation. The result is always an
+/// expression.
+pub(crate) enum CsFold<'a> {
+    /// The basic case: a field expression for one or more selflike args. E.g.
+    /// for `PartialEq::eq` this is something like `self.x == other.x`.
+    Single(&'a FieldInfo),
+
+    /// The combination of two field expressions. E.g. for `PartialEq::eq` this
+    /// is something like `<field1 equality> && <field2 equality>`.
+    Combine(Span, P<Expr>, P<Expr>),
+
+    // The fallback case for a struct or enum variant with no fields.
+    Fieldless,
+}
+
+/// Folds over fields, combining the expressions for each field in a sequence.
+/// Statics may not be folded over.
+pub(crate) fn cs_fold<F>(
+    use_foldl: bool,
+    cx: &ExtCtxt<'_>,
+    trait_span: Span,
+    substructure: &Substructure<'_>,
+    mut f: F,
+) -> P<Expr>
+where
+    F: FnMut(&ExtCtxt<'_>, CsFold<'_>) -> P<Expr>,
+{
+    match substructure.fields {
+        EnumMatching(.., all_fields) | Struct(_, all_fields) => {
+            if all_fields.is_empty() {
+                return f(cx, CsFold::Fieldless);
+            }
+
+            let (base_field, rest) = if use_foldl {
+                all_fields.split_first().unwrap()
+            } else {
+                all_fields.split_last().unwrap()
+            };
+
+            let base_expr = f(cx, CsFold::Single(base_field));
+
+            let op = |old, field: &FieldInfo| {
+                let new = f(cx, CsFold::Single(field));
+                f(cx, CsFold::Combine(field.span, old, new))
+            };
+
+            if use_foldl {
+                rest.iter().fold(base_expr, op)
+            } else {
+                rest.iter().rfold(base_expr, op)
+            }
+        }
+        EnumDiscr(discr_field, match_expr) => {
+            let discr_check_expr = f(cx, CsFold::Single(discr_field));
+            if let Some(match_expr) = match_expr {
+                if use_foldl {
+                    f(cx, CsFold::Combine(trait_span, discr_check_expr, match_expr.clone()))
+                } else {
+                    f(cx, CsFold::Combine(trait_span, match_expr.clone(), discr_check_expr))
+                }
+            } else {
+                discr_check_expr
+            }
+        }
+        StaticEnum(..) | StaticStruct(..) => {
+            cx.dcx().span_bug(trait_span, "static function in `derive`")
+        }
+        AllFieldlessEnum(..) => cx.dcx().span_bug(trait_span, "fieldless enum in `derive`"),
+    }
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs
new file mode 100644
index 00000000000..f01d586033e
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs
@@ -0,0 +1,204 @@
+//! A mini version of ast::Ty, which is easier to use, and features an explicit `Self` type to use
+//! when specifying impls to be derived.
+
+pub(crate) use Ty::*;
+
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind};
+use rustc_expand::base::ExtCtxt;
+use rustc_span::source_map::respan;
+use rustc_span::symbol::{kw, Ident, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use thin_vec::ThinVec;
+
+/// A path, e.g., `::std::option::Option::<i32>` (global). Has support
+/// for type parameters.
+#[derive(Clone)]
+pub(crate) struct Path {
+    path: Vec<Symbol>,
+    params: Vec<Box<Ty>>,
+    kind: PathKind,
+}
+
+#[derive(Clone)]
+pub(crate) enum PathKind {
+    Local,
+    Global,
+    Std,
+}
+
+impl Path {
+    pub fn new(path: Vec<Symbol>) -> Path {
+        Path::new_(path, Vec::new(), PathKind::Std)
+    }
+    pub fn new_local(path: Symbol) -> Path {
+        Path::new_(vec![path], Vec::new(), PathKind::Local)
+    }
+    pub fn new_(path: Vec<Symbol>, params: Vec<Box<Ty>>, kind: PathKind) -> Path {
+        Path { path, params, kind }
+    }
+
+    pub fn to_ty(
+        &self,
+        cx: &ExtCtxt<'_>,
+        span: Span,
+        self_ty: Ident,
+        self_generics: &Generics,
+    ) -> P<ast::Ty> {
+        cx.ty_path(self.to_path(cx, span, self_ty, self_generics))
+    }
+    pub fn to_path(
+        &self,
+        cx: &ExtCtxt<'_>,
+        span: Span,
+        self_ty: Ident,
+        self_generics: &Generics,
+    ) -> ast::Path {
+        let mut idents = self.path.iter().map(|s| Ident::new(*s, span)).collect();
+        let tys = self.params.iter().map(|t| t.to_ty(cx, span, self_ty, self_generics));
+        let params = tys.map(GenericArg::Type).collect();
+
+        match self.kind {
+            PathKind::Global => cx.path_all(span, true, idents, params),
+            PathKind::Local => cx.path_all(span, false, idents, params),
+            PathKind::Std => {
+                let def_site = cx.with_def_site_ctxt(DUMMY_SP);
+                idents.insert(0, Ident::new(kw::DollarCrate, def_site));
+                cx.path_all(span, false, idents, params)
+            }
+        }
+    }
+}
+
+/// A type. Supports pointers, Self, and literals.
+#[derive(Clone)]
+pub(crate) enum Ty {
+    Self_,
+    /// A reference.
+    Ref(Box<Ty>, ast::Mutability),
+    /// `mod::mod::Type<[lifetime], [Params...]>`, including a plain type
+    /// parameter, and things like `i32`
+    Path(Path),
+    /// For () return types.
+    Unit,
+}
+
+pub(crate) fn self_ref() -> Ty {
+    Ref(Box::new(Self_), ast::Mutability::Not)
+}
+
+impl Ty {
+    pub fn to_ty(
+        &self,
+        cx: &ExtCtxt<'_>,
+        span: Span,
+        self_ty: Ident,
+        self_generics: &Generics,
+    ) -> P<ast::Ty> {
+        match self {
+            Ref(ty, mutbl) => {
+                let raw_ty = ty.to_ty(cx, span, self_ty, self_generics);
+                cx.ty_ref(span, raw_ty, None, *mutbl)
+            }
+            Path(p) => p.to_ty(cx, span, self_ty, self_generics),
+            Self_ => cx.ty_path(self.to_path(cx, span, self_ty, self_generics)),
+            Unit => {
+                let ty = ast::TyKind::Tup(ThinVec::new());
+                cx.ty(span, ty)
+            }
+        }
+    }
+
+    pub fn to_path(
+        &self,
+        cx: &ExtCtxt<'_>,
+        span: Span,
+        self_ty: Ident,
+        generics: &Generics,
+    ) -> ast::Path {
+        match self {
+            Self_ => {
+                let params: Vec<_> = generics
+                    .params
+                    .iter()
+                    .map(|param| match param.kind {
+                        GenericParamKind::Lifetime { .. } => {
+                            GenericArg::Lifetime(ast::Lifetime { id: param.id, ident: param.ident })
+                        }
+                        GenericParamKind::Type { .. } => {
+                            GenericArg::Type(cx.ty_ident(span, param.ident))
+                        }
+                        GenericParamKind::Const { .. } => {
+                            GenericArg::Const(cx.const_ident(span, param.ident))
+                        }
+                    })
+                    .collect();
+
+                cx.path_all(span, false, vec![self_ty], params)
+            }
+            Path(p) => p.to_path(cx, span, self_ty, generics),
+            Ref(..) => cx.dcx().span_bug(span, "ref in a path in generic `derive`"),
+            Unit => cx.dcx().span_bug(span, "unit in a path in generic `derive`"),
+        }
+    }
+}
+
+fn mk_ty_param(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    name: Symbol,
+    bounds: &[Path],
+    self_ident: Ident,
+    self_generics: &Generics,
+) -> ast::GenericParam {
+    let bounds = bounds
+        .iter()
+        .map(|b| {
+            let path = b.to_path(cx, span, self_ident, self_generics);
+            cx.trait_bound(path, false)
+        })
+        .collect();
+    cx.typaram(span, Ident::new(name, span), bounds, None)
+}
+
+/// Bounds on type parameters.
+#[derive(Clone)]
+pub(crate) struct Bounds {
+    pub bounds: Vec<(Symbol, Vec<Path>)>,
+}
+
+impl Bounds {
+    pub fn empty() -> Bounds {
+        Bounds { bounds: Vec::new() }
+    }
+    pub fn to_generics(
+        &self,
+        cx: &ExtCtxt<'_>,
+        span: Span,
+        self_ty: Ident,
+        self_generics: &Generics,
+    ) -> Generics {
+        let params = self
+            .bounds
+            .iter()
+            .map(|&(name, ref bounds)| mk_ty_param(cx, span, name, bounds, self_ty, self_generics))
+            .collect();
+
+        Generics {
+            params,
+            where_clause: ast::WhereClause {
+                has_where_token: false,
+                predicates: ThinVec::new(),
+                span,
+            },
+            span,
+        }
+    }
+}
+
+pub(crate) fn get_explicit_self(cx: &ExtCtxt<'_>, span: Span) -> (P<Expr>, ast::ExplicitSelf) {
+    // This constructs a fresh `self` path.
+    let self_path = cx.expr_self(span);
+    let self_ty = respan(span, SelfKind::Region(None, ast::Mutability::Not));
+    (self_path, self_ty)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/hash.rs b/compiler/rustc_builtin_macros/src/deriving/hash.rs
new file mode 100644
index 00000000000..dcd92819865
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/hash.rs
@@ -0,0 +1,78 @@
+use crate::deriving::generic::ty::*;
+use crate::deriving::generic::*;
+use crate::deriving::{path_std, pathvec_std};
+use rustc_ast::{MetaItem, Mutability};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use thin_vec::thin_vec;
+
+pub(crate) fn expand_deriving_hash(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    is_const: bool,
+) {
+    let path = Path::new_(pathvec_std!(hash::Hash), vec![], PathKind::Std);
+
+    let typaram = sym::__H;
+
+    let arg = Path::new_local(typaram);
+    let hash_trait_def = TraitDef {
+        span,
+        path,
+        skip_path_as_bound: false,
+        needs_copy_as_bound_if_packed: true,
+        additional_bounds: Vec::new(),
+        supports_unions: false,
+        methods: vec![MethodDef {
+            name: sym::hash,
+            generics: Bounds { bounds: vec![(typaram, vec![path_std!(hash::Hasher)])] },
+            explicit_self: true,
+            nonself_args: vec![(Ref(Box::new(Path(arg)), Mutability::Mut), sym::state)],
+            ret_ty: Unit,
+            attributes: thin_vec![cx.attr_word(sym::inline, span)],
+            fieldless_variants_strategy: FieldlessVariantsStrategy::Unify,
+            combine_substructure: combine_substructure(Box::new(|a, b, c| {
+                hash_substructure(a, b, c)
+            })),
+        }],
+        associated_types: Vec::new(),
+        is_const,
+    };
+
+    hash_trait_def.expand(cx, mitem, item, push);
+}
+
+fn hash_substructure(cx: &ExtCtxt<'_>, trait_span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
+    let [state_expr] = substr.nonselflike_args else {
+        cx.dcx().span_bug(trait_span, "incorrect number of arguments in `derive(Hash)`");
+    };
+    let call_hash = |span, expr| {
+        let hash_path = {
+            let strs = cx.std_path(&[sym::hash, sym::Hash, sym::hash]);
+
+            cx.expr_path(cx.path_global(span, strs))
+        };
+        let expr = cx.expr_call(span, hash_path, thin_vec![expr, state_expr.clone()]);
+        cx.stmt_expr(expr)
+    };
+
+    let (stmts, match_expr) = match substr.fields {
+        Struct(_, fields) | EnumMatching(.., fields) => {
+            let stmts =
+                fields.iter().map(|field| call_hash(field.span, field.self_expr.clone())).collect();
+            (stmts, None)
+        }
+        EnumDiscr(discr_field, match_expr) => {
+            assert!(discr_field.other_selflike_exprs.is_empty());
+            let stmts = thin_vec![call_hash(discr_field.span, discr_field.self_expr.clone())];
+            (stmts, match_expr.clone())
+        }
+        _ => cx.dcx().span_bug(trait_span, "impossible substructure in `derive(Hash)`"),
+    };
+
+    BlockOrExpr::new_mixed(stmts, match_expr)
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/mod.rs b/compiler/rustc_builtin_macros/src/deriving/mod.rs
new file mode 100644
index 00000000000..32936ac183d
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs
@@ -0,0 +1,133 @@
+//! The compiler code necessary to implement the `#[derive]` extensions.
+
+use rustc_ast as ast;
+use rustc_ast::ptr::P;
+use rustc_ast::{GenericArg, MetaItem};
+use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, MultiItemModifier};
+use rustc_span::symbol::{sym, Symbol};
+use rustc_span::Span;
+use thin_vec::{thin_vec, ThinVec};
+
+macro path_local($x:ident) {
+    generic::ty::Path::new_local(sym::$x)
+}
+
+macro pathvec_std($($rest:ident)::+) {{
+    vec![ $( sym::$rest ),+ ]
+}}
+
+macro path_std($($x:tt)*) {
+    generic::ty::Path::new( pathvec_std!( $($x)* ) )
+}
+
+pub(crate) mod bounds;
+pub(crate) mod clone;
+pub(crate) mod debug;
+pub(crate) mod decodable;
+pub(crate) mod default;
+pub(crate) mod encodable;
+pub(crate) mod hash;
+pub(crate) mod smart_ptr;
+
+#[path = "cmp/eq.rs"]
+pub(crate) mod eq;
+#[path = "cmp/ord.rs"]
+pub(crate) mod ord;
+#[path = "cmp/partial_eq.rs"]
+pub(crate) mod partial_eq;
+#[path = "cmp/partial_ord.rs"]
+pub(crate) mod partial_ord;
+
+pub(crate) mod generic;
+
+pub(crate) type BuiltinDeriveFn =
+    fn(&ExtCtxt<'_>, Span, &MetaItem, &Annotatable, &mut dyn FnMut(Annotatable), bool);
+
+pub(crate) struct BuiltinDerive(pub(crate) BuiltinDeriveFn);
+
+impl MultiItemModifier for BuiltinDerive {
+    fn expand(
+        &self,
+        ecx: &mut ExtCtxt<'_>,
+        span: Span,
+        meta_item: &MetaItem,
+        item: Annotatable,
+        is_derive_const: bool,
+    ) -> ExpandResult<Vec<Annotatable>, Annotatable> {
+        // FIXME: Built-in derives often forget to give spans contexts,
+        // so we are doing it here in a centralized way.
+        let span = ecx.with_def_site_ctxt(span);
+        let mut items = Vec::new();
+        match item {
+            Annotatable::Stmt(stmt) => {
+                if let ast::StmtKind::Item(item) = stmt.into_inner().kind {
+                    (self.0)(
+                        ecx,
+                        span,
+                        meta_item,
+                        &Annotatable::Item(item),
+                        &mut |a| {
+                            // Cannot use 'ecx.stmt_item' here, because we need to pass 'ecx'
+                            // to the function
+                            items.push(Annotatable::Stmt(P(ast::Stmt {
+                                id: ast::DUMMY_NODE_ID,
+                                kind: ast::StmtKind::Item(a.expect_item()),
+                                span,
+                            })));
+                        },
+                        is_derive_const,
+                    );
+                } else {
+                    unreachable!("should have already errored on non-item statement")
+                }
+            }
+            _ => {
+                (self.0)(ecx, span, meta_item, &item, &mut |a| items.push(a), is_derive_const);
+            }
+        }
+        ExpandResult::Ready(items)
+    }
+}
+
+/// Constructs an expression that calls an intrinsic
+fn call_intrinsic(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    intrinsic: Symbol,
+    args: ThinVec<P<ast::Expr>>,
+) -> P<ast::Expr> {
+    let span = cx.with_def_site_ctxt(span);
+    let path = cx.std_path(&[sym::intrinsics, intrinsic]);
+    cx.expr_call_global(span, path, args)
+}
+
+/// Constructs an expression that calls the `unreachable` intrinsic.
+fn call_unreachable(cx: &ExtCtxt<'_>, span: Span) -> P<ast::Expr> {
+    let span = cx.with_def_site_ctxt(span);
+    let path = cx.std_path(&[sym::intrinsics, sym::unreachable]);
+    let call = cx.expr_call_global(span, path, ThinVec::new());
+
+    cx.expr_block(P(ast::Block {
+        stmts: thin_vec![cx.stmt_expr(call)],
+        id: ast::DUMMY_NODE_ID,
+        rules: ast::BlockCheckMode::Unsafe(ast::CompilerGenerated),
+        span,
+        tokens: None,
+        could_be_bare_literal: false,
+    }))
+}
+
+fn assert_ty_bounds(
+    cx: &ExtCtxt<'_>,
+    stmts: &mut ThinVec<ast::Stmt>,
+    ty: P<ast::Ty>,
+    span: Span,
+    assert_path: &[Symbol],
+) {
+    // Deny anonymous structs or unions to avoid weird errors.
+    assert!(!ty.kind.is_anon_adt(), "Anonymous structs or unions cannot be type parameters");
+    // Generate statement `let _: assert_path<ty>;`.
+    let span = cx.with_def_site_ctxt(span);
+    let assert_path = cx.path_all(span, true, cx.std_path(assert_path), vec![GenericArg::Type(ty)]);
+    stmts.push(cx.stmt_let_type_only(span, cx.ty_path(assert_path)));
+}
diff --git a/compiler/rustc_builtin_macros/src/deriving/smart_ptr.rs b/compiler/rustc_builtin_macros/src/deriving/smart_ptr.rs
new file mode 100644
index 00000000000..ea054a7e355
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/deriving/smart_ptr.rs
@@ -0,0 +1,140 @@
+use std::mem::swap;
+
+use ast::HasAttrs;
+use rustc_ast::{
+    self as ast, GenericArg, GenericBound, GenericParamKind, ItemKind, MetaItem,
+    TraitBoundModifiers,
+};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{sym, Ident};
+use rustc_span::Span;
+use smallvec::{smallvec, SmallVec};
+use thin_vec::{thin_vec, ThinVec};
+
+macro_rules! path {
+    ($span:expr, $($part:ident)::*) => { vec![$(Ident::new(sym::$part, $span),)*] }
+}
+
+pub fn expand_deriving_smart_ptr(
+    cx: &ExtCtxt<'_>,
+    span: Span,
+    _mitem: &MetaItem,
+    item: &Annotatable,
+    push: &mut dyn FnMut(Annotatable),
+    _is_const: bool,
+) {
+    let (name_ident, generics) = if let Annotatable::Item(aitem) = item
+        && let ItemKind::Struct(_, g) = &aitem.kind
+    {
+        (aitem.ident, g)
+    } else {
+        cx.dcx().struct_span_err(span, "`SmartPointer` can only be derived on `struct`s").emit();
+        return;
+    };
+
+    // Convert generic parameters (from the struct) into generic args.
+    let mut pointee_param = None;
+    let mut multiple_pointee_diag: SmallVec<[_; 2]> = smallvec![];
+    let self_params = generics
+        .params
+        .iter()
+        .enumerate()
+        .map(|(idx, p)| match p.kind {
+            GenericParamKind::Lifetime => GenericArg::Lifetime(cx.lifetime(p.span(), p.ident)),
+            GenericParamKind::Type { .. } => {
+                if p.attrs().iter().any(|attr| attr.has_name(sym::pointee)) {
+                    if pointee_param.is_some() {
+                        multiple_pointee_diag.push(cx.dcx().struct_span_err(
+                            p.span(),
+                            "`SmartPointer` can only admit one type as pointee",
+                        ));
+                    } else {
+                        pointee_param = Some(idx);
+                    }
+                }
+                GenericArg::Type(cx.ty_ident(p.span(), p.ident))
+            }
+            GenericParamKind::Const { .. } => GenericArg::Const(cx.const_ident(p.span(), p.ident)),
+        })
+        .collect::<Vec<_>>();
+    let Some(pointee_param_idx) = pointee_param else {
+        cx.dcx().struct_span_err(
+            span,
+            "At least one generic type should be designated as `#[pointee]` in order to derive `SmartPointer` traits",
+        ).emit();
+        return;
+    };
+    if !multiple_pointee_diag.is_empty() {
+        for diag in multiple_pointee_diag {
+            diag.emit();
+        }
+        return;
+    }
+
+    // Create the type of `self`.
+    let path = cx.path_all(span, false, vec![name_ident], self_params.clone());
+    let self_type = cx.ty_path(path);
+
+    // Declare helper function that adds implementation blocks.
+    // FIXME(dingxiangfei2009): Investigate the set of attributes on target struct to be propagated to impls
+    let attrs = thin_vec![cx.attr_word(sym::automatically_derived, span),];
+    let mut add_impl_block = |generics, trait_symbol, trait_args| {
+        let mut parts = path!(span, core::ops);
+        parts.push(Ident::new(trait_symbol, span));
+        let trait_path = cx.path_all(span, true, parts, trait_args);
+        let trait_ref = cx.trait_ref(trait_path);
+        let item = cx.item(
+            span,
+            Ident::empty(),
+            attrs.clone(),
+            ast::ItemKind::Impl(Box::new(ast::Impl {
+                safety: ast::Safety::Default,
+                polarity: ast::ImplPolarity::Positive,
+                defaultness: ast::Defaultness::Final,
+                constness: ast::Const::No,
+                generics,
+                of_trait: Some(trait_ref),
+                self_ty: self_type.clone(),
+                items: ThinVec::new(),
+            })),
+        );
+        push(Annotatable::Item(item));
+    };
+
+    // Create unsized `self`, that is, one where the `#[pointee]` type arg is replaced with `__S`. For
+    // example, instead of `MyType<'a, T>`, it will be `MyType<'a, __S>`.
+    let s_ty = cx.ty_ident(span, Ident::new(sym::__S, span));
+    let mut alt_self_params = self_params;
+    alt_self_params[pointee_param_idx] = GenericArg::Type(s_ty.clone());
+    let alt_self_type = cx.ty_path(cx.path_all(span, false, vec![name_ident], alt_self_params));
+
+    // Find the `#[pointee]` parameter and add an `Unsize<__S>` bound to it.
+    let mut impl_generics = generics.clone();
+    {
+        let p = &mut impl_generics.params[pointee_param_idx];
+        let arg = GenericArg::Type(s_ty.clone());
+        let unsize = cx.path_all(span, true, path!(span, core::marker::Unsize), vec![arg]);
+        p.bounds.push(cx.trait_bound(unsize, false));
+        let mut attrs = thin_vec![];
+        swap(&mut p.attrs, &mut attrs);
+        p.attrs = attrs.into_iter().filter(|attr| !attr.has_name(sym::pointee)).collect();
+    }
+
+    // Add the `__S: ?Sized` extra parameter to the impl block.
+    let sized = cx.path_global(span, path!(span, core::marker::Sized));
+    let bound = GenericBound::Trait(
+        cx.poly_trait_ref(span, sized),
+        TraitBoundModifiers {
+            polarity: ast::BoundPolarity::Maybe(span),
+            constness: ast::BoundConstness::Never,
+            asyncness: ast::BoundAsyncness::Normal,
+        },
+    );
+    let extra_param = cx.typaram(span, Ident::new(sym::__S, span), vec![bound], None);
+    impl_generics.params.push(extra_param);
+
+    // Add the impl blocks for `DispatchFromDyn` and `CoerceUnsized`.
+    let gen_args = vec![GenericArg::Type(alt_self_type.clone())];
+    add_impl_block(impl_generics.clone(), sym::DispatchFromDyn, gen_args.clone());
+    add_impl_block(impl_generics.clone(), sym::CoerceUnsized, gen_args.clone());
+}
diff --git a/compiler/rustc_builtin_macros/src/edition_panic.rs b/compiler/rustc_builtin_macros/src/edition_panic.rs
new file mode 100644
index 00000000000..cc385bade47
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/edition_panic.rs
@@ -0,0 +1,86 @@
+use rustc_ast::ptr::P;
+use rustc_ast::token::Delimiter;
+use rustc_ast::tokenstream::{DelimSpan, TokenStream};
+use rustc_ast::*;
+use rustc_expand::base::*;
+use rustc_span::edition::Edition;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+/// This expands to either
+/// - `$crate::panic::panic_2015!(...)` or
+/// - `$crate::panic::panic_2021!(...)`
+/// depending on the edition.
+///
+/// This is used for both std::panic!() and core::panic!().
+///
+/// `$crate` will refer to either the `std` or `core` crate depending on which
+/// one we're expanding from.
+pub(crate) fn expand_panic<'cx>(
+    cx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    let mac = if use_panic_2021(sp) { sym::panic_2021 } else { sym::panic_2015 };
+    expand(mac, cx, sp, tts)
+}
+
+/// This expands to either
+/// - `$crate::panic::unreachable_2015!(...)` or
+/// - `$crate::panic::unreachable_2021!(...)`
+/// depending on the edition.
+pub(crate) fn expand_unreachable<'cx>(
+    cx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    let mac = if use_panic_2021(sp) { sym::unreachable_2021 } else { sym::unreachable_2015 };
+    expand(mac, cx, sp, tts)
+}
+
+fn expand<'cx>(
+    mac: rustc_span::Symbol,
+    cx: &'cx ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    let sp = cx.with_call_site_ctxt(sp);
+
+    ExpandResult::Ready(MacEager::expr(
+        cx.expr(
+            sp,
+            ExprKind::MacCall(P(MacCall {
+                path: Path {
+                    span: sp,
+                    segments: cx
+                        .std_path(&[sym::panic, mac])
+                        .into_iter()
+                        .map(|ident| PathSegment::from_ident(ident))
+                        .collect(),
+                    tokens: None,
+                },
+                args: P(DelimArgs {
+                    dspan: DelimSpan::from_single(sp),
+                    delim: Delimiter::Parenthesis,
+                    tokens: tts,
+                }),
+            })),
+        ),
+    ))
+}
+
+pub(crate) fn use_panic_2021(mut span: Span) -> bool {
+    // To determine the edition, we check the first span up the expansion
+    // stack that does not have #[allow_internal_unstable(edition_panic)].
+    // (To avoid using the edition of e.g. the assert!() or debug_assert!() definition.)
+    loop {
+        let expn = span.ctxt().outer_expn_data();
+        if let Some(features) = expn.allow_internal_unstable {
+            if features.iter().any(|&f| f == sym::edition_panic) {
+                span = expn.call_site;
+                continue;
+            }
+        }
+        break expn.edition >= Edition::Edition2021;
+    }
+}
diff --git a/compiler/rustc_builtin_macros/src/env.rs b/compiler/rustc_builtin_macros/src/env.rs
new file mode 100644
index 00000000000..b03e14cf263
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/env.rs
@@ -0,0 +1,157 @@
+// The compiler code necessary to support the env! extension. Eventually this
+// should all get sucked into either the compiler syntax extension plugin
+// interface.
+//
+
+use crate::errors;
+use crate::util::{expr_to_string, get_exprs_from_tts, get_single_str_from_tts};
+use rustc_ast::token::{self, LitKind};
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast::{AstDeref, ExprKind, GenericArg, Mutability};
+use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacEager, MacroExpanderResult};
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::Span;
+use std::env;
+use std::env::VarError;
+use thin_vec::thin_vec;
+
+fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Result<Symbol, VarError> {
+    let var = var.as_str();
+    if let Some(value) = cx.sess.opts.logical_env.get(var) {
+        return Ok(Symbol::intern(value));
+    }
+    // If the environment variable was not defined with the `--env-set` option, we try to retrieve it
+    // from rustc's environment.
+    Ok(Symbol::intern(&env::var(var)?))
+}
+
+pub(crate) fn expand_option_env<'cx>(
+    cx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "option_env!") else {
+        return ExpandResult::Retry(());
+    };
+    let var = match mac {
+        Ok(var) => var,
+        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
+    };
+
+    let sp = cx.with_def_site_ctxt(sp);
+    let value = lookup_env(cx, var).ok();
+    cx.sess.psess.env_depinfo.borrow_mut().insert((var, value));
+    let e = match value {
+        None => {
+            let lt = cx.lifetime(sp, Ident::new(kw::StaticLifetime, sp));
+            cx.expr_path(cx.path_all(
+                sp,
+                true,
+                cx.std_path(&[sym::option, sym::Option, sym::None]),
+                vec![GenericArg::Type(cx.ty_ref(
+                    sp,
+                    cx.ty_ident(sp, Ident::new(sym::str, sp)),
+                    Some(lt),
+                    Mutability::Not,
+                ))],
+            ))
+        }
+        Some(value) => cx.expr_call_global(
+            sp,
+            cx.std_path(&[sym::option, sym::Option, sym::Some]),
+            thin_vec![cx.expr_str(sp, value)],
+        ),
+    };
+    ExpandResult::Ready(MacEager::expr(e))
+}
+
+pub(crate) fn expand_env<'cx>(
+    cx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    let ExpandResult::Ready(mac) = get_exprs_from_tts(cx, tts) else {
+        return ExpandResult::Retry(());
+    };
+    let mut exprs = match mac {
+        Ok(exprs) if exprs.is_empty() || exprs.len() > 2 => {
+            let guar = cx.dcx().emit_err(errors::EnvTakesArgs { span: sp });
+            return ExpandResult::Ready(DummyResult::any(sp, guar));
+        }
+        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
+        Ok(exprs) => exprs.into_iter(),
+    };
+
+    let var_expr = exprs.next().unwrap();
+    let ExpandResult::Ready(mac) = expr_to_string(cx, var_expr.clone(), "expected string literal")
+    else {
+        return ExpandResult::Retry(());
+    };
+    let var = match mac {
+        Ok((var, _)) => var,
+        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
+    };
+
+    let custom_msg = match exprs.next() {
+        None => None,
+        Some(second) => {
+            let ExpandResult::Ready(mac) = expr_to_string(cx, second, "expected string literal")
+            else {
+                return ExpandResult::Retry(());
+            };
+            match mac {
+                Ok((s, _)) => Some(s),
+                Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
+            }
+        }
+    };
+
+    let span = cx.with_def_site_ctxt(sp);
+    let value = lookup_env(cx, var);
+    cx.sess.psess.env_depinfo.borrow_mut().insert((var, value.as_ref().ok().copied()));
+    let e = match value {
+        Err(err) => {
+            let ExprKind::Lit(token::Lit {
+                kind: LitKind::Str | LitKind::StrRaw(..), symbol, ..
+            }) = &var_expr.kind
+            else {
+                unreachable!("`expr_to_string` ensures this is a string lit")
+            };
+
+            let guar = match err {
+                VarError::NotPresent => {
+                    if let Some(msg_from_user) = custom_msg {
+                        cx.dcx()
+                            .emit_err(errors::EnvNotDefinedWithUserMessage { span, msg_from_user })
+                    } else if is_cargo_env_var(var.as_str()) {
+                        cx.dcx().emit_err(errors::EnvNotDefined::CargoEnvVar {
+                            span,
+                            var: *symbol,
+                            var_expr: var_expr.ast_deref(),
+                        })
+                    } else {
+                        cx.dcx().emit_err(errors::EnvNotDefined::CustomEnvVar {
+                            span,
+                            var: *symbol,
+                            var_expr: var_expr.ast_deref(),
+                        })
+                    }
+                }
+                VarError::NotUnicode(_) => {
+                    cx.dcx().emit_err(errors::EnvNotUnicode { span, var: *symbol })
+                }
+            };
+
+            return ExpandResult::Ready(DummyResult::any(sp, guar));
+        }
+        Ok(value) => cx.expr_str(span, value),
+    };
+    ExpandResult::Ready(MacEager::expr(e))
+}
+
+/// Returns `true` if an environment variable from `env!` is one used by Cargo.
+fn is_cargo_env_var(var: &str) -> bool {
+    var.starts_with("CARGO_")
+        || var.starts_with("DEP_")
+        || matches!(var, "OUT_DIR" | "OPT_LEVEL" | "PROFILE" | "HOST" | "TARGET")
+}
diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs
new file mode 100644
index 00000000000..ed2f98f2a39
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/errors.rs
@@ -0,0 +1,874 @@
+use rustc_errors::{
+    codes::*, Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level, MultiSpan,
+    SingleLabelManySpans, SubdiagMessageOp, Subdiagnostic,
+};
+use rustc_macros::{Diagnostic, Subdiagnostic};
+use rustc_span::{symbol::Ident, Span, Symbol};
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_requires_cfg_pattern)]
+pub(crate) struct RequiresCfgPattern {
+    #[primary_span]
+    #[label]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_expected_one_cfg_pattern)]
+pub(crate) struct OneCfgPattern {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_alloc_error_must_be_fn)]
+pub(crate) struct AllocErrorMustBeFn {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_assert_requires_boolean)]
+pub(crate) struct AssertRequiresBoolean {
+    #[primary_span]
+    #[label]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_assert_requires_expression)]
+pub(crate) struct AssertRequiresExpression {
+    #[primary_span]
+    pub(crate) span: Span,
+    #[suggestion(code = "", applicability = "maybe-incorrect")]
+    pub(crate) token: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_assert_missing_comma)]
+pub(crate) struct AssertMissingComma {
+    #[primary_span]
+    pub(crate) span: Span,
+    #[suggestion(code = ", ", applicability = "maybe-incorrect", style = "short")]
+    pub(crate) comma: Span,
+}
+
+#[derive(Diagnostic)]
+pub(crate) enum CfgAccessibleInvalid {
+    #[diag(builtin_macros_cfg_accessible_unspecified_path)]
+    UnspecifiedPath(#[primary_span] Span),
+    #[diag(builtin_macros_cfg_accessible_multiple_paths)]
+    MultiplePaths(#[primary_span] Span),
+    #[diag(builtin_macros_cfg_accessible_literal_path)]
+    LiteralPath(#[primary_span] Span),
+    #[diag(builtin_macros_cfg_accessible_has_args)]
+    HasArguments(#[primary_span] Span),
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_cfg_accessible_indeterminate)]
+pub(crate) struct CfgAccessibleIndeterminate {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_missing_literal)]
+#[note]
+pub(crate) struct ConcatMissingLiteral {
+    #[primary_span]
+    pub(crate) spans: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_bytestr)]
+pub(crate) struct ConcatBytestr {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_c_str_lit)]
+pub(crate) struct ConcatCStrLit {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_export_macro_rules)]
+pub(crate) struct ExportMacroRules {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_proc_macro)]
+pub(crate) struct ProcMacro {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_invalid_crate_attribute)]
+pub(crate) struct InvalidCrateAttr {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_non_abi)]
+pub(crate) struct NonABI {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_trace_macros)]
+pub(crate) struct TraceMacros {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_bench_sig)]
+pub(crate) struct BenchSig {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_alloc_must_statics)]
+pub(crate) struct AllocMustStatics {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_bytes_invalid)]
+pub(crate) struct ConcatBytesInvalid {
+    #[primary_span]
+    pub(crate) span: Span,
+    pub(crate) lit_kind: &'static str,
+    #[subdiagnostic]
+    pub(crate) sugg: Option<ConcatBytesInvalidSuggestion>,
+}
+
+#[derive(Subdiagnostic)]
+pub(crate) enum ConcatBytesInvalidSuggestion {
+    #[suggestion(
+        builtin_macros_byte_char,
+        code = "b{snippet}",
+        applicability = "machine-applicable"
+    )]
+    CharLit {
+        #[primary_span]
+        span: Span,
+        snippet: String,
+    },
+    #[suggestion(
+        builtin_macros_byte_str,
+        code = "b{snippet}",
+        applicability = "machine-applicable"
+    )]
+    StrLit {
+        #[primary_span]
+        span: Span,
+        snippet: String,
+    },
+    #[suggestion(
+        builtin_macros_number_array,
+        code = "[{snippet}]",
+        applicability = "machine-applicable"
+    )]
+    IntLit {
+        #[primary_span]
+        span: Span,
+        snippet: String,
+    },
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_bytes_oob)]
+pub(crate) struct ConcatBytesOob {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_bytes_non_u8)]
+pub(crate) struct ConcatBytesNonU8 {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_bytes_missing_literal)]
+#[note]
+pub(crate) struct ConcatBytesMissingLiteral {
+    #[primary_span]
+    pub(crate) spans: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_bytes_array)]
+pub(crate) struct ConcatBytesArray {
+    #[primary_span]
+    pub(crate) span: Span,
+    #[note]
+    #[help]
+    pub(crate) bytestr: bool,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_bytes_bad_repeat)]
+pub(crate) struct ConcatBytesBadRepeat {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_idents_missing_args)]
+pub(crate) struct ConcatIdentsMissingArgs {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_idents_missing_comma)]
+pub(crate) struct ConcatIdentsMissingComma {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_concat_idents_ident_args)]
+pub(crate) struct ConcatIdentsIdentArgs {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_bad_derive_target, code = E0774)]
+pub(crate) struct BadDeriveTarget {
+    #[primary_span]
+    #[label]
+    pub(crate) span: Span,
+    #[label(builtin_macros_label2)]
+    pub(crate) item: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_tests_not_support)]
+pub(crate) struct TestsNotSupport {}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_unexpected_lit, code = E0777)]
+pub(crate) struct BadDeriveLit {
+    #[primary_span]
+    #[label]
+    pub(crate) span: Span,
+    #[subdiagnostic]
+    pub help: BadDeriveLitHelp,
+}
+
+#[derive(Subdiagnostic)]
+pub(crate) enum BadDeriveLitHelp {
+    #[help(builtin_macros_str_lit)]
+    StrLit { sym: Symbol },
+    #[help(builtin_macros_other)]
+    Other,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_derive_path_args_list)]
+pub(crate) struct DerivePathArgsList {
+    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_derive_path_args_value)]
+pub(crate) struct DerivePathArgsValue {
+    #[suggestion(code = "", applicability = "machine-applicable")]
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_derive_unsafe_path)]
+pub(crate) struct DeriveUnsafePath {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_no_default_variant)]
+#[help]
+pub(crate) struct NoDefaultVariant {
+    #[primary_span]
+    pub(crate) span: Span,
+    #[subdiagnostic]
+    pub(crate) suggs: Vec<NoDefaultVariantSugg>,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(
+    builtin_macros_suggestion,
+    code = "#[default] {ident}",
+    applicability = "maybe-incorrect",
+    style = "tool-only"
+)]
+pub(crate) struct NoDefaultVariantSugg {
+    #[primary_span]
+    pub(crate) span: Span,
+    pub(crate) ident: Ident,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_multiple_defaults)]
+#[note]
+pub(crate) struct MultipleDefaults {
+    #[primary_span]
+    pub(crate) span: Span,
+    #[label]
+    pub(crate) first: Span,
+    #[label(builtin_macros_additional)]
+    pub additional: Vec<Span>,
+    #[subdiagnostic]
+    pub suggs: Vec<MultipleDefaultsSugg>,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(
+    builtin_macros_suggestion,
+    applicability = "maybe-incorrect",
+    style = "tool-only"
+)]
+pub(crate) struct MultipleDefaultsSugg {
+    #[suggestion_part(code = "")]
+    pub(crate) spans: Vec<Span>,
+    pub(crate) ident: Ident,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_non_unit_default)]
+#[help]
+pub(crate) struct NonUnitDefault {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_non_exhaustive_default)]
+#[help]
+pub(crate) struct NonExhaustiveDefault {
+    #[primary_span]
+    pub(crate) span: Span,
+    #[label]
+    pub(crate) non_exhaustive: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_multiple_default_attrs)]
+#[note]
+pub(crate) struct MultipleDefaultAttrs {
+    #[primary_span]
+    pub(crate) span: Span,
+    #[label]
+    pub(crate) first: Span,
+    #[label(builtin_macros_label_again)]
+    pub(crate) first_rest: Span,
+    #[help]
+    pub(crate) rest: MultiSpan,
+    pub(crate) only_one: bool,
+    #[subdiagnostic]
+    pub(crate) sugg: MultipleDefaultAttrsSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(
+    builtin_macros_help,
+    applicability = "machine-applicable",
+    style = "tool-only"
+)]
+pub(crate) struct MultipleDefaultAttrsSugg {
+    #[suggestion_part(code = "")]
+    pub(crate) spans: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_default_arg)]
+pub(crate) struct DefaultHasArg {
+    #[primary_span]
+    #[suggestion(code = "#[default]", style = "hidden", applicability = "maybe-incorrect")]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_derive_macro_call)]
+pub(crate) struct DeriveMacroCall {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_cannot_derive_union)]
+pub(crate) struct DeriveUnion {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_env_takes_args)]
+pub(crate) struct EnvTakesArgs {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+pub(crate) struct EnvNotDefinedWithUserMessage {
+    pub(crate) span: Span,
+    pub(crate) msg_from_user: Symbol,
+}
+
+// Hand-written implementation to support custom user messages.
+impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for EnvNotDefinedWithUserMessage {
+    #[track_caller]
+    fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> {
+        #[expect(
+            rustc::untranslatable_diagnostic,
+            reason = "cannot translate user-provided messages"
+        )]
+        let mut diag = Diag::new(dcx, level, self.msg_from_user.to_string());
+        diag.span(self.span);
+        diag
+    }
+}
+
+#[derive(Diagnostic)]
+pub(crate) enum EnvNotDefined<'a> {
+    #[diag(builtin_macros_env_not_defined)]
+    #[help(builtin_macros_cargo)]
+    CargoEnvVar {
+        #[primary_span]
+        span: Span,
+        var: Symbol,
+        var_expr: &'a rustc_ast::Expr,
+    },
+    #[diag(builtin_macros_env_not_defined)]
+    #[help(builtin_macros_custom)]
+    CustomEnvVar {
+        #[primary_span]
+        span: Span,
+        var: Symbol,
+        var_expr: &'a rustc_ast::Expr,
+    },
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_env_not_unicode)]
+pub(crate) struct EnvNotUnicode {
+    #[primary_span]
+    pub(crate) span: Span,
+    pub(crate) var: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_format_requires_string)]
+pub(crate) struct FormatRequiresString {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_format_duplicate_arg)]
+pub(crate) struct FormatDuplicateArg {
+    #[primary_span]
+    pub(crate) span: Span,
+    #[label(builtin_macros_label1)]
+    pub(crate) prev: Span,
+    #[label(builtin_macros_label2)]
+    pub(crate) duplicate: Span,
+    pub(crate) ident: Ident,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_format_positional_after_named)]
+pub(crate) struct PositionalAfterNamed {
+    #[primary_span]
+    #[label]
+    pub(crate) span: Span,
+    #[label(builtin_macros_named_args)]
+    pub(crate) args: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_format_string_invalid)]
+pub(crate) struct InvalidFormatString {
+    #[primary_span]
+    #[label]
+    pub(crate) span: Span,
+    pub(crate) desc: String,
+    pub(crate) label1: String,
+    #[subdiagnostic]
+    pub(crate) note_: Option<InvalidFormatStringNote>,
+    #[subdiagnostic]
+    pub(crate) label_: Option<InvalidFormatStringLabel>,
+    #[subdiagnostic]
+    pub(crate) sugg_: Option<InvalidFormatStringSuggestion>,
+}
+
+#[derive(Subdiagnostic)]
+#[note(builtin_macros_note)]
+pub(crate) struct InvalidFormatStringNote {
+    pub(crate) note: String,
+}
+
+#[derive(Subdiagnostic)]
+#[label(builtin_macros_second_label)]
+pub(crate) struct InvalidFormatStringLabel {
+    #[primary_span]
+    pub(crate) span: Span,
+    pub(crate) label: String,
+}
+
+#[derive(Subdiagnostic)]
+pub(crate) enum InvalidFormatStringSuggestion {
+    #[multipart_suggestion(
+        builtin_macros_format_use_positional,
+        style = "verbose",
+        applicability = "machine-applicable"
+    )]
+    UsePositional {
+        #[suggestion_part(code = "{len}")]
+        captured: Span,
+        len: String,
+        #[suggestion_part(code = ", {arg}")]
+        span: Span,
+        arg: String,
+    },
+    #[suggestion(
+        builtin_macros_format_remove_raw_ident,
+        code = "",
+        applicability = "machine-applicable"
+    )]
+    RemoveRawIdent {
+        #[primary_span]
+        span: Span,
+    },
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_format_no_arg_named)]
+#[note]
+#[note(builtin_macros_note2)]
+pub(crate) struct FormatNoArgNamed {
+    #[primary_span]
+    pub(crate) span: Span,
+    pub(crate) name: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_format_unknown_trait)]
+#[note]
+pub(crate) struct FormatUnknownTrait<'a> {
+    #[primary_span]
+    pub(crate) span: Span,
+    pub(crate) ty: &'a str,
+    #[subdiagnostic]
+    pub(crate) suggs: Vec<FormatUnknownTraitSugg>,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(
+    builtin_macros_suggestion,
+    code = "{fmt}",
+    style = "tool-only",
+    applicability = "maybe-incorrect"
+)]
+pub(crate) struct FormatUnknownTraitSugg {
+    #[primary_span]
+    pub span: Span,
+    pub fmt: &'static str,
+    pub trait_name: &'static str,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_format_unused_arg)]
+pub(crate) struct FormatUnusedArg {
+    #[primary_span]
+    #[label(builtin_macros_format_unused_arg)]
+    pub(crate) span: Span,
+    pub(crate) named: bool,
+}
+
+// Allow the singular form to be a subdiagnostic of the multiple-unused
+// form of diagnostic.
+impl Subdiagnostic for FormatUnusedArg {
+    fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
+        self,
+        diag: &mut Diag<'_, G>,
+        f: &F,
+    ) {
+        diag.arg("named", self.named);
+        let msg = f(diag, crate::fluent_generated::builtin_macros_format_unused_arg.into());
+        diag.span_label(self.span, msg);
+    }
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_format_unused_args)]
+pub(crate) struct FormatUnusedArgs {
+    #[primary_span]
+    pub(crate) unused: Vec<Span>,
+    #[label]
+    pub(crate) fmt: Span,
+    #[subdiagnostic]
+    pub(crate) unused_labels: Vec<FormatUnusedArg>,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_format_pos_mismatch)]
+pub(crate) struct FormatPositionalMismatch {
+    #[primary_span]
+    pub(crate) span: MultiSpan,
+    pub(crate) n: usize,
+    pub(crate) desc: String,
+    #[subdiagnostic]
+    pub(crate) highlight: SingleLabelManySpans,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_format_redundant_args)]
+pub(crate) struct FormatRedundantArgs {
+    #[primary_span]
+    pub(crate) span: MultiSpan,
+    pub(crate) n: usize,
+
+    #[note]
+    pub(crate) note: MultiSpan,
+
+    #[subdiagnostic]
+    pub(crate) sugg: Option<FormatRedundantArgsSugg>,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(builtin_macros_suggestion, applicability = "machine-applicable")]
+pub(crate) struct FormatRedundantArgsSugg {
+    #[suggestion_part(code = "")]
+    pub(crate) spans: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_test_case_non_item)]
+pub(crate) struct TestCaseNonItem {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_test_bad_fn)]
+pub(crate) struct TestBadFn {
+    #[primary_span]
+    pub(crate) span: Span,
+    #[label]
+    pub(crate) cause: Span,
+    pub(crate) kind: &'static str,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_explicit_register_name)]
+pub(crate) struct AsmExplicitRegisterName {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_mutually_exclusive)]
+pub(crate) struct AsmMutuallyExclusive {
+    #[primary_span]
+    pub(crate) spans: Vec<Span>,
+    pub(crate) opt1: &'static str,
+    pub(crate) opt2: &'static str,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_pure_combine)]
+pub(crate) struct AsmPureCombine {
+    #[primary_span]
+    pub(crate) spans: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_pure_no_output)]
+pub(crate) struct AsmPureNoOutput {
+    #[primary_span]
+    pub(crate) spans: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_modifier_invalid)]
+pub(crate) struct AsmModifierInvalid {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_requires_template)]
+pub(crate) struct AsmRequiresTemplate {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_expected_comma)]
+pub(crate) struct AsmExpectedComma {
+    #[primary_span]
+    #[label]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_underscore_input)]
+pub(crate) struct AsmUnderscoreInput {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_sym_no_path)]
+pub(crate) struct AsmSymNoPath {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_expected_other)]
+pub(crate) struct AsmExpectedOther {
+    #[primary_span]
+    #[label(builtin_macros_asm_expected_other)]
+    pub(crate) span: Span,
+    pub(crate) is_global_asm: bool,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_duplicate_arg)]
+pub(crate) struct AsmDuplicateArg {
+    #[primary_span]
+    #[label(builtin_macros_arg)]
+    pub(crate) span: Span,
+    #[label]
+    pub(crate) prev: Span,
+    pub(crate) name: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_pos_after)]
+pub(crate) struct AsmPositionalAfter {
+    #[primary_span]
+    #[label(builtin_macros_pos)]
+    pub(crate) span: Span,
+    #[label(builtin_macros_named)]
+    pub(crate) named: Vec<Span>,
+    #[label(builtin_macros_explicit)]
+    pub(crate) explicit: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_noreturn)]
+pub(crate) struct AsmNoReturn {
+    #[primary_span]
+    pub(crate) outputs_sp: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_mayunwind)]
+pub(crate) struct AsmMayUnwind {
+    #[primary_span]
+    pub(crate) labels_sp: Vec<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_global_asm_clobber_abi)]
+pub(crate) struct GlobalAsmClobberAbi {
+    #[primary_span]
+    pub(crate) spans: Vec<Span>,
+}
+
+pub(crate) struct AsmClobberNoReg {
+    pub(crate) spans: Vec<Span>,
+    pub(crate) clobbers: Vec<Span>,
+}
+
+impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AsmClobberNoReg {
+    fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> {
+        // eager translation as `span_labels` takes `AsRef<str>`
+        let lbl1 = dcx.eagerly_translate_to_string(
+            crate::fluent_generated::builtin_macros_asm_clobber_abi,
+            [].into_iter(),
+        );
+        let lbl2 = dcx.eagerly_translate_to_string(
+            crate::fluent_generated::builtin_macros_asm_clobber_outputs,
+            [].into_iter(),
+        );
+        Diag::new(dcx, level, crate::fluent_generated::builtin_macros_asm_clobber_no_reg)
+            .with_span(self.spans.clone())
+            .with_span_labels(self.clobbers, &lbl1)
+            .with_span_labels(self.spans, &lbl2)
+    }
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_asm_opt_already_provided)]
+pub(crate) struct AsmOptAlreadyprovided {
+    #[primary_span]
+    #[label]
+    pub(crate) span: Span,
+    pub(crate) symbol: Symbol,
+    #[suggestion(code = "", applicability = "machine-applicable", style = "tool-only")]
+    pub(crate) full_span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_test_runner_invalid)]
+pub(crate) struct TestRunnerInvalid {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_test_runner_nargs)]
+pub(crate) struct TestRunnerNargs {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_expected_register_class_or_explicit_register)]
+pub(crate) struct ExpectedRegisterClassOrExplicitRegister {
+    #[primary_span]
+    pub(crate) span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_expected_comma_in_list)]
+pub(crate) struct ExpectedCommaInList {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_only_one_argument)]
+pub(crate) struct OnlyOneArgument<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub name: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_takes_no_arguments)]
+pub(crate) struct TakesNoArguments<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub name: &'a str,
+}
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
new file mode 100644
index 00000000000..5cb0407bd59
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -0,0 +1,1015 @@
+use crate::errors;
+use crate::util::expr_to_spanned_string;
+use parse::Position::ArgumentNamed;
+use rustc_ast::ptr::P;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast::{token, StmtKind};
+use rustc_ast::{
+    Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs,
+    FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount,
+    FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait, Recovered,
+};
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{Applicability, Diag, MultiSpan, PResult, SingleLabelManySpans};
+use rustc_expand::base::*;
+use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
+use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiag, LintId};
+use rustc_parse_format as parse;
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::{BytePos, ErrorGuaranteed, InnerSpan, Span};
+
+// The format_args!() macro is expanded in three steps:
+//  1. First, `parse_args` will parse the `(literal, arg, arg, name=arg, name=arg)` syntax,
+//     but doesn't parse the template (the literal) itself.
+//  2. Second, `make_format_args` will parse the template, the format options, resolve argument references,
+//     produce diagnostics, and turn the whole thing into a `FormatArgs` AST node.
+//  3. Much later, in AST lowering (rustc_ast_lowering), that `FormatArgs` structure will be turned
+//     into the expression of type `core::fmt::Arguments`.
+
+// See rustc_ast/src/format.rs for the FormatArgs structure and glossary.
+
+// Only used in parse_args and report_invalid_references,
+// to indicate how a referred argument was used.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum PositionUsedAs {
+    Placeholder(Option<Span>),
+    Precision,
+    Width,
+}
+use PositionUsedAs::*;
+
+#[derive(Debug)]
+struct MacroInput {
+    fmtstr: P<Expr>,
+    args: FormatArguments,
+    /// Whether the first argument was a string literal or a result from eager macro expansion.
+    /// If it's not a string literal, we disallow implicit argument capturing.
+    ///
+    /// This does not correspond to whether we can treat spans to the literal normally, as the whole
+    /// invocation might be the result of another macro expansion, in which case this flag may still be true.
+    ///
+    /// See [RFC 2795] for more information.
+    ///
+    /// [RFC 2795]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html#macro-hygiene
+    is_direct_literal: bool,
+}
+
+/// Parses the arguments from the given list of tokens, returning the diagnostic
+/// if there's a parse error so we can continue parsing other format!
+/// expressions.
+///
+/// If parsing succeeds, the return value is:
+///
+/// ```text
+/// Ok((fmtstr, parsed arguments))
+/// ```
+fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> {
+    let mut args = FormatArguments::new();
+
+    let mut p = ecx.new_parser_from_tts(tts);
+
+    if p.token == token::Eof {
+        return Err(ecx.dcx().create_err(errors::FormatRequiresString { span: sp }));
+    }
+
+    let first_token = &p.token;
+
+    let fmtstr = if let token::Literal(lit) = first_token.kind
+        && matches!(lit.kind, token::Str | token::StrRaw(_))
+    {
+        // This allows us to properly handle cases when the first comma
+        // after the format string is mistakenly replaced with any operator,
+        // which cause the expression parser to eat too much tokens.
+        p.parse_literal_maybe_minus()?
+    } else {
+        // Otherwise, we fall back to the expression parser.
+        p.parse_expr()?
+    };
+
+    // Only allow implicit captures to be used when the argument is a direct literal
+    // instead of a macro expanding to one.
+    let is_direct_literal = matches!(fmtstr.kind, ExprKind::Lit(_));
+
+    let mut first = true;
+
+    while p.token != token::Eof {
+        if !p.eat(&token::Comma) {
+            if first {
+                p.clear_expected_tokens();
+            }
+
+            match p.expect(&token::Comma) {
+                Err(err) => {
+                    match token::TokenKind::Comma.similar_tokens() {
+                        Some(tks) if tks.contains(&p.token.kind) => {
+                            // If a similar token is found, then it may be a typo. We
+                            // consider it as a comma, and continue parsing.
+                            err.emit();
+                            p.bump();
+                        }
+                        // Otherwise stop the parsing and return the error.
+                        _ => return Err(err),
+                    }
+                }
+                Ok(Recovered::Yes(_)) => (),
+                Ok(Recovered::No) => unreachable!(),
+            }
+        }
+        first = false;
+        if p.token == token::Eof {
+            break;
+        } // accept trailing commas
+        match p.token.ident() {
+            Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => {
+                p.bump();
+                p.expect(&token::Eq)?;
+                let expr = p.parse_expr()?;
+                if let Some((_, prev)) = args.by_name(ident.name) {
+                    ecx.dcx().emit_err(errors::FormatDuplicateArg {
+                        span: ident.span,
+                        prev: prev.kind.ident().unwrap().span,
+                        duplicate: ident.span,
+                        ident,
+                    });
+                    continue;
+                }
+                args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr });
+            }
+            _ => {
+                let expr = p.parse_expr()?;
+                if !args.named_args().is_empty() {
+                    return Err(ecx.dcx().create_err(errors::PositionalAfterNamed {
+                        span: expr.span,
+                        args: args
+                            .named_args()
+                            .iter()
+                            .filter_map(|a| a.kind.ident().map(|ident| (a, ident)))
+                            .map(|(arg, n)| n.span.to(arg.expr.span))
+                            .collect(),
+                    }));
+                }
+                args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr });
+            }
+        }
+    }
+    Ok(MacroInput { fmtstr, args, is_direct_literal })
+}
+
+fn make_format_args(
+    ecx: &mut ExtCtxt<'_>,
+    input: MacroInput,
+    append_newline: bool,
+) -> ExpandResult<Result<FormatArgs, ErrorGuaranteed>, ()> {
+    let msg = "format argument must be a string literal";
+    let unexpanded_fmt_span = input.fmtstr.span;
+
+    let MacroInput { fmtstr: efmt, mut args, is_direct_literal } = input;
+
+    let (fmt_str, fmt_style, fmt_span) = {
+        let ExpandResult::Ready(mac) = expr_to_spanned_string(ecx, efmt.clone(), msg) else {
+            return ExpandResult::Retry(());
+        };
+        match mac {
+            Ok(mut fmt) if append_newline => {
+                fmt.0 = Symbol::intern(&format!("{}\n", fmt.0));
+                fmt
+            }
+            Ok(fmt) => fmt,
+            Err(err) => {
+                let guar = match err {
+                    Ok((mut err, suggested)) => {
+                        if !suggested {
+                            if let ExprKind::Block(block, None) = &efmt.kind
+                                && block.stmts.len() == 1
+                                && let StmtKind::Expr(expr) = &block.stmts[0].kind
+                                && let ExprKind::Path(None, path) = &expr.kind
+                                && path.is_potential_trivial_const_arg()
+                            {
+                                err.multipart_suggestion(
+                                    "quote your inlined format argument to use as string literal",
+                                    vec![
+                                        (unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()),
+                                        (unexpanded_fmt_span.shrink_to_lo(), "\"".to_string()),
+                                    ],
+                                    Applicability::MaybeIncorrect,
+                                );
+                            } else {
+                                let sugg_fmt = match args.explicit_args().len() {
+                                    0 => "{}".to_string(),
+                                    _ => {
+                                        format!("{}{{}}", "{} ".repeat(args.explicit_args().len()))
+                                    }
+                                };
+                                err.span_suggestion(
+                                    unexpanded_fmt_span.shrink_to_lo(),
+                                    "you might be missing a string literal to format with",
+                                    format!("\"{sugg_fmt}\", "),
+                                    Applicability::MaybeIncorrect,
+                                );
+                            }
+                        }
+                        err.emit()
+                    }
+                    Err(guar) => guar,
+                };
+                return ExpandResult::Ready(Err(guar));
+            }
+        }
+    };
+
+    let str_style = match fmt_style {
+        rustc_ast::StrStyle::Cooked => None,
+        rustc_ast::StrStyle::Raw(raw) => Some(raw as usize),
+    };
+
+    let fmt_str = fmt_str.as_str(); // for the suggestions below
+    let fmt_snippet = ecx.source_map().span_to_snippet(unexpanded_fmt_span).ok();
+    let mut parser = parse::Parser::new(
+        fmt_str,
+        str_style,
+        fmt_snippet,
+        append_newline,
+        parse::ParseMode::Format,
+    );
+
+    let mut pieces = Vec::new();
+    while let Some(piece) = parser.next() {
+        if !parser.errors.is_empty() {
+            break;
+        } else {
+            pieces.push(piece);
+        }
+    }
+
+    let is_source_literal = parser.is_source_literal;
+
+    if !parser.errors.is_empty() {
+        let err = parser.errors.remove(0);
+        let sp = if is_source_literal {
+            fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end))
+        } else {
+            // The format string could be another macro invocation, e.g.:
+            //     format!(concat!("abc", "{}"), 4);
+            // However, `err.span` is an inner span relative to the *result* of
+            // the macro invocation, which is why we would get a nonsensical
+            // result calling `fmt_span.from_inner(err.span)` as above, and
+            // might even end up inside a multibyte character (issue #86085).
+            // Therefore, we conservatively report the error for the entire
+            // argument span here.
+            fmt_span
+        };
+        let mut e = errors::InvalidFormatString {
+            span: sp,
+            note_: None,
+            label_: None,
+            sugg_: None,
+            desc: err.description,
+            label1: err.label,
+        };
+        if let Some(note) = err.note {
+            e.note_ = Some(errors::InvalidFormatStringNote { note });
+        }
+        if let Some((label, span)) = err.secondary_label
+            && is_source_literal
+        {
+            e.label_ = Some(errors::InvalidFormatStringLabel {
+                span: fmt_span.from_inner(InnerSpan::new(span.start, span.end)),
+                label,
+            });
+        }
+        match err.suggestion {
+            parse::Suggestion::None => {}
+            parse::Suggestion::UsePositional => {
+                let captured_arg_span =
+                    fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
+                if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) {
+                    let span = match args.unnamed_args().last() {
+                        Some(arg) => arg.expr.span,
+                        None => fmt_span,
+                    };
+                    e.sugg_ = Some(errors::InvalidFormatStringSuggestion::UsePositional {
+                        captured: captured_arg_span,
+                        len: args.unnamed_args().len().to_string(),
+                        span: span.shrink_to_hi(),
+                        arg,
+                    });
+                }
+            }
+            parse::Suggestion::RemoveRawIdent(span) => {
+                if is_source_literal {
+                    let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
+                    e.sugg_ = Some(errors::InvalidFormatStringSuggestion::RemoveRawIdent { span })
+                }
+            }
+        }
+        let guar = ecx.dcx().emit_err(e);
+        return ExpandResult::Ready(Err(guar));
+    }
+
+    let to_span = |inner_span: parse::InnerSpan| {
+        is_source_literal.then(|| {
+            fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end })
+        })
+    };
+
+    let mut used = vec![false; args.explicit_args().len()];
+    let mut invalid_refs = Vec::new();
+    let mut numeric_references_to_named_arg = Vec::new();
+
+    enum ArgRef<'a> {
+        Index(usize),
+        Name(&'a str, Option<Span>),
+    }
+    use ArgRef::*;
+
+    let mut unnamed_arg_after_named_arg = false;
+
+    let mut lookup_arg = |arg: ArgRef<'_>,
+                          span: Option<Span>,
+                          used_as: PositionUsedAs,
+                          kind: FormatArgPositionKind|
+     -> FormatArgPosition {
+        let index = match arg {
+            Index(index) => {
+                if let Some(arg) = args.by_index(index) {
+                    used[index] = true;
+                    if arg.kind.ident().is_some() {
+                        // This was a named argument, but it was used as a positional argument.
+                        numeric_references_to_named_arg.push((index, span, used_as));
+                    }
+                    Ok(index)
+                } else {
+                    // Doesn't exist as an explicit argument.
+                    invalid_refs.push((index, span, used_as, kind));
+                    Err(index)
+                }
+            }
+            Name(name, span) => {
+                let name = Symbol::intern(name);
+                if let Some((index, _)) = args.by_name(name) {
+                    // Name found in `args`, so we resolve it to its index.
+                    if index < args.explicit_args().len() {
+                        // Mark it as used, if it was an explicit argument.
+                        used[index] = true;
+                    }
+                    Ok(index)
+                } else {
+                    // Name not found in `args`, so we add it as an implicitly captured argument.
+                    let span = span.unwrap_or(fmt_span);
+                    let ident = Ident::new(name, span);
+                    let expr = if is_direct_literal {
+                        ecx.expr_ident(span, ident)
+                    } else {
+                        // For the moment capturing variables from format strings expanded from macros is
+                        // disabled (see RFC #2795)
+                        let guar = ecx.dcx().emit_err(errors::FormatNoArgNamed { span, name });
+                        unnamed_arg_after_named_arg = true;
+                        DummyResult::raw_expr(span, Some(guar))
+                    };
+                    Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }))
+                }
+            }
+        };
+        FormatArgPosition { index, kind, span }
+    };
+
+    let mut template = Vec::new();
+    let mut unfinished_literal = String::new();
+    let mut placeholder_index = 0;
+
+    for piece in &pieces {
+        match *piece {
+            parse::Piece::String(s) => {
+                unfinished_literal.push_str(s);
+            }
+            parse::Piece::NextArgument(box parse::Argument { position, position_span, format }) => {
+                if !unfinished_literal.is_empty() {
+                    template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
+                    unfinished_literal.clear();
+                }
+
+                let span = parser.arg_places.get(placeholder_index).and_then(|&s| to_span(s));
+                placeholder_index += 1;
+
+                let position_span = to_span(position_span);
+                let argument = match position {
+                    parse::ArgumentImplicitlyIs(i) => lookup_arg(
+                        Index(i),
+                        position_span,
+                        Placeholder(span),
+                        FormatArgPositionKind::Implicit,
+                    ),
+                    parse::ArgumentIs(i) => lookup_arg(
+                        Index(i),
+                        position_span,
+                        Placeholder(span),
+                        FormatArgPositionKind::Number,
+                    ),
+                    parse::ArgumentNamed(name) => lookup_arg(
+                        Name(name, position_span),
+                        position_span,
+                        Placeholder(span),
+                        FormatArgPositionKind::Named,
+                    ),
+                };
+
+                let alignment = match format.align {
+                    parse::AlignUnknown => None,
+                    parse::AlignLeft => Some(FormatAlignment::Left),
+                    parse::AlignRight => Some(FormatAlignment::Right),
+                    parse::AlignCenter => Some(FormatAlignment::Center),
+                };
+
+                let format_trait = match format.ty {
+                    "" => FormatTrait::Display,
+                    "?" => FormatTrait::Debug,
+                    "e" => FormatTrait::LowerExp,
+                    "E" => FormatTrait::UpperExp,
+                    "o" => FormatTrait::Octal,
+                    "p" => FormatTrait::Pointer,
+                    "b" => FormatTrait::Binary,
+                    "x" => FormatTrait::LowerHex,
+                    "X" => FormatTrait::UpperHex,
+                    _ => {
+                        invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span);
+                        FormatTrait::Display
+                    }
+                };
+
+                let precision_span = format.precision_span.and_then(to_span);
+                let precision = match format.precision {
+                    parse::CountIs(n) => Some(FormatCount::Literal(n)),
+                    parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
+                        Name(name, to_span(name_span)),
+                        precision_span,
+                        Precision,
+                        FormatArgPositionKind::Named,
+                    ))),
+                    parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
+                        Index(i),
+                        precision_span,
+                        Precision,
+                        FormatArgPositionKind::Number,
+                    ))),
+                    parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
+                        Index(i),
+                        precision_span,
+                        Precision,
+                        FormatArgPositionKind::Implicit,
+                    ))),
+                    parse::CountImplied => None,
+                };
+
+                let width_span = format.width_span.and_then(to_span);
+                let width = match format.width {
+                    parse::CountIs(n) => Some(FormatCount::Literal(n)),
+                    parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
+                        Name(name, to_span(name_span)),
+                        width_span,
+                        Width,
+                        FormatArgPositionKind::Named,
+                    ))),
+                    parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
+                        Index(i),
+                        width_span,
+                        Width,
+                        FormatArgPositionKind::Number,
+                    ))),
+                    parse::CountIsStar(_) => unreachable!(),
+                    parse::CountImplied => None,
+                };
+
+                template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
+                    argument,
+                    span,
+                    format_trait,
+                    format_options: FormatOptions {
+                        fill: format.fill,
+                        alignment,
+                        sign: format.sign.map(|s| match s {
+                            parse::Sign::Plus => FormatSign::Plus,
+                            parse::Sign::Minus => FormatSign::Minus,
+                        }),
+                        alternate: format.alternate,
+                        zero_pad: format.zero_pad,
+                        debug_hex: format.debug_hex.map(|s| match s {
+                            parse::DebugHex::Lower => FormatDebugHex::Lower,
+                            parse::DebugHex::Upper => FormatDebugHex::Upper,
+                        }),
+                        precision,
+                        width,
+                    },
+                }));
+            }
+        }
+    }
+
+    if !unfinished_literal.is_empty() {
+        template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
+    }
+
+    if !invalid_refs.is_empty() {
+        report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser);
+    }
+
+    let unused = used
+        .iter()
+        .enumerate()
+        .filter(|&(_, used)| !used)
+        .map(|(i, _)| {
+            let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
+            (args.explicit_args()[i].expr.span, named)
+        })
+        .collect::<Vec<_>>();
+
+    let has_unused = !unused.is_empty();
+    if has_unused {
+        // If there's a lot of unused arguments,
+        // let's check if this format arguments looks like another syntax (printf / shell).
+        let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2;
+        report_missing_placeholders(
+            ecx,
+            unused,
+            &used,
+            &args,
+            &pieces,
+            detect_foreign_fmt,
+            str_style,
+            fmt_str,
+            fmt_span,
+        );
+    }
+
+    // Only check for unused named argument names if there are no other errors to avoid causing
+    // too much noise in output errors, such as when a named argument is entirely unused.
+    if invalid_refs.is_empty() && !has_unused && !unnamed_arg_after_named_arg {
+        for &(index, span, used_as) in &numeric_references_to_named_arg {
+            let (position_sp_to_replace, position_sp_for_msg) = match used_as {
+                Placeholder(pspan) => (span, pspan),
+                Precision => {
+                    // Strip the leading `.` for precision.
+                    let span = span.map(|span| span.with_lo(span.lo() + BytePos(1)));
+                    (span, span)
+                }
+                Width => (span, span),
+            };
+            let arg_name = args.explicit_args()[index].kind.ident().unwrap();
+            ecx.buffered_early_lint.push(BufferedEarlyLint {
+                span: arg_name.span.into(),
+                node_id: rustc_ast::CRATE_NODE_ID,
+                lint_id: LintId::of(NAMED_ARGUMENTS_USED_POSITIONALLY),
+                diagnostic: BuiltinLintDiag::NamedArgumentUsedPositionally {
+                    position_sp_to_replace,
+                    position_sp_for_msg,
+                    named_arg_sp: arg_name.span,
+                    named_arg_name: arg_name.name.to_string(),
+                    is_formatting_arg: matches!(used_as, Width | Precision),
+                },
+            });
+        }
+    }
+
+    ExpandResult::Ready(Ok(FormatArgs { span: fmt_span, template, arguments: args }))
+}
+
+fn invalid_placeholder_type_error(
+    ecx: &ExtCtxt<'_>,
+    ty: &str,
+    ty_span: Option<parse::InnerSpan>,
+    fmt_span: Span,
+) {
+    let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end)));
+    let suggs = if let Some(sp) = sp {
+        [
+            ("", "Display"),
+            ("?", "Debug"),
+            ("e", "LowerExp"),
+            ("E", "UpperExp"),
+            ("o", "Octal"),
+            ("p", "Pointer"),
+            ("b", "Binary"),
+            ("x", "LowerHex"),
+            ("X", "UpperHex"),
+        ]
+        .into_iter()
+        .map(|(fmt, trait_name)| errors::FormatUnknownTraitSugg { span: sp, fmt, trait_name })
+        .collect()
+    } else {
+        vec![]
+    };
+    ecx.dcx().emit_err(errors::FormatUnknownTrait { span: sp.unwrap_or(fmt_span), ty, suggs });
+}
+
+fn report_missing_placeholders(
+    ecx: &ExtCtxt<'_>,
+    unused: Vec<(Span, bool)>,
+    used: &[bool],
+    args: &FormatArguments,
+    pieces: &[parse::Piece<'_>],
+    detect_foreign_fmt: bool,
+    str_style: Option<usize>,
+    fmt_str: &str,
+    fmt_span: Span,
+) {
+    let mut diag = if let &[(span, named)] = &unused[..] {
+        ecx.dcx().create_err(errors::FormatUnusedArg { span, named })
+    } else {
+        let unused_labels =
+            unused.iter().map(|&(span, named)| errors::FormatUnusedArg { span, named }).collect();
+        let unused_spans = unused.iter().map(|&(span, _)| span).collect();
+        ecx.dcx().create_err(errors::FormatUnusedArgs {
+            fmt: fmt_span,
+            unused: unused_spans,
+            unused_labels,
+        })
+    };
+
+    let placeholders = pieces
+        .iter()
+        .filter_map(|piece| {
+            if let parse::Piece::NextArgument(argument) = piece
+                && let ArgumentNamed(binding) = argument.position
+            {
+                let span = fmt_span.from_inner(InnerSpan::new(
+                    argument.position_span.start,
+                    argument.position_span.end,
+                ));
+                Some((span, binding))
+            } else {
+                None
+            }
+        })
+        .collect::<Vec<_>>();
+
+    if !placeholders.is_empty() {
+        if let Some(new_diag) = report_redundant_format_arguments(ecx, args, used, placeholders) {
+            diag.cancel();
+            new_diag.emit();
+            return;
+        }
+    }
+
+    // Used to ensure we only report translations for *one* kind of foreign format.
+    let mut found_foreign = false;
+
+    // Decide if we want to look for foreign formatting directives.
+    if detect_foreign_fmt {
+        use super::format_foreign as foreign;
+
+        // The set of foreign substitutions we've explained. This prevents spamming the user
+        // with `%d should be written as {}` over and over again.
+        let mut explained = FxHashSet::default();
+
+        macro_rules! check_foreign {
+            ($kind:ident) => {{
+                let mut show_doc_note = false;
+
+                let mut suggestions = vec![];
+                // account for `"` and account for raw strings `r#`
+                let padding = str_style.map(|i| i + 2).unwrap_or(1);
+                for sub in foreign::$kind::iter_subs(fmt_str, padding) {
+                    let (trn, success) = match sub.translate() {
+                        Ok(trn) => (trn, true),
+                        Err(Some(msg)) => (msg, false),
+
+                        // If it has no translation, don't call it out specifically.
+                        _ => continue,
+                    };
+
+                    let pos = sub.position();
+                    let sub = String::from(sub.as_str());
+                    if explained.contains(&sub) {
+                        continue;
+                    }
+                    explained.insert(sub);
+
+                    if !found_foreign {
+                        found_foreign = true;
+                        show_doc_note = true;
+                    }
+
+                    let sp = fmt_span.from_inner(pos);
+
+                    if success {
+                        suggestions.push((sp, trn));
+                    } else {
+                        diag.span_note(
+                            sp,
+                            format!("format specifiers use curly braces, and {}", trn),
+                        );
+                    }
+                }
+
+                if show_doc_note {
+                    diag.note(concat!(
+                        stringify!($kind),
+                        " formatting is not supported; see the documentation for `std::fmt`",
+                    ));
+                }
+                if suggestions.len() > 0 {
+                    diag.multipart_suggestion(
+                        "format specifiers use curly braces",
+                        suggestions,
+                        Applicability::MachineApplicable,
+                    );
+                }
+            }};
+        }
+
+        check_foreign!(printf);
+        if !found_foreign {
+            check_foreign!(shell);
+        }
+    }
+    if !found_foreign && unused.len() == 1 {
+        diag.span_label(fmt_span, "formatting specifier missing");
+    }
+
+    diag.emit();
+}
+
+/// This function detects and reports unused format!() arguments that are
+/// redundant due to implicit captures (e.g. `format!("{x}", x)`).
+fn report_redundant_format_arguments<'a>(
+    ecx: &ExtCtxt<'a>,
+    args: &FormatArguments,
+    used: &[bool],
+    placeholders: Vec<(Span, &str)>,
+) -> Option<Diag<'a>> {
+    let mut fmt_arg_indices = vec![];
+    let mut args_spans = vec![];
+    let mut fmt_spans = vec![];
+
+    for (i, unnamed_arg) in args.unnamed_args().iter().enumerate().rev() {
+        let Some(ty) = unnamed_arg.expr.to_ty() else { continue };
+        let Some(argument_binding) = ty.kind.is_simple_path() else { continue };
+        let argument_binding = argument_binding.as_str();
+
+        if used[i] {
+            continue;
+        }
+
+        let matching_placeholders = placeholders
+            .iter()
+            .filter(|(_, inline_binding)| argument_binding == *inline_binding)
+            .map(|(span, _)| span)
+            .collect::<Vec<_>>();
+
+        if !matching_placeholders.is_empty() {
+            fmt_arg_indices.push(i);
+            args_spans.push(unnamed_arg.expr.span);
+            for span in &matching_placeholders {
+                if fmt_spans.contains(*span) {
+                    continue;
+                }
+                fmt_spans.push(**span);
+            }
+        }
+    }
+
+    if !args_spans.is_empty() {
+        let multispan = MultiSpan::from(fmt_spans);
+        let mut suggestion_spans = vec![];
+
+        for (arg_span, fmt_arg_idx) in args_spans.iter().zip(fmt_arg_indices.iter()) {
+            let span = if fmt_arg_idx + 1 == args.explicit_args().len() {
+                *arg_span
+            } else {
+                arg_span.until(args.explicit_args()[*fmt_arg_idx + 1].expr.span)
+            };
+
+            suggestion_spans.push(span);
+        }
+
+        let sugg = if args.named_args().len() == 0 {
+            Some(errors::FormatRedundantArgsSugg { spans: suggestion_spans })
+        } else {
+            None
+        };
+
+        return Some(ecx.dcx().create_err(errors::FormatRedundantArgs {
+            n: args_spans.len(),
+            span: MultiSpan::from(args_spans),
+            note: multispan,
+            sugg,
+        }));
+    }
+
+    None
+}
+
+/// Handle invalid references to positional arguments. Output different
+/// errors for the case where all arguments are positional and for when
+/// there are named arguments or numbered positional arguments in the
+/// format string.
+fn report_invalid_references(
+    ecx: &ExtCtxt<'_>,
+    invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
+    template: &[FormatArgsPiece],
+    fmt_span: Span,
+    args: &FormatArguments,
+    parser: parse::Parser<'_>,
+) {
+    let num_args_desc = match args.explicit_args().len() {
+        0 => "no arguments were given".to_string(),
+        1 => "there is 1 argument".to_string(),
+        n => format!("there are {n} arguments"),
+    };
+
+    let mut e;
+
+    if template.iter().all(|piece| match piece {
+        FormatArgsPiece::Placeholder(FormatPlaceholder {
+            argument: FormatArgPosition { kind: FormatArgPositionKind::Number, .. },
+            ..
+        }) => false,
+        FormatArgsPiece::Placeholder(FormatPlaceholder {
+            format_options:
+                FormatOptions {
+                    precision:
+                        Some(FormatCount::Argument(FormatArgPosition {
+                            kind: FormatArgPositionKind::Number,
+                            ..
+                        })),
+                    ..
+                }
+                | FormatOptions {
+                    width:
+                        Some(FormatCount::Argument(FormatArgPosition {
+                            kind: FormatArgPositionKind::Number,
+                            ..
+                        })),
+                    ..
+                },
+            ..
+        }) => false,
+        _ => true,
+    }) {
+        // There are no numeric positions.
+        // Collect all the implicit positions:
+        let mut spans = Vec::new();
+        let mut num_placeholders = 0;
+        for piece in template {
+            let mut placeholder = None;
+            // `{arg:.*}`
+            if let FormatArgsPiece::Placeholder(FormatPlaceholder {
+                format_options:
+                    FormatOptions {
+                        precision:
+                            Some(FormatCount::Argument(FormatArgPosition {
+                                span,
+                                kind: FormatArgPositionKind::Implicit,
+                                ..
+                            })),
+                        ..
+                    },
+                ..
+            }) = piece
+            {
+                placeholder = *span;
+                num_placeholders += 1;
+            }
+            // `{}`
+            if let FormatArgsPiece::Placeholder(FormatPlaceholder {
+                argument: FormatArgPosition { kind: FormatArgPositionKind::Implicit, .. },
+                span,
+                ..
+            }) = piece
+            {
+                placeholder = *span;
+                num_placeholders += 1;
+            }
+            // For `{:.*}`, we only push one span.
+            spans.extend(placeholder);
+        }
+        let span = if spans.is_empty() {
+            MultiSpan::from_span(fmt_span)
+        } else {
+            MultiSpan::from_spans(spans)
+        };
+        e = ecx.dcx().create_err(errors::FormatPositionalMismatch {
+            span,
+            n: num_placeholders,
+            desc: num_args_desc,
+            highlight: SingleLabelManySpans {
+                spans: args.explicit_args().iter().map(|arg| arg.expr.span).collect(),
+                label: "",
+            },
+        });
+        // Point out `{:.*}` placeholders: those take an extra argument.
+        let mut has_precision_star = false;
+        for piece in template {
+            if let FormatArgsPiece::Placeholder(FormatPlaceholder {
+                format_options:
+                    FormatOptions {
+                        precision:
+                            Some(FormatCount::Argument(FormatArgPosition {
+                                index,
+                                span: Some(span),
+                                kind: FormatArgPositionKind::Implicit,
+                                ..
+                            })),
+                        ..
+                    },
+                ..
+            }) = piece
+            {
+                let (Ok(index) | Err(index)) = index;
+                has_precision_star = true;
+                e.span_label(
+                    *span,
+                    format!(
+                        "this precision flag adds an extra required argument at position {}, which is why there {} expected",
+                        index,
+                        if num_placeholders == 1 {
+                            "is 1 argument".to_string()
+                        } else {
+                            format!("are {num_placeholders} arguments")
+                        },
+                    ),
+                );
+            }
+        }
+        if has_precision_star {
+            e.note("positional arguments are zero-based");
+        }
+    } else {
+        let mut indexes: Vec<_> = invalid_refs.iter().map(|&(index, _, _, _)| index).collect();
+        // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)`
+        // for `println!("{7:7$}", 1);`
+        indexes.sort();
+        indexes.dedup();
+        let span: MultiSpan = if !parser.is_source_literal || parser.arg_places.is_empty() {
+            MultiSpan::from_span(fmt_span)
+        } else {
+            MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect())
+        };
+        let arg_list = if let &[index] = &indexes[..] {
+            format!("argument {index}")
+        } else {
+            let tail = indexes.pop().unwrap();
+            format!(
+                "arguments {head} and {tail}",
+                head = indexes.into_iter().map(|i| i.to_string()).collect::<Vec<_>>().join(", ")
+            )
+        };
+        e = ecx.dcx().struct_span_err(
+            span,
+            format!("invalid reference to positional {arg_list} ({num_args_desc})"),
+        );
+        e.note("positional arguments are zero-based");
+    }
+
+    if template.iter().any(|piece| match piece {
+        FormatArgsPiece::Placeholder(FormatPlaceholder { format_options: f, .. }) => {
+            *f != FormatOptions::default()
+        }
+        _ => false,
+    }) {
+        e.note("for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html");
+    }
+
+    e.emit();
+}
+
+fn expand_format_args_impl<'cx>(
+    ecx: &'cx mut ExtCtxt<'_>,
+    mut sp: Span,
+    tts: TokenStream,
+    nl: bool,
+) -> MacroExpanderResult<'cx> {
+    sp = ecx.with_def_site_ctxt(sp);
+    ExpandResult::Ready(match parse_args(ecx, sp, tts) {
+        Ok(input) => {
+            let ExpandResult::Ready(mac) = make_format_args(ecx, input, nl) else {
+                return ExpandResult::Retry(());
+            };
+            match mac {
+                Ok(format_args) => {
+                    MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args))))
+                }
+                Err(guar) => MacEager::expr(DummyResult::raw_expr(sp, Some(guar))),
+            }
+        }
+        Err(err) => {
+            let guar = err.emit();
+            DummyResult::any(sp, guar)
+        }
+    })
+}
+
+pub(crate) fn expand_format_args<'cx>(
+    ecx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    expand_format_args_impl(ecx, sp, tts, false)
+}
+
+pub(crate) fn expand_format_args_nl<'cx>(
+    ecx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    expand_format_args_impl(ecx, sp, tts, true)
+}
diff --git a/compiler/rustc_builtin_macros/src/format_foreign.rs b/compiler/rustc_builtin_macros/src/format_foreign.rs
new file mode 100644
index 00000000000..bc2c6def68a
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/format_foreign.rs
@@ -0,0 +1,822 @@
+pub(crate) mod printf {
+    use super::strcursor::StrCursor as Cur;
+    use rustc_span::InnerSpan;
+
+    /// Represents a single `printf`-style substitution.
+    #[derive(Clone, PartialEq, Debug)]
+    pub enum Substitution<'a> {
+        /// A formatted output substitution with its internal byte offset.
+        Format(Format<'a>),
+        /// A literal `%%` escape, with its start and end indices.
+        Escape((usize, usize)),
+    }
+
+    impl<'a> Substitution<'a> {
+        pub fn as_str(&self) -> &str {
+            match self {
+                Substitution::Format(fmt) => fmt.span,
+                Substitution::Escape(_) => "%%",
+            }
+        }
+
+        pub fn position(&self) -> InnerSpan {
+            match self {
+                Substitution::Format(fmt) => fmt.position,
+                &Substitution::Escape((start, end)) => InnerSpan::new(start, end),
+            }
+        }
+
+        pub fn set_position(&mut self, start: usize, end: usize) {
+            match self {
+                Substitution::Format(fmt) => fmt.position = InnerSpan::new(start, end),
+                Substitution::Escape(pos) => *pos = (start, end),
+            }
+        }
+
+        /// Translate this substitution into an equivalent Rust formatting directive.
+        ///
+        /// This ignores cases where the substitution does not have an exact equivalent, or where
+        /// the substitution would be unnecessary.
+        pub fn translate(&self) -> Result<String, Option<String>> {
+            match self {
+                Substitution::Format(fmt) => fmt.translate(),
+                Substitution::Escape(_) => Err(None),
+            }
+        }
+    }
+
+    #[derive(Clone, PartialEq, Debug)]
+    /// A single `printf`-style formatting directive.
+    pub struct Format<'a> {
+        /// The entire original formatting directive.
+        pub span: &'a str,
+        /// The (1-based) parameter to be converted.
+        pub parameter: Option<u16>,
+        /// Formatting flags.
+        pub flags: &'a str,
+        /// Minimum width of the output.
+        pub width: Option<Num>,
+        /// Precision of the conversion.
+        pub precision: Option<Num>,
+        /// Length modifier for the conversion.
+        pub length: Option<&'a str>,
+        /// Type of parameter being converted.
+        pub type_: &'a str,
+        /// Byte offset for the start and end of this formatting directive.
+        pub position: InnerSpan,
+    }
+
+    impl Format<'_> {
+        /// Translate this directive into an equivalent Rust formatting directive.
+        ///
+        /// Returns `Err` in cases where the `printf` directive does not have an exact Rust
+        /// equivalent, rather than guessing.
+        pub fn translate(&self) -> Result<String, Option<String>> {
+            use std::fmt::Write;
+
+            let (c_alt, c_zero, c_left, c_plus) = {
+                let mut c_alt = false;
+                let mut c_zero = false;
+                let mut c_left = false;
+                let mut c_plus = false;
+                for c in self.flags.chars() {
+                    match c {
+                        '#' => c_alt = true,
+                        '0' => c_zero = true,
+                        '-' => c_left = true,
+                        '+' => c_plus = true,
+                        _ => {
+                            return Err(Some(format!("the flag `{c}` is unknown or unsupported")));
+                        }
+                    }
+                }
+                (c_alt, c_zero, c_left, c_plus)
+            };
+
+            // Has a special form in Rust for numbers.
+            let fill = c_zero.then_some("0");
+
+            let align = c_left.then_some("<");
+
+            // Rust doesn't have an equivalent to the `' '` flag.
+            let sign = c_plus.then_some("+");
+
+            // Not *quite* the same, depending on the type...
+            let alt = c_alt;
+
+            let width = match self.width {
+                Some(Num::Next) => {
+                    // NOTE: Rust doesn't support this.
+                    return Err(Some(
+                        "you have to use a positional or named parameter for the width".to_string(),
+                    ));
+                }
+                w @ Some(Num::Arg(_)) => w,
+                w @ Some(Num::Num(_)) => w,
+                None => None,
+            };
+
+            let precision = self.precision;
+
+            // NOTE: although length *can* have an effect, we can't duplicate the effect in Rust, so
+            // we just ignore it.
+
+            let (type_, use_zero_fill, is_int) = match self.type_ {
+                "d" | "i" | "u" => (None, true, true),
+                "f" | "F" => (None, false, false),
+                "s" | "c" => (None, false, false),
+                "e" | "E" => (Some(self.type_), true, false),
+                "x" | "X" | "o" => (Some(self.type_), true, true),
+                "p" => (Some(self.type_), false, true),
+                "g" => (Some("e"), true, false),
+                "G" => (Some("E"), true, false),
+                _ => {
+                    return Err(Some(format!(
+                        "the conversion specifier `{}` is unknown or unsupported",
+                        self.type_
+                    )));
+                }
+            };
+
+            let (fill, width, precision) = match (is_int, width, precision) {
+                (true, Some(_), Some(_)) => {
+                    // Rust can't duplicate this insanity.
+                    return Err(Some(
+                        "width and precision cannot both be specified for integer conversions"
+                            .to_string(),
+                    ));
+                }
+                (true, None, Some(p)) => (Some("0"), Some(p), None),
+                (true, w, None) => (fill, w, None),
+                (false, w, p) => (fill, w, p),
+            };
+
+            let align = match (self.type_, width.is_some(), align.is_some()) {
+                ("s", true, false) => Some(">"),
+                _ => align,
+            };
+
+            let (fill, zero_fill) = match (fill, use_zero_fill) {
+                (Some("0"), true) => (None, true),
+                (fill, _) => (fill, false),
+            };
+
+            let alt = match type_ {
+                Some("x" | "X") => alt,
+                _ => false,
+            };
+
+            let has_options = fill.is_some()
+                || align.is_some()
+                || sign.is_some()
+                || alt
+                || zero_fill
+                || width.is_some()
+                || precision.is_some()
+                || type_.is_some();
+
+            // Initialise with a rough guess.
+            let cap = self.span.len() + if has_options { 2 } else { 0 };
+            let mut s = String::with_capacity(cap);
+
+            s.push('{');
+
+            if let Some(arg) = self.parameter {
+                match write!(
+                    s,
+                    "{}",
+                    match arg.checked_sub(1) {
+                        Some(a) => a,
+                        None => return Err(None),
+                    }
+                ) {
+                    Err(_) => return Err(None),
+                    _ => {}
+                }
+            }
+
+            if has_options {
+                s.push(':');
+
+                let align = if let Some(fill) = fill {
+                    s.push_str(fill);
+                    align.or(Some(">"))
+                } else {
+                    align
+                };
+
+                if let Some(align) = align {
+                    s.push_str(align);
+                }
+
+                if let Some(sign) = sign {
+                    s.push_str(sign);
+                }
+
+                if alt {
+                    s.push('#');
+                }
+
+                if zero_fill {
+                    s.push('0');
+                }
+
+                if let Some(width) = width {
+                    match width.translate(&mut s) {
+                        Err(_) => return Err(None),
+                        _ => {}
+                    }
+                }
+
+                if let Some(precision) = precision {
+                    s.push('.');
+                    match precision.translate(&mut s) {
+                        Err(_) => return Err(None),
+                        _ => {}
+                    }
+                }
+
+                if let Some(type_) = type_ {
+                    s.push_str(type_);
+                }
+            }
+
+            s.push('}');
+            Ok(s)
+        }
+    }
+
+    /// A general number used in a `printf` formatting directive.
+    #[derive(Copy, Clone, PartialEq, Debug)]
+    pub enum Num {
+        // The range of these values is technically bounded by `NL_ARGMAX`... but, at least for GNU
+        // libc, it apparently has no real fixed limit. A `u16` is used here on the basis that it
+        // is *vanishingly* unlikely that *anyone* is going to try formatting something wider, or
+        // with more precision, than 32 thousand positions which is so wide it couldn't possibly fit
+        // on a screen.
+        /// A specific, fixed value.
+        Num(u16),
+        /// The value is derived from a positional argument.
+        Arg(u16),
+        /// The value is derived from the "next" unconverted argument.
+        Next,
+    }
+
+    impl Num {
+        fn from_str(s: &str, arg: Option<&str>) -> Option<Self> {
+            if let Some(arg) = arg {
+                arg.parse().ok().map(|arg| Num::Arg(arg))
+            } else if s == "*" {
+                Some(Num::Next)
+            } else {
+                s.parse().ok().map(|num| Num::Num(num))
+            }
+        }
+
+        fn translate(&self, s: &mut String) -> std::fmt::Result {
+            use std::fmt::Write;
+            match *self {
+                Num::Num(n) => write!(s, "{n}"),
+                Num::Arg(n) => {
+                    let n = n.checked_sub(1).ok_or(std::fmt::Error)?;
+                    write!(s, "{n}$")
+                }
+                Num::Next => write!(s, "*"),
+            }
+        }
+    }
+
+    /// Returns an iterator over all substitutions in a given string.
+    pub fn iter_subs(s: &str, start_pos: usize) -> Substitutions<'_> {
+        Substitutions { s, pos: start_pos }
+    }
+
+    /// Iterator over substitutions in a string.
+    pub struct Substitutions<'a> {
+        s: &'a str,
+        pos: usize,
+    }
+
+    impl<'a> Iterator for Substitutions<'a> {
+        type Item = Substitution<'a>;
+        fn next(&mut self) -> Option<Self::Item> {
+            let (mut sub, tail) = parse_next_substitution(self.s)?;
+            self.s = tail;
+            let InnerSpan { start, end } = sub.position();
+            sub.set_position(start + self.pos, end + self.pos);
+            self.pos += end;
+            Some(sub)
+        }
+
+        fn size_hint(&self) -> (usize, Option<usize>) {
+            // Substitutions are at least 2 characters long.
+            (0, Some(self.s.len() / 2))
+        }
+    }
+
+    enum State {
+        Start,
+        Flags,
+        Width,
+        WidthArg,
+        Prec,
+        PrecInner,
+        Length,
+        Type,
+    }
+
+    /// Parse the next substitution from the input string.
+    pub fn parse_next_substitution(s: &str) -> Option<(Substitution<'_>, &str)> {
+        use self::State::*;
+
+        let at = {
+            let start = s.find('%')?;
+            if let '%' = s[start + 1..].chars().next()? {
+                return Some((Substitution::Escape((start, start + 2)), &s[start + 2..]));
+            }
+
+            Cur::new_at(s, start)
+        };
+
+        // This is meant to be a translation of the following regex:
+        //
+        // ```regex
+        // (?x)
+        // ^ %
+        // (?: (?P<parameter> \d+) \$ )?
+        // (?P<flags> [-+ 0\#']* )
+        // (?P<width> \d+ | \* (?: (?P<widtha> \d+) \$ )? )?
+        // (?: \. (?P<precision> \d+ | \* (?: (?P<precisiona> \d+) \$ )? ) )?
+        // (?P<length>
+        //     # Standard
+        //     hh | h | ll | l | L | z | j | t
+        //
+        //     # Other
+        //     | I32 | I64 | I | q
+        // )?
+        // (?P<type> . )
+        // ```
+
+        // Used to establish the full span at the end.
+        let start = at;
+        // The current position within the string.
+        let mut at = at.at_next_cp()?;
+        // `c` is the next codepoint, `next` is a cursor after it.
+        let (mut c, mut next) = at.next_cp()?;
+
+        // Update `at`, `c`, and `next`, exiting if we're out of input.
+        macro_rules! move_to {
+            ($cur:expr) => {{
+                at = $cur;
+                let (c_, next_) = at.next_cp()?;
+                c = c_;
+                next = next_;
+            }};
+        }
+
+        // Constructs a result when parsing fails.
+        //
+        // Note: `move` used to capture copies of the cursors as they are *now*.
+        let fallback = move || {
+            Some((
+                Substitution::Format(Format {
+                    span: start.slice_between(next).unwrap(),
+                    parameter: None,
+                    flags: "",
+                    width: None,
+                    precision: None,
+                    length: None,
+                    type_: at.slice_between(next).unwrap(),
+                    position: InnerSpan::new(start.at, next.at),
+                }),
+                next.slice_after(),
+            ))
+        };
+
+        // Next parsing state.
+        let mut state = Start;
+
+        // Sadly, Rust isn't *quite* smart enough to know these *must* be initialised by the end.
+        let mut parameter: Option<u16> = None;
+        let mut flags: &str = "";
+        let mut width: Option<Num> = None;
+        let mut precision: Option<Num> = None;
+        let mut length: Option<&str> = None;
+        let mut type_: &str = "";
+        let end: Cur<'_>;
+
+        if let Start = state {
+            match c {
+                '1'..='9' => {
+                    let end = at_next_cp_while(next, char::is_ascii_digit);
+                    match end.next_cp() {
+                        // Yes, this *is* the parameter.
+                        Some(('$', end2)) => {
+                            state = Flags;
+                            parameter = Some(at.slice_between(end).unwrap().parse().unwrap());
+                            move_to!(end2);
+                        }
+                        // Wait, no, actually, it's the width.
+                        Some(_) => {
+                            state = Prec;
+                            parameter = None;
+                            flags = "";
+                            width = at.slice_between(end).and_then(|num| Num::from_str(num, None));
+                            if width.is_none() {
+                                return fallback();
+                            }
+                            move_to!(end);
+                        }
+                        // It's invalid, is what it is.
+                        None => return fallback(),
+                    }
+                }
+                _ => {
+                    state = Flags;
+                    parameter = None;
+                    move_to!(at);
+                }
+            }
+        }
+
+        if let Flags = state {
+            let end = at_next_cp_while(at, is_flag);
+            state = Width;
+            flags = at.slice_between(end).unwrap();
+            move_to!(end);
+        }
+
+        if let Width = state {
+            match c {
+                '*' => {
+                    state = WidthArg;
+                    move_to!(next);
+                }
+                '1'..='9' => {
+                    let end = at_next_cp_while(next, char::is_ascii_digit);
+                    state = Prec;
+                    width = at.slice_between(end).and_then(|num| Num::from_str(num, None));
+                    if width.is_none() {
+                        return fallback();
+                    }
+                    move_to!(end);
+                }
+                _ => {
+                    state = Prec;
+                    width = None;
+                    move_to!(at);
+                }
+            }
+        }
+
+        if let WidthArg = state {
+            let end = at_next_cp_while(at, char::is_ascii_digit);
+            match end.next_cp() {
+                Some(('$', end2)) => {
+                    state = Prec;
+                    width = Num::from_str("", at.slice_between(end));
+                    move_to!(end2);
+                }
+                _ => {
+                    state = Prec;
+                    width = Some(Num::Next);
+                    move_to!(end);
+                }
+            }
+        }
+
+        if let Prec = state {
+            match c {
+                '.' => {
+                    state = PrecInner;
+                    move_to!(next);
+                }
+                _ => {
+                    state = Length;
+                    precision = None;
+                    move_to!(at);
+                }
+            }
+        }
+
+        if let PrecInner = state {
+            match c {
+                '*' => {
+                    let end = at_next_cp_while(next, char::is_ascii_digit);
+                    match end.next_cp() {
+                        Some(('$', end2)) => {
+                            state = Length;
+                            precision = Num::from_str("*", next.slice_between(end));
+                            move_to!(end2);
+                        }
+                        _ => {
+                            state = Length;
+                            precision = Some(Num::Next);
+                            move_to!(end);
+                        }
+                    }
+                }
+                '0'..='9' => {
+                    let end = at_next_cp_while(next, char::is_ascii_digit);
+                    state = Length;
+                    precision = at.slice_between(end).and_then(|num| Num::from_str(num, None));
+                    move_to!(end);
+                }
+                _ => return fallback(),
+            }
+        }
+
+        if let Length = state {
+            let c1_next1 = next.next_cp();
+            match (c, c1_next1) {
+                ('h', Some(('h', next1))) | ('l', Some(('l', next1))) => {
+                    state = Type;
+                    length = Some(at.slice_between(next1).unwrap());
+                    move_to!(next1);
+                }
+
+                ('h' | 'l' | 'L' | 'z' | 'j' | 't' | 'q', _) => {
+                    state = Type;
+                    length = Some(at.slice_between(next).unwrap());
+                    move_to!(next);
+                }
+
+                ('I', _) => {
+                    let end = next
+                        .at_next_cp()
+                        .and_then(|end| end.at_next_cp())
+                        .map(|end| (next.slice_between(end).unwrap(), end));
+                    let end = match end {
+                        Some(("32" | "64", end)) => end,
+                        _ => next,
+                    };
+                    state = Type;
+                    length = Some(at.slice_between(end).unwrap());
+                    move_to!(end);
+                }
+
+                _ => {
+                    state = Type;
+                    length = None;
+                    move_to!(at);
+                }
+            }
+        }
+
+        if let Type = state {
+            type_ = at.slice_between(next).unwrap();
+
+            // Don't use `move_to!` here, as we *can* be at the end of the input.
+            at = next;
+        }
+
+        let _ = c; // to avoid never used value
+
+        end = at;
+        let position = InnerSpan::new(start.at, end.at);
+
+        let f = Format {
+            span: start.slice_between(end).unwrap(),
+            parameter,
+            flags,
+            width,
+            precision,
+            length,
+            type_,
+            position,
+        };
+        Some((Substitution::Format(f), end.slice_after()))
+    }
+
+    fn at_next_cp_while<F>(mut cur: Cur<'_>, mut pred: F) -> Cur<'_>
+    where
+        F: FnMut(&char) -> bool,
+    {
+        loop {
+            match cur.next_cp() {
+                Some((c, next)) => {
+                    if pred(&c) {
+                        cur = next;
+                    } else {
+                        return cur;
+                    }
+                }
+                None => return cur,
+            }
+        }
+    }
+
+    fn is_flag(c: &char) -> bool {
+        matches!(c, '0' | '-' | '+' | ' ' | '#' | '\'')
+    }
+
+    #[cfg(test)]
+    mod tests;
+}
+
+pub mod shell {
+    use super::strcursor::StrCursor as Cur;
+    use rustc_span::InnerSpan;
+
+    #[derive(Clone, PartialEq, Debug)]
+    pub enum Substitution<'a> {
+        Ordinal(u8, (usize, usize)),
+        Name(&'a str, (usize, usize)),
+        Escape((usize, usize)),
+    }
+
+    impl Substitution<'_> {
+        pub fn as_str(&self) -> String {
+            match self {
+                Substitution::Ordinal(n, _) => format!("${n}"),
+                Substitution::Name(n, _) => format!("${n}"),
+                Substitution::Escape(_) => "$$".into(),
+            }
+        }
+
+        pub fn position(&self) -> InnerSpan {
+            let (Self::Ordinal(_, pos) | Self::Name(_, pos) | Self::Escape(pos)) = self;
+            InnerSpan::new(pos.0, pos.1)
+        }
+
+        pub fn set_position(&mut self, start: usize, end: usize) {
+            let (Self::Ordinal(_, pos) | Self::Name(_, pos) | Self::Escape(pos)) = self;
+            *pos = (start, end);
+        }
+
+        pub fn translate(&self) -> Result<String, Option<String>> {
+            match self {
+                Substitution::Ordinal(n, _) => Ok(format!("{{{}}}", n)),
+                Substitution::Name(n, _) => Ok(format!("{{{}}}", n)),
+                Substitution::Escape(_) => Err(None),
+            }
+        }
+    }
+
+    /// Returns an iterator over all substitutions in a given string.
+    pub fn iter_subs(s: &str, start_pos: usize) -> Substitutions<'_> {
+        Substitutions { s, pos: start_pos }
+    }
+
+    /// Iterator over substitutions in a string.
+    pub struct Substitutions<'a> {
+        s: &'a str,
+        pos: usize,
+    }
+
+    impl<'a> Iterator for Substitutions<'a> {
+        type Item = Substitution<'a>;
+        fn next(&mut self) -> Option<Self::Item> {
+            let (mut sub, tail) = parse_next_substitution(self.s)?;
+            self.s = tail;
+            let InnerSpan { start, end } = sub.position();
+            sub.set_position(start + self.pos, end + self.pos);
+            self.pos += end;
+            Some(sub)
+        }
+
+        fn size_hint(&self) -> (usize, Option<usize>) {
+            (0, Some(self.s.len()))
+        }
+    }
+
+    /// Parse the next substitution from the input string.
+    pub fn parse_next_substitution(s: &str) -> Option<(Substitution<'_>, &str)> {
+        let at = {
+            let start = s.find('$')?;
+            match s[start + 1..].chars().next()? {
+                '$' => return Some((Substitution::Escape((start, start + 2)), &s[start + 2..])),
+                c @ '0'..='9' => {
+                    let n = (c as u8) - b'0';
+                    return Some((Substitution::Ordinal(n, (start, start + 2)), &s[start + 2..]));
+                }
+                _ => { /* fall-through */ }
+            }
+
+            Cur::new_at(s, start)
+        };
+
+        let at = at.at_next_cp()?;
+        let (c, inner) = at.next_cp()?;
+
+        if !is_ident_head(c) {
+            None
+        } else {
+            let end = at_next_cp_while(inner, is_ident_tail);
+            let slice = at.slice_between(end).unwrap();
+            let start = at.at - 1;
+            let end_pos = at.at + slice.len();
+            Some((Substitution::Name(slice, (start, end_pos)), end.slice_after()))
+        }
+    }
+
+    fn at_next_cp_while<F>(mut cur: Cur<'_>, mut pred: F) -> Cur<'_>
+    where
+        F: FnMut(char) -> bool,
+    {
+        loop {
+            match cur.next_cp() {
+                Some((c, next)) => {
+                    if pred(c) {
+                        cur = next;
+                    } else {
+                        return cur;
+                    }
+                }
+                None => return cur,
+            }
+        }
+    }
+
+    fn is_ident_head(c: char) -> bool {
+        c.is_ascii_alphabetic() || c == '_'
+    }
+
+    fn is_ident_tail(c: char) -> bool {
+        c.is_ascii_alphanumeric() || c == '_'
+    }
+
+    #[cfg(test)]
+    mod tests;
+}
+
+mod strcursor {
+    pub struct StrCursor<'a> {
+        s: &'a str,
+        pub at: usize,
+    }
+
+    impl<'a> StrCursor<'a> {
+        pub fn new_at(s: &'a str, at: usize) -> StrCursor<'a> {
+            StrCursor { s, at }
+        }
+
+        pub fn at_next_cp(mut self) -> Option<StrCursor<'a>> {
+            match self.try_seek_right_cp() {
+                true => Some(self),
+                false => None,
+            }
+        }
+
+        pub fn next_cp(mut self) -> Option<(char, StrCursor<'a>)> {
+            let cp = self.cp_after()?;
+            self.seek_right(cp.len_utf8());
+            Some((cp, self))
+        }
+
+        fn slice_before(&self) -> &'a str {
+            &self.s[0..self.at]
+        }
+
+        pub fn slice_after(&self) -> &'a str {
+            &self.s[self.at..]
+        }
+
+        pub fn slice_between(&self, until: StrCursor<'a>) -> Option<&'a str> {
+            if !str_eq_literal(self.s, until.s) {
+                None
+            } else {
+                use std::cmp::{max, min};
+                let beg = min(self.at, until.at);
+                let end = max(self.at, until.at);
+                Some(&self.s[beg..end])
+            }
+        }
+
+        fn cp_after(&self) -> Option<char> {
+            self.slice_after().chars().next()
+        }
+
+        fn try_seek_right_cp(&mut self) -> bool {
+            match self.slice_after().chars().next() {
+                Some(c) => {
+                    self.at += c.len_utf8();
+                    true
+                }
+                None => false,
+            }
+        }
+
+        fn seek_right(&mut self, bytes: usize) {
+            self.at += bytes;
+        }
+    }
+
+    impl Copy for StrCursor<'_> {}
+
+    impl<'a> Clone for StrCursor<'a> {
+        fn clone(&self) -> StrCursor<'a> {
+            *self
+        }
+    }
+
+    impl std::fmt::Debug for StrCursor<'_> {
+        fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            write!(fmt, "StrCursor({:?} | {:?})", self.slice_before(), self.slice_after())
+        }
+    }
+
+    fn str_eq_literal(a: &str, b: &str) -> bool {
+        a.as_bytes().as_ptr() == b.as_bytes().as_ptr() && a.len() == b.len()
+    }
+}
diff --git a/compiler/rustc_builtin_macros/src/format_foreign/printf/tests.rs b/compiler/rustc_builtin_macros/src/format_foreign/printf/tests.rs
new file mode 100644
index 00000000000..df773910dbc
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/format_foreign/printf/tests.rs
@@ -0,0 +1,145 @@
+use super::{iter_subs, parse_next_substitution as pns, Format as F, Num as N, Substitution as S};
+
+macro_rules! assert_eq_pnsat {
+    ($lhs:expr, $rhs:expr) => {
+        assert_eq!(
+            pns($lhs).and_then(|(s, _)| s.translate().ok()),
+            $rhs.map(<String as From<&str>>::from)
+        )
+    };
+}
+
+#[test]
+fn test_escape() {
+    assert_eq!(pns("has no escapes"), None);
+    assert_eq!(pns("has no escapes, either %"), None);
+    assert_eq!(pns("*so* has a %% escape"), Some((S::Escape((11, 13)), " escape")));
+    assert_eq!(pns("%% leading escape"), Some((S::Escape((0, 2)), " leading escape")));
+    assert_eq!(pns("trailing escape %%"), Some((S::Escape((16, 18)), "")));
+}
+
+#[test]
+fn test_parse() {
+    macro_rules! assert_pns_eq_sub {
+        ($in_:expr, {
+            $param:expr, $flags:expr,
+            $width:expr, $prec:expr, $len:expr, $type_:expr,
+            $pos:expr,
+        }) => {
+            assert_eq!(
+                pns(concat!($in_, "!")),
+                Some((
+                    S::Format(F {
+                        span: $in_,
+                        parameter: $param,
+                        flags: $flags,
+                        width: $width,
+                        precision: $prec,
+                        length: $len,
+                        type_: $type_,
+                        position: rustc_span::InnerSpan::new($pos.0, $pos.1),
+                    }),
+                    "!"
+                ))
+            )
+        };
+    }
+
+    assert_pns_eq_sub!("%!",
+        { None, "", None, None, None, "!", (0, 2), });
+    assert_pns_eq_sub!("%c",
+        { None, "", None, None, None, "c", (0, 2), });
+    assert_pns_eq_sub!("%s",
+        { None, "", None, None, None, "s", (0, 2), });
+    assert_pns_eq_sub!("%06d",
+        { None, "0", Some(N::Num(6)), None, None, "d", (0, 4), });
+    assert_pns_eq_sub!("%4.2f",
+        { None, "", Some(N::Num(4)), Some(N::Num(2)), None, "f", (0, 5), });
+    assert_pns_eq_sub!("%#x",
+        { None, "#", None, None, None, "x", (0, 3), });
+    assert_pns_eq_sub!("%-10s",
+        { None, "-", Some(N::Num(10)), None, None, "s", (0, 5), });
+    assert_pns_eq_sub!("%*s",
+        { None, "", Some(N::Next), None, None, "s", (0, 3), });
+    assert_pns_eq_sub!("%-10.*s",
+        { None, "-", Some(N::Num(10)), Some(N::Next), None, "s", (0, 7), });
+    assert_pns_eq_sub!("%-*.*s",
+        { None, "-", Some(N::Next), Some(N::Next), None, "s", (0, 6), });
+    assert_pns_eq_sub!("%.6i",
+        { None, "", None, Some(N::Num(6)), None, "i", (0, 4), });
+    assert_pns_eq_sub!("%+i",
+        { None, "+", None, None, None, "i", (0, 3), });
+    assert_pns_eq_sub!("%08X",
+        { None, "0", Some(N::Num(8)), None, None, "X", (0, 4), });
+    assert_pns_eq_sub!("%lu",
+        { None, "", None, None, Some("l"), "u", (0, 3), });
+    assert_pns_eq_sub!("%Iu",
+        { None, "", None, None, Some("I"), "u", (0, 3), });
+    assert_pns_eq_sub!("%I32u",
+        { None, "", None, None, Some("I32"), "u", (0, 5), });
+    assert_pns_eq_sub!("%I64u",
+        { None, "", None, None, Some("I64"), "u", (0, 5), });
+    assert_pns_eq_sub!("%'d",
+        { None, "'", None, None, None, "d", (0, 3), });
+    assert_pns_eq_sub!("%10s",
+        { None, "", Some(N::Num(10)), None, None, "s", (0, 4), });
+    assert_pns_eq_sub!("%-10.10s",
+        { None, "-", Some(N::Num(10)), Some(N::Num(10)), None, "s", (0, 8), });
+    assert_pns_eq_sub!("%1$d",
+        { Some(1), "", None, None, None, "d", (0, 4), });
+    assert_pns_eq_sub!("%2$.*3$d",
+        { Some(2), "", None, Some(N::Arg(3)), None, "d", (0, 8), });
+    assert_pns_eq_sub!("%1$*2$.*3$d",
+        { Some(1), "", Some(N::Arg(2)), Some(N::Arg(3)), None, "d", (0, 11), });
+    assert_pns_eq_sub!("%-8ld",
+        { None, "-", Some(N::Num(8)), None, Some("l"), "d", (0, 5), });
+}
+
+#[test]
+fn test_iter() {
+    let s = "The %d'th word %% is: `%.*s` %!\n";
+    let subs: Vec<_> = iter_subs(s, 0).map(|sub| sub.translate().ok()).collect();
+    assert_eq!(
+        subs.iter().map(Option::as_deref).collect::<Vec<_>>(),
+        vec![Some("{}"), None, Some("{:.*}"), None]
+    );
+}
+
+/// Checks that the translations are what we expect.
+#[test]
+fn test_translation() {
+    assert_eq_pnsat!("%c", Some("{}"));
+    assert_eq_pnsat!("%d", Some("{}"));
+    assert_eq_pnsat!("%u", Some("{}"));
+    assert_eq_pnsat!("%x", Some("{:x}"));
+    assert_eq_pnsat!("%X", Some("{:X}"));
+    assert_eq_pnsat!("%e", Some("{:e}"));
+    assert_eq_pnsat!("%E", Some("{:E}"));
+    assert_eq_pnsat!("%f", Some("{}"));
+    assert_eq_pnsat!("%g", Some("{:e}"));
+    assert_eq_pnsat!("%G", Some("{:E}"));
+    assert_eq_pnsat!("%s", Some("{}"));
+    assert_eq_pnsat!("%p", Some("{:p}"));
+
+    assert_eq_pnsat!("%06d", Some("{:06}"));
+    assert_eq_pnsat!("%4.2f", Some("{:4.2}"));
+    assert_eq_pnsat!("%#x", Some("{:#x}"));
+    assert_eq_pnsat!("%-10s", Some("{:<10}"));
+    assert_eq_pnsat!("%*s", None);
+    assert_eq_pnsat!("%-10.*s", Some("{:<10.*}"));
+    assert_eq_pnsat!("%-*.*s", None);
+    assert_eq_pnsat!("%.6i", Some("{:06}"));
+    assert_eq_pnsat!("%+i", Some("{:+}"));
+    assert_eq_pnsat!("%08X", Some("{:08X}"));
+    assert_eq_pnsat!("%lu", Some("{}"));
+    assert_eq_pnsat!("%Iu", Some("{}"));
+    assert_eq_pnsat!("%I32u", Some("{}"));
+    assert_eq_pnsat!("%I64u", Some("{}"));
+    assert_eq_pnsat!("%'d", None);
+    assert_eq_pnsat!("%10s", Some("{:>10}"));
+    assert_eq_pnsat!("%-10.10s", Some("{:<10.10}"));
+    assert_eq_pnsat!("%1$d", Some("{0}"));
+    assert_eq_pnsat!("%2$.*3$d", Some("{1:02$}"));
+    assert_eq_pnsat!("%1$*2$.*3$s", Some("{0:>1$.2$}"));
+    assert_eq_pnsat!("%-8ld", Some("{:<8}"));
+}
diff --git a/compiler/rustc_builtin_macros/src/format_foreign/shell/tests.rs b/compiler/rustc_builtin_macros/src/format_foreign/shell/tests.rs
new file mode 100644
index 00000000000..93a7afcd6e8
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/format_foreign/shell/tests.rs
@@ -0,0 +1,56 @@
+use super::{parse_next_substitution as pns, Substitution as S};
+
+macro_rules! assert_eq_pnsat {
+    ($lhs:expr, $rhs:expr) => {
+        assert_eq!(
+            pns($lhs).and_then(|(f, _)| f.translate().ok()),
+            $rhs.map(<String as From<&str>>::from)
+        )
+    };
+}
+
+#[test]
+fn test_escape() {
+    assert_eq!(pns("has no escapes"), None);
+    assert_eq!(pns("has no escapes, either $"), None);
+    assert_eq!(pns("*so* has a $$ escape"), Some((S::Escape((11, 13)), " escape")));
+    assert_eq!(pns("$$ leading escape"), Some((S::Escape((0, 2)), " leading escape")));
+    assert_eq!(pns("trailing escape $$"), Some((S::Escape((16, 18)), "")));
+}
+
+#[test]
+fn test_parse() {
+    macro_rules! assert_pns_eq_sub {
+        ($in_:expr, $kind:ident($arg:expr, $pos:expr)) => {
+            assert_eq!(pns(concat!($in_, "!")), Some((S::$kind($arg.into(), $pos), "!")))
+        };
+    }
+
+    assert_pns_eq_sub!("$0", Ordinal(0, (0, 2)));
+    assert_pns_eq_sub!("$1", Ordinal(1, (0, 2)));
+    assert_pns_eq_sub!("$9", Ordinal(9, (0, 2)));
+    assert_pns_eq_sub!("$N", Name("N", (0, 2)));
+    assert_pns_eq_sub!("$NAME", Name("NAME", (0, 5)));
+}
+
+#[test]
+fn test_iter() {
+    use super::iter_subs;
+    let s = "The $0'th word $$ is: `$WORD` $!\n";
+    let subs: Vec<_> = iter_subs(s, 0).map(|sub| sub.translate().ok()).collect();
+    assert_eq!(
+        subs.iter().map(Option::as_deref).collect::<Vec<_>>(),
+        vec![Some("{0}"), None, Some("{WORD}")]
+    );
+}
+
+#[test]
+fn test_translation() {
+    assert_eq_pnsat!("$0", Some("{0}"));
+    assert_eq_pnsat!("$9", Some("{9}"));
+    assert_eq_pnsat!("$1", Some("{1}"));
+    assert_eq_pnsat!("$10", Some("{1}"));
+    assert_eq_pnsat!("$stuff", Some("{stuff}"));
+    assert_eq_pnsat!("$NAME", Some("{NAME}"));
+    assert_eq_pnsat!("$PREFIX/bin", Some("{PREFIX}"));
+}
diff --git a/compiler/rustc_builtin_macros/src/global_allocator.rs b/compiler/rustc_builtin_macros/src/global_allocator.rs
new file mode 100644
index 00000000000..b44ff979303
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/global_allocator.rs
@@ -0,0 +1,174 @@
+use crate::util::check_builtin_macro_attribute;
+
+use crate::errors;
+use rustc_ast::expand::allocator::{
+    global_fn_name, AllocatorMethod, AllocatorMethodInput, AllocatorTy, ALLOCATOR_METHODS,
+};
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, AttrVec, Expr, FnHeader, FnSig, Generics, Param, StmtKind};
+use rustc_ast::{Fn, ItemKind, Mutability, Safety, Stmt, Ty, TyKind};
+use rustc_expand::base::{Annotatable, ExtCtxt};
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::Span;
+use thin_vec::{thin_vec, ThinVec};
+
+pub(crate) fn expand(
+    ecx: &mut ExtCtxt<'_>,
+    _span: Span,
+    meta_item: &ast::MetaItem,
+    item: Annotatable,
+) -> Vec<Annotatable> {
+    check_builtin_macro_attribute(ecx, meta_item, sym::global_allocator);
+
+    let orig_item = item.clone();
+
+    // Allow using `#[global_allocator]` on an item statement
+    // FIXME - if we get deref patterns, use them to reduce duplication here
+    let (item, is_stmt, ty_span) = if let Annotatable::Item(item) = &item
+        && let ItemKind::Static(box ast::StaticItem { ty, .. }) = &item.kind
+    {
+        (item, false, ecx.with_def_site_ctxt(ty.span))
+    } else if let Annotatable::Stmt(stmt) = &item
+        && let StmtKind::Item(item) = &stmt.kind
+        && let ItemKind::Static(box ast::StaticItem { ty, .. }) = &item.kind
+    {
+        (item, true, ecx.with_def_site_ctxt(ty.span))
+    } else {
+        ecx.dcx().emit_err(errors::AllocMustStatics { span: item.span() });
+        return vec![orig_item];
+    };
+
+    // Generate a bunch of new items using the AllocFnFactory
+    let span = ecx.with_def_site_ctxt(item.span);
+    let f = AllocFnFactory { span, ty_span, global: item.ident, cx: ecx };
+
+    // Generate item statements for the allocator methods.
+    let stmts = ALLOCATOR_METHODS.iter().map(|method| f.allocator_fn(method)).collect();
+
+    // Generate anonymous constant serving as container for the allocator methods.
+    let const_ty = ecx.ty(ty_span, TyKind::Tup(ThinVec::new()));
+    let const_body = ecx.expr_block(ecx.block(span, stmts));
+    let const_item = ecx.item_const(span, Ident::new(kw::Underscore, span), const_ty, const_body);
+    let const_item = if is_stmt {
+        Annotatable::Stmt(P(ecx.stmt_item(span, const_item)))
+    } else {
+        Annotatable::Item(const_item)
+    };
+
+    // Return the original item and the new methods.
+    vec![orig_item, const_item]
+}
+
+struct AllocFnFactory<'a, 'b> {
+    span: Span,
+    ty_span: Span,
+    global: Ident,
+    cx: &'b ExtCtxt<'a>,
+}
+
+impl AllocFnFactory<'_, '_> {
+    fn allocator_fn(&self, method: &AllocatorMethod) -> Stmt {
+        let mut abi_args = ThinVec::new();
+        let args = method.inputs.iter().map(|input| self.arg_ty(input, &mut abi_args)).collect();
+        let result = self.call_allocator(method.name, args);
+        let output_ty = self.ret_ty(&method.output);
+        let decl = self.cx.fn_decl(abi_args, ast::FnRetTy::Ty(output_ty));
+        let header = FnHeader { safety: Safety::Unsafe(self.span), ..FnHeader::default() };
+        let sig = FnSig { decl, header, span: self.span };
+        let body = Some(self.cx.block_expr(result));
+        let kind = ItemKind::Fn(Box::new(Fn {
+            defaultness: ast::Defaultness::Final,
+            sig,
+            generics: Generics::default(),
+            body,
+        }));
+        let item = self.cx.item(
+            self.span,
+            Ident::from_str_and_span(&global_fn_name(method.name), self.span),
+            self.attrs(),
+            kind,
+        );
+        self.cx.stmt_item(self.ty_span, item)
+    }
+
+    fn call_allocator(&self, method: Symbol, mut args: ThinVec<P<Expr>>) -> P<Expr> {
+        let method = self.cx.std_path(&[sym::alloc, sym::GlobalAlloc, method]);
+        let method = self.cx.expr_path(self.cx.path(self.ty_span, method));
+        let allocator = self.cx.path_ident(self.ty_span, self.global);
+        let allocator = self.cx.expr_path(allocator);
+        let allocator = self.cx.expr_addr_of(self.ty_span, allocator);
+        args.insert(0, allocator);
+
+        self.cx.expr_call(self.ty_span, method, args)
+    }
+
+    fn attrs(&self) -> AttrVec {
+        thin_vec![self.cx.attr_word(sym::rustc_std_internal_symbol, self.span)]
+    }
+
+    fn arg_ty(&self, input: &AllocatorMethodInput, args: &mut ThinVec<Param>) -> P<Expr> {
+        match input.ty {
+            AllocatorTy::Layout => {
+                // If an allocator method is ever introduced having multiple
+                // Layout arguments, these argument names need to be
+                // disambiguated somehow. Currently the generated code would
+                // fail to compile with "identifier is bound more than once in
+                // this parameter list".
+                let size = Ident::from_str_and_span("size", self.span);
+                let align = Ident::from_str_and_span("align", self.span);
+
+                let usize = self.cx.path_ident(self.span, Ident::new(sym::usize, self.span));
+                let ty_usize = self.cx.ty_path(usize);
+                args.push(self.cx.param(self.span, size, ty_usize.clone()));
+                args.push(self.cx.param(self.span, align, ty_usize));
+
+                let layout_new =
+                    self.cx.std_path(&[sym::alloc, sym::Layout, sym::from_size_align_unchecked]);
+                let layout_new = self.cx.expr_path(self.cx.path(self.span, layout_new));
+                let size = self.cx.expr_ident(self.span, size);
+                let align = self.cx.expr_ident(self.span, align);
+                let layout = self.cx.expr_call(self.span, layout_new, thin_vec![size, align]);
+                layout
+            }
+
+            AllocatorTy::Ptr => {
+                let ident = Ident::from_str_and_span(input.name, self.span);
+                args.push(self.cx.param(self.span, ident, self.ptr_u8()));
+                self.cx.expr_ident(self.span, ident)
+            }
+
+            AllocatorTy::Usize => {
+                let ident = Ident::from_str_and_span(input.name, self.span);
+                args.push(self.cx.param(self.span, ident, self.usize()));
+                self.cx.expr_ident(self.span, ident)
+            }
+
+            AllocatorTy::ResultPtr | AllocatorTy::Unit => {
+                panic!("can't convert AllocatorTy to an argument")
+            }
+        }
+    }
+
+    fn ret_ty(&self, ty: &AllocatorTy) -> P<Ty> {
+        match *ty {
+            AllocatorTy::ResultPtr => self.ptr_u8(),
+
+            AllocatorTy::Unit => self.cx.ty(self.span, TyKind::Tup(ThinVec::new())),
+
+            AllocatorTy::Layout | AllocatorTy::Usize | AllocatorTy::Ptr => {
+                panic!("can't convert `AllocatorTy` to an output")
+            }
+        }
+    }
+
+    fn usize(&self) -> P<Ty> {
+        let usize = self.cx.path_ident(self.span, Ident::new(sym::usize, self.span));
+        self.cx.ty_path(usize)
+    }
+
+    fn ptr_u8(&self) -> P<Ty> {
+        let u8 = self.cx.path_ident(self.span, Ident::new(sym::u8, self.span));
+        let ty_u8 = self.cx.ty_path(u8);
+        self.cx.ty_ptr(self.span, ty_u8, Mutability::Mut)
+    }
+}
diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs
new file mode 100644
index 00000000000..f8d93666145
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/lib.rs
@@ -0,0 +1,135 @@
+//! This crate contains implementations of built-in macros and other code generating facilities
+//! injecting code into the crate before it is lowered to HIR.
+
+// tidy-alphabetical-start
+#![allow(internal_features)]
+#![allow(rustc::diagnostic_outside_of_impl)]
+#![allow(rustc::untranslatable_diagnostic)]
+#![cfg_attr(bootstrap, feature(lint_reasons))]
+#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
+#![doc(rust_logo)]
+#![feature(assert_matches)]
+#![feature(box_patterns)]
+#![feature(decl_macro)]
+#![feature(if_let_guard)]
+#![feature(let_chains)]
+#![feature(proc_macro_internals)]
+#![feature(proc_macro_quote)]
+#![feature(rustdoc_internals)]
+#![feature(try_blocks)]
+// tidy-alphabetical-end
+
+extern crate proc_macro;
+
+use crate::deriving::*;
+use rustc_expand::base::{MacroExpanderFn, ResolverExpand, SyntaxExtensionKind};
+use rustc_expand::proc_macro::BangProcMacro;
+use rustc_span::symbol::sym;
+
+mod alloc_error_handler;
+mod assert;
+mod cfg;
+mod cfg_accessible;
+mod cfg_eval;
+mod compile_error;
+mod concat;
+mod concat_bytes;
+mod concat_idents;
+mod derive;
+mod deriving;
+mod edition_panic;
+mod env;
+mod errors;
+mod format;
+mod format_foreign;
+mod global_allocator;
+mod log_syntax;
+mod pattern_type;
+mod source_util;
+mod test;
+mod trace_macros;
+
+pub mod asm;
+pub mod cmdline_attrs;
+pub mod proc_macro_harness;
+pub mod standard_library_imports;
+pub mod test_harness;
+pub mod util;
+
+rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
+
+pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
+    let mut register = |name, kind| resolver.register_builtin_macro(name, kind);
+    macro register_bang($($name:ident: $f:expr,)*) {
+        $(register(sym::$name, SyntaxExtensionKind::LegacyBang(Box::new($f as MacroExpanderFn)));)*
+    }
+    macro register_attr($($name:ident: $f:expr,)*) {
+        $(register(sym::$name, SyntaxExtensionKind::LegacyAttr(Box::new($f)));)*
+    }
+    macro register_derive($($name:ident: $f:expr,)*) {
+        $(register(sym::$name, SyntaxExtensionKind::LegacyDerive(Box::new(BuiltinDerive($f))));)*
+    }
+
+    register_bang! {
+        // tidy-alphabetical-start
+        asm: asm::expand_asm,
+        assert: assert::expand_assert,
+        cfg: cfg::expand_cfg,
+        column: source_util::expand_column,
+        compile_error: compile_error::expand_compile_error,
+        concat: concat::expand_concat,
+        concat_bytes: concat_bytes::expand_concat_bytes,
+        concat_idents: concat_idents::expand_concat_idents,
+        const_format_args: format::expand_format_args,
+        core_panic: edition_panic::expand_panic,
+        env: env::expand_env,
+        file: source_util::expand_file,
+        format_args: format::expand_format_args,
+        format_args_nl: format::expand_format_args_nl,
+        global_asm: asm::expand_global_asm,
+        include: source_util::expand_include,
+        include_bytes: source_util::expand_include_bytes,
+        include_str: source_util::expand_include_str,
+        line: source_util::expand_line,
+        log_syntax: log_syntax::expand_log_syntax,
+        module_path: source_util::expand_mod,
+        option_env: env::expand_option_env,
+        pattern_type: pattern_type::expand,
+        std_panic: edition_panic::expand_panic,
+        stringify: source_util::expand_stringify,
+        trace_macros: trace_macros::expand_trace_macros,
+        unreachable: edition_panic::expand_unreachable,
+        // tidy-alphabetical-end
+    }
+
+    register_attr! {
+        alloc_error_handler: alloc_error_handler::expand,
+        bench: test::expand_bench,
+        cfg_accessible: cfg_accessible::Expander,
+        cfg_eval: cfg_eval::expand,
+        derive: derive::Expander { is_const: false },
+        derive_const: derive::Expander { is_const: true },
+        global_allocator: global_allocator::expand,
+        test: test::expand_test,
+        test_case: test::expand_test_case,
+    }
+
+    register_derive! {
+        Clone: clone::expand_deriving_clone,
+        Copy: bounds::expand_deriving_copy,
+        ConstParamTy: bounds::expand_deriving_const_param_ty,
+        Debug: debug::expand_deriving_debug,
+        Default: default::expand_deriving_default,
+        Eq: eq::expand_deriving_eq,
+        Hash: hash::expand_deriving_hash,
+        Ord: ord::expand_deriving_ord,
+        PartialEq: partial_eq::expand_deriving_partial_eq,
+        PartialOrd: partial_ord::expand_deriving_partial_ord,
+        RustcDecodable: decodable::expand_deriving_rustc_decodable,
+        RustcEncodable: encodable::expand_deriving_rustc_encodable,
+        SmartPointer: smart_ptr::expand_deriving_smart_ptr,
+    }
+
+    let client = proc_macro::bridge::client::Client::expand1(proc_macro::quote);
+    register(sym::quote, SyntaxExtensionKind::Bang(Box::new(BangProcMacro { client })));
+}
diff --git a/compiler/rustc_builtin_macros/src/log_syntax.rs b/compiler/rustc_builtin_macros/src/log_syntax.rs
new file mode 100644
index 00000000000..205f21ae7c9
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/log_syntax.rs
@@ -0,0 +1,14 @@
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast_pretty::pprust;
+use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
+
+pub(crate) fn expand_log_syntax<'cx>(
+    _cx: &'cx mut ExtCtxt<'_>,
+    sp: rustc_span::Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    println!("{}", pprust::tts_to_string(&tts));
+
+    // any so that `log_syntax` can be invoked as an expression and item.
+    ExpandResult::Ready(DummyResult::any_valid(sp))
+}
diff --git a/compiler/rustc_builtin_macros/src/pattern_type.rs b/compiler/rustc_builtin_macros/src/pattern_type.rs
new file mode 100644
index 00000000000..31f5656df13
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/pattern_type.rs
@@ -0,0 +1,29 @@
+use rustc_ast::{ast, ptr::P, tokenstream::TokenStream, Pat, Ty};
+use rustc_errors::PResult;
+use rustc_expand::base::{self, DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
+use rustc_span::{sym, Span};
+
+pub(crate) fn expand<'cx>(
+    cx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    let (ty, pat) = match parse_pat_ty(cx, tts) {
+        Ok(parsed) => parsed,
+        Err(err) => {
+            return ExpandResult::Ready(DummyResult::any(sp, err.emit()));
+        }
+    };
+
+    ExpandResult::Ready(base::MacEager::ty(cx.ty(sp, ast::TyKind::Pat(ty, pat))))
+}
+
+fn parse_pat_ty<'a>(cx: &mut ExtCtxt<'a>, stream: TokenStream) -> PResult<'a, (P<Ty>, P<Pat>)> {
+    let mut parser = cx.new_parser_from_tts(stream);
+
+    let ty = parser.parse_ty()?;
+    parser.eat_keyword(sym::is);
+    let pat = parser.parse_pat_no_top_alt(None, None)?;
+
+    Ok((ty, pat))
+}
diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
new file mode 100644
index 00000000000..99d0191958d
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
@@ -0,0 +1,387 @@
+use crate::errors;
+use rustc_ast::ptr::P;
+use rustc_ast::visit::{self, Visitor};
+use rustc_ast::{self as ast, attr, NodeId};
+use rustc_ast_pretty::pprust;
+use rustc_errors::DiagCtxtHandle;
+use rustc_expand::base::{parse_macro_name_and_helper_attrs, ExtCtxt, ResolverExpand};
+use rustc_expand::expand::{AstFragment, ExpansionConfig};
+use rustc_feature::Features;
+use rustc_session::Session;
+use rustc_span::hygiene::AstPass;
+use rustc_span::source_map::SourceMap;
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use smallvec::smallvec;
+use std::mem;
+use thin_vec::{thin_vec, ThinVec};
+
+struct ProcMacroDerive {
+    id: NodeId,
+    trait_name: Symbol,
+    function_name: Ident,
+    span: Span,
+    attrs: Vec<Symbol>,
+}
+
+struct ProcMacroDef {
+    id: NodeId,
+    function_name: Ident,
+    span: Span,
+}
+
+enum ProcMacro {
+    Derive(ProcMacroDerive),
+    Attr(ProcMacroDef),
+    Bang(ProcMacroDef),
+}
+
+struct CollectProcMacros<'a> {
+    macros: Vec<ProcMacro>,
+    in_root: bool,
+    dcx: DiagCtxtHandle<'a>,
+    source_map: &'a SourceMap,
+    is_proc_macro_crate: bool,
+    is_test_crate: bool,
+}
+
+pub fn inject(
+    krate: &mut ast::Crate,
+    sess: &Session,
+    features: &Features,
+    resolver: &mut dyn ResolverExpand,
+    is_proc_macro_crate: bool,
+    has_proc_macro_decls: bool,
+    is_test_crate: bool,
+    dcx: DiagCtxtHandle<'_>,
+) {
+    let ecfg = ExpansionConfig::default("proc_macro".to_string(), features);
+    let mut cx = ExtCtxt::new(sess, ecfg, resolver, None);
+
+    let mut collect = CollectProcMacros {
+        macros: Vec::new(),
+        in_root: true,
+        dcx,
+        source_map: sess.source_map(),
+        is_proc_macro_crate,
+        is_test_crate,
+    };
+
+    if has_proc_macro_decls || is_proc_macro_crate {
+        visit::walk_crate(&mut collect, krate);
+    }
+    let macros = collect.macros;
+
+    if !is_proc_macro_crate {
+        return;
+    }
+
+    if is_test_crate {
+        return;
+    }
+
+    let decls = mk_decls(&mut cx, &macros);
+    krate.items.push(decls);
+}
+
+impl<'a> CollectProcMacros<'a> {
+    fn check_not_pub_in_root(&self, vis: &ast::Visibility, sp: Span) {
+        if self.is_proc_macro_crate && self.in_root && vis.kind.is_pub() {
+            self.dcx.emit_err(errors::ProcMacro { span: sp });
+        }
+    }
+
+    fn collect_custom_derive(&mut self, item: &'a ast::Item, attr: &'a ast::Attribute) {
+        let Some((trait_name, proc_attrs)) =
+            parse_macro_name_and_helper_attrs(self.dcx, attr, "derive")
+        else {
+            return;
+        };
+
+        if self.in_root && item.vis.kind.is_pub() {
+            self.macros.push(ProcMacro::Derive(ProcMacroDerive {
+                id: item.id,
+                span: item.span,
+                trait_name,
+                function_name: item.ident,
+                attrs: proc_attrs,
+            }));
+        } else {
+            let msg = if !self.in_root {
+                "functions tagged with `#[proc_macro_derive]` must \
+                 currently reside in the root of the crate"
+            } else {
+                "functions tagged with `#[proc_macro_derive]` must be `pub`"
+            };
+            self.dcx.span_err(self.source_map.guess_head_span(item.span), msg);
+        }
+    }
+
+    fn collect_attr_proc_macro(&mut self, item: &'a ast::Item) {
+        if self.in_root && item.vis.kind.is_pub() {
+            self.macros.push(ProcMacro::Attr(ProcMacroDef {
+                id: item.id,
+                span: item.span,
+                function_name: item.ident,
+            }));
+        } else {
+            let msg = if !self.in_root {
+                "functions tagged with `#[proc_macro_attribute]` must \
+                 currently reside in the root of the crate"
+            } else {
+                "functions tagged with `#[proc_macro_attribute]` must be `pub`"
+            };
+            self.dcx.span_err(self.source_map.guess_head_span(item.span), msg);
+        }
+    }
+
+    fn collect_bang_proc_macro(&mut self, item: &'a ast::Item) {
+        if self.in_root && item.vis.kind.is_pub() {
+            self.macros.push(ProcMacro::Bang(ProcMacroDef {
+                id: item.id,
+                span: item.span,
+                function_name: item.ident,
+            }));
+        } else {
+            let msg = if !self.in_root {
+                "functions tagged with `#[proc_macro]` must \
+                 currently reside in the root of the crate"
+            } else {
+                "functions tagged with `#[proc_macro]` must be `pub`"
+            };
+            self.dcx.span_err(self.source_map.guess_head_span(item.span), msg);
+        }
+    }
+}
+
+impl<'a> Visitor<'a> for CollectProcMacros<'a> {
+    fn visit_item(&mut self, item: &'a ast::Item) {
+        if let ast::ItemKind::MacroDef(..) = item.kind {
+            if self.is_proc_macro_crate && attr::contains_name(&item.attrs, sym::macro_export) {
+                self.dcx.emit_err(errors::ExportMacroRules {
+                    span: self.source_map.guess_head_span(item.span),
+                });
+            }
+        }
+
+        // First up, make sure we're checking a bare function. If we're not then
+        // we're just not interested in this item.
+        //
+        // If we find one, try to locate a `#[proc_macro_derive]` attribute on it.
+        let is_fn = matches!(item.kind, ast::ItemKind::Fn(..));
+
+        let mut found_attr: Option<&'a ast::Attribute> = None;
+
+        for attr in &item.attrs {
+            if attr.is_proc_macro_attr() {
+                if let Some(prev_attr) = found_attr {
+                    let prev_item = prev_attr.get_normal_item();
+                    let item = attr.get_normal_item();
+                    let path_str = pprust::path_to_string(&item.path);
+                    let msg = if item.path.segments[0].ident.name
+                        == prev_item.path.segments[0].ident.name
+                    {
+                        format!(
+                            "only one `#[{path_str}]` attribute is allowed on any given function",
+                        )
+                    } else {
+                        format!(
+                            "`#[{}]` and `#[{}]` attributes cannot both be applied
+                            to the same function",
+                            path_str,
+                            pprust::path_to_string(&prev_item.path),
+                        )
+                    };
+
+                    self.dcx
+                        .struct_span_err(attr.span, msg)
+                        .with_span_label(prev_attr.span, "previous attribute here")
+                        .emit();
+
+                    return;
+                }
+
+                found_attr = Some(attr);
+            }
+        }
+
+        let Some(attr) = found_attr else {
+            self.check_not_pub_in_root(&item.vis, self.source_map.guess_head_span(item.span));
+            let prev_in_root = mem::replace(&mut self.in_root, false);
+            visit::walk_item(self, item);
+            self.in_root = prev_in_root;
+            return;
+        };
+
+        if !is_fn {
+            let msg = format!(
+                "the `#[{}]` attribute may only be used on bare functions",
+                pprust::path_to_string(&attr.get_normal_item().path),
+            );
+
+            self.dcx.span_err(attr.span, msg);
+            return;
+        }
+
+        if self.is_test_crate {
+            return;
+        }
+
+        if !self.is_proc_macro_crate {
+            let msg = format!(
+                "the `#[{}]` attribute is only usable with crates of the `proc-macro` crate type",
+                pprust::path_to_string(&attr.get_normal_item().path),
+            );
+
+            self.dcx.span_err(attr.span, msg);
+            return;
+        }
+
+        if attr.has_name(sym::proc_macro_derive) {
+            self.collect_custom_derive(item, attr);
+        } else if attr.has_name(sym::proc_macro_attribute) {
+            self.collect_attr_proc_macro(item);
+        } else if attr.has_name(sym::proc_macro) {
+            self.collect_bang_proc_macro(item);
+        };
+
+        let prev_in_root = mem::replace(&mut self.in_root, false);
+        visit::walk_item(self, item);
+        self.in_root = prev_in_root;
+    }
+}
+
+// Creates a new module which looks like:
+//
+//      const _: () = {
+//          extern crate proc_macro;
+//
+//          use proc_macro::bridge::client::ProcMacro;
+//
+//          #[rustc_proc_macro_decls]
+//          #[used]
+//          #[allow(deprecated)]
+//          static DECLS: &[ProcMacro] = &[
+//              ProcMacro::custom_derive($name_trait1, &[], ::$name1);
+//              ProcMacro::custom_derive($name_trait2, &["attribute_name"], ::$name2);
+//              // ...
+//          ];
+//      }
+fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
+    let expn_id = cx.resolver.expansion_for_ast_pass(
+        DUMMY_SP,
+        AstPass::ProcMacroHarness,
+        &[sym::rustc_attrs, sym::proc_macro_internals],
+        None,
+    );
+    let span = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id());
+
+    let proc_macro = Ident::new(sym::proc_macro, span);
+    let krate = cx.item(span, proc_macro, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None));
+
+    let bridge = Ident::new(sym::bridge, span);
+    let client = Ident::new(sym::client, span);
+    let proc_macro_ty = Ident::new(sym::ProcMacro, span);
+    let custom_derive = Ident::new(sym::custom_derive, span);
+    let attr = Ident::new(sym::attr, span);
+    let bang = Ident::new(sym::bang, span);
+
+    // We add NodeIds to 'resolver.proc_macros' in the order
+    // that we generate expressions. The position of each NodeId
+    // in the 'proc_macros' Vec corresponds to its position
+    // in the static array that will be generated
+    let decls = macros
+        .iter()
+        .map(|m| {
+            let harness_span = span;
+            let span = match m {
+                ProcMacro::Derive(m) => m.span,
+                ProcMacro::Attr(m) | ProcMacro::Bang(m) => m.span,
+            };
+            let local_path = |cx: &ExtCtxt<'_>, name| cx.expr_path(cx.path(span, vec![name]));
+            let proc_macro_ty_method_path = |cx: &ExtCtxt<'_>, method| {
+                cx.expr_path(cx.path(
+                    span.with_ctxt(harness_span.ctxt()),
+                    vec![proc_macro, bridge, client, proc_macro_ty, method],
+                ))
+            };
+            match m {
+                ProcMacro::Derive(cd) => {
+                    cx.resolver.declare_proc_macro(cd.id);
+                    cx.expr_call(
+                        span,
+                        proc_macro_ty_method_path(cx, custom_derive),
+                        thin_vec![
+                            cx.expr_str(span, cd.trait_name),
+                            cx.expr_array_ref(
+                                span,
+                                cd.attrs
+                                    .iter()
+                                    .map(|&s| cx.expr_str(span, s))
+                                    .collect::<ThinVec<_>>(),
+                            ),
+                            local_path(cx, cd.function_name),
+                        ],
+                    )
+                }
+                ProcMacro::Attr(ca) | ProcMacro::Bang(ca) => {
+                    cx.resolver.declare_proc_macro(ca.id);
+                    let ident = match m {
+                        ProcMacro::Attr(_) => attr,
+                        ProcMacro::Bang(_) => bang,
+                        ProcMacro::Derive(_) => unreachable!(),
+                    };
+
+                    cx.expr_call(
+                        span,
+                        proc_macro_ty_method_path(cx, ident),
+                        thin_vec![
+                            cx.expr_str(span, ca.function_name.name),
+                            local_path(cx, ca.function_name),
+                        ],
+                    )
+                }
+            }
+        })
+        .collect();
+
+    let decls_static = cx
+        .item_static(
+            span,
+            Ident::new(sym::_DECLS, span),
+            cx.ty_ref(
+                span,
+                cx.ty(
+                    span,
+                    ast::TyKind::Slice(
+                        cx.ty_path(cx.path(span, vec![proc_macro, bridge, client, proc_macro_ty])),
+                    ),
+                ),
+                None,
+                ast::Mutability::Not,
+            ),
+            ast::Mutability::Not,
+            cx.expr_array_ref(span, decls),
+        )
+        .map(|mut i| {
+            i.attrs.push(cx.attr_word(sym::rustc_proc_macro_decls, span));
+            i.attrs.push(cx.attr_word(sym::used, span));
+            i.attrs.push(cx.attr_nested_word(sym::allow, sym::deprecated, span));
+            i
+        });
+
+    let block = cx.expr_block(
+        cx.block(span, thin_vec![cx.stmt_item(span, krate), cx.stmt_item(span, decls_static)]),
+    );
+
+    let anon_constant = cx.item_const(
+        span,
+        Ident::new(kw::Underscore, span),
+        cx.ty(span, ast::TyKind::Tup(ThinVec::new())),
+        block,
+    );
+
+    // Integrate the new item into existing module structures.
+    let items = AstFragment::Items(smallvec![anon_constant]);
+    cx.monotonic_expander().fully_expand_fragment(items).make_items().pop().unwrap()
+}
diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs
new file mode 100644
index 00000000000..dc1d82df0c3
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/source_util.rs
@@ -0,0 +1,343 @@
+use crate::util::{
+    check_zero_tts, get_single_str_from_tts, get_single_str_spanned_from_tts, parse_expr,
+};
+use rustc_ast as ast;
+use rustc_ast::ptr::P;
+use rustc_ast::token;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast_pretty::pprust;
+use rustc_data_structures::sync::Lrc;
+use rustc_expand::base::{
+    resolve_path, DummyResult, ExpandResult, ExtCtxt, MacEager, MacResult, MacroExpanderResult,
+};
+use rustc_expand::module::DirOwnership;
+use rustc_lint_defs::BuiltinLintDiag;
+use rustc_parse::parser::{ForceCollect, Parser};
+use rustc_parse::{new_parser_from_file, unwrap_or_emit_fatal};
+use rustc_session::lint::builtin::INCOMPLETE_INCLUDE;
+use rustc_span::source_map::SourceMap;
+use rustc_span::symbol::Symbol;
+use rustc_span::{Pos, Span};
+use smallvec::SmallVec;
+use std::path::{Path, PathBuf};
+use std::rc::Rc;
+
+// These macros all relate to the file system; they either return
+// the column/row/filename of the expression, or they include
+// a given file into the current one.
+
+/// line!(): expands to the current line number
+pub(crate) fn expand_line(
+    cx: &mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'static> {
+    let sp = cx.with_def_site_ctxt(sp);
+    check_zero_tts(cx, sp, tts, "line!");
+
+    let topmost = cx.expansion_cause().unwrap_or(sp);
+    let loc = cx.source_map().lookup_char_pos(topmost.lo());
+
+    ExpandResult::Ready(MacEager::expr(cx.expr_u32(topmost, loc.line as u32)))
+}
+
+/* column!(): expands to the current column number */
+pub(crate) fn expand_column(
+    cx: &mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'static> {
+    let sp = cx.with_def_site_ctxt(sp);
+    check_zero_tts(cx, sp, tts, "column!");
+
+    let topmost = cx.expansion_cause().unwrap_or(sp);
+    let loc = cx.source_map().lookup_char_pos(topmost.lo());
+
+    ExpandResult::Ready(MacEager::expr(cx.expr_u32(topmost, loc.col.to_usize() as u32 + 1)))
+}
+
+/// file!(): expands to the current filename */
+/// The source_file (`loc.file`) contains a bunch more information we could spit
+/// out if we wanted.
+pub(crate) fn expand_file(
+    cx: &mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'static> {
+    let sp = cx.with_def_site_ctxt(sp);
+    check_zero_tts(cx, sp, tts, "file!");
+
+    let topmost = cx.expansion_cause().unwrap_or(sp);
+    let loc = cx.source_map().lookup_char_pos(topmost.lo());
+
+    use rustc_session::{config::RemapPathScopeComponents, RemapFileNameExt};
+    ExpandResult::Ready(MacEager::expr(cx.expr_str(
+        topmost,
+        Symbol::intern(
+            &loc.file.name.for_scope(cx.sess, RemapPathScopeComponents::MACRO).to_string_lossy(),
+        ),
+    )))
+}
+
+pub(crate) fn expand_stringify(
+    cx: &mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'static> {
+    let sp = cx.with_def_site_ctxt(sp);
+    let s = pprust::tts_to_string(&tts);
+    ExpandResult::Ready(MacEager::expr(cx.expr_str(sp, Symbol::intern(&s))))
+}
+
+pub(crate) fn expand_mod(
+    cx: &mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'static> {
+    let sp = cx.with_def_site_ctxt(sp);
+    check_zero_tts(cx, sp, tts, "module_path!");
+    let mod_path = &cx.current_expansion.module.mod_path;
+    let string = mod_path.iter().map(|x| x.to_string()).collect::<Vec<String>>().join("::");
+
+    ExpandResult::Ready(MacEager::expr(cx.expr_str(sp, Symbol::intern(&string))))
+}
+
+/// include! : parse the given file as an expr
+/// This is generally a bad idea because it's going to behave
+/// unhygienically.
+pub(crate) fn expand_include<'cx>(
+    cx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'cx> {
+    let sp = cx.with_def_site_ctxt(sp);
+    let ExpandResult::Ready(mac) = get_single_str_from_tts(cx, sp, tts, "include!") else {
+        return ExpandResult::Retry(());
+    };
+    let file = match mac {
+        Ok(file) => file,
+        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
+    };
+    // The file will be added to the code map by the parser
+    let file = match resolve_path(&cx.sess, file.as_str(), sp) {
+        Ok(f) => f,
+        Err(err) => {
+            let guar = err.emit();
+            return ExpandResult::Ready(DummyResult::any(sp, guar));
+        }
+    };
+    let p = unwrap_or_emit_fatal(new_parser_from_file(cx.psess(), &file, Some(sp)));
+
+    // If in the included file we have e.g., `mod bar;`,
+    // then the path of `bar.rs` should be relative to the directory of `file`.
+    // See https://github.com/rust-lang/rust/pull/69838/files#r395217057 for a discussion.
+    // `MacroExpander::fully_expand_fragment` later restores, so "stack discipline" is maintained.
+    let dir_path = file.parent().unwrap_or(&file).to_owned();
+    cx.current_expansion.module = Rc::new(cx.current_expansion.module.with_dir_path(dir_path));
+    cx.current_expansion.dir_ownership = DirOwnership::Owned { relative: None };
+
+    struct ExpandInclude<'a> {
+        p: Parser<'a>,
+        node_id: ast::NodeId,
+    }
+    impl<'a> MacResult for ExpandInclude<'a> {
+        fn make_expr(mut self: Box<ExpandInclude<'a>>) -> Option<P<ast::Expr>> {
+            let expr = parse_expr(&mut self.p).ok()?;
+            if self.p.token != token::Eof {
+                self.p.psess.buffer_lint(
+                    INCOMPLETE_INCLUDE,
+                    self.p.token.span,
+                    self.node_id,
+                    BuiltinLintDiag::IncompleteInclude,
+                );
+            }
+            Some(expr)
+        }
+
+        fn make_items(mut self: Box<ExpandInclude<'a>>) -> Option<SmallVec<[P<ast::Item>; 1]>> {
+            let mut ret = SmallVec::new();
+            loop {
+                match self.p.parse_item(ForceCollect::No) {
+                    Err(err) => {
+                        err.emit();
+                        break;
+                    }
+                    Ok(Some(item)) => ret.push(item),
+                    Ok(None) => {
+                        if self.p.token != token::Eof {
+                            let token = pprust::token_to_string(&self.p.token);
+                            let msg = format!("expected item, found `{token}`");
+                            self.p.dcx().span_err(self.p.token.span, msg);
+                        }
+
+                        break;
+                    }
+                }
+            }
+            Some(ret)
+        }
+    }
+
+    ExpandResult::Ready(Box::new(ExpandInclude { p, node_id: cx.current_expansion.lint_node_id }))
+}
+
+/// `include_str!`: read the given file, insert it as a literal string expr
+pub(crate) fn expand_include_str(
+    cx: &mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'static> {
+    let sp = cx.with_def_site_ctxt(sp);
+    let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_str!")
+    else {
+        return ExpandResult::Retry(());
+    };
+    let (path, path_span) = match mac {
+        Ok(res) => res,
+        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
+    };
+    ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
+        Ok((bytes, bsp)) => match std::str::from_utf8(&bytes) {
+            Ok(src) => {
+                let interned_src = Symbol::intern(src);
+                MacEager::expr(cx.expr_str(cx.with_def_site_ctxt(bsp), interned_src))
+            }
+            Err(_) => {
+                let guar = cx.dcx().span_err(sp, format!("`{path}` wasn't a utf-8 file"));
+                DummyResult::any(sp, guar)
+            }
+        },
+        Err(dummy) => dummy,
+    })
+}
+
+pub(crate) fn expand_include_bytes(
+    cx: &mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> MacroExpanderResult<'static> {
+    let sp = cx.with_def_site_ctxt(sp);
+    let ExpandResult::Ready(mac) = get_single_str_spanned_from_tts(cx, sp, tts, "include_bytes!")
+    else {
+        return ExpandResult::Retry(());
+    };
+    let (path, path_span) = match mac {
+        Ok(res) => res,
+        Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)),
+    };
+    ExpandResult::Ready(match load_binary_file(cx, path.as_str().as_ref(), sp, path_span) {
+        Ok((bytes, _bsp)) => {
+            // Don't care about getting the span for the raw bytes,
+            // because the console can't really show them anyway.
+            let expr = cx.expr(sp, ast::ExprKind::IncludedBytes(bytes));
+            MacEager::expr(expr)
+        }
+        Err(dummy) => dummy,
+    })
+}
+
+fn load_binary_file(
+    cx: &ExtCtxt<'_>,
+    original_path: &Path,
+    macro_span: Span,
+    path_span: Span,
+) -> Result<(Lrc<[u8]>, Span), Box<dyn MacResult>> {
+    let resolved_path = match resolve_path(&cx.sess, original_path, macro_span) {
+        Ok(path) => path,
+        Err(err) => {
+            let guar = err.emit();
+            return Err(DummyResult::any(macro_span, guar));
+        }
+    };
+    match cx.source_map().load_binary_file(&resolved_path) {
+        Ok(data) => Ok(data),
+        Err(io_err) => {
+            let mut err = cx.dcx().struct_span_err(
+                macro_span,
+                format!("couldn't read `{}`: {io_err}", resolved_path.display()),
+            );
+
+            if original_path.is_relative() {
+                let source_map = cx.sess.source_map();
+                let new_path = source_map
+                    .span_to_filename(macro_span.source_callsite())
+                    .into_local_path()
+                    .and_then(|src| find_path_suggestion(source_map, src.parent()?, original_path))
+                    .and_then(|path| path.into_os_string().into_string().ok());
+
+                if let Some(new_path) = new_path {
+                    err.span_suggestion(
+                        path_span,
+                        "there is a file with the same name in a different directory",
+                        format!("\"{}\"", new_path.replace('\\', "/").escape_debug()),
+                        rustc_lint_defs::Applicability::MachineApplicable,
+                    );
+                }
+            }
+            let guar = err.emit();
+            Err(DummyResult::any(macro_span, guar))
+        }
+    }
+}
+
+fn find_path_suggestion(
+    source_map: &SourceMap,
+    base_dir: &Path,
+    wanted_path: &Path,
+) -> Option<PathBuf> {
+    // Fix paths that assume they're relative to cargo manifest dir
+    let mut base_c = base_dir.components();
+    let mut wanted_c = wanted_path.components();
+    let mut without_base = None;
+    while let Some(wanted_next) = wanted_c.next() {
+        if wanted_c.as_path().file_name().is_none() {
+            break;
+        }
+        // base_dir may be absolute
+        while let Some(base_next) = base_c.next() {
+            if base_next == wanted_next {
+                without_base = Some(wanted_c.as_path());
+                break;
+            }
+        }
+    }
+    let root_absolute = without_base.into_iter().map(PathBuf::from);
+
+    let base_dir_components = base_dir.components().count();
+    // Avoid going all the way to the root dir
+    let max_parent_components = if base_dir.is_relative() {
+        base_dir_components + 1
+    } else {
+        base_dir_components.saturating_sub(1)
+    };
+
+    // Try with additional leading ../
+    let mut prefix = PathBuf::new();
+    let add = std::iter::from_fn(|| {
+        prefix.push("..");
+        Some(prefix.join(wanted_path))
+    })
+    .take(max_parent_components.min(3));
+
+    // Try without leading directories
+    let mut trimmed_path = wanted_path;
+    let remove = std::iter::from_fn(|| {
+        let mut components = trimmed_path.components();
+        let removed = components.next()?;
+        trimmed_path = components.as_path();
+        let _ = trimmed_path.file_name()?; // ensure there is a file name left
+        Some([
+            Some(trimmed_path.to_path_buf()),
+            (removed != std::path::Component::ParentDir)
+                .then(|| Path::new("..").join(trimmed_path)),
+        ])
+    })
+    .flatten()
+    .flatten()
+    .take(4);
+
+    root_absolute
+        .chain(add)
+        .chain(remove)
+        .find(|new_path| source_map.file_exists(&base_dir.join(&new_path)))
+}
diff --git a/compiler/rustc_builtin_macros/src/standard_library_imports.rs b/compiler/rustc_builtin_macros/src/standard_library_imports.rs
new file mode 100644
index 00000000000..9bcd793c450
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/standard_library_imports.rs
@@ -0,0 +1,105 @@
+use rustc_ast::{self as ast, attr};
+use rustc_expand::base::{ExtCtxt, ResolverExpand};
+use rustc_expand::expand::ExpansionConfig;
+use rustc_feature::Features;
+use rustc_session::Session;
+use rustc_span::edition::Edition::*;
+use rustc_span::hygiene::AstPass;
+use rustc_span::symbol::{kw, sym, Ident, Symbol};
+use rustc_span::DUMMY_SP;
+use thin_vec::thin_vec;
+
+pub fn inject(
+    krate: &mut ast::Crate,
+    pre_configured_attrs: &[ast::Attribute],
+    resolver: &mut dyn ResolverExpand,
+    sess: &Session,
+    features: &Features,
+) -> usize {
+    let orig_num_items = krate.items.len();
+    let edition = sess.psess.edition;
+
+    // the first name in this list is the crate name of the crate with the prelude
+    let names: &[Symbol] = if attr::contains_name(pre_configured_attrs, sym::no_core) {
+        return 0;
+    } else if attr::contains_name(pre_configured_attrs, sym::no_std) {
+        if attr::contains_name(pre_configured_attrs, sym::compiler_builtins) {
+            &[sym::core]
+        } else {
+            &[sym::core, sym::compiler_builtins]
+        }
+    } else {
+        &[sym::std]
+    };
+
+    let expn_id = resolver.expansion_for_ast_pass(
+        DUMMY_SP,
+        AstPass::StdImports,
+        &[sym::prelude_import],
+        None,
+    );
+    let span = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id());
+    let call_site = DUMMY_SP.with_call_site_ctxt(expn_id.to_expn_id());
+
+    let ecfg = ExpansionConfig::default("std_lib_injection".to_string(), features);
+    let cx = ExtCtxt::new(sess, ecfg, resolver, None);
+
+    // .rev() to preserve ordering above in combination with insert(0, ...)
+    for &name in names.iter().rev() {
+        let ident_span = if edition >= Edition2018 { span } else { call_site };
+        let item = if name == sym::compiler_builtins {
+            // compiler_builtins is a private implementation detail. We only
+            // need to insert it into the crate graph for linking and should not
+            // expose any of its public API.
+            //
+            // FIXME(#113634) We should inject this during post-processing like
+            // we do for the panic runtime, profiler runtime, etc.
+            cx.item(
+                span,
+                Ident::new(kw::Underscore, ident_span),
+                thin_vec![],
+                ast::ItemKind::ExternCrate(Some(name)),
+            )
+        } else {
+            cx.item(
+                span,
+                Ident::new(name, ident_span),
+                thin_vec![cx.attr_word(sym::macro_use, span)],
+                ast::ItemKind::ExternCrate(None),
+            )
+        };
+        krate.items.insert(0, item);
+    }
+
+    // The crates have been injected, the assumption is that the first one is
+    // the one with the prelude.
+    let name = names[0];
+
+    let root = (edition == Edition2015).then_some(kw::PathRoot);
+
+    let import_path = root
+        .iter()
+        .chain(&[name, sym::prelude])
+        .chain(&[match edition {
+            Edition2015 => sym::rust_2015,
+            Edition2018 => sym::rust_2018,
+            Edition2021 => sym::rust_2021,
+            Edition2024 => sym::rust_2024,
+        }])
+        .map(|&symbol| Ident::new(symbol, span))
+        .collect();
+
+    let use_item = cx.item(
+        span,
+        Ident::empty(),
+        thin_vec![cx.attr_word(sym::prelude_import, span)],
+        ast::ItemKind::Use(ast::UseTree {
+            prefix: cx.path(span, import_path),
+            kind: ast::UseTreeKind::Glob,
+            span,
+        }),
+    );
+
+    krate.items.insert(0, use_item);
+    krate.items.len() - orig_num_items
+}
diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs
new file mode 100644
index 00000000000..c0310a2f4b0
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/test.rs
@@ -0,0 +1,620 @@
+//! The expansion from a test function to the appropriate test struct for libtest
+//! Ideally, this code would be in libtest but for efficiency and error messages it lives here.
+
+use crate::errors;
+use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute};
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, attr, GenericParamKind};
+use rustc_ast_pretty::pprust;
+use rustc_errors::{Applicability, Diag, Level};
+use rustc_expand::base::*;
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::{ErrorGuaranteed, FileNameDisplayPreference, Span};
+use std::assert_matches::assert_matches;
+use std::iter;
+use thin_vec::{thin_vec, ThinVec};
+use tracing::debug;
+
+/// #[test_case] is used by custom test authors to mark tests
+/// When building for test, it needs to make the item public and gensym the name
+/// Otherwise, we'll omit the item. This behavior means that any item annotated
+/// with #[test_case] is never addressable.
+///
+/// We mark item with an inert attribute "rustc_test_marker" which the test generation
+/// logic will pick up on.
+pub(crate) fn expand_test_case(
+    ecx: &mut ExtCtxt<'_>,
+    attr_sp: Span,
+    meta_item: &ast::MetaItem,
+    anno_item: Annotatable,
+) -> Vec<Annotatable> {
+    check_builtin_macro_attribute(ecx, meta_item, sym::test_case);
+    warn_on_duplicate_attribute(ecx, &anno_item, sym::test_case);
+
+    if !ecx.ecfg.should_test {
+        return vec![];
+    }
+
+    let sp = ecx.with_def_site_ctxt(attr_sp);
+    let (mut item, is_stmt) = match anno_item {
+        Annotatable::Item(item) => (item, false),
+        Annotatable::Stmt(stmt) if let ast::StmtKind::Item(_) = stmt.kind => {
+            if let ast::StmtKind::Item(i) = stmt.into_inner().kind {
+                (i, true)
+            } else {
+                unreachable!()
+            }
+        }
+        _ => {
+            ecx.dcx().emit_err(errors::TestCaseNonItem { span: anno_item.span() });
+            return vec![];
+        }
+    };
+    item = item.map(|mut item| {
+        let test_path_symbol = Symbol::intern(&item_path(
+            // skip the name of the root module
+            &ecx.current_expansion.module.mod_path[1..],
+            &item.ident,
+        ));
+        item.vis = ast::Visibility {
+            span: item.vis.span,
+            kind: ast::VisibilityKind::Public,
+            tokens: None,
+        };
+        item.ident.span = item.ident.span.with_ctxt(sp.ctxt());
+        item.attrs.push(ecx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, sp));
+        item
+    });
+
+    let ret = if is_stmt {
+        Annotatable::Stmt(P(ecx.stmt_item(item.span, item)))
+    } else {
+        Annotatable::Item(item)
+    };
+
+    vec![ret]
+}
+
+pub(crate) fn expand_test(
+    cx: &mut ExtCtxt<'_>,
+    attr_sp: Span,
+    meta_item: &ast::MetaItem,
+    item: Annotatable,
+) -> Vec<Annotatable> {
+    check_builtin_macro_attribute(cx, meta_item, sym::test);
+    warn_on_duplicate_attribute(cx, &item, sym::test);
+    expand_test_or_bench(cx, attr_sp, item, false)
+}
+
+pub(crate) fn expand_bench(
+    cx: &mut ExtCtxt<'_>,
+    attr_sp: Span,
+    meta_item: &ast::MetaItem,
+    item: Annotatable,
+) -> Vec<Annotatable> {
+    check_builtin_macro_attribute(cx, meta_item, sym::bench);
+    warn_on_duplicate_attribute(cx, &item, sym::bench);
+    expand_test_or_bench(cx, attr_sp, item, true)
+}
+
+pub(crate) fn expand_test_or_bench(
+    cx: &ExtCtxt<'_>,
+    attr_sp: Span,
+    item: Annotatable,
+    is_bench: bool,
+) -> Vec<Annotatable> {
+    // If we're not in test configuration, remove the annotated item
+    if !cx.ecfg.should_test {
+        return vec![];
+    }
+
+    let (item, is_stmt) = match item {
+        Annotatable::Item(i) => (i, false),
+        Annotatable::Stmt(stmt) if matches!(stmt.kind, ast::StmtKind::Item(_)) => {
+            // FIXME: Use an 'if let' guard once they are implemented
+            if let ast::StmtKind::Item(i) = stmt.into_inner().kind {
+                (i, true)
+            } else {
+                unreachable!()
+            }
+        }
+        other => {
+            not_testable_error(cx, attr_sp, None);
+            return vec![other];
+        }
+    };
+
+    let ast::ItemKind::Fn(fn_) = &item.kind else {
+        not_testable_error(cx, attr_sp, Some(&item));
+        return if is_stmt {
+            vec![Annotatable::Stmt(P(cx.stmt_item(item.span, item)))]
+        } else {
+            vec![Annotatable::Item(item)]
+        };
+    };
+
+    // check_*_signature will report any errors in the type so compilation
+    // will fail. We shouldn't try to expand in this case because the errors
+    // would be spurious.
+    let check_result = if is_bench {
+        check_bench_signature(cx, &item, fn_)
+    } else {
+        check_test_signature(cx, &item, fn_)
+    };
+    if check_result.is_err() {
+        return if is_stmt {
+            vec![Annotatable::Stmt(P(cx.stmt_item(item.span, item)))]
+        } else {
+            vec![Annotatable::Item(item)]
+        };
+    }
+
+    let sp = cx.with_def_site_ctxt(item.span);
+    let ret_ty_sp = cx.with_def_site_ctxt(fn_.sig.decl.output.span());
+    let attr_sp = cx.with_def_site_ctxt(attr_sp);
+
+    let test_id = Ident::new(sym::test, attr_sp);
+
+    // creates test::$name
+    let test_path = |name| cx.path(ret_ty_sp, vec![test_id, Ident::from_str_and_span(name, sp)]);
+
+    // creates test::ShouldPanic::$name
+    let should_panic_path = |name| {
+        cx.path(
+            sp,
+            vec![
+                test_id,
+                Ident::from_str_and_span("ShouldPanic", sp),
+                Ident::from_str_and_span(name, sp),
+            ],
+        )
+    };
+
+    // creates test::TestType::$name
+    let test_type_path = |name| {
+        cx.path(
+            sp,
+            vec![
+                test_id,
+                Ident::from_str_and_span("TestType", sp),
+                Ident::from_str_and_span(name, sp),
+            ],
+        )
+    };
+
+    // creates $name: $expr
+    let field = |name, expr| cx.field_imm(sp, Ident::from_str_and_span(name, sp), expr);
+
+    // Adds `#[coverage(off)]` to a closure, so it won't be instrumented in
+    // `-Cinstrument-coverage` builds.
+    // This requires `#[allow_internal_unstable(coverage_attribute)]` on the
+    // corresponding macro declaration in `core::macros`.
+    let coverage_off = |mut expr: P<ast::Expr>| {
+        assert_matches!(expr.kind, ast::ExprKind::Closure(_));
+        expr.attrs.push(cx.attr_nested_word(sym::coverage, sym::off, sp));
+        expr
+    };
+
+    let test_fn = if is_bench {
+        // A simple ident for a lambda
+        let b = Ident::from_str_and_span("b", attr_sp);
+
+        cx.expr_call(
+            sp,
+            cx.expr_path(test_path("StaticBenchFn")),
+            thin_vec![
+                // #[coverage(off)]
+                // |b| self::test::assert_test_result(
+                coverage_off(cx.lambda1(
+                    sp,
+                    cx.expr_call(
+                        sp,
+                        cx.expr_path(test_path("assert_test_result")),
+                        thin_vec![
+                            // super::$test_fn(b)
+                            cx.expr_call(
+                                ret_ty_sp,
+                                cx.expr_path(cx.path(sp, vec![item.ident])),
+                                thin_vec![cx.expr_ident(sp, b)],
+                            ),
+                        ],
+                    ),
+                    b,
+                )), // )
+            ],
+        )
+    } else {
+        cx.expr_call(
+            sp,
+            cx.expr_path(test_path("StaticTestFn")),
+            thin_vec![
+                // #[coverage(off)]
+                // || {
+                coverage_off(cx.lambda0(
+                    sp,
+                    // test::assert_test_result(
+                    cx.expr_call(
+                        sp,
+                        cx.expr_path(test_path("assert_test_result")),
+                        thin_vec![
+                            // $test_fn()
+                            cx.expr_call(
+                                ret_ty_sp,
+                                cx.expr_path(cx.path(sp, vec![item.ident])),
+                                ThinVec::new(),
+                            ), // )
+                        ],
+                    ), // }
+                )), // )
+            ],
+        )
+    };
+
+    let test_path_symbol = Symbol::intern(&item_path(
+        // skip the name of the root module
+        &cx.current_expansion.module.mod_path[1..],
+        &item.ident,
+    ));
+
+    let location_info = get_location_info(cx, &item);
+
+    let mut test_const =
+        cx.item(
+            sp,
+            Ident::new(item.ident.name, sp),
+            thin_vec![
+                // #[cfg(test)]
+                cx.attr_nested_word(sym::cfg, sym::test, attr_sp),
+                // #[rustc_test_marker = "test_case_sort_key"]
+                cx.attr_name_value_str(sym::rustc_test_marker, test_path_symbol, attr_sp),
+            ],
+            // const $ident: test::TestDescAndFn =
+            ast::ItemKind::Const(
+                ast::ConstItem {
+                    defaultness: ast::Defaultness::Final,
+                    generics: ast::Generics::default(),
+                    ty: cx.ty(sp, ast::TyKind::Path(None, test_path("TestDescAndFn"))),
+                    // test::TestDescAndFn {
+                    expr: Some(
+                        cx.expr_struct(
+                            sp,
+                            test_path("TestDescAndFn"),
+                            thin_vec![
+                        // desc: test::TestDesc {
+                        field(
+                            "desc",
+                            cx.expr_struct(
+                                sp,
+                                test_path("TestDesc"),
+                                thin_vec![
+                                    // name: "path::to::test"
+                                    field(
+                                        "name",
+                                        cx.expr_call(
+                                            sp,
+                                            cx.expr_path(test_path("StaticTestName")),
+                                            thin_vec![cx.expr_str(sp, test_path_symbol)],
+                                        ),
+                                    ),
+                                    // ignore: true | false
+                                    field("ignore", cx.expr_bool(sp, should_ignore(&item)),),
+                                    // ignore_message: Some("...") | None
+                                    field(
+                                        "ignore_message",
+                                        if let Some(msg) = should_ignore_message(&item) {
+                                            cx.expr_some(sp, cx.expr_str(sp, msg))
+                                        } else {
+                                            cx.expr_none(sp)
+                                        },
+                                    ),
+                                    // source_file: <relative_path_of_source_file>
+                                    field("source_file", cx.expr_str(sp, location_info.0)),
+                                    // start_line: start line of the test fn identifier.
+                                    field("start_line", cx.expr_usize(sp, location_info.1)),
+                                    // start_col: start column of the test fn identifier.
+                                    field("start_col", cx.expr_usize(sp, location_info.2)),
+                                    // end_line: end line of the test fn identifier.
+                                    field("end_line", cx.expr_usize(sp, location_info.3)),
+                                    // end_col: end column of the test fn identifier.
+                                    field("end_col", cx.expr_usize(sp, location_info.4)),
+                                    // compile_fail: true | false
+                                    field("compile_fail", cx.expr_bool(sp, false)),
+                                    // no_run: true | false
+                                    field("no_run", cx.expr_bool(sp, false)),
+                                    // should_panic: ...
+                                    field(
+                                        "should_panic",
+                                        match should_panic(cx, &item) {
+                                            // test::ShouldPanic::No
+                                            ShouldPanic::No => {
+                                                cx.expr_path(should_panic_path("No"))
+                                            }
+                                            // test::ShouldPanic::Yes
+                                            ShouldPanic::Yes(None) => {
+                                                cx.expr_path(should_panic_path("Yes"))
+                                            }
+                                            // test::ShouldPanic::YesWithMessage("...")
+                                            ShouldPanic::Yes(Some(sym)) => cx.expr_call(
+                                                sp,
+                                                cx.expr_path(should_panic_path("YesWithMessage")),
+                                                thin_vec![cx.expr_str(sp, sym)],
+                                            ),
+                                        },
+                                    ),
+                                    // test_type: ...
+                                    field(
+                                        "test_type",
+                                        match test_type(cx) {
+                                            // test::TestType::UnitTest
+                                            TestType::UnitTest => {
+                                                cx.expr_path(test_type_path("UnitTest"))
+                                            }
+                                            // test::TestType::IntegrationTest
+                                            TestType::IntegrationTest => {
+                                                cx.expr_path(test_type_path("IntegrationTest"))
+                                            }
+                                            // test::TestPath::Unknown
+                                            TestType::Unknown => {
+                                                cx.expr_path(test_type_path("Unknown"))
+                                            }
+                                        },
+                                    ),
+                                    // },
+                                ],
+                            ),
+                        ),
+                        // testfn: test::StaticTestFn(...) | test::StaticBenchFn(...)
+                        field("testfn", test_fn), // }
+                    ],
+                        ), // }
+                    ),
+                }
+                .into(),
+            ),
+        );
+    test_const = test_const.map(|mut tc| {
+        tc.vis.kind = ast::VisibilityKind::Public;
+        tc
+    });
+
+    // extern crate test
+    let test_extern = cx.item(sp, test_id, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None));
+
+    debug!("synthetic test item:\n{}\n", pprust::item_to_string(&test_const));
+
+    if is_stmt {
+        vec![
+            // Access to libtest under a hygienic name
+            Annotatable::Stmt(P(cx.stmt_item(sp, test_extern))),
+            // The generated test case
+            Annotatable::Stmt(P(cx.stmt_item(sp, test_const))),
+            // The original item
+            Annotatable::Stmt(P(cx.stmt_item(sp, item))),
+        ]
+    } else {
+        vec![
+            // Access to libtest under a hygienic name
+            Annotatable::Item(test_extern),
+            // The generated test case
+            Annotatable::Item(test_const),
+            // The original item
+            Annotatable::Item(item),
+        ]
+    }
+}
+
+fn not_testable_error(cx: &ExtCtxt<'_>, attr_sp: Span, item: Option<&ast::Item>) {
+    let dcx = cx.dcx();
+    let msg = "the `#[test]` attribute may only be used on a non-associated function";
+    let level = match item.map(|i| &i.kind) {
+        // These were a warning before #92959 and need to continue being that to avoid breaking
+        // stable user code (#94508).
+        Some(ast::ItemKind::MacCall(_)) => Level::Warning,
+        _ => Level::Error,
+    };
+    let mut err = Diag::<()>::new(dcx, level, msg);
+    err.span(attr_sp);
+    if let Some(item) = item {
+        err.span_label(
+            item.span,
+            format!(
+                "expected a non-associated function, found {} {}",
+                item.kind.article(),
+                item.kind.descr()
+            ),
+        );
+    }
+    err.with_span_label(attr_sp, "the `#[test]` macro causes a function to be run as a test and has no effect on non-functions")
+        .with_span_suggestion(attr_sp,
+            "replace with conditional compilation to make the item only exist when tests are being run",
+            "#[cfg(test)]",
+            Applicability::MaybeIncorrect)
+        .emit();
+}
+
+fn get_location_info(cx: &ExtCtxt<'_>, item: &ast::Item) -> (Symbol, usize, usize, usize, usize) {
+    let span = item.ident.span;
+    let (source_file, lo_line, lo_col, hi_line, hi_col) =
+        cx.sess.source_map().span_to_location_info(span);
+
+    let file_name = match source_file {
+        Some(sf) => sf.name.display(FileNameDisplayPreference::Remapped).to_string(),
+        None => "no-location".to_string(),
+    };
+
+    (Symbol::intern(&file_name), lo_line, lo_col, hi_line, hi_col)
+}
+
+fn item_path(mod_path: &[Ident], item_ident: &Ident) -> String {
+    mod_path
+        .iter()
+        .chain(iter::once(item_ident))
+        .map(|x| x.to_string())
+        .collect::<Vec<String>>()
+        .join("::")
+}
+
+enum ShouldPanic {
+    No,
+    Yes(Option<Symbol>),
+}
+
+fn should_ignore(i: &ast::Item) -> bool {
+    attr::contains_name(&i.attrs, sym::ignore)
+}
+
+fn should_ignore_message(i: &ast::Item) -> Option<Symbol> {
+    match attr::find_by_name(&i.attrs, sym::ignore) {
+        Some(attr) => {
+            match attr.meta_item_list() {
+                // Handle #[ignore(bar = "foo")]
+                Some(_) => None,
+                // Handle #[ignore] and #[ignore = "message"]
+                None => attr.value_str(),
+            }
+        }
+        None => None,
+    }
+}
+
+fn should_panic(cx: &ExtCtxt<'_>, i: &ast::Item) -> ShouldPanic {
+    match attr::find_by_name(&i.attrs, sym::should_panic) {
+        Some(attr) => {
+            match attr.meta_item_list() {
+                // Handle #[should_panic(expected = "foo")]
+                Some(list) => {
+                    let msg = list
+                        .iter()
+                        .find(|mi| mi.has_name(sym::expected))
+                        .and_then(|mi| mi.meta_item())
+                        .and_then(|mi| mi.value_str());
+                    if list.len() != 1 || msg.is_none() {
+                        cx.dcx()
+                            .struct_span_warn(
+                                attr.span,
+                                "argument must be of the form: \
+                             `expected = \"error message\"`",
+                            )
+                            .with_note(
+                                "errors in this attribute were erroneously \
+                                allowed and will become a hard error in a \
+                                future release",
+                            )
+                            .emit();
+                        ShouldPanic::Yes(None)
+                    } else {
+                        ShouldPanic::Yes(msg)
+                    }
+                }
+                // Handle #[should_panic] and #[should_panic = "expected"]
+                None => ShouldPanic::Yes(attr.value_str()),
+            }
+        }
+        None => ShouldPanic::No,
+    }
+}
+
+enum TestType {
+    UnitTest,
+    IntegrationTest,
+    Unknown,
+}
+
+/// Attempts to determine the type of test.
+/// Since doctests are created without macro expanding, only possible variants here
+/// are `UnitTest`, `IntegrationTest` or `Unknown`.
+fn test_type(cx: &ExtCtxt<'_>) -> TestType {
+    // Root path from context contains the topmost sources directory of the crate.
+    // I.e., for `project` with sources in `src` and tests in `tests` folders
+    // (no matter how many nested folders lie inside),
+    // there will be two different root paths: `/project/src` and `/project/tests`.
+    let crate_path = cx.root_path.as_path();
+
+    if crate_path.ends_with("src") {
+        // `/src` folder contains unit-tests.
+        TestType::UnitTest
+    } else if crate_path.ends_with("tests") {
+        // `/tests` folder contains integration tests.
+        TestType::IntegrationTest
+    } else {
+        // Crate layout doesn't match expected one, test type is unknown.
+        TestType::Unknown
+    }
+}
+
+fn check_test_signature(
+    cx: &ExtCtxt<'_>,
+    i: &ast::Item,
+    f: &ast::Fn,
+) -> Result<(), ErrorGuaranteed> {
+    let has_should_panic_attr = attr::contains_name(&i.attrs, sym::should_panic);
+    let dcx = cx.dcx();
+
+    if let ast::Safety::Unsafe(span) = f.sig.header.safety {
+        return Err(dcx.emit_err(errors::TestBadFn { span: i.span, cause: span, kind: "unsafe" }));
+    }
+
+    if let Some(coroutine_kind) = f.sig.header.coroutine_kind {
+        match coroutine_kind {
+            ast::CoroutineKind::Async { span, .. } => {
+                return Err(dcx.emit_err(errors::TestBadFn {
+                    span: i.span,
+                    cause: span,
+                    kind: "async",
+                }));
+            }
+            ast::CoroutineKind::Gen { span, .. } => {
+                return Err(dcx.emit_err(errors::TestBadFn {
+                    span: i.span,
+                    cause: span,
+                    kind: "gen",
+                }));
+            }
+            ast::CoroutineKind::AsyncGen { span, .. } => {
+                return Err(dcx.emit_err(errors::TestBadFn {
+                    span: i.span,
+                    cause: span,
+                    kind: "async gen",
+                }));
+            }
+        }
+    }
+
+    // If the termination trait is active, the compiler will check that the output
+    // type implements the `Termination` trait as `libtest` enforces that.
+    let has_output = match &f.sig.decl.output {
+        ast::FnRetTy::Default(..) => false,
+        ast::FnRetTy::Ty(t) if t.kind.is_unit() => false,
+        _ => true,
+    };
+
+    if !f.sig.decl.inputs.is_empty() {
+        return Err(dcx.span_err(i.span, "functions used as tests can not have any arguments"));
+    }
+
+    if has_should_panic_attr && has_output {
+        return Err(dcx.span_err(i.span, "functions using `#[should_panic]` must return `()`"));
+    }
+
+    if f.generics.params.iter().any(|param| !matches!(param.kind, GenericParamKind::Lifetime)) {
+        return Err(dcx.span_err(
+            i.span,
+            "functions used as tests can not have any non-lifetime generic parameters",
+        ));
+    }
+
+    Ok(())
+}
+
+fn check_bench_signature(
+    cx: &ExtCtxt<'_>,
+    i: &ast::Item,
+    f: &ast::Fn,
+) -> Result<(), ErrorGuaranteed> {
+    // N.B., inadequate check, but we're running
+    // well before resolve, can't get too deep.
+    if f.sig.decl.inputs.len() != 1 {
+        return Err(cx.dcx().emit_err(errors::BenchSig { span: i.span }));
+    }
+    Ok(())
+}
diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs
new file mode 100644
index 00000000000..9d032eb190a
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/test_harness.rs
@@ -0,0 +1,411 @@
+// Code that generates a test runner to run all the tests in a crate
+
+use rustc_ast as ast;
+use rustc_ast::entry::EntryPointType;
+use rustc_ast::mut_visit::*;
+use rustc_ast::ptr::P;
+use rustc_ast::visit::{walk_item, Visitor};
+use rustc_ast::{attr, ModKind};
+use rustc_errors::DiagCtxtHandle;
+use rustc_expand::base::{ExtCtxt, ResolverExpand};
+use rustc_expand::expand::{AstFragment, ExpansionConfig};
+use rustc_feature::Features;
+use rustc_lint_defs::BuiltinLintDiag;
+use rustc_session::lint::builtin::UNNAMEABLE_TEST_ITEMS;
+use rustc_session::Session;
+use rustc_span::hygiene::{AstPass, SyntaxContext, Transparency};
+use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::{Span, DUMMY_SP};
+use rustc_target::spec::PanicStrategy;
+use smallvec::{smallvec, SmallVec};
+use thin_vec::{thin_vec, ThinVec};
+use tracing::debug;
+
+use std::{iter, mem};
+
+use crate::errors;
+
+#[derive(Clone)]
+struct Test {
+    span: Span,
+    ident: Ident,
+    name: Symbol,
+}
+
+struct TestCtxt<'a> {
+    ext_cx: ExtCtxt<'a>,
+    panic_strategy: PanicStrategy,
+    def_site: Span,
+    test_cases: Vec<Test>,
+    reexport_test_harness_main: Option<Symbol>,
+    test_runner: Option<ast::Path>,
+}
+
+/// Traverse the crate, collecting all the test functions, eliding any
+/// existing main functions, and synthesizing a main test harness
+pub fn inject(
+    krate: &mut ast::Crate,
+    sess: &Session,
+    features: &Features,
+    resolver: &mut dyn ResolverExpand,
+) {
+    let dcx = sess.dcx();
+    let panic_strategy = sess.panic_strategy();
+    let platform_panic_strategy = sess.target.panic_strategy;
+
+    // Check for #![reexport_test_harness_main = "some_name"] which gives the
+    // main test function the name `some_name` without hygiene. This needs to be
+    // unconditional, so that the attribute is still marked as used in
+    // non-test builds.
+    let reexport_test_harness_main =
+        attr::first_attr_value_str_by_name(&krate.attrs, sym::reexport_test_harness_main);
+
+    // Do this here so that the test_runner crate attribute gets marked as used
+    // even in non-test builds
+    let test_runner = get_test_runner(dcx, krate);
+
+    if sess.is_test_crate() {
+        let panic_strategy = match (panic_strategy, sess.opts.unstable_opts.panic_abort_tests) {
+            (PanicStrategy::Abort, true) => PanicStrategy::Abort,
+            (PanicStrategy::Abort, false) => {
+                if panic_strategy == platform_panic_strategy {
+                    // Silently allow compiling with panic=abort on these platforms,
+                    // but with old behavior (abort if a test fails).
+                } else {
+                    dcx.emit_err(errors::TestsNotSupport {});
+                }
+                PanicStrategy::Unwind
+            }
+            (PanicStrategy::Unwind, _) => PanicStrategy::Unwind,
+        };
+        generate_test_harness(
+            sess,
+            resolver,
+            reexport_test_harness_main,
+            krate,
+            features,
+            panic_strategy,
+            test_runner,
+        )
+    }
+}
+
+struct TestHarnessGenerator<'a> {
+    cx: TestCtxt<'a>,
+    tests: Vec<Test>,
+}
+
+impl TestHarnessGenerator<'_> {
+    fn add_test_cases(&mut self, node_id: ast::NodeId, span: Span, prev_tests: Vec<Test>) {
+        let mut tests = mem::replace(&mut self.tests, prev_tests);
+
+        if !tests.is_empty() {
+            // Create an identifier that will hygienically resolve the test
+            // case name, even in another module.
+            let expn_id = self.cx.ext_cx.resolver.expansion_for_ast_pass(
+                span,
+                AstPass::TestHarness,
+                &[],
+                Some(node_id),
+            );
+            for test in &mut tests {
+                // See the comment on `mk_main` for why we're using
+                // `apply_mark` directly.
+                test.ident.span =
+                    test.ident.span.apply_mark(expn_id.to_expn_id(), Transparency::Opaque);
+            }
+            self.cx.test_cases.extend(tests);
+        }
+    }
+}
+
+impl<'a> MutVisitor for TestHarnessGenerator<'a> {
+    fn visit_crate(&mut self, c: &mut ast::Crate) {
+        let prev_tests = mem::take(&mut self.tests);
+        noop_visit_crate(c, self);
+        self.add_test_cases(ast::CRATE_NODE_ID, c.spans.inner_span, prev_tests);
+
+        // Create a main function to run our tests
+        c.items.push(mk_main(&mut self.cx));
+    }
+
+    fn flat_map_item(&mut self, i: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
+        let mut item = i.into_inner();
+        if let Some(name) = get_test_name(&item) {
+            debug!("this is a test item");
+
+            let test = Test { span: item.span, ident: item.ident, name };
+            self.tests.push(test);
+        }
+
+        // We don't want to recurse into anything other than mods, since
+        // mods or tests inside of functions will break things
+        if let ast::ItemKind::Mod(_, ModKind::Loaded(.., ast::ModSpans { inner_span: span, .. })) =
+            item.kind
+        {
+            let prev_tests = mem::take(&mut self.tests);
+            noop_visit_item_kind(&mut item.kind, self);
+            self.add_test_cases(item.id, span, prev_tests);
+        } else {
+            // But in those cases, we emit a lint to warn the user of these missing tests.
+            walk_item(&mut InnerItemLinter { sess: self.cx.ext_cx.sess }, &item);
+        }
+        smallvec![P(item)]
+    }
+}
+
+struct InnerItemLinter<'a> {
+    sess: &'a Session,
+}
+
+impl<'a> Visitor<'a> for InnerItemLinter<'_> {
+    fn visit_item(&mut self, i: &'a ast::Item) {
+        if let Some(attr) = attr::find_by_name(&i.attrs, sym::rustc_test_marker) {
+            self.sess.psess.buffer_lint(
+                UNNAMEABLE_TEST_ITEMS,
+                attr.span,
+                i.id,
+                BuiltinLintDiag::UnnameableTestItems,
+            );
+        }
+    }
+}
+
+fn entry_point_type(item: &ast::Item, at_root: bool) -> EntryPointType {
+    match item.kind {
+        ast::ItemKind::Fn(..) => {
+            rustc_ast::entry::entry_point_type(&item.attrs, at_root, Some(item.ident.name))
+        }
+        _ => EntryPointType::None,
+    }
+}
+
+/// A folder used to remove any entry points (like fn main) because the harness
+/// coroutine will provide its own
+struct EntryPointCleaner<'a> {
+    // Current depth in the ast
+    sess: &'a Session,
+    depth: usize,
+    def_site: Span,
+}
+
+impl<'a> MutVisitor for EntryPointCleaner<'a> {
+    fn flat_map_item(&mut self, i: P<ast::Item>) -> SmallVec<[P<ast::Item>; 1]> {
+        self.depth += 1;
+        let item = noop_flat_map_item(i, self).expect_one("noop did something");
+        self.depth -= 1;
+
+        // Remove any #[rustc_main] or #[start] from the AST so it doesn't
+        // clash with the one we're going to add, but mark it as
+        // #[allow(dead_code)] to avoid printing warnings.
+        let item = match entry_point_type(&item, self.depth == 0) {
+            EntryPointType::MainNamed | EntryPointType::RustcMainAttr | EntryPointType::Start => {
+                item.map(|ast::Item { id, ident, attrs, kind, vis, span, tokens }| {
+                    let allow_dead_code = attr::mk_attr_nested_word(
+                        &self.sess.psess.attr_id_generator,
+                        ast::AttrStyle::Outer,
+                        ast::Safety::Default,
+                        sym::allow,
+                        sym::dead_code,
+                        self.def_site,
+                    );
+                    let attrs = attrs
+                        .into_iter()
+                        .filter(|attr| {
+                            !attr.has_name(sym::rustc_main) && !attr.has_name(sym::start)
+                        })
+                        .chain(iter::once(allow_dead_code))
+                        .collect();
+
+                    ast::Item { id, ident, attrs, kind, vis, span, tokens }
+                })
+            }
+            EntryPointType::None | EntryPointType::OtherMain => item,
+        };
+
+        smallvec![item]
+    }
+}
+
+/// Crawl over the crate, inserting test reexports and the test main function
+fn generate_test_harness(
+    sess: &Session,
+    resolver: &mut dyn ResolverExpand,
+    reexport_test_harness_main: Option<Symbol>,
+    krate: &mut ast::Crate,
+    features: &Features,
+    panic_strategy: PanicStrategy,
+    test_runner: Option<ast::Path>,
+) {
+    let econfig = ExpansionConfig::default("test".to_string(), features);
+    let ext_cx = ExtCtxt::new(sess, econfig, resolver, None);
+
+    let expn_id = ext_cx.resolver.expansion_for_ast_pass(
+        DUMMY_SP,
+        AstPass::TestHarness,
+        &[sym::test, sym::rustc_attrs, sym::coverage_attribute],
+        None,
+    );
+    let def_site = DUMMY_SP.with_def_site_ctxt(expn_id.to_expn_id());
+
+    // Remove the entry points
+    let mut cleaner = EntryPointCleaner { sess, depth: 0, def_site };
+    cleaner.visit_crate(krate);
+
+    let cx = TestCtxt {
+        ext_cx,
+        panic_strategy,
+        def_site,
+        test_cases: Vec::new(),
+        reexport_test_harness_main,
+        test_runner,
+    };
+
+    TestHarnessGenerator { cx, tests: Vec::new() }.visit_crate(krate);
+}
+
+/// Creates a function item for use as the main function of a test build.
+/// This function will call the `test_runner` as specified by the crate attribute
+///
+/// By default this expands to
+///
+/// ```ignore (messes with test internals)
+/// #[rustc_main]
+/// pub fn main() {
+///     extern crate test;
+///     test::test_main_static(&[
+///         &test_const1,
+///         &test_const2,
+///         &test_const3,
+///     ]);
+/// }
+/// ```
+///
+/// Most of the Ident have the usual def-site hygiene for the AST pass. The
+/// exception is the `test_const`s. These have a syntax context that has two
+/// opaque marks: one from the expansion of `test` or `test_case`, and one
+/// generated  in `TestHarnessGenerator::flat_map_item`. When resolving this
+/// identifier after failing to find a matching identifier in the root module
+/// we remove the outer mark, and try resolving at its def-site, which will
+/// then resolve to `test_const`.
+///
+/// The expansion here can be controlled by two attributes:
+///
+/// [`TestCtxt::reexport_test_harness_main`] provides a different name for the `main`
+/// function and [`TestCtxt::test_runner`] provides a path that replaces
+/// `test::test_main_static`.
+fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
+    let sp = cx.def_site;
+    let ecx = &cx.ext_cx;
+    let test_id = Ident::new(sym::test, sp);
+
+    let runner_name = match cx.panic_strategy {
+        PanicStrategy::Unwind => "test_main_static",
+        PanicStrategy::Abort => "test_main_static_abort",
+    };
+
+    // test::test_main_static(...)
+    let mut test_runner = cx
+        .test_runner
+        .clone()
+        .unwrap_or_else(|| ecx.path(sp, vec![test_id, Ident::from_str_and_span(runner_name, sp)]));
+
+    test_runner.span = sp;
+
+    let test_main_path_expr = ecx.expr_path(test_runner);
+    let call_test_main = ecx.expr_call(sp, test_main_path_expr, thin_vec![mk_tests_slice(cx, sp)]);
+    let call_test_main = ecx.stmt_expr(call_test_main);
+
+    // extern crate test
+    let test_extern_stmt = ecx.stmt_item(
+        sp,
+        ecx.item(sp, test_id, ast::AttrVec::new(), ast::ItemKind::ExternCrate(None)),
+    );
+
+    // #[rustc_main]
+    let main_attr = ecx.attr_word(sym::rustc_main, sp);
+    // #[coverage(off)]
+    let coverage_attr = ecx.attr_nested_word(sym::coverage, sym::off, sp);
+
+    // pub fn main() { ... }
+    let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(ThinVec::new()));
+
+    // If no test runner is provided we need to import the test crate
+    let main_body = if cx.test_runner.is_none() {
+        ecx.block(sp, thin_vec![test_extern_stmt, call_test_main])
+    } else {
+        ecx.block(sp, thin_vec![call_test_main])
+    };
+
+    let decl = ecx.fn_decl(ThinVec::new(), ast::FnRetTy::Ty(main_ret_ty));
+    let sig = ast::FnSig { decl, header: ast::FnHeader::default(), span: sp };
+    let defaultness = ast::Defaultness::Final;
+    let main = ast::ItemKind::Fn(Box::new(ast::Fn {
+        defaultness,
+        sig,
+        generics: ast::Generics::default(),
+        body: Some(main_body),
+    }));
+
+    // Honor the reexport_test_harness_main attribute
+    let main_id = match cx.reexport_test_harness_main {
+        Some(sym) => Ident::new(sym, sp.with_ctxt(SyntaxContext::root())),
+        None => Ident::new(sym::main, sp),
+    };
+
+    let main = P(ast::Item {
+        ident: main_id,
+        attrs: thin_vec![main_attr, coverage_attr],
+        id: ast::DUMMY_NODE_ID,
+        kind: main,
+        vis: ast::Visibility { span: sp, kind: ast::VisibilityKind::Public, tokens: None },
+        span: sp,
+        tokens: None,
+    });
+
+    // Integrate the new item into existing module structures.
+    let main = AstFragment::Items(smallvec![main]);
+    cx.ext_cx.monotonic_expander().fully_expand_fragment(main).make_items().pop().unwrap()
+}
+
+/// Creates a slice containing every test like so:
+/// &[&test1, &test2]
+fn mk_tests_slice(cx: &TestCtxt<'_>, sp: Span) -> P<ast::Expr> {
+    debug!("building test vector from {} tests", cx.test_cases.len());
+    let ecx = &cx.ext_cx;
+
+    let mut tests = cx.test_cases.clone();
+    tests.sort_by(|a, b| a.name.as_str().cmp(b.name.as_str()));
+
+    ecx.expr_array_ref(
+        sp,
+        tests
+            .iter()
+            .map(|test| {
+                ecx.expr_addr_of(test.span, ecx.expr_path(ecx.path(test.span, vec![test.ident])))
+            })
+            .collect(),
+    )
+}
+
+fn get_test_name(i: &ast::Item) -> Option<Symbol> {
+    attr::first_attr_value_str_by_name(&i.attrs, sym::rustc_test_marker)
+}
+
+fn get_test_runner(dcx: DiagCtxtHandle<'_>, krate: &ast::Crate) -> Option<ast::Path> {
+    let test_attr = attr::find_by_name(&krate.attrs, sym::test_runner)?;
+    let meta_list = test_attr.meta_item_list()?;
+    let span = test_attr.span;
+    match &*meta_list {
+        [single] => match single.meta_item() {
+            Some(meta_item) if meta_item.is_word() => return Some(meta_item.path.clone()),
+            _ => {
+                dcx.emit_err(errors::TestRunnerInvalid { span });
+            }
+        },
+        _ => {
+            dcx.emit_err(errors::TestRunnerNargs { span });
+        }
+    }
+    None
+}
diff --git a/compiler/rustc_builtin_macros/src/trace_macros.rs b/compiler/rustc_builtin_macros/src/trace_macros.rs
new file mode 100644
index 00000000000..4833ec32f76
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/trace_macros.rs
@@ -0,0 +1,30 @@
+use crate::errors;
+use rustc_ast::tokenstream::{TokenStream, TokenTree};
+use rustc_expand::base::{DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
+use rustc_span::symbol::kw;
+use rustc_span::Span;
+
+pub(crate) fn expand_trace_macros(
+    cx: &mut ExtCtxt<'_>,
+    sp: Span,
+    tt: TokenStream,
+) -> MacroExpanderResult<'static> {
+    let mut cursor = tt.trees();
+    let mut err = false;
+    let value = match &cursor.next() {
+        Some(TokenTree::Token(token, _)) if token.is_keyword(kw::True) => true,
+        Some(TokenTree::Token(token, _)) if token.is_keyword(kw::False) => false,
+        _ => {
+            err = true;
+            false
+        }
+    };
+    err |= cursor.next().is_some();
+    if err {
+        cx.dcx().emit_err(errors::TraceMacros { span: sp });
+    } else {
+        cx.set_trace_macros(value);
+    }
+
+    ExpandResult::Ready(DummyResult::any_valid(sp))
+}
diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs
new file mode 100644
index 00000000000..652e34268ea
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/util.rs
@@ -0,0 +1,228 @@
+use crate::errors;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_ast::{self as ast, attr, ptr::P, token, AttrStyle, Attribute, MetaItem};
+use rustc_errors::{Applicability, Diag, ErrorGuaranteed};
+use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt};
+use rustc_expand::expand::AstFragment;
+use rustc_feature::AttributeTemplate;
+use rustc_lint_defs::{builtin::DUPLICATE_MACRO_ATTRIBUTES, BuiltinLintDiag};
+use rustc_parse::{parser, validate_attr};
+use rustc_session::errors::report_lit_error;
+use rustc_span::{BytePos, Span, Symbol};
+
+pub(crate) fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaItem, name: Symbol) {
+    // All the built-in macro attributes are "words" at the moment.
+    let template = AttributeTemplate { word: true, ..Default::default() };
+    validate_attr::check_builtin_meta_item(
+        &ecx.sess.psess,
+        meta_item,
+        AttrStyle::Outer,
+        name,
+        template,
+    );
+}
+
+/// Emit a warning if the item is annotated with the given attribute. This is used to diagnose when
+/// an attribute may have been mistakenly duplicated.
+pub(crate) fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name: Symbol) {
+    let attrs: Option<&[Attribute]> = match item {
+        Annotatable::Item(item) => Some(&item.attrs),
+        Annotatable::TraitItem(item) => Some(&item.attrs),
+        Annotatable::ImplItem(item) => Some(&item.attrs),
+        Annotatable::ForeignItem(item) => Some(&item.attrs),
+        Annotatable::Expr(expr) => Some(&expr.attrs),
+        Annotatable::Arm(arm) => Some(&arm.attrs),
+        Annotatable::ExprField(field) => Some(&field.attrs),
+        Annotatable::PatField(field) => Some(&field.attrs),
+        Annotatable::GenericParam(param) => Some(&param.attrs),
+        Annotatable::Param(param) => Some(&param.attrs),
+        Annotatable::FieldDef(def) => Some(&def.attrs),
+        Annotatable::Variant(variant) => Some(&variant.attrs),
+        _ => None,
+    };
+    if let Some(attrs) = attrs {
+        if let Some(attr) = attr::find_by_name(attrs, name) {
+            ecx.psess().buffer_lint(
+                DUPLICATE_MACRO_ATTRIBUTES,
+                attr.span,
+                ecx.current_expansion.lint_node_id,
+                BuiltinLintDiag::DuplicateMacroAttribute,
+            );
+        }
+    }
+}
+
+/// `Ok` represents successfully retrieving the string literal at the correct
+/// position, e.g., `println("abc")`.
+type ExprToSpannedStringResult<'a> = Result<(Symbol, ast::StrStyle, Span), UnexpectedExprKind<'a>>;
+
+/// - `Ok` is returned when the conversion to a string literal is unsuccessful,
+/// but another type of expression is obtained instead.
+/// - `Err` is returned when the conversion process fails.
+type UnexpectedExprKind<'a> = Result<(Diag<'a>, bool /* has_suggestions */), ErrorGuaranteed>;
+
+/// Extracts a string literal from the macro expanded version of `expr`,
+/// returning a diagnostic error of `err_msg` if `expr` is not a string literal.
+/// The returned bool indicates whether an applicable suggestion has already been
+/// added to the diagnostic to avoid emitting multiple suggestions. `Err(Err(ErrorGuaranteed))`
+/// indicates that an ast error was encountered.
+// FIXME(Nilstrieb) Make this function setup translatable
+#[allow(rustc::untranslatable_diagnostic)]
+pub(crate) fn expr_to_spanned_string<'a>(
+    cx: &'a mut ExtCtxt<'_>,
+    expr: P<ast::Expr>,
+    err_msg: &'static str,
+) -> ExpandResult<ExprToSpannedStringResult<'a>, ()> {
+    if !cx.force_mode
+        && let ast::ExprKind::MacCall(m) = &expr.kind
+        && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err()
+    {
+        return ExpandResult::Retry(());
+    }
+
+    // Perform eager expansion on the expression.
+    // We want to be able to handle e.g., `concat!("foo", "bar")`.
+    let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
+
+    ExpandResult::Ready(Err(match expr.kind {
+        ast::ExprKind::Lit(token_lit) => match ast::LitKind::from_token_lit(token_lit) {
+            Ok(ast::LitKind::Str(s, style)) => {
+                return ExpandResult::Ready(Ok((s, style, expr.span)));
+            }
+            Ok(ast::LitKind::ByteStr(..)) => {
+                let mut err = cx.dcx().struct_span_err(expr.span, err_msg);
+                let span = expr.span.shrink_to_lo();
+                err.span_suggestion(
+                    span.with_hi(span.lo() + BytePos(1)),
+                    "consider removing the leading `b`",
+                    "",
+                    Applicability::MaybeIncorrect,
+                );
+                Ok((err, true))
+            }
+            Ok(ast::LitKind::Err(guar)) => Err(guar),
+            Err(err) => Err(report_lit_error(&cx.sess.psess, err, token_lit, expr.span)),
+            _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
+        },
+        ast::ExprKind::Err(guar) => Err(guar),
+        ast::ExprKind::Dummy => {
+            cx.dcx().span_bug(expr.span, "tried to get a string literal from `ExprKind::Dummy`")
+        }
+        _ => Ok((cx.dcx().struct_span_err(expr.span, err_msg), false)),
+    }))
+}
+
+/// Extracts a string literal from the macro expanded version of `expr`,
+/// emitting `err_msg` if `expr` is not a string literal. This does not stop
+/// compilation on error, merely emits a non-fatal error and returns `Err`.
+pub(crate) fn expr_to_string(
+    cx: &mut ExtCtxt<'_>,
+    expr: P<ast::Expr>,
+    err_msg: &'static str,
+) -> ExpandResult<Result<(Symbol, ast::StrStyle), ErrorGuaranteed>, ()> {
+    expr_to_spanned_string(cx, expr, err_msg).map(|res| {
+        res.map_err(|err| match err {
+            Ok((err, _)) => err.emit(),
+            Err(guar) => guar,
+        })
+        .map(|(symbol, style, _)| (symbol, style))
+    })
+}
+
+/// Non-fatally assert that `tts` is empty. Note that this function
+/// returns even when `tts` is non-empty, macros that *need* to stop
+/// compilation should call `cx.diagnostic().abort_if_errors()`
+/// (this should be done as rarely as possible).
+pub(crate) fn check_zero_tts(cx: &ExtCtxt<'_>, span: Span, tts: TokenStream, name: &str) {
+    if !tts.is_empty() {
+        cx.dcx().emit_err(errors::TakesNoArguments { span, name });
+    }
+}
+
+/// Parse an expression. On error, emit it, advancing to `Eof`, and return `Err`.
+pub(crate) fn parse_expr(p: &mut parser::Parser<'_>) -> Result<P<ast::Expr>, ErrorGuaranteed> {
+    let guar = match p.parse_expr() {
+        Ok(expr) => return Ok(expr),
+        Err(err) => err.emit(),
+    };
+    while p.token != token::Eof {
+        p.bump();
+    }
+    Err(guar)
+}
+
+/// Interpreting `tts` as a comma-separated sequence of expressions,
+/// expect exactly one string literal, or emit an error and return `Err`.
+pub(crate) fn get_single_str_from_tts(
+    cx: &mut ExtCtxt<'_>,
+    span: Span,
+    tts: TokenStream,
+    name: &str,
+) -> ExpandResult<Result<Symbol, ErrorGuaranteed>, ()> {
+    get_single_str_spanned_from_tts(cx, span, tts, name).map(|res| res.map(|(s, _)| s))
+}
+
+pub(crate) fn get_single_str_spanned_from_tts(
+    cx: &mut ExtCtxt<'_>,
+    span: Span,
+    tts: TokenStream,
+    name: &str,
+) -> ExpandResult<Result<(Symbol, Span), ErrorGuaranteed>, ()> {
+    let mut p = cx.new_parser_from_tts(tts);
+    if p.token == token::Eof {
+        let guar = cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
+        return ExpandResult::Ready(Err(guar));
+    }
+    let ret = match parse_expr(&mut p) {
+        Ok(ret) => ret,
+        Err(guar) => return ExpandResult::Ready(Err(guar)),
+    };
+    let _ = p.eat(&token::Comma);
+
+    if p.token != token::Eof {
+        cx.dcx().emit_err(errors::OnlyOneArgument { span, name });
+    }
+    expr_to_spanned_string(cx, ret, "argument must be a string literal").map(|res| {
+        res.map_err(|err| match err {
+            Ok((err, _)) => err.emit(),
+            Err(guar) => guar,
+        })
+        .map(|(symbol, _style, span)| (symbol, span))
+    })
+}
+
+/// Extracts comma-separated expressions from `tts`.
+/// On error, emit it, and return `Err`.
+pub(crate) fn get_exprs_from_tts(
+    cx: &mut ExtCtxt<'_>,
+    tts: TokenStream,
+) -> ExpandResult<Result<Vec<P<ast::Expr>>, ErrorGuaranteed>, ()> {
+    let mut p = cx.new_parser_from_tts(tts);
+    let mut es = Vec::new();
+    while p.token != token::Eof {
+        let expr = match parse_expr(&mut p) {
+            Ok(expr) => expr,
+            Err(guar) => return ExpandResult::Ready(Err(guar)),
+        };
+        if !cx.force_mode
+            && let ast::ExprKind::MacCall(m) = &expr.kind
+            && cx.resolver.macro_accessible(cx.current_expansion.id, &m.path).is_err()
+        {
+            return ExpandResult::Retry(());
+        }
+
+        // Perform eager expansion on the expression.
+        // We want to be able to handle e.g., `concat!("foo", "bar")`.
+        let expr = cx.expander().fully_expand_fragment(AstFragment::Expr(expr)).make_expr();
+
+        es.push(expr);
+        if p.eat(&token::Comma) {
+            continue;
+        }
+        if p.token != token::Eof {
+            let guar = cx.dcx().emit_err(errors::ExpectedCommaInList { span: p.token.span });
+            return ExpandResult::Ready(Err(guar));
+        }
+    }
+    ExpandResult::Ready(Ok(es))
+}