diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index 2100487107517..b15e2d084ef7f 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -1,271 +1,42 @@
-use ArgumentType::*;
-use Position::*;
-
-use rustc_ast as ast;
 use rustc_ast::ptr::P;
+use rustc_ast::token;
 use rustc_ast::tokenstream::TokenStream;
-use rustc_ast::visit::{self, Visitor};
-use rustc_ast::{token, BlockCheckMode, UnsafeSource};
-use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_ast::Expr;
+use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::{pluralize, Applicability, MultiSpan, PResult};
 use rustc_expand::base::{self, *};
 use rustc_parse_format as parse;
-use rustc_span::symbol::{sym, Ident, Symbol};
+use rustc_span::symbol::{Ident, Symbol};
 use rustc_span::{BytePos, InnerSpan, Span};
-use smallvec::SmallVec;
 
 use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
 use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId};
-use rustc_parse_format::Count;
-use std::borrow::Cow;
-use std::collections::hash_map::Entry;
-
-#[derive(PartialEq)]
-enum ArgumentType {
-    Placeholder(&'static str),
-    Count,
-}
-
-enum Position {
-    Exact(usize),
-    Capture(usize),
-    Named(Symbol, InnerSpan),
-}
-
-/// Indicates how positional named argument (i.e. an named argument which is used by position
-/// instead of by name) is used in format string
-/// * `Arg` is the actual argument to print
-/// * `Width` is width format argument
-/// * `Precision` is precion format argument
-/// Example: `{Arg:Width$.Precision$}
-#[derive(Debug, Eq, PartialEq)]
-enum PositionalNamedArgType {
-    Arg,
-    Width,
-    Precision,
-}
-
-/// Contains information necessary to create a lint for a positional named argument
-#[derive(Debug)]
-struct PositionalNamedArg {
-    ty: PositionalNamedArgType,
-    /// The piece of the using this argument (multiple pieces can use the same argument)
-    cur_piece: usize,
-    /// The InnerSpan for in the string to be replaced with the named argument
-    /// This will be None when the position is implicit
-    inner_span_to_replace: Option<rustc_parse_format::InnerSpan>,
-    /// The name to use instead of the position
-    replacement: Symbol,
-    /// The span for the positional named argument (so the lint can point a message to it)
-    positional_named_arg_span: Span,
-    has_formatting: bool,
-}
-
-impl PositionalNamedArg {
-    /// Determines:
-    /// 1) span to be replaced with the name of the named argument and
-    /// 2) span to be underlined for error messages
-    fn get_positional_arg_spans(&self, cx: &Context<'_, '_>) -> (Option<Span>, Option<Span>) {
-        if let Some(inner_span) = &self.inner_span_to_replace {
-            let span =
-                cx.fmtsp.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end });
-            (Some(span), Some(span))
-        } else if self.ty == PositionalNamedArgType::Arg {
-            // In the case of a named argument whose position is implicit, if the argument *has*
-            // formatting, there will not be a span to replace. Instead, we insert the name after
-            // the `{`, which will be the first character of arg_span. If the argument does *not*
-            // have formatting, there may or may not be a span to replace. This is because
-            // whitespace is allowed in arguments without formatting (such as `format!("{  }", 1);`)
-            // but is not allowed in arguments with formatting (an error will be generated in cases
-            // like `format!("{ :1.1}", 1.0f32);`.
-            // For the message span, if there is formatting, we want to use the opening `{` and the
-            // next character, which will the `:` indicating the start of formatting. If there is
-            // not any formatting, we want to underline the entire span.
-            cx.arg_spans.get(self.cur_piece).map_or((None, None), |arg_span| {
-                if self.has_formatting {
-                    (
-                        Some(arg_span.with_lo(arg_span.lo() + BytePos(1)).shrink_to_lo()),
-                        Some(arg_span.with_hi(arg_span.lo() + BytePos(2))),
-                    )
-                } else {
-                    let replace_start = arg_span.lo() + BytePos(1);
-                    let replace_end = arg_span.hi() - BytePos(1);
-                    let to_replace = arg_span.with_lo(replace_start).with_hi(replace_end);
-                    (Some(to_replace), Some(*arg_span))
-                }
-            })
-        } else {
-            (None, None)
-        }
-    }
-}
-
-/// Encapsulates all the named arguments that have been used positionally
-#[derive(Debug)]
-struct PositionalNamedArgsLint {
-    positional_named_args: Vec<PositionalNamedArg>,
-}
-
-impl PositionalNamedArgsLint {
-    /// For a given positional argument, check if the index is for a named argument.
-    ///
-    /// Since positional arguments are required to come before named arguments, if the positional
-    /// index is greater than or equal to the start of named arguments, we know it's a named
-    /// argument used positionally.
-    ///
-    /// Example:
-    /// println!("{} {} {2}", 0, a=1, b=2);
-    ///
-    /// In this case, the first piece (`{}`) would be ArgumentImplicitlyIs with an index of 0. The
-    /// total number of arguments is 3 and the number of named arguments is 2, so the start of named
-    /// arguments is index 1. Therefore, the index of 0 is okay.
-    ///
-    /// The second piece (`{}`) would be ArgumentImplicitlyIs with an index of 1, which is the start
-    /// of named arguments, and so we should add a lint to use the named argument `a`.
-    ///
-    /// The third piece (`{2}`) would be ArgumentIs with an index of 2, which is greater than the
-    /// start of named arguments, and so we should add a lint to use the named argument `b`.
-    ///
-    /// This same check also works for width and precision formatting when either or both are
-    /// CountIsParam, which contains an index into the arguments.
-    fn maybe_add_positional_named_arg(
-        &mut self,
-        arg: Option<&FormatArg>,
-        ty: PositionalNamedArgType,
-        cur_piece: usize,
-        inner_span_to_replace: Option<rustc_parse_format::InnerSpan>,
-        has_formatting: bool,
-    ) {
-        if let Some(arg) = arg {
-            if let Some(name) = arg.name {
-                self.push(name, ty, cur_piece, inner_span_to_replace, has_formatting)
-            }
-        }
-    }
 
-    /// Construct a PositionalNamedArg struct and push it into the vec of positional
-    /// named arguments.
-    fn push(
-        &mut self,
-        arg_name: Ident,
-        ty: PositionalNamedArgType,
-        cur_piece: usize,
-        inner_span_to_replace: Option<rustc_parse_format::InnerSpan>,
-        has_formatting: bool,
-    ) {
-        // In FormatSpec, `precision_span` starts at the leading `.`, which we want to keep in
-        // the lint suggestion, so increment `start` by 1 when `PositionalArgumentType` is
-        // `Precision`.
-        let inner_span_to_replace = if ty == PositionalNamedArgType::Precision {
-            inner_span_to_replace
-                .map(|is| rustc_parse_format::InnerSpan { start: is.start + 1, end: is.end })
-        } else {
-            inner_span_to_replace
-        };
-        self.positional_named_args.push(PositionalNamedArg {
-            ty,
-            cur_piece,
-            inner_span_to_replace,
-            replacement: arg_name.name,
-            positional_named_arg_span: arg_name.span,
-            has_formatting,
-        });
-    }
-}
-
-struct Context<'a, 'b> {
-    ecx: &'a mut ExtCtxt<'b>,
-    /// The macro's call site. References to unstable formatting internals must
-    /// use this span to pass the stability checker.
-    macsp: Span,
-    /// The span of the format string literal.
-    fmtsp: Span,
-
-    /// List of parsed argument expressions.
-    /// Named expressions are resolved early, and are appended to the end of
-    /// argument expressions.
-    ///
-    /// Example showing the various data structures in motion:
-    ///
-    /// * Original: `"{foo:o} {:o} {foo:x} {0:x} {1:o} {:x} {1:x} {0:o}"`
-    /// * Implicit argument resolution: `"{foo:o} {0:o} {foo:x} {0:x} {1:o} {1:x} {1:x} {0:o}"`
-    /// * Name resolution: `"{2:o} {0:o} {2:x} {0:x} {1:o} {1:x} {1:x} {0:o}"`
-    /// * `arg_types` (in JSON): `[[0, 1, 0], [0, 1, 1], [0, 1]]`
-    /// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]`
-    /// * `names` (in JSON): `{"foo": 2}`
-    args: Vec<FormatArg>,
-    /// The number of arguments that were added by implicit capturing.
-    num_captured_args: usize,
-    /// Placeholder slot numbers indexed by argument.
-    arg_types: Vec<Vec<usize>>,
-    /// Unique format specs seen for each argument.
-    arg_unique_types: Vec<Vec<ArgumentType>>,
-    /// Map from named arguments to their resolved indices.
-    names: FxHashMap<Symbol, usize>,
-
-    /// The latest consecutive literal strings, or empty if there weren't any.
-    literal: String,
+mod ast;
+use ast::*;
 
-    /// Collection of the compiled `rt::Argument` structures
-    pieces: Vec<P<ast::Expr>>,
-    /// Collection of string literals
-    str_pieces: Vec<P<ast::Expr>>,
-    /// Stays `true` if all formatting parameters are default (as in "{}{}").
-    all_pieces_simple: bool,
+mod expand;
+use expand::expand_parsed_format_args;
 
-    /// Mapping between positional argument references and indices into the
-    /// final generated static argument array. We record the starting indices
-    /// corresponding to each positional argument, and number of references
-    /// consumed so far for each argument, to facilitate correct `Position`
-    /// mapping in `build_piece`. In effect this can be seen as a "flattened"
-    /// version of `arg_unique_types`.
-    ///
-    /// Again with the example described above in docstring for `args`:
-    ///
-    /// * `arg_index_map` (in JSON): `[[0, 1, 0], [2, 3, 3], [4, 5]]`
-    arg_index_map: Vec<Vec<usize>>,
+// 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` structure.
+//  3. Finally, `expand_parsed_format_args` will turn that `FormatArgs` structure
+//     into the expression that the macro expands to.
 
-    /// Starting offset of count argument slots.
-    count_args_index_offset: usize,
+// See format/ast.rs for the FormatArgs structure and glossary.
 
-    /// Count argument slots and tracking data structures.
-    /// Count arguments are separately tracked for de-duplication in case
-    /// multiple references are made to one argument. For example, in this
-    /// format string:
-    ///
-    /// * Original: `"{:.*} {:.foo$} {1:.*} {:.0$}"`
-    /// * Implicit argument resolution: `"{1:.0$} {2:.foo$} {1:.3$} {4:.0$}"`
-    /// * Name resolution: `"{1:.0$} {2:.5$} {1:.3$} {4:.0$}"`
-    /// * `count_positions` (in JSON): `{0: 0, 5: 1, 3: 2}`
-    /// * `count_args`: `vec![0, 5, 3]`
-    count_args: Vec<usize>,
-    /// Relative slot numbers for count arguments.
-    count_positions: FxHashMap<usize, usize>,
-    /// Number of count slots assigned.
-    count_positions_count: usize,
-
-    /// Current position of the implicit positional arg pointer, as if it
-    /// still existed in this phase of processing.
-    /// Used only for `all_pieces_simple` tracking in `build_piece`.
-    curarg: usize,
-    /// Current piece being evaluated, used for error reporting.
-    curpiece: usize,
-    /// Keep track of invalid references to positional arguments.
-    invalid_refs: Vec<(usize, usize)>,
-    /// Spans of all the formatting arguments, in order.
-    arg_spans: Vec<Span>,
-    /// All the formatting arguments that have formatting flags set, in order for diagnostics.
-    arg_with_formatting: Vec<parse::FormatSpec<'a>>,
-
-    /// Whether this format string came from a string literal, as opposed to a macro.
-    is_literal: bool,
-    unused_names_lint: PositionalNamedArgsLint,
-}
-
-pub struct FormatArg {
-    expr: P<ast::Expr>,
-    name: Option<Ident>,
+// 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::*;
 
 /// 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!
@@ -274,15 +45,14 @@ pub struct FormatArg {
 /// If parsing succeeds, the return value is:
 ///
 /// ```text
-/// Some((fmtstr, parsed arguments, index map for named arguments))
+/// Ok((fmtstr, parsed arguments))
 /// ```
 fn parse_args<'a>(
     ecx: &mut ExtCtxt<'a>,
     sp: Span,
     tts: TokenStream,
-) -> PResult<'a, (P<ast::Expr>, Vec<FormatArg>, FxHashMap<Symbol, usize>)> {
-    let mut args = Vec::<FormatArg>::new();
-    let mut names = FxHashMap::<Symbol, usize>::default();
+) -> PResult<'a, (P<Expr>, FormatArguments)> {
+    let mut args = FormatArguments::new();
 
     let mut p = ecx.new_parser_from_tts(tts);
 
@@ -311,7 +81,6 @@ fn parse_args<'a>(
     };
 
     let mut first = true;
-    let mut named = false;
 
     while p.token != token::Eof {
         if !p.eat(&token::Comma) {
@@ -343,879 +112,54 @@ fn parse_args<'a>(
         } // accept trailing commas
         match p.token.ident() {
             Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => {
-                named = true;
                 p.bump();
                 p.expect(&token::Eq)?;
-                let e = p.parse_expr()?;
-                if let Some(&prev) = names.get(&ident.name) {
-                    ecx.struct_span_err(e.span, &format!("duplicate argument named `{}`", ident))
-                        .span_label(args[prev].expr.span, "previously here")
-                        .span_label(e.span, "duplicate argument")
-                        .emit();
+                let expr = p.parse_expr()?;
+                if let Some((_, prev)) = args.by_name(ident.name) {
+                    ecx.struct_span_err(
+                        ident.span,
+                        &format!("duplicate argument named `{}`", ident),
+                    )
+                    .span_label(prev.kind.ident().unwrap().span, "previously here")
+                    .span_label(ident.span, "duplicate argument")
+                    .emit();
                     continue;
                 }
-
-                // Resolve names into slots early.
-                // Since all the positional args are already seen at this point
-                // if the input is valid, we can simply append to the positional
-                // args. And remember the names.
-                let slot = args.len();
-                names.insert(ident.name, slot);
-                args.push(FormatArg { expr: e, name: Some(ident) });
+                args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr });
             }
             _ => {
-                let e = p.parse_expr()?;
-                if named {
+                let expr = p.parse_expr()?;
+                if !args.named_args().is_empty() {
                     let mut err = ecx.struct_span_err(
-                        e.span,
+                        expr.span,
                         "positional arguments cannot follow named arguments",
                     );
-                    err.span_label(e.span, "positional arguments must be before named arguments");
-                    for &pos in names.values() {
-                        err.span_label(args[pos].expr.span, "named argument");
+                    err.span_label(
+                        expr.span,
+                        "positional arguments must be before named arguments",
+                    );
+                    for arg in args.named_args() {
+                        if let Some(name) = arg.kind.ident() {
+                            err.span_label(name.span.to(arg.expr.span), "named argument");
+                        }
                     }
                     err.emit();
                 }
-                args.push(FormatArg { expr: e, name: None });
+                args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr });
             }
         }
     }
-    Ok((fmtstr, args, names))
+    Ok((fmtstr, args))
 }
 
-impl<'a, 'b> Context<'a, 'b> {
-    /// The number of arguments that were explicitly given.
-    fn num_args(&self) -> usize {
-        self.args.len() - self.num_captured_args
-    }
-
-    fn resolve_name_inplace(&mut self, p: &mut parse::Piece<'_>) {
-        // NOTE: the `unwrap_or` branch is needed in case of invalid format
-        // arguments, e.g., `format_args!("{foo}")`.
-        let lookup = |s: &str| self.names.get(&Symbol::intern(s)).copied().unwrap_or(0);
-
-        match *p {
-            parse::String(_) => {}
-            parse::NextArgument(ref mut arg) => {
-                if let parse::ArgumentNamed(s) = arg.position {
-                    arg.position = parse::ArgumentIs(lookup(s));
-                }
-                if let parse::CountIsName(s, _) = arg.format.width {
-                    arg.format.width = parse::CountIsParam(lookup(s));
-                }
-                if let parse::CountIsName(s, _) = arg.format.precision {
-                    arg.format.precision = parse::CountIsParam(lookup(s));
-                }
-            }
-        }
-    }
-
-    /// Verifies one piece of a parse string, and remembers it if valid.
-    /// All errors are not emitted as fatal so we can continue giving errors
-    /// about this and possibly other format strings.
-    fn verify_piece(&mut self, p: &parse::Piece<'a>) {
-        match *p {
-            parse::String(..) => {}
-            parse::NextArgument(ref arg) => {
-                // width/precision first, if they have implicit positional
-                // parameters it makes more sense to consume them first.
-                self.verify_count(
-                    arg.format.width,
-                    &arg.format.width_span,
-                    PositionalNamedArgType::Width,
-                );
-                self.verify_count(
-                    arg.format.precision,
-                    &arg.format.precision_span,
-                    PositionalNamedArgType::Precision,
-                );
-
-                let has_precision = arg.format.precision != Count::CountImplied;
-                let has_width = arg.format.width != Count::CountImplied;
-
-                if has_precision || has_width {
-                    // push before named params are resolved to aid diagnostics
-                    self.arg_with_formatting.push(arg.format);
-                }
-
-                // argument second, if it's an implicit positional parameter
-                // it's written second, so it should come after width/precision.
-                let pos = match arg.position {
-                    parse::ArgumentIs(i) => {
-                        self.unused_names_lint.maybe_add_positional_named_arg(
-                            self.args.get(i),
-                            PositionalNamedArgType::Arg,
-                            self.curpiece,
-                            Some(arg.position_span),
-                            has_precision || has_width,
-                        );
-
-                        Exact(i)
-                    }
-                    parse::ArgumentImplicitlyIs(i) => {
-                        self.unused_names_lint.maybe_add_positional_named_arg(
-                            self.args.get(i),
-                            PositionalNamedArgType::Arg,
-                            self.curpiece,
-                            None,
-                            has_precision || has_width,
-                        );
-                        Exact(i)
-                    }
-                    parse::ArgumentNamed(s) => {
-                        let symbol = Symbol::intern(s);
-                        let span = arg.position_span;
-                        Named(symbol, InnerSpan::new(span.start, span.end))
-                    }
-                };
-
-                let ty = Placeholder(match arg.format.ty {
-                    "" => "Display",
-                    "?" => "Debug",
-                    "e" => "LowerExp",
-                    "E" => "UpperExp",
-                    "o" => "Octal",
-                    "p" => "Pointer",
-                    "b" => "Binary",
-                    "x" => "LowerHex",
-                    "X" => "UpperHex",
-                    _ => {
-                        let fmtsp = self.fmtsp;
-                        let sp = arg
-                            .format
-                            .ty_span
-                            .map(|sp| fmtsp.from_inner(InnerSpan::new(sp.start, sp.end)));
-                        let mut err = self.ecx.struct_span_err(
-                            sp.unwrap_or(fmtsp),
-                            &format!("unknown format trait `{}`", arg.format.ty),
-                        );
-                        err.note(
-                            "the only appropriate formatting traits are:\n\
-                                - ``, which uses the `Display` trait\n\
-                                - `?`, which uses the `Debug` trait\n\
-                                - `e`, which uses the `LowerExp` trait\n\
-                                - `E`, which uses the `UpperExp` trait\n\
-                                - `o`, which uses the `Octal` trait\n\
-                                - `p`, which uses the `Pointer` trait\n\
-                                - `b`, which uses the `Binary` trait\n\
-                                - `x`, which uses the `LowerHex` trait\n\
-                                - `X`, which uses the `UpperHex` trait",
-                        );
-                        if let Some(sp) = sp {
-                            for (fmt, name) in &[
-                                ("", "Display"),
-                                ("?", "Debug"),
-                                ("e", "LowerExp"),
-                                ("E", "UpperExp"),
-                                ("o", "Octal"),
-                                ("p", "Pointer"),
-                                ("b", "Binary"),
-                                ("x", "LowerHex"),
-                                ("X", "UpperHex"),
-                            ] {
-                                // FIXME: rustfix (`run-rustfix`) fails to apply suggestions.
-                                // > "Cannot replace slice of data that was already replaced"
-                                err.tool_only_span_suggestion(
-                                    sp,
-                                    &format!("use the `{}` trait", name),
-                                    *fmt,
-                                    Applicability::MaybeIncorrect,
-                                );
-                            }
-                        }
-                        err.emit();
-                        "<invalid>"
-                    }
-                });
-                self.verify_arg_type(pos, ty);
-                self.curpiece += 1;
-            }
-        }
-    }
-
-    fn verify_count(
-        &mut self,
-        c: parse::Count<'_>,
-        inner_span: &Option<rustc_parse_format::InnerSpan>,
-        named_arg_type: PositionalNamedArgType,
-    ) {
-        match c {
-            parse::CountImplied | parse::CountIs(..) => {}
-            parse::CountIsParam(i) | parse::CountIsStar(i) => {
-                self.unused_names_lint.maybe_add_positional_named_arg(
-                    self.args.get(i),
-                    named_arg_type,
-                    self.curpiece,
-                    *inner_span,
-                    true,
-                );
-                self.verify_arg_type(Exact(i), Count);
-            }
-            parse::CountIsName(s, span) => {
-                self.verify_arg_type(
-                    Named(Symbol::intern(s), InnerSpan::new(span.start, span.end)),
-                    Count,
-                );
-            }
-        }
-    }
-
-    fn describe_num_args(&self) -> Cow<'_, str> {
-        match self.num_args() {
-            0 => "no arguments were given".into(),
-            1 => "there is 1 argument".into(),
-            x => format!("there are {} arguments", x).into(),
-        }
-    }
-
-    /// 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(&self, numbered_position_args: bool) {
-        let mut e;
-        let sp = if !self.arg_spans.is_empty() {
-            // Point at the formatting arguments.
-            MultiSpan::from_spans(self.arg_spans.clone())
-        } else {
-            MultiSpan::from_span(self.fmtsp)
-        };
-        let refs =
-            self.invalid_refs.iter().map(|(r, pos)| (r.to_string(), self.arg_spans.get(*pos)));
-
-        let mut zero_based_note = false;
-
-        let count = self.pieces.len()
-            + self
-                .arg_with_formatting
-                .iter()
-                .filter(|fmt| matches!(fmt.precision, parse::CountIsStar(_)))
-                .count();
-        if self.names.is_empty() && !numbered_position_args && count != self.num_args() {
-            e = self.ecx.struct_span_err(
-                sp,
-                &format!(
-                    "{} positional argument{} in format string, but {}",
-                    count,
-                    pluralize!(count),
-                    self.describe_num_args(),
-                ),
-            );
-            for arg in &self.args {
-                // Point at the arguments that will be formatted.
-                e.span_label(arg.expr.span, "");
-            }
-        } else {
-            let (mut refs, spans): (Vec<_>, Vec<_>) = refs.unzip();
-            // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)`
-            // for `println!("{7:7$}", 1);`
-            refs.sort();
-            refs.dedup();
-            let spans: Vec<_> = spans.into_iter().filter_map(|sp| sp.copied()).collect();
-            let sp = if self.arg_spans.is_empty() || spans.is_empty() {
-                MultiSpan::from_span(self.fmtsp)
-            } else {
-                MultiSpan::from_spans(spans)
-            };
-            let arg_list = if refs.len() == 1 {
-                format!("argument {}", refs[0])
-            } else {
-                let reg = refs.pop().unwrap();
-                format!("arguments {head} and {tail}", head = refs.join(", "), tail = reg)
-            };
-
-            e = self.ecx.struct_span_err(
-                sp,
-                &format!(
-                    "invalid reference to positional {} ({})",
-                    arg_list,
-                    self.describe_num_args()
-                ),
-            );
-            zero_based_note = true;
-        };
-
-        for fmt in &self.arg_with_formatting {
-            if let Some(span) = fmt.precision_span {
-                let span = self.fmtsp.from_inner(InnerSpan::new(span.start, span.end));
-                match fmt.precision {
-                    parse::CountIsParam(pos) if pos >= self.num_args() => {
-                        e.span_label(
-                            span,
-                            &format!(
-                                "this precision flag expects an `usize` argument at position {}, \
-                             but {}",
-                                pos,
-                                self.describe_num_args(),
-                            ),
-                        );
-                        zero_based_note = true;
-                    }
-                    parse::CountIsStar(pos) => {
-                        let count = self.pieces.len()
-                            + self
-                                .arg_with_formatting
-                                .iter()
-                                .filter(|fmt| matches!(fmt.precision, parse::CountIsStar(_)))
-                                .count();
-                        e.span_label(
-                            span,
-                            &format!(
-                            "this precision flag adds an extra required argument at position {}, \
-                             which is why there {} expected",
-                            pos,
-                            if count == 1 {
-                                "is 1 argument".to_string()
-                            } else {
-                                format!("are {} arguments", count)
-                            },
-                        ),
-                        );
-                        if let Some(arg) = self.args.get(pos) {
-                            e.span_label(
-                                arg.expr.span,
-                                "this parameter corresponds to the precision flag",
-                            );
-                        }
-                        zero_based_note = true;
-                    }
-                    _ => {}
-                }
-            }
-            if let Some(span) = fmt.width_span {
-                let span = self.fmtsp.from_inner(InnerSpan::new(span.start, span.end));
-                match fmt.width {
-                    parse::CountIsParam(pos) if pos >= self.num_args() => {
-                        e.span_label(
-                            span,
-                            &format!(
-                                "this width flag expects an `usize` argument at position {}, \
-                             but {}",
-                                pos,
-                                self.describe_num_args(),
-                            ),
-                        );
-                        zero_based_note = true;
-                    }
-                    _ => {}
-                }
-            }
-        }
-        if zero_based_note {
-            e.note("positional arguments are zero-based");
-        }
-        if !self.arg_with_formatting.is_empty() {
-            e.note(
-                "for information about formatting flags, visit \
-                    https://doc.rust-lang.org/std/fmt/index.html",
-            );
-        }
-
-        e.emit();
-    }
-
-    /// Actually verifies and tracks a given format placeholder
-    /// (a.k.a. argument).
-    fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) {
-        if let Exact(arg) = arg {
-            if arg >= self.num_args() {
-                self.invalid_refs.push((arg, self.curpiece));
-                return;
-            }
-        }
-
-        match arg {
-            Exact(arg) | Capture(arg) => {
-                match ty {
-                    Placeholder(_) => {
-                        // record every (position, type) combination only once
-                        let seen_ty = &mut self.arg_unique_types[arg];
-                        let i = seen_ty.iter().position(|x| *x == ty).unwrap_or_else(|| {
-                            let i = seen_ty.len();
-                            seen_ty.push(ty);
-                            i
-                        });
-                        self.arg_types[arg].push(i);
-                    }
-                    Count => {
-                        if let Entry::Vacant(e) = self.count_positions.entry(arg) {
-                            let i = self.count_positions_count;
-                            e.insert(i);
-                            self.count_args.push(arg);
-                            self.count_positions_count += 1;
-                        }
-                    }
-                }
-            }
-
-            Named(name, span) => {
-                match self.names.get(&name) {
-                    Some(&idx) => {
-                        // Treat as positional arg.
-                        self.verify_arg_type(Capture(idx), ty)
-                    }
-                    None => {
-                        // For the moment capturing variables from format strings expanded from macros is
-                        // disabled (see RFC #2795)
-                        if self.is_literal {
-                            // Treat this name as a variable to capture from the surrounding scope
-                            let idx = self.args.len();
-                            self.arg_types.push(Vec::new());
-                            self.arg_unique_types.push(Vec::new());
-                            let span = if self.is_literal {
-                                self.fmtsp.from_inner(span)
-                            } else {
-                                self.fmtsp
-                            };
-                            self.num_captured_args += 1;
-                            self.args.push(FormatArg {
-                                expr: self.ecx.expr_ident(span, Ident::new(name, span)),
-                                name: Some(Ident::new(name, span)),
-                            });
-                            self.names.insert(name, idx);
-                            self.verify_arg_type(Capture(idx), ty)
-                        } else {
-                            let msg = format!("there is no argument named `{}`", name);
-                            let sp = if self.is_literal {
-                                self.fmtsp.from_inner(span)
-                            } else {
-                                self.fmtsp
-                            };
-                            let mut err = self.ecx.struct_span_err(sp, &msg);
-
-                            err.note(&format!(
-                                "did you intend to capture a variable `{}` from \
-                                 the surrounding scope?",
-                                name
-                            ));
-                            err.note(
-                                "to avoid ambiguity, `format_args!` cannot capture variables \
-                                 when the format string is expanded from a macro",
-                            );
-
-                            err.emit();
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /// Builds the mapping between format placeholders and argument objects.
-    fn build_index_map(&mut self) {
-        // NOTE: Keep the ordering the same as `into_expr`'s expansion would do!
-        let args_len = self.args.len();
-        self.arg_index_map.reserve(args_len);
-
-        let mut sofar = 0usize;
-
-        // Map the arguments
-        for i in 0..args_len {
-            let arg_types = &self.arg_types[i];
-            let arg_offsets = arg_types.iter().map(|offset| sofar + *offset).collect::<Vec<_>>();
-            self.arg_index_map.push(arg_offsets);
-            sofar += self.arg_unique_types[i].len();
-        }
-
-        // Record starting index for counts, which appear just after arguments
-        self.count_args_index_offset = sofar;
-    }
-
-    fn rtpath(ecx: &ExtCtxt<'_>, s: Symbol) -> Vec<Ident> {
-        ecx.std_path(&[sym::fmt, sym::rt, sym::v1, s])
-    }
-
-    fn build_count(&self, c: parse::Count<'_>) -> P<ast::Expr> {
-        let sp = self.macsp;
-        let count = |c, arg| {
-            let mut path = Context::rtpath(self.ecx, sym::Count);
-            path.push(Ident::new(c, sp));
-            match arg {
-                Some(arg) => self.ecx.expr_call_global(sp, path, vec![arg]),
-                None => self.ecx.expr_path(self.ecx.path_global(sp, path)),
-            }
-        };
-        match c {
-            parse::CountIs(i) => count(sym::Is, Some(self.ecx.expr_usize(sp, i))),
-            parse::CountIsParam(i) | parse::CountIsStar(i) => {
-                // This needs mapping too, as `i` is referring to a macro
-                // argument. If `i` is not found in `count_positions` then
-                // the error had already been emitted elsewhere.
-                let i = self.count_positions.get(&i).cloned().unwrap_or(0)
-                    + self.count_args_index_offset;
-                count(sym::Param, Some(self.ecx.expr_usize(sp, i)))
-            }
-            parse::CountImplied => count(sym::Implied, None),
-            // should never be the case, names are already resolved
-            parse::CountIsName(..) => panic!("should never happen"),
-        }
-    }
-
-    /// Build a literal expression from the accumulated string literals
-    fn build_literal_string(&mut self) -> P<ast::Expr> {
-        let sp = self.fmtsp;
-        let s = Symbol::intern(&self.literal);
-        self.literal.clear();
-        self.ecx.expr_str(sp, s)
-    }
-
-    /// Builds a static `rt::Argument` from a `parse::Piece` or append
-    /// to the `literal` string.
-    fn build_piece(
-        &mut self,
-        piece: &parse::Piece<'a>,
-        arg_index_consumed: &mut Vec<usize>,
-    ) -> Option<P<ast::Expr>> {
-        let sp = self.macsp;
-        match *piece {
-            parse::String(s) => {
-                self.literal.push_str(s);
-                None
-            }
-            parse::NextArgument(ref arg) => {
-                // Build the position
-                let pos = {
-                    match arg.position {
-                        parse::ArgumentIs(i, ..) | parse::ArgumentImplicitlyIs(i) => {
-                            // Map to index in final generated argument array
-                            // in case of multiple types specified
-                            let arg_idx = match arg_index_consumed.get_mut(i) {
-                                None => 0, // error already emitted elsewhere
-                                Some(offset) => {
-                                    let idx_map = &self.arg_index_map[i];
-                                    // unwrap_or branch: error already emitted elsewhere
-                                    let arg_idx = *idx_map.get(*offset).unwrap_or(&0);
-                                    *offset += 1;
-                                    arg_idx
-                                }
-                            };
-                            self.ecx.expr_usize(sp, arg_idx)
-                        }
-
-                        // should never be the case, because names are already
-                        // resolved.
-                        parse::ArgumentNamed(..) => panic!("should never happen"),
-                    }
-                };
-
-                let simple_arg = parse::Argument {
-                    position: {
-                        // We don't have ArgumentNext any more, so we have to
-                        // track the current argument ourselves.
-                        let i = self.curarg;
-                        self.curarg += 1;
-                        parse::ArgumentIs(i)
-                    },
-                    position_span: arg.position_span,
-                    format: parse::FormatSpec {
-                        fill: None,
-                        align: parse::AlignUnknown,
-                        flags: 0,
-                        precision: parse::CountImplied,
-                        precision_span: arg.format.precision_span,
-                        width: parse::CountImplied,
-                        width_span: arg.format.width_span,
-                        ty: arg.format.ty,
-                        ty_span: arg.format.ty_span,
-                    },
-                };
-
-                let fill = arg.format.fill.unwrap_or(' ');
-                let pos_simple = arg.position.index() == simple_arg.position.index();
-
-                if !pos_simple || arg.format != simple_arg.format {
-                    self.all_pieces_simple = false;
-                }
-
-                // Build the format
-                let fill = self.ecx.expr_char(sp, fill);
-                let align = |name| {
-                    let mut p = Context::rtpath(self.ecx, sym::Alignment);
-                    p.push(Ident::new(name, sp));
-                    self.ecx.path_global(sp, p)
-                };
-                let align = match arg.format.align {
-                    parse::AlignLeft => align(sym::Left),
-                    parse::AlignRight => align(sym::Right),
-                    parse::AlignCenter => align(sym::Center),
-                    parse::AlignUnknown => align(sym::Unknown),
-                };
-                let align = self.ecx.expr_path(align);
-                let flags = self.ecx.expr_u32(sp, arg.format.flags);
-                let prec = self.build_count(arg.format.precision);
-                let width = self.build_count(arg.format.width);
-                let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, sym::FormatSpec));
-                let fmt = self.ecx.expr_struct(
-                    sp,
-                    path,
-                    vec![
-                        self.ecx.field_imm(sp, Ident::new(sym::fill, sp), fill),
-                        self.ecx.field_imm(sp, Ident::new(sym::align, sp), align),
-                        self.ecx.field_imm(sp, Ident::new(sym::flags, sp), flags),
-                        self.ecx.field_imm(sp, Ident::new(sym::precision, sp), prec),
-                        self.ecx.field_imm(sp, Ident::new(sym::width, sp), width),
-                    ],
-                );
-
-                let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, sym::Argument));
-                Some(self.ecx.expr_struct(
-                    sp,
-                    path,
-                    vec![
-                        self.ecx.field_imm(sp, Ident::new(sym::position, sp), pos),
-                        self.ecx.field_imm(sp, Ident::new(sym::format, sp), fmt),
-                    ],
-                ))
-            }
-        }
-    }
-
-    /// Actually builds the expression which the format_args! block will be
-    /// expanded to.
-    fn into_expr(self) -> P<ast::Expr> {
-        let mut original_args = self.args;
-        let mut fmt_args = Vec::with_capacity(
-            self.arg_unique_types.iter().map(|v| v.len()).sum::<usize>() + self.count_args.len(),
-        );
-
-        // First, build up the static array which will become our precompiled
-        // format "string"
-        let pieces = self.ecx.expr_array_ref(self.fmtsp, self.str_pieces);
-
-        // We need to construct a &[ArgumentV1] to pass into the fmt::Arguments
-        // constructor. In general the expressions in this slice might be
-        // permuted from their order in original_args (such as in the case of
-        // "{1} {0}"), or may have multiple entries referring to the same
-        // element of original_args ("{0} {0}").
-        //
-        // The following vector has one item per element of our output slice,
-        // identifying the index of which element of original_args it's passing,
-        // and that argument's type.
-        let mut fmt_arg_index_and_ty = SmallVec::<[(usize, &ArgumentType); 8]>::new();
-        for (i, unique_types) in self.arg_unique_types.iter().enumerate() {
-            fmt_arg_index_and_ty.extend(unique_types.iter().map(|ty| (i, ty)));
-        }
-        fmt_arg_index_and_ty.extend(self.count_args.iter().map(|&i| (i, &Count)));
-
-        // Figure out whether there are permuted or repeated elements. If not,
-        // we can generate simpler code.
-        //
-        // The sequence has no indices out of order or repeated if: for every
-        // adjacent pair of elements, the first one's index is less than the
-        // second one's index.
-        let nicely_ordered =
-            fmt_arg_index_and_ty.array_windows().all(|[(i, _i_ty), (j, _j_ty)]| i < j);
-
-        // We want to emit:
-        //
-        //     [ArgumentV1::new(&$arg0, …), ArgumentV1::new(&$arg1, …), …]
-        //
-        // However, it's only legal to do so if $arg0, $arg1, … were written in
-        // exactly that order by the programmer. When arguments are permuted, we
-        // want them evaluated in the order written by the programmer, not in
-        // the order provided to fmt::Arguments. When arguments are repeated, we
-        // want the expression evaluated only once.
-        //
-        // Further, if any arg _after the first one_ contains a yield point such
-        // as `await` or `yield`, the above short form is inconvenient for the
-        // caller because it would keep a temporary of type ArgumentV1 alive
-        // across the yield point. ArgumentV1 can't implement Send since it
-        // holds a type-erased arbitrary type.
-        //
-        // Thus in the not nicely ordered case, and in the yielding case, we
-        // emit the following instead:
-        //
-        //     match (&$arg0, &$arg1, …) {
-        //         args => [ArgumentV1::new(args.$i, …), ArgumentV1::new(args.$j, …), …]
-        //     }
-        //
-        // for the sequence of indices $i, $j, … governed by fmt_arg_index_and_ty.
-        // This more verbose representation ensures that all arguments are
-        // evaluated a single time each, in the order written by the programmer,
-        // and that the surrounding future/generator (if any) is Send whenever
-        // possible.
-        let no_need_for_match = nicely_ordered
-            && !original_args.iter().skip(1).any(|arg| may_contain_yield_point(&arg.expr));
-
-        for (arg_index, arg_ty) in fmt_arg_index_and_ty {
-            let e = &mut original_args[arg_index].expr;
-            let span = e.span;
-            let arg = if no_need_for_match {
-                let expansion_span = e.span.with_ctxt(self.macsp.ctxt());
-                // The indices are strictly ordered so e has not been taken yet.
-                self.ecx.expr_addr_of(expansion_span, P(e.take()))
-            } else {
-                let def_site = self.ecx.with_def_site_ctxt(span);
-                let args_tuple = self.ecx.expr_ident(def_site, Ident::new(sym::args, def_site));
-                let member = Ident::new(sym::integer(arg_index), def_site);
-                self.ecx.expr(def_site, ast::ExprKind::Field(args_tuple, member))
-            };
-            fmt_args.push(Context::format_arg(self.ecx, self.macsp, span, arg_ty, arg));
-        }
-
-        let args_array = self.ecx.expr_array(self.macsp, fmt_args);
-        let args_slice = self.ecx.expr_addr_of(
-            self.macsp,
-            if no_need_for_match {
-                args_array
-            } else {
-                // In the !no_need_for_match case, none of the exprs were moved
-                // away in the previous loop.
-                //
-                // This uses the arg span for `&arg` so that borrowck errors
-                // point to the specific expression passed to the macro (the
-                // span is otherwise unavailable in the MIR used by borrowck).
-                let heads = original_args
-                    .into_iter()
-                    .map(|arg| {
-                        self.ecx.expr_addr_of(arg.expr.span.with_ctxt(self.macsp.ctxt()), arg.expr)
-                    })
-                    .collect();
-
-                let pat = self.ecx.pat_ident(self.macsp, Ident::new(sym::args, self.macsp));
-                let arm = self.ecx.arm(self.macsp, pat, args_array);
-                let head = self.ecx.expr(self.macsp, ast::ExprKind::Tup(heads));
-                self.ecx.expr_match(self.macsp, head, vec![arm])
-            },
-        );
-
-        // Now create the fmt::Arguments struct with all our locals we created.
-        let (fn_name, fn_args) = if self.all_pieces_simple {
-            ("new_v1", vec![pieces, args_slice])
-        } else {
-            // Build up the static array which will store our precompiled
-            // nonstandard placeholders, if there are any.
-            let fmt = self.ecx.expr_array_ref(self.macsp, self.pieces);
-
-            let path = self.ecx.std_path(&[sym::fmt, sym::UnsafeArg, sym::new]);
-            let unsafe_arg = self.ecx.expr_call_global(self.macsp, path, Vec::new());
-            let unsafe_expr = self.ecx.expr_block(P(ast::Block {
-                stmts: vec![self.ecx.stmt_expr(unsafe_arg)],
-                id: ast::DUMMY_NODE_ID,
-                rules: BlockCheckMode::Unsafe(UnsafeSource::CompilerGenerated),
-                span: self.macsp,
-                tokens: None,
-                could_be_bare_literal: false,
-            }));
-
-            ("new_v1_formatted", vec![pieces, args_slice, fmt, unsafe_expr])
-        };
-
-        let path = self.ecx.std_path(&[sym::fmt, sym::Arguments, Symbol::intern(fn_name)]);
-        self.ecx.expr_call_global(self.macsp, path, fn_args)
-    }
-
-    fn format_arg(
-        ecx: &ExtCtxt<'_>,
-        macsp: Span,
-        mut sp: Span,
-        ty: &ArgumentType,
-        arg: P<ast::Expr>,
-    ) -> P<ast::Expr> {
-        sp = ecx.with_def_site_ctxt(sp);
-        let trait_ = match *ty {
-            Placeholder(trait_) if trait_ == "<invalid>" => return DummyResult::raw_expr(sp, true),
-            Placeholder(trait_) => trait_,
-            Count => {
-                let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, sym::from_usize]);
-                return ecx.expr_call_global(macsp, path, vec![arg]);
-            }
-        };
-        let new_fn_name = match trait_ {
-            "Display" => "new_display",
-            "Debug" => "new_debug",
-            "LowerExp" => "new_lower_exp",
-            "UpperExp" => "new_upper_exp",
-            "Octal" => "new_octal",
-            "Pointer" => "new_pointer",
-            "Binary" => "new_binary",
-            "LowerHex" => "new_lower_hex",
-            "UpperHex" => "new_upper_hex",
-            _ => unreachable!(),
-        };
-
-        let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, Symbol::intern(new_fn_name)]);
-        ecx.expr_call_global(sp, path, vec![arg])
-    }
-}
-
-fn expand_format_args_impl<'cx>(
-    ecx: &'cx mut ExtCtxt<'_>,
-    mut sp: Span,
-    tts: TokenStream,
-    nl: bool,
-) -> Box<dyn base::MacResult + 'cx> {
-    sp = ecx.with_def_site_ctxt(sp);
-    match parse_args(ecx, sp, tts) {
-        Ok((efmt, args, names)) => {
-            MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt, args, names, nl))
-        }
-        Err(mut err) => {
-            err.emit();
-            DummyResult::any(sp)
-        }
-    }
-}
-
-pub fn expand_format_args<'cx>(
-    ecx: &'cx mut ExtCtxt<'_>,
-    sp: Span,
-    tts: TokenStream,
-) -> Box<dyn base::MacResult + 'cx> {
-    expand_format_args_impl(ecx, sp, tts, false)
-}
-
-pub fn expand_format_args_nl<'cx>(
-    ecx: &'cx mut ExtCtxt<'_>,
-    sp: Span,
-    tts: TokenStream,
-) -> Box<dyn base::MacResult + 'cx> {
-    expand_format_args_impl(ecx, sp, tts, true)
-}
-
-fn create_lints_for_named_arguments_used_positionally(cx: &mut Context<'_, '_>) {
-    for named_arg in &cx.unused_names_lint.positional_named_args {
-        let (position_sp_to_replace, position_sp_for_msg) = named_arg.get_positional_arg_spans(cx);
-
-        let msg = format!("named argument `{}` is not used by name", named_arg.replacement);
-
-        cx.ecx.buffered_early_lint.push(BufferedEarlyLint {
-            span: MultiSpan::from_span(named_arg.positional_named_arg_span),
-            msg: msg.into(),
-            node_id: ast::CRATE_NODE_ID,
-            lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY),
-            diagnostic: BuiltinLintDiagnostics::NamedArgumentUsedPositionally {
-                position_sp_to_replace,
-                position_sp_for_msg,
-                named_arg_sp: named_arg.positional_named_arg_span,
-                named_arg_name: named_arg.replacement.to_string(),
-                is_formatting_arg: named_arg.ty != PositionalNamedArgType::Arg,
-            },
-        });
-    }
-}
-
-/// Take the various parts of `format_args!(efmt, args..., name=names...)`
-/// and construct the appropriate formatting expression.
-pub fn expand_preparsed_format_args(
+pub fn make_format_args(
     ecx: &mut ExtCtxt<'_>,
-    sp: Span,
-    efmt: P<ast::Expr>,
-    args: Vec<FormatArg>,
-    names: FxHashMap<Symbol, usize>,
+    efmt: P<Expr>,
+    mut args: FormatArguments,
     append_newline: bool,
-) -> P<ast::Expr> {
-    // NOTE: this verbose way of initializing `Vec<Vec<ArgumentType>>` is because
-    // `ArgumentType` does not derive `Clone`.
-    let arg_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect();
-    let arg_unique_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect();
-
-    let mut macsp = ecx.call_site();
-    macsp = ecx.with_def_site_ctxt(macsp);
-
+) -> Result<FormatArgs, ()> {
     let msg = "format argument must be a string literal";
-    let fmt_sp = efmt.span;
-    let efmt_kind_is_lit: bool = matches!(efmt.kind, ast::ExprKind::Lit(_));
+    let fmt_span = efmt.span;
     let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) {
         Ok(mut fmt) if append_newline => {
             fmt.0 = Symbol::intern(&format!("{}\n", fmt.0));
@@ -1224,13 +168,13 @@ pub fn expand_preparsed_format_args(
         Ok(fmt) => fmt,
         Err(err) => {
             if let Some((mut err, suggested)) = err {
-                let sugg_fmt = match args.len() {
+                let sugg_fmt = match args.explicit_args().len() {
                     0 => "{}".to_string(),
-                    _ => format!("{}{{}}", "{} ".repeat(args.len())),
+                    _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())),
                 };
                 if !suggested {
                     err.span_suggestion(
-                        fmt_sp.shrink_to_lo(),
+                        fmt_span.shrink_to_lo(),
                         "you might be missing a string literal to format with",
                         format!("\"{}\", ", sugg_fmt),
                         Applicability::MaybeIncorrect,
@@ -1238,17 +182,17 @@ pub fn expand_preparsed_format_args(
                 }
                 err.emit();
             }
-            return DummyResult::raw_expr(sp, true);
+            return Err(());
         }
     };
 
     let str_style = match fmt_style {
-        ast::StrStyle::Cooked => None,
-        ast::StrStyle::Raw(raw) => Some(raw as usize),
+        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(fmt_sp).ok();
+    let fmt_snippet = ecx.source_map().span_to_snippet(fmt_span).ok();
     let mut parser = parse::Parser::new(
         fmt_str,
         str_style,
@@ -1257,18 +201,20 @@ pub fn expand_preparsed_format_args(
         parse::ParseMode::Format,
     );
 
-    let mut unverified_pieces = Vec::new();
+    let mut pieces = Vec::new();
     while let Some(piece) = parser.next() {
         if !parser.errors.is_empty() {
             break;
         } else {
-            unverified_pieces.push(piece);
+            pieces.push(piece);
         }
     }
 
+    let is_literal = parser.is_literal;
+
     if !parser.errors.is_empty() {
         let err = parser.errors.remove(0);
-        let sp = if efmt_kind_is_lit {
+        let sp = if is_literal {
             fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end))
         } else {
             // The format string could be another macro invocation, e.g.:
@@ -1286,25 +232,21 @@ pub fn expand_preparsed_format_args(
         if let Some(note) = err.note {
             e.note(&note);
         }
-        if let Some((label, span)) = err.secondary_label {
-            if efmt_kind_is_lit {
-                e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label);
-            }
+        if let Some((label, span)) = err.secondary_label && is_literal {
+            e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label);
         }
         if err.should_be_replaced_with_positional_argument {
             let captured_arg_span =
                 fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
-            let n_positional_args =
-                args.iter().rposition(|arg| arg.name.is_none()).map_or(0, |i| i + 1);
             if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) {
-                let span = match args[..n_positional_args].last() {
+                let span = match args.unnamed_args().last() {
                     Some(arg) => arg.expr.span,
-                    None => fmt_sp,
+                    None => fmt_span,
                 };
                 e.multipart_suggestion_verbose(
                     "consider using a positional formatting argument instead",
                     vec![
-                        (captured_arg_span, n_positional_args.to_string()),
+                        (captured_arg_span, args.unnamed_args().len().to_string()),
                         (span.shrink_to_hi(), format!(", {}", arg)),
                     ],
                     Applicability::MachineApplicable,
@@ -1312,241 +254,626 @@ pub fn expand_preparsed_format_args(
             }
         }
         e.emit();
-        return DummyResult::raw_expr(sp, true);
+        return Err(());
     }
 
-    let arg_spans = parser
-        .arg_places
-        .iter()
-        .map(|span| fmt_span.from_inner(InnerSpan::new(span.start, span.end)))
-        .collect();
+    let to_span = |inner_span: rustc_parse_format::InnerSpan| {
+        is_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_refences_to_named_arg = Vec::new();
 
-    let mut cx = Context {
-        ecx,
-        args,
-        num_captured_args: 0,
-        arg_types,
-        arg_unique_types,
-        names,
-        curarg: 0,
-        curpiece: 0,
-        arg_index_map: Vec::new(),
-        count_args: Vec::new(),
-        count_positions: FxHashMap::default(),
-        count_positions_count: 0,
-        count_args_index_offset: 0,
-        literal: String::new(),
-        pieces: Vec::with_capacity(unverified_pieces.len()),
-        str_pieces: Vec::with_capacity(unverified_pieces.len()),
-        all_pieces_simple: true,
-        macsp,
-        fmtsp: fmt_span,
-        invalid_refs: Vec::new(),
-        arg_spans,
-        arg_with_formatting: Vec::new(),
-        is_literal: parser.is_literal,
-        unused_names_lint: PositionalNamedArgsLint { positional_named_args: vec![] },
+    enum ArgRef<'a> {
+        Index(usize),
+        Name(&'a str, Option<Span>),
+    }
+    use ArgRef::*;
+
+    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_refences_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_literal {
+                        ecx.expr_ident(span, ident)
+                    } else {
+                        // For the moment capturing variables from format strings expanded from macros is
+                        // disabled (see RFC #2795)
+                        ecx.struct_span_err(span, &format!("there is no argument named `{name}`"))
+                            .note(format!("did you intend to capture a variable `{name}` from the surrounding scope?"))
+                            .note("to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro")
+                            .emit();
+                        DummyResult::raw_expr(span, true)
+                    };
+                    Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }))
+                }
+            }
+        };
+        FormatArgPosition { index, kind, span }
     };
 
-    // This needs to happen *after* the Parser has consumed all pieces to create all the spans
-    let pieces = unverified_pieces
-        .into_iter()
-        .map(|mut piece| {
-            cx.verify_piece(&piece);
-            cx.resolve_name_inplace(&mut piece);
-            piece
-        })
-        .collect::<Vec<_>>();
+    let mut template = Vec::new();
+    let mut unfinished_literal = String::new();
+    let mut placeholder_index = 0;
 
-    let numbered_position_args = pieces.iter().any(|arg: &parse::Piece<'_>| match *arg {
-        parse::String(_) => false,
-        parse::NextArgument(arg) => matches!(arg.position, parse::Position::ArgumentIs(..)),
-    });
+    for piece in pieces {
+        match piece {
+            parse::Piece::String(s) => {
+                unfinished_literal.push_str(s);
+            }
+            parse::Piece::NextArgument(parse::Argument { position, position_span, format }) => {
+                if !unfinished_literal.is_empty() {
+                    template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
+                    unfinished_literal.clear();
+                }
 
-    cx.build_index_map();
+                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 mut arg_index_consumed = vec![0usize; cx.arg_index_map.len()];
+                let alignment = match format.align {
+                    parse::AlignUnknown => None,
+                    parse::AlignLeft => Some(FormatAlignment::Left),
+                    parse::AlignRight => Some(FormatAlignment::Right),
+                    parse::AlignCenter => Some(FormatAlignment::Center),
+                };
 
-    for piece in pieces {
-        if let Some(piece) = cx.build_piece(&piece, &mut arg_index_consumed) {
-            let s = cx.build_literal_string();
-            cx.str_pieces.push(s);
-            cx.pieces.push(piece);
+                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,
+                        flags: format.flags,
+                        precision,
+                        width,
+                    },
+                }));
+            }
         }
     }
 
-    if !cx.literal.is_empty() {
-        let s = cx.build_literal_string();
-        cx.str_pieces.push(s);
+    if !unfinished_literal.is_empty() {
+        template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
     }
 
-    if !cx.invalid_refs.is_empty() {
-        cx.report_invalid_references(numbered_position_args);
+    if !invalid_refs.is_empty() {
+        report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser);
     }
 
-    // Make sure that all arguments were used and all arguments have types.
-    let errs = cx
-        .arg_types
+    let unused = used
         .iter()
         .enumerate()
-        .filter(|(i, ty)| ty.is_empty() && !cx.count_positions.contains_key(&i))
+        .filter(|&(_, used)| !used)
         .map(|(i, _)| {
-            let msg = if cx.args[i].name.is_some() {
+            let msg = if let FormatArgumentKind::Named(_) = args.explicit_args()[i].kind {
                 "named argument never used"
             } else {
                 "argument never used"
             };
-            (cx.args[i].expr.span, msg)
+            (args.explicit_args()[i].expr.span, msg)
         })
         .collect::<Vec<_>>();
 
-    let errs_len = errs.len();
-    if !errs.is_empty() {
-        let args_used = cx.arg_types.len() - errs_len;
-        let args_unused = errs_len;
+    if !unused.is_empty() {
+        // 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, detect_foreign_fmt, str_style, fmt_str, fmt_span);
+    }
 
-        let mut diag = {
-            if let [(sp, msg)] = &errs[..] {
-                let mut diag = cx.ecx.struct_span_err(*sp, *msg);
-                diag.span_label(*sp, *msg);
-                diag
-            } else {
-                let mut diag = cx.ecx.struct_span_err(
-                    errs.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
-                    "multiple unused formatting arguments",
-                );
-                diag.span_label(cx.fmtsp, "multiple missing formatting specifiers");
-                for (sp, msg) in errs {
-                    diag.span_label(sp, msg);
+    // 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() && ecx.sess.err_count() == 0 {
+        for &(index, span, used_as) in &numeric_refences_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)
                 }
-                diag
-            }
-        };
+                Width => (span, span),
+            };
+            let arg_name = args.explicit_args()[index].kind.ident().unwrap();
+            ecx.buffered_early_lint.push(BufferedEarlyLint {
+                span: arg_name.span.into(),
+                msg: format!("named argument `{}` is not used by name", arg_name.name).into(),
+                node_id: rustc_ast::CRATE_NODE_ID,
+                lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY),
+                diagnostic: BuiltinLintDiagnostics::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),
+                },
+            });
+        }
+    }
 
-        // 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 args_used < args_unused {
-            use super::format_foreign as foreign;
+    Ok(FormatArgs { span: fmt_span, template, arguments: args })
+}
 
-            // 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();
+fn invalid_placeholder_type_error(
+    ecx: &ExtCtxt<'_>,
+    ty: &str,
+    ty_span: Option<rustc_parse_format::InnerSpan>,
+    fmt_span: Span,
+) {
+    let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end)));
+    let mut err =
+        ecx.struct_span_err(sp.unwrap_or(fmt_span), &format!("unknown format trait `{}`", ty));
+    err.note(
+        "the only appropriate formatting traits are:\n\
+                                - ``, which uses the `Display` trait\n\
+                                - `?`, which uses the `Debug` trait\n\
+                                - `e`, which uses the `LowerExp` trait\n\
+                                - `E`, which uses the `UpperExp` trait\n\
+                                - `o`, which uses the `Octal` trait\n\
+                                - `p`, which uses the `Pointer` trait\n\
+                                - `b`, which uses the `Binary` trait\n\
+                                - `x`, which uses the `LowerHex` trait\n\
+                                - `X`, which uses the `UpperHex` trait",
+    );
+    if let Some(sp) = sp {
+        for (fmt, name) in &[
+            ("", "Display"),
+            ("?", "Debug"),
+            ("e", "LowerExp"),
+            ("E", "UpperExp"),
+            ("o", "Octal"),
+            ("p", "Pointer"),
+            ("b", "Binary"),
+            ("x", "LowerHex"),
+            ("X", "UpperHex"),
+        ] {
+            err.tool_only_span_suggestion(
+                sp,
+                &format!("use the `{}` trait", name),
+                *fmt,
+                Applicability::MaybeIncorrect,
+            );
+        }
+    }
+    err.emit();
+}
 
-            macro_rules! check_foreign {
-                ($kind:ident) => {{
-                    let mut show_doc_note = false;
+fn report_missing_placeholders(
+    ecx: &mut ExtCtxt<'_>,
+    unused: Vec<(Span, &str)>,
+    detect_foreign_fmt: bool,
+    str_style: Option<usize>,
+    fmt_str: &str,
+    fmt_span: Span,
+) {
+    let mut diag = if let &[(span, msg)] = &unused[..] {
+        let mut diag = ecx.struct_span_err(span, msg);
+        diag.span_label(span, msg);
+        diag
+    } else {
+        let mut diag = ecx.struct_span_err(
+            unused.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
+            "multiple unused formatting arguments",
+        );
+        diag.span_label(fmt_span, "multiple missing formatting specifiers");
+        for &(span, msg) in &unused {
+            diag.span_label(span, msg);
+        }
+        diag
+    };
 
-                    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),
+    // 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.clone());
 
-                            // If it has no translation, don't call it out specifically.
-                            _ => continue,
-                        };
+                    if !found_foreign {
+                        found_foreign = true;
+                        show_doc_note = true;
+                    }
 
-                        let pos = sub.position();
-                        let sub = String::from(sub.as_str());
-                        if explained.contains(&sub) {
-                            continue;
-                        }
-                        explained.insert(sub.clone());
+                    if let Some(inner_sp) = pos {
+                        let sp = fmt_span.from_inner(inner_sp);
 
-                        if !found_foreign {
-                            found_foreign = true;
-                            show_doc_note = true;
+                        if success {
+                            suggestions.push((sp, trn));
+                        } else {
+                            diag.span_note(
+                                sp,
+                                &format!("format specifiers use curly braces, and {}", trn),
+                            );
                         }
-
-                        if let Some(inner_sp) = pos {
-                            let sp = fmt_sp.from_inner(inner_sp);
-
-                            if success {
-                                suggestions.push((sp, trn));
-                            } else {
-                                diag.span_note(
-                                    sp,
-                                    &format!("format specifiers use curly braces, and {}", trn),
-                                );
-                            }
+                    } else {
+                        if success {
+                            diag.help(&format!("`{}` should be written as `{}`", sub, trn));
                         } else {
-                            if success {
-                                diag.help(&format!("`{}` should be written as `{}`", sub, trn));
-                            } else {
-                                diag.note(&format!(
-                                    "`{}` should use curly braces, and {}",
-                                    sub, trn
-                                ));
-                            }
+                            diag.note(&format!("`{}` should use curly braces, and {}", sub, trn));
                         }
                     }
+                }
 
-                    if show_doc_note {
-                        diag.note(concat!(
-                            stringify!($kind),
-                            " formatting 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 && errs_len == 1 {
-            diag.span_label(cx.fmtsp, "formatting specifier missing");
+                if show_doc_note {
+                    diag.note(concat!(
+                        stringify!($kind),
+                        " formatting not supported; see the documentation for `std::fmt`",
+                    ));
+                }
+                if suggestions.len() > 0 {
+                    diag.multipart_suggestion(
+                        "format specifiers use curly braces",
+                        suggestions,
+                        Applicability::MachineApplicable,
+                    );
+                }
+            }};
         }
 
-        diag.emit();
-    } else if cx.invalid_refs.is_empty() && cx.ecx.sess.err_count() == 0 {
-        // 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.
-        create_lints_for_named_arguments_used_positionally(&mut cx);
+        check_foreign!(printf);
+        if !found_foreign {
+            check_foreign!(shell);
+        }
+    }
+    if !found_foreign && unused.len() == 1 {
+        diag.span_label(fmt_span, "formatting specifier missing");
     }
 
-    cx.into_expr()
+    diag.emit();
 }
 
-fn may_contain_yield_point(e: &ast::Expr) -> bool {
-    struct MayContainYieldPoint(bool);
+/// 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: &mut 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 {} arguments", n),
+    };
+
+    let mut e;
 
-    impl Visitor<'_> for MayContainYieldPoint {
-        fn visit_expr(&mut self, e: &ast::Expr) {
-            if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind {
-                self.0 = true;
-            } else {
-                visit::walk_expr(self, 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);
         }
-
-        fn visit_mac_call(&mut self, _: &ast::MacCall) {
-            self.0 = true;
+        let span = if spans.is_empty() {
+            MultiSpan::from_span(fmt_span)
+        } else {
+            MultiSpan::from_spans(spans)
+        };
+        e = ecx.struct_span_err(
+            span,
+            &format!(
+                "{} positional argument{} in format string, but {}",
+                num_placeholders,
+                pluralize!(num_placeholders),
+                num_args_desc,
+            ),
+        );
+        for arg in args.explicit_args() {
+            e.span_label(arg.expr.span, "");
+        }
+        // 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 {} arguments", num_placeholders)
+                        },
+                    ),
+                );
+            }
+        }
+        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_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.struct_span_err(
+            span,
+            &format!("invalid reference to positional {} ({})", arg_list, num_args_desc),
+        );
+        e.note("positional arguments are zero-based");
+    }
 
-        fn visit_attribute(&mut self, _: &ast::Attribute) {
-            // Conservatively assume this may be a proc macro attribute in
-            // expression position.
-            self.0 = true;
+    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 visit_item(&mut self, _: &ast::Item) {
-            // Do not recurse into nested items.
+fn expand_format_args_impl<'cx>(
+    ecx: &'cx mut ExtCtxt<'_>,
+    mut sp: Span,
+    tts: TokenStream,
+    nl: bool,
+) -> Box<dyn base::MacResult + 'cx> {
+    sp = ecx.with_def_site_ctxt(sp);
+    match parse_args(ecx, sp, tts) {
+        Ok((efmt, args)) => {
+            if let Ok(format_args) = make_format_args(ecx, efmt, args, nl) {
+                MacEager::expr(expand_parsed_format_args(ecx, format_args))
+            } else {
+                MacEager::expr(DummyResult::raw_expr(sp, true))
+            }
+        }
+        Err(mut err) => {
+            err.emit();
+            DummyResult::any(sp)
         }
     }
+}
+
+pub fn expand_format_args<'cx>(
+    ecx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+    expand_format_args_impl(ecx, sp, tts, false)
+}
 
-    let mut visitor = MayContainYieldPoint(false);
-    visitor.visit_expr(e);
-    visitor.0
+pub fn expand_format_args_nl<'cx>(
+    ecx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+    expand_format_args_impl(ecx, sp, tts, true)
 }
diff --git a/compiler/rustc_builtin_macros/src/format/ast.rs b/compiler/rustc_builtin_macros/src/format/ast.rs
new file mode 100644
index 0000000000000..01dbffa21b8aa
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/format/ast.rs
@@ -0,0 +1,240 @@
+use rustc_ast::ptr::P;
+use rustc_ast::Expr;
+use rustc_data_structures::fx::FxHashMap;
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::Span;
+
+// Definitions:
+//
+// format_args!("hello {abc:.xyz$}!!", abc="world");
+// └──────────────────────────────────────────────┘
+//                     FormatArgs
+//
+// format_args!("hello {abc:.xyz$}!!", abc="world");
+//                                     └─────────┘
+//                                      argument
+//
+// format_args!("hello {abc:.xyz$}!!", abc="world");
+//              └───────────────────┘
+//                     template
+//
+// format_args!("hello {abc:.xyz$}!!", abc="world");
+//               └────┘└─────────┘└┘
+//                      pieces
+//
+// format_args!("hello {abc:.xyz$}!!", abc="world");
+//               └────┘           └┘
+//                   literal pieces
+//
+// format_args!("hello {abc:.xyz$}!!", abc="world");
+//                     └─────────┘
+//                     placeholder
+//
+// format_args!("hello {abc:.xyz$}!!", abc="world");
+//                      └─┘  └─┘
+//                      positions (could be names, numbers, empty, or `*`)
+
+/// (Parsed) format args.
+///
+/// Basically the "AST" for a complete `format_args!()`.
+///
+/// E.g., `format_args!("hello {name}");`.
+#[derive(Clone, Debug)]
+pub struct FormatArgs {
+    pub span: Span,
+    pub template: Vec<FormatArgsPiece>,
+    pub arguments: FormatArguments,
+}
+
+/// A piece of a format template string.
+///
+/// E.g. "hello" or "{name}".
+#[derive(Clone, Debug)]
+pub enum FormatArgsPiece {
+    Literal(Symbol),
+    Placeholder(FormatPlaceholder),
+}
+
+/// The arguments to format_args!().
+///
+/// E.g. `1, 2, name="ferris", n=3`,
+/// but also implicit captured arguments like `x` in `format_args!("{x}")`.
+#[derive(Clone, Debug)]
+pub struct FormatArguments {
+    arguments: Vec<FormatArgument>,
+    num_unnamed_args: usize,
+    num_explicit_args: usize,
+    names: FxHashMap<Symbol, usize>,
+}
+
+impl FormatArguments {
+    pub fn new() -> Self {
+        Self {
+            arguments: Vec::new(),
+            names: FxHashMap::default(),
+            num_unnamed_args: 0,
+            num_explicit_args: 0,
+        }
+    }
+
+    pub fn add(&mut self, arg: FormatArgument) -> usize {
+        let index = self.arguments.len();
+        if let Some(name) = arg.kind.ident() {
+            self.names.insert(name.name, index);
+        } else if self.names.is_empty() {
+            // Only count the unnamed args before the first named arg.
+            // (Any later ones are errors.)
+            self.num_unnamed_args += 1;
+        }
+        if !matches!(arg.kind, FormatArgumentKind::Captured(..)) {
+            // This is an explicit argument.
+            // Make sure that all arguments so far are explcit.
+            assert_eq!(
+                self.num_explicit_args,
+                self.arguments.len(),
+                "captured arguments must be added last"
+            );
+            self.num_explicit_args += 1;
+        }
+        self.arguments.push(arg);
+        index
+    }
+
+    pub fn by_name(&self, name: Symbol) -> Option<(usize, &FormatArgument)> {
+        let i = *self.names.get(&name)?;
+        Some((i, &self.arguments[i]))
+    }
+
+    pub fn by_index(&self, i: usize) -> Option<&FormatArgument> {
+        (i < self.num_explicit_args).then(|| &self.arguments[i])
+    }
+
+    pub fn unnamed_args(&self) -> &[FormatArgument] {
+        &self.arguments[..self.num_unnamed_args]
+    }
+
+    pub fn named_args(&self) -> &[FormatArgument] {
+        &self.arguments[self.num_unnamed_args..self.num_explicit_args]
+    }
+
+    pub fn explicit_args(&self) -> &[FormatArgument] {
+        &self.arguments[..self.num_explicit_args]
+    }
+
+    pub fn into_vec(self) -> Vec<FormatArgument> {
+        self.arguments
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct FormatArgument {
+    pub kind: FormatArgumentKind,
+    pub expr: P<Expr>,
+}
+
+#[derive(Clone, Debug)]
+pub enum FormatArgumentKind {
+    /// `format_args(…, arg)`
+    Normal,
+    /// `format_args(…, arg = 1)`
+    Named(Ident),
+    /// `format_args("… {arg} …")`
+    Captured(Ident),
+}
+
+impl FormatArgumentKind {
+    pub fn ident(&self) -> Option<Ident> {
+        match self {
+            &Self::Normal => None,
+            &Self::Named(id) => Some(id),
+            &Self::Captured(id) => Some(id),
+        }
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct FormatPlaceholder {
+    /// Index into [`FormatArgs::arguments`].
+    pub argument: FormatArgPosition,
+    /// The span inside the format string for the full `{…}` placeholder.
+    pub span: Option<Span>,
+    /// `{}`, `{:?}`, or `{:x}`, etc.
+    pub format_trait: FormatTrait,
+    /// `{}` or `{:.5}` or `{:-^20}`, etc.
+    pub format_options: FormatOptions,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct FormatArgPosition {
+    /// Which argument this position refers to (Ok),
+    /// or would've referred to if it existed (Err).
+    pub index: Result<usize, usize>,
+    /// What kind of position this is. See [`FormatArgPositionKind`].
+    pub kind: FormatArgPositionKind,
+    /// The span of the name or number.
+    pub span: Option<Span>,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum FormatArgPositionKind {
+    /// `{}` or `{:.*}`
+    Implicit,
+    /// `{1}` or `{:1$}` or `{:.1$}`
+    Number,
+    /// `{a}` or `{:a$}` or `{:.a$}`
+    Named,
+}
+
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+pub enum FormatTrait {
+    /// `{}`
+    Display,
+    /// `{:?}`
+    Debug,
+    /// `{:e}`
+    LowerExp,
+    /// `{:E}`
+    UpperExp,
+    /// `{:o}`
+    Octal,
+    /// `{:p}`
+    Pointer,
+    /// `{:b}`
+    Binary,
+    /// `{:x}`
+    LowerHex,
+    /// `{:X}`
+    UpperHex,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub struct FormatOptions {
+    /// The width. E.g. `{:5}` or `{:width$}`.
+    pub width: Option<FormatCount>,
+    /// The precision. E.g. `{:.5}` or `{:.precision$}`.
+    pub precision: Option<FormatCount>,
+    /// The alignment. E.g. `{:>}` or `{:<}` or `{:^}`.
+    pub alignment: Option<FormatAlignment>,
+    /// The fill character. E.g. the `.` in `{:.>10}`.
+    pub fill: Option<char>,
+    /// The `+`, `-`, `0`, `#`, `x?` and `X?` flags.
+    pub flags: u32,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum FormatAlignment {
+    /// `{:<}`
+    Left,
+    /// `{:>}`
+    Right,
+    /// `{:^}`
+    Center,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum FormatCount {
+    /// `{:5}` or `{:.5}`
+    Literal(usize),
+    /// `{:.*}`, `{:.5$}`, or `{:a$}`, etc.
+    Argument(FormatArgPosition),
+}
diff --git a/compiler/rustc_builtin_macros/src/format/expand.rs b/compiler/rustc_builtin_macros/src/format/expand.rs
new file mode 100644
index 0000000000000..9dde5efcb28b7
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/format/expand.rs
@@ -0,0 +1,353 @@
+use super::*;
+use rustc_ast as ast;
+use rustc_ast::visit::{self, Visitor};
+use rustc_ast::{BlockCheckMode, UnsafeSource};
+use rustc_data_structures::fx::FxIndexSet;
+use rustc_span::{sym, symbol::kw};
+
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+enum ArgumentType {
+    Format(FormatTrait),
+    Usize,
+}
+
+fn make_argument(ecx: &ExtCtxt<'_>, sp: Span, arg: P<ast::Expr>, ty: ArgumentType) -> P<ast::Expr> {
+    // Generate:
+    //     ::core::fmt::ArgumentV1::new_…(arg)
+    use ArgumentType::*;
+    use FormatTrait::*;
+    ecx.expr_call_global(
+        sp,
+        ecx.std_path(&[
+            sym::fmt,
+            sym::ArgumentV1,
+            match ty {
+                Format(Display) => sym::new_display,
+                Format(Debug) => sym::new_debug,
+                Format(LowerExp) => sym::new_lower_exp,
+                Format(UpperExp) => sym::new_upper_exp,
+                Format(Octal) => sym::new_octal,
+                Format(Pointer) => sym::new_pointer,
+                Format(Binary) => sym::new_binary,
+                Format(LowerHex) => sym::new_lower_hex,
+                Format(UpperHex) => sym::new_upper_hex,
+                Usize => sym::from_usize,
+            },
+        ]),
+        vec![arg],
+    )
+}
+
+fn make_count(
+    ecx: &ExtCtxt<'_>,
+    sp: Span,
+    count: &Option<FormatCount>,
+    argmap: &mut FxIndexSet<(usize, ArgumentType)>,
+) -> P<ast::Expr> {
+    // Generate:
+    //     ::core::fmt::rt::v1::Count::…(…)
+    match count {
+        Some(FormatCount::Literal(n)) => ecx.expr_call_global(
+            sp,
+            ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Is]),
+            vec![ecx.expr_usize(sp, *n)],
+        ),
+        Some(FormatCount::Argument(arg)) => {
+            if let Ok(arg_index) = arg.index {
+                let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize));
+                ecx.expr_call_global(
+                    sp,
+                    ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Param]),
+                    vec![ecx.expr_usize(sp, i)],
+                )
+            } else {
+                DummyResult::raw_expr(sp, true)
+            }
+        }
+        None => ecx.expr_path(ecx.path_global(
+            sp,
+            ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Implied]),
+        )),
+    }
+}
+
+fn make_format_spec(
+    ecx: &ExtCtxt<'_>,
+    sp: Span,
+    placeholder: &FormatPlaceholder,
+    argmap: &mut FxIndexSet<(usize, ArgumentType)>,
+) -> P<ast::Expr> {
+    // Generate:
+    //     ::core::fmt::rt::v1::Argument {
+    //         position: 0usize,
+    //         format: ::core::fmt::rt::v1::FormatSpec {
+    //             fill: ' ',
+    //             align: ::core::fmt::rt::v1::Alignment::Unknown,
+    //             flags: 0u32,
+    //             precision: ::core::fmt::rt::v1::Count::Implied,
+    //             width: ::core::fmt::rt::v1::Count::Implied,
+    //         },
+    //     }
+    let position = match placeholder.argument.index {
+        Ok(arg_index) => {
+            let (i, _) =
+                argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait)));
+            ecx.expr_usize(sp, i)
+        }
+        Err(_) => DummyResult::raw_expr(sp, true),
+    };
+    let fill = ecx.expr_char(sp, placeholder.format_options.fill.unwrap_or(' '));
+    let align = ecx.expr_path(ecx.path_global(
+        sp,
+        ecx.std_path(&[
+            sym::fmt,
+            sym::rt,
+            sym::v1,
+            sym::Alignment,
+            match placeholder.format_options.alignment {
+                Some(FormatAlignment::Left) => sym::Left,
+                Some(FormatAlignment::Right) => sym::Right,
+                Some(FormatAlignment::Center) => sym::Center,
+                None => sym::Unknown,
+            },
+        ]),
+    ));
+    let flags = ecx.expr_u32(sp, placeholder.format_options.flags);
+    let prec = make_count(ecx, sp, &placeholder.format_options.precision, argmap);
+    let width = make_count(ecx, sp, &placeholder.format_options.width, argmap);
+    ecx.expr_struct(
+        sp,
+        ecx.path_global(sp, ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Argument])),
+        vec![
+            ecx.field_imm(sp, Ident::new(sym::position, sp), position),
+            ecx.field_imm(
+                sp,
+                Ident::new(sym::format, sp),
+                ecx.expr_struct(
+                    sp,
+                    ecx.path_global(
+                        sp,
+                        ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::FormatSpec]),
+                    ),
+                    vec![
+                        ecx.field_imm(sp, Ident::new(sym::fill, sp), fill),
+                        ecx.field_imm(sp, Ident::new(sym::align, sp), align),
+                        ecx.field_imm(sp, Ident::new(sym::flags, sp), flags),
+                        ecx.field_imm(sp, Ident::new(sym::precision, sp), prec),
+                        ecx.field_imm(sp, Ident::new(sym::width, sp), width),
+                    ],
+                ),
+            ),
+        ],
+    )
+}
+
+pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P<ast::Expr> {
+    let macsp = ecx.with_def_site_ctxt(ecx.call_site());
+
+    let lit_pieces = ecx.expr_array_ref(
+        fmt.span,
+        fmt.template
+            .iter()
+            .enumerate()
+            .filter_map(|(i, piece)| match piece {
+                &FormatArgsPiece::Literal(s) => Some(ecx.expr_str(fmt.span, s)),
+                &FormatArgsPiece::Placeholder(_) => {
+                    // Inject empty string before placeholders when not already preceded by a literal piece.
+                    if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) {
+                        Some(ecx.expr_str(fmt.span, kw::Empty))
+                    } else {
+                        None
+                    }
+                }
+            })
+            .collect(),
+    );
+
+    // Whether we'll use the `Arguments::new_v1_formatted` form (true),
+    // or the `Arguments::new_v1` form (false).
+    let mut use_format_options = false;
+
+    // Create a list of all _unique_ (argument, format trait) combinations.
+    // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)]
+    let mut argmap = FxIndexSet::default();
+    for piece in &fmt.template {
+        let FormatArgsPiece::Placeholder(placeholder) = piece else { continue };
+        if placeholder.format_options != Default::default() {
+            // Can't use basic form if there's any formatting options.
+            use_format_options = true;
+        }
+        if let Ok(index) = placeholder.argument.index {
+            if !argmap.insert((index, ArgumentType::Format(placeholder.format_trait))) {
+                // Duplicate (argument, format trait) combination,
+                // which we'll only put once in the args array.
+                use_format_options = true;
+            }
+        }
+    }
+
+    let format_options = use_format_options.then(|| {
+        // Generate:
+        //     &[format_spec_0, format_spec_1, format_spec_2]
+        ecx.expr_array_ref(
+            macsp,
+            fmt.template
+                .iter()
+                .filter_map(|piece| {
+                    let FormatArgsPiece::Placeholder(placeholder) = piece else { return None };
+                    Some(make_format_spec(ecx, macsp, placeholder, &mut argmap))
+                })
+                .collect(),
+        )
+    });
+
+    let arguments = fmt.arguments.into_vec();
+
+    // If the args array contains exactly all the original arguments once,
+    // in order, we can use a simple array instead of a `match` construction.
+    // However, if there's a yield point in any argument except the first one,
+    // we don't do this, because an ArgumentV1 cannot be kept across yield points.
+    let use_simple_array = argmap.len() == arguments.len()
+        && argmap.iter().enumerate().all(|(i, &(j, _))| i == j)
+        && arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr));
+
+    let args = if use_simple_array {
+        // Generate:
+        //     &[
+        //         ::core::fmt::ArgumentV1::new_display(&arg0),
+        //         ::core::fmt::ArgumentV1::new_lower_hex(&arg1),
+        //         ::core::fmt::ArgumentV1::new_debug(&arg2),
+        //     ]
+        ecx.expr_array_ref(
+            macsp,
+            arguments
+                .into_iter()
+                .zip(argmap)
+                .map(|(arg, (_, ty))| {
+                    let sp = arg.expr.span.with_ctxt(macsp.ctxt());
+                    make_argument(ecx, sp, ecx.expr_addr_of(sp, arg.expr), ty)
+                })
+                .collect(),
+        )
+    } else {
+        // Generate:
+        //     match (&arg0, &arg1, &arg2) {
+        //         args => &[
+        //             ::core::fmt::ArgumentV1::new_display(args.0),
+        //             ::core::fmt::ArgumentV1::new_lower_hex(args.1),
+        //             ::core::fmt::ArgumentV1::new_debug(args.0),
+        //         ]
+        //     }
+        let args_ident = Ident::new(sym::args, macsp);
+        let args = argmap
+            .iter()
+            .map(|&(arg_index, ty)| {
+                if let Some(arg) = arguments.get(arg_index) {
+                    let sp = arg.expr.span.with_ctxt(macsp.ctxt());
+                    make_argument(
+                        ecx,
+                        sp,
+                        ecx.expr_field(
+                            sp,
+                            ecx.expr_ident(macsp, args_ident),
+                            Ident::new(sym::integer(arg_index), macsp),
+                        ),
+                        ty,
+                    )
+                } else {
+                    DummyResult::raw_expr(macsp, true)
+                }
+            })
+            .collect();
+        ecx.expr_addr_of(
+            macsp,
+            ecx.expr_match(
+                macsp,
+                ecx.expr_tuple(
+                    macsp,
+                    arguments
+                        .into_iter()
+                        .map(|arg| {
+                            ecx.expr_addr_of(arg.expr.span.with_ctxt(macsp.ctxt()), arg.expr)
+                        })
+                        .collect(),
+                ),
+                vec![ecx.arm(macsp, ecx.pat_ident(macsp, args_ident), ecx.expr_array(macsp, args))],
+            ),
+        )
+    };
+
+    if let Some(format_options) = format_options {
+        // Generate:
+        //     ::core::fmt::Arguments::new_v1_formatted(
+        //         lit_pieces,
+        //         args,
+        //         format_options,
+        //         unsafe { ::core::fmt::UnsafeArg::new() }
+        //     )
+        ecx.expr_call_global(
+            macsp,
+            ecx.std_path(&[sym::fmt, sym::Arguments, sym::new_v1_formatted]),
+            vec![
+                lit_pieces,
+                args,
+                format_options,
+                ecx.expr_block(P(ast::Block {
+                    stmts: vec![ecx.stmt_expr(ecx.expr_call_global(
+                        macsp,
+                        ecx.std_path(&[sym::fmt, sym::UnsafeArg, sym::new]),
+                        Vec::new(),
+                    ))],
+                    id: ast::DUMMY_NODE_ID,
+                    rules: BlockCheckMode::Unsafe(UnsafeSource::CompilerGenerated),
+                    span: macsp,
+                    tokens: None,
+                    could_be_bare_literal: false,
+                })),
+            ],
+        )
+    } else {
+        // Generate:
+        //     ::core::fmt::Arguments::new_v1(
+        //         lit_pieces,
+        //         args,
+        //     )
+        ecx.expr_call_global(
+            macsp,
+            ecx.std_path(&[sym::fmt, sym::Arguments, sym::new_v1]),
+            vec![lit_pieces, args],
+        )
+    }
+}
+
+fn may_contain_yield_point(e: &ast::Expr) -> bool {
+    struct MayContainYieldPoint(bool);
+
+    impl Visitor<'_> for MayContainYieldPoint {
+        fn visit_expr(&mut self, e: &ast::Expr) {
+            if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind {
+                self.0 = true;
+            } else {
+                visit::walk_expr(self, e);
+            }
+        }
+
+        fn visit_mac_call(&mut self, _: &ast::MacCall) {
+            self.0 = true;
+        }
+
+        fn visit_attribute(&mut self, _: &ast::Attribute) {
+            // Conservatively assume this may be a proc macro attribute in
+            // expression position.
+            self.0 = true;
+        }
+
+        fn visit_item(&mut self, _: &ast::Item) {
+            // Do not recurse into nested items.
+        }
+    }
+
+    let mut visitor = MayContainYieldPoint(false);
+    visitor.visit_expr(e);
+    visitor.0
+}
diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs
index 0de27d3d4070e..f058503064bf1 100644
--- a/compiler/rustc_builtin_macros/src/lib.rs
+++ b/compiler/rustc_builtin_macros/src/lib.rs
@@ -7,6 +7,7 @@
 #![feature(box_patterns)]
 #![feature(decl_macro)]
 #![feature(if_let_guard)]
+#![feature(is_some_with)]
 #![feature(is_sorted)]
 #![feature(let_chains)]
 #![feature(proc_macro_internals)]
diff --git a/compiler/rustc_expand/src/build.rs b/compiler/rustc_expand/src/build.rs
index 50d2be3cee5e0..0952e65cfee3d 100644
--- a/compiler/rustc_expand/src/build.rs
+++ b/compiler/rustc_expand/src/build.rs
@@ -252,6 +252,10 @@ impl<'a> ExtCtxt<'a> {
         self.expr_ident(span, Ident::with_dummy_span(kw::SelfLower))
     }
 
+    pub fn expr_field(&self, span: Span, expr: P<Expr>, field: Ident) -> P<ast::Expr> {
+        self.expr(span, ast::ExprKind::Field(expr, field))
+    }
+
     pub fn expr_binary(
         &self,
         sp: Span,
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 502ef67fc6767..7657f9486c227 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -991,7 +991,18 @@ symbols! {
         never_type,
         never_type_fallback,
         new,
+        new_binary,
+        new_debug,
+        new_display,
+        new_lower_exp,
+        new_lower_hex,
+        new_octal,
+        new_pointer,
         new_unchecked,
+        new_upper_exp,
+        new_upper_hex,
+        new_v1,
+        new_v1_formatted,
         next,
         nll,
         no,
diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
index 68602640a24f2..21a402c7b9da8 100644
--- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
+++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
@@ -237,21 +237,19 @@ LL |     #[suggestion(typeck::suggestion, code = "{name}")]
    |                                             ^^^^^^^^
 
 error: invalid format string: expected `'}'` but string was terminated
-  --> $DIR/diagnostic-derive.rs:175:16
+  --> $DIR/diagnostic-derive.rs:175:10
    |
 LL | #[derive(Diagnostic)]
-   |           -    ^ expected `'}'` in format string
-   |           |
-   |           because of this opening brace
+   |          ^^^^^^^^^^ expected `'}'` in format string
    |
    = note: if you intended to print `{`, you can escape it using `{{`
    = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: invalid format string: unmatched `}` found
-  --> $DIR/diagnostic-derive.rs:185:15
+  --> $DIR/diagnostic-derive.rs:185:10
    |
 LL | #[derive(Diagnostic)]
-   |               ^ unmatched `}` in format string
+   |          ^^^^^^^^^^ unmatched `}` in format string
    |
    = note: if you intended to print `}`, you can escape it using `}}`
    = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/src/test/ui/fmt/format-args-capture-issue-93378.rs b/src/test/ui/fmt/format-args-capture-issue-93378.rs
index 6744444426472..9d722a0287a5c 100644
--- a/src/test/ui/fmt/format-args-capture-issue-93378.rs
+++ b/src/test/ui/fmt/format-args-capture-issue-93378.rs
@@ -3,9 +3,9 @@ fn main() {
     let b = "b";
 
     println!("{a} {b} {} {} {c} {}", c = "c");
-    //~^ ERROR: invalid reference to positional arguments 1 and 2 (there is 1 argument)
+    //~^ ERROR: 3 positional arguments in format string, but there is 1 argument
 
     let n = 1;
     println!("{a:.n$} {b:.*}");
-    //~^ ERROR: invalid reference to positional argument 0 (no arguments were given)
+    //~^ ERROR: 1 positional argument in format string, but no arguments were given
 }
diff --git a/src/test/ui/fmt/format-args-capture-issue-93378.stderr b/src/test/ui/fmt/format-args-capture-issue-93378.stderr
index b8e2b2afb3867..6429b0d46f6af 100644
--- a/src/test/ui/fmt/format-args-capture-issue-93378.stderr
+++ b/src/test/ui/fmt/format-args-capture-issue-93378.stderr
@@ -1,19 +1,14 @@
-error: invalid reference to positional arguments 1 and 2 (there is 1 argument)
-  --> $DIR/format-args-capture-issue-93378.rs:5:26
+error: 3 positional arguments in format string, but there is 1 argument
+  --> $DIR/format-args-capture-issue-93378.rs:5:23
    |
 LL |     println!("{a} {b} {} {} {c} {}", c = "c");
-   |                          ^^     ^^
-   |
-   = note: positional arguments are zero-based
+   |                       ^^ ^^     ^^       ---
 
-error: invalid reference to positional argument 0 (no arguments were given)
-  --> $DIR/format-args-capture-issue-93378.rs:9:23
+error: 1 positional argument in format string, but no arguments were given
+  --> $DIR/format-args-capture-issue-93378.rs:9:26
    |
 LL |     println!("{a:.n$} {b:.*}");
-   |                   -   ^^^--^
-   |                   |      |
-   |                   |      this precision flag adds an extra required argument at position 0, which is why there are 3 arguments expected
-   |                   this parameter corresponds to the precision flag
+   |                          ^^ this precision flag adds an extra required argument at position 0, which is why there is 1 argument expected
    |
    = note: positional arguments are zero-based
    = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
diff --git a/src/test/ui/fmt/ifmt-bad-arg.rs b/src/test/ui/fmt/ifmt-bad-arg.rs
index f00cb05c9ebc3..68861d7bf3faf 100644
--- a/src/test/ui/fmt/ifmt-bad-arg.rs
+++ b/src/test/ui/fmt/ifmt-bad-arg.rs
@@ -20,9 +20,9 @@ fn main() {
     //~^ ERROR: invalid reference to positional argument 2 (there are 2 arguments)
 
     format!("{} {value} {} {}", 1, value=2);
-    //~^ ERROR: invalid reference to positional argument 2 (there are 2 arguments)
+    //~^ ERROR: 3 positional arguments in format string, but there are 2 arguments
     format!("{name} {value} {} {} {} {} {} {}", 0, name=1, value=2);
-    //~^ ERROR: invalid reference to positional arguments 3, 4 and 5 (there are 3 arguments)
+    //~^ ERROR: 6 positional arguments in format string, but there are 3 arguments
 
     format!("{} {foo} {} {bar} {}", 1, 2, 3);
     //~^ ERROR: cannot find value `foo` in this scope
@@ -79,7 +79,7 @@ tenth number: {}",
     //~^ ERROR 4 positional arguments in format string, but there are 3 arguments
     //~| ERROR mismatched types
     println!("{} {:07$.*} {}", 1, 3.2, 4);
-    //~^ ERROR 4 positional arguments in format string, but there are 3 arguments
+    //~^ ERROR invalid reference to positional arguments 3 and 7 (there are 3 arguments)
     //~| ERROR mismatched types
     println!("{} {:07$} {}", 1, 3.2, 4);
     //~^ ERROR invalid reference to positional argument 7 (there are 3 arguments)
@@ -95,5 +95,5 @@ tenth number: {}",
     println!("{:.*}");
     //~^ ERROR 2 positional arguments in format string, but no arguments were given
     println!("{:.0$}");
-    //~^ ERROR 1 positional argument in format string, but no arguments were given
+    //~^ ERROR invalid reference to positional argument 0 (no arguments were given)
 }
diff --git a/src/test/ui/fmt/ifmt-bad-arg.stderr b/src/test/ui/fmt/ifmt-bad-arg.stderr
index dbb4bc6d9370e..1b595a50e9984 100644
--- a/src/test/ui/fmt/ifmt-bad-arg.stderr
+++ b/src/test/ui/fmt/ifmt-bad-arg.stderr
@@ -5,10 +5,10 @@ LL |     format!("{}");
    |              ^^
 
 error: invalid reference to positional argument 1 (there is 1 argument)
-  --> $DIR/ifmt-bad-arg.rs:9:14
+  --> $DIR/ifmt-bad-arg.rs:9:15
    |
 LL |     format!("{1}", 1);
-   |              ^^^
+   |               ^
    |
    = note: positional arguments are zero-based
 
@@ -27,36 +27,32 @@ LL |     format!("{} {}");
    |              ^^ ^^
 
 error: invalid reference to positional argument 1 (there is 1 argument)
-  --> $DIR/ifmt-bad-arg.rs:16:18
+  --> $DIR/ifmt-bad-arg.rs:16:19
    |
 LL |     format!("{0} {1}", 1);
-   |                  ^^^
+   |                   ^
    |
    = note: positional arguments are zero-based
 
 error: invalid reference to positional argument 2 (there are 2 arguments)
-  --> $DIR/ifmt-bad-arg.rs:19:22
+  --> $DIR/ifmt-bad-arg.rs:19:23
    |
 LL |     format!("{0} {1} {2}", 1, 2);
-   |                      ^^^
+   |                       ^
    |
    = note: positional arguments are zero-based
 
-error: invalid reference to positional argument 2 (there are 2 arguments)
-  --> $DIR/ifmt-bad-arg.rs:22:28
+error: 3 positional arguments in format string, but there are 2 arguments
+  --> $DIR/ifmt-bad-arg.rs:22:14
    |
 LL |     format!("{} {value} {} {}", 1, value=2);
-   |                            ^^
-   |
-   = note: positional arguments are zero-based
+   |              ^^         ^^ ^^   -        -
 
-error: invalid reference to positional arguments 3, 4 and 5 (there are 3 arguments)
-  --> $DIR/ifmt-bad-arg.rs:24:38
+error: 6 positional arguments in format string, but there are 3 arguments
+  --> $DIR/ifmt-bad-arg.rs:24:29
    |
 LL |     format!("{name} {value} {} {} {} {} {} {}", 0, name=1, value=2);
-   |                                      ^^ ^^ ^^
-   |
-   = note: positional arguments are zero-based
+   |                             ^^ ^^ ^^ ^^ ^^ ^^   -       -        -
 
 error: multiple unused formatting arguments
   --> $DIR/ifmt-bad-arg.rs:32:17
@@ -117,20 +113,20 @@ LL |     format!("{} {}", 1, 2, foo=1, bar=2);
    |             multiple missing formatting specifiers
 
 error: duplicate argument named `foo`
-  --> $DIR/ifmt-bad-arg.rs:40:33
+  --> $DIR/ifmt-bad-arg.rs:40:29
    |
 LL |     format!("{foo}", foo=1, foo=2);
-   |                          -      ^ duplicate argument
-   |                          |
-   |                          previously here
+   |                      ---    ^^^ duplicate argument
+   |                      |
+   |                      previously here
 
 error: positional arguments cannot follow named arguments
   --> $DIR/ifmt-bad-arg.rs:41:35
    |
 LL |     format!("{foo} {} {}", foo=1, 2);
-   |                                -  ^ positional arguments must be before named arguments
-   |                                |
-   |                                named argument
+   |                            -----  ^ positional arguments must be before named arguments
+   |                            |
+   |                            named argument
 
 error: named argument never used
   --> $DIR/ifmt-bad-arg.rs:45:51
@@ -191,33 +187,26 @@ error: 4 positional arguments in format string, but there are 3 arguments
    |
 LL |     println!("{} {:.*} {}", 1, 3.2, 4);
    |               ^^ ^^--^ ^^   -  ---  -
-   |                    |           |
-   |                    |           this parameter corresponds to the precision flag
+   |                    |
    |                    this precision flag adds an extra required argument at position 1, which is why there are 4 arguments expected
    |
    = note: positional arguments are zero-based
    = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
 
-error: 4 positional arguments in format string, but there are 3 arguments
-  --> $DIR/ifmt-bad-arg.rs:81:15
+error: invalid reference to positional arguments 3 and 7 (there are 3 arguments)
+  --> $DIR/ifmt-bad-arg.rs:81:21
    |
 LL |     println!("{} {:07$.*} {}", 1, 3.2, 4);
-   |               ^^ ^^^----^ ^^   -  ---  -
-   |                     | |           |
-   |                     | |           this parameter corresponds to the precision flag
-   |                     | this precision flag adds an extra required argument at position 1, which is why there are 4 arguments expected
-   |                     this width flag expects an `usize` argument at position 7, but there are 3 arguments
+   |                     ^^     ^
    |
    = note: positional arguments are zero-based
    = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
 
 error: invalid reference to positional argument 7 (there are 3 arguments)
-  --> $DIR/ifmt-bad-arg.rs:84:18
+  --> $DIR/ifmt-bad-arg.rs:84:21
    |
 LL |     println!("{} {:07$} {}", 1, 3.2, 4);
-   |                  ^^^--^
-   |                     |
-   |                     this width flag expects an `usize` argument at position 7, but there are 3 arguments
+   |                     ^^
    |
    = note: positional arguments are zero-based
    = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
@@ -240,24 +229,19 @@ LL |     println!("{:foo}", 1);
            - `X`, which uses the `UpperHex` trait
 
 error: invalid reference to positional arguments 4, 5, 6 and 7 (there is 1 argument)
-  --> $DIR/ifmt-bad-arg.rs:87:15
+  --> $DIR/ifmt-bad-arg.rs:87:16
    |
 LL |     println!("{5} {:4$} {6:7$}", 1);
-   |               ^^^ ^^--^ ^^^--^
-   |                     |      |
-   |                     |      this width flag expects an `usize` argument at position 7, but there is 1 argument
-   |                     this width flag expects an `usize` argument at position 4, but there is 1 argument
+   |                ^    ^^   ^ ^^
    |
    = note: positional arguments are zero-based
    = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
 
 error: invalid reference to positional argument 0 (no arguments were given)
-  --> $DIR/ifmt-bad-arg.rs:90:15
+  --> $DIR/ifmt-bad-arg.rs:90:20
    |
 LL |     println!("{foo:0$}");
-   |               ^^^^^--^
-   |                    |
-   |                    this width flag expects an `usize` argument at position 0, but no arguments were given
+   |                    ^^
    |
    = note: positional arguments are zero-based
    = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
@@ -273,13 +257,11 @@ LL |     println!("{:.*}");
    = note: positional arguments are zero-based
    = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
 
-error: 1 positional argument in format string, but no arguments were given
-  --> $DIR/ifmt-bad-arg.rs:97:15
+error: invalid reference to positional argument 0 (no arguments were given)
+  --> $DIR/ifmt-bad-arg.rs:97:16
    |
 LL |     println!("{:.0$}");
-   |               ^^---^
-   |                 |
-   |                 this precision flag expects an `usize` argument at position 0, but no arguments were given
+   |                ^^^^
    |
    = note: positional arguments are zero-based
    = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
@@ -318,10 +300,10 @@ error[E0308]: mismatched types
   --> $DIR/ifmt-bad-arg.rs:78:32
    |
 LL |     println!("{} {:.*} {}", 1, 3.2, 4);
-   |     ---------------------------^^^----
-   |     |                          |
-   |     |                          expected `usize`, found floating-point number
-   |     arguments to this function are incorrect
+   |                                ^^^
+   |                                |
+   |                                expected `usize`, found floating-point number
+   |                                arguments to this function are incorrect
    |
    = note: expected reference `&usize`
               found reference `&{float}`
@@ -336,10 +318,10 @@ error[E0308]: mismatched types
   --> $DIR/ifmt-bad-arg.rs:81:35
    |
 LL |     println!("{} {:07$.*} {}", 1, 3.2, 4);
-   |     ------------------------------^^^----
-   |     |                             |
-   |     |                             expected `usize`, found floating-point number
-   |     arguments to this function are incorrect
+   |                                   ^^^
+   |                                   |
+   |                                   expected `usize`, found floating-point number
+   |                                   arguments to this function are incorrect
    |
    = note: expected reference `&usize`
               found reference `&{float}`
diff --git a/src/test/ui/issues/issue-75307.rs b/src/test/ui/issues/issue-75307.rs
index 2fe112a3b95d4..cffa6bea8ed38 100644
--- a/src/test/ui/issues/issue-75307.rs
+++ b/src/test/ui/issues/issue-75307.rs
@@ -1,3 +1,3 @@
 fn main() {
-    format!(r"{}{}{}", named_arg=1); //~ ERROR invalid reference to positional arguments 1 and 2
+    format!(r"{}{}{}", named_arg=1); //~ ERROR 3 positional arguments in format string, but there is 1 argument
 }
diff --git a/src/test/ui/issues/issue-75307.stderr b/src/test/ui/issues/issue-75307.stderr
index 10c952006c208..c5b0b11e7d099 100644
--- a/src/test/ui/issues/issue-75307.stderr
+++ b/src/test/ui/issues/issue-75307.stderr
@@ -1,10 +1,8 @@
-error: invalid reference to positional arguments 1 and 2 (there is 1 argument)
-  --> $DIR/issue-75307.rs:2:17
+error: 3 positional arguments in format string, but there is 1 argument
+  --> $DIR/issue-75307.rs:2:15
    |
 LL |     format!(r"{}{}{}", named_arg=1);
-   |                 ^^^^
-   |
-   = note: positional arguments are zero-based
+   |               ^^^^^^             -
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/macros/format-parse-errors.stderr b/src/test/ui/macros/format-parse-errors.stderr
index 1a7578e6076eb..f9ea4c63377b0 100644
--- a/src/test/ui/macros/format-parse-errors.stderr
+++ b/src/test/ui/macros/format-parse-errors.stderr
@@ -22,7 +22,7 @@ error: positional arguments cannot follow named arguments
   --> $DIR/format-parse-errors.rs:10:9
    |
 LL |         foo = foo,
-   |               --- named argument
+   |         --------- named argument
 LL |         bar,
    |         ^^^ positional arguments must be before named arguments
 
diff --git a/src/test/ui/macros/issue-99265.stderr b/src/test/ui/macros/issue-99265.stderr
index 2bfeedd7d0737..9185dbff61ee0 100644
--- a/src/test/ui/macros/issue-99265.stderr
+++ b/src/test/ui/macros/issue-99265.stderr
@@ -77,18 +77,18 @@ help: use the named argument by name to avoid ambiguity
 LL |     println!("Hello {:width$}!", "x", width = 5);
    |                       ~~~~~~
 
-warning: named argument `width` is not used by name
-  --> $DIR/issue-99265.rs:23:46
+warning: named argument `f` is not used by name
+  --> $DIR/issue-99265.rs:23:33
    |
 LL |     println!("Hello {:1$.2$}!", f = 0.02f32, width = 5, precision = 2);
-   |                       --                     ^^^^^ this named argument is referred to by position in formatting string
-   |                       |
-   |                       this formatting argument uses named argument `width` by position
+   |                     --------    ^ this named argument is referred to by position in formatting string
+   |                     |
+   |                     this formatting argument uses named argument `f` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |     println!("Hello {:width$.2$}!", f = 0.02f32, width = 5, precision = 2);
-   |                       ~~~~~~
+LL |     println!("Hello {f:1$.2$}!", f = 0.02f32, width = 5, precision = 2);
+   |                      +
 
 warning: named argument `precision` is not used by name
   --> $DIR/issue-99265.rs:23:57
@@ -103,31 +103,31 @@ help: use the named argument by name to avoid ambiguity
 LL |     println!("Hello {:1$.precision$}!", f = 0.02f32, width = 5, precision = 2);
    |                          ~~~~~~~~~~
 
-warning: named argument `f` is not used by name
-  --> $DIR/issue-99265.rs:23:33
+warning: named argument `width` is not used by name
+  --> $DIR/issue-99265.rs:23:46
    |
 LL |     println!("Hello {:1$.2$}!", f = 0.02f32, width = 5, precision = 2);
-   |                     --          ^ this named argument is referred to by position in formatting string
-   |                     |
-   |                     this formatting argument uses named argument `f` by position
+   |                       --                     ^^^^^ this named argument is referred to by position in formatting string
+   |                       |
+   |                       this formatting argument uses named argument `width` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |     println!("Hello {f:1$.2$}!", f = 0.02f32, width = 5, precision = 2);
-   |                      +
+LL |     println!("Hello {:width$.2$}!", f = 0.02f32, width = 5, precision = 2);
+   |                       ~~~~~~
 
-warning: named argument `width` is not used by name
-  --> $DIR/issue-99265.rs:31:47
+warning: named argument `f` is not used by name
+  --> $DIR/issue-99265.rs:31:34
    |
 LL |     println!("Hello {0:1$.2$}!", f = 0.02f32, width = 5, precision = 2);
-   |                        --                     ^^^^^ this named argument is referred to by position in formatting string
-   |                        |
-   |                        this formatting argument uses named argument `width` by position
+   |                     ---------    ^ this named argument is referred to by position in formatting string
+   |                     |
+   |                     this formatting argument uses named argument `f` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |     println!("Hello {0:width$.2$}!", f = 0.02f32, width = 5, precision = 2);
-   |                        ~~~~~~
+LL |     println!("Hello {f:1$.2$}!", f = 0.02f32, width = 5, precision = 2);
+   |                      ~
 
 warning: named argument `precision` is not used by name
   --> $DIR/issue-99265.rs:31:58
@@ -142,32 +142,32 @@ help: use the named argument by name to avoid ambiguity
 LL |     println!("Hello {0:1$.precision$}!", f = 0.02f32, width = 5, precision = 2);
    |                           ~~~~~~~~~~
 
-warning: named argument `f` is not used by name
-  --> $DIR/issue-99265.rs:31:34
+warning: named argument `width` is not used by name
+  --> $DIR/issue-99265.rs:31:47
    |
 LL |     println!("Hello {0:1$.2$}!", f = 0.02f32, width = 5, precision = 2);
-   |                      -           ^ this named argument is referred to by position in formatting string
-   |                      |
-   |                      this formatting argument uses named argument `f` by position
+   |                        --                     ^^^^^ this named argument is referred to by position in formatting string
+   |                        |
+   |                        this formatting argument uses named argument `width` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |     println!("Hello {f:1$.2$}!", f = 0.02f32, width = 5, precision = 2);
-   |                      ~
+LL |     println!("Hello {0:width$.2$}!", f = 0.02f32, width = 5, precision = 2);
+   |                        ~~~~~~
 
-warning: named argument `width` is not used by name
-  --> $DIR/issue-99265.rs:52:9
+warning: named argument `f` is not used by name
+  --> $DIR/issue-99265.rs:49:9
    |
 LL |         "{}, Hello {1:2$.3$} {4:5$.6$}! {1}",
-   |                       -- this formatting argument uses named argument `width` by position
+   |                    --------- this formatting argument uses named argument `f` by position
 ...
-LL |         width = 5,
-   |         ^^^^^ this named argument is referred to by position in formatting string
+LL |         f = 0.02f32,
+   |         ^ this named argument is referred to by position in formatting string
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |         "{}, Hello {1:width$.3$} {4:5$.6$}! {1}",
-   |                       ~~~~~~
+LL |         "{}, Hello {f:2$.3$} {4:5$.6$}! {1}",
+   |                     ~
 
 warning: named argument `precision` is not used by name
   --> $DIR/issue-99265.rs:54:9
@@ -183,33 +183,33 @@ help: use the named argument by name to avoid ambiguity
 LL |         "{}, Hello {1:2$.precision$} {4:5$.6$}! {1}",
    |                          ~~~~~~~~~~
 
-warning: named argument `f` is not used by name
-  --> $DIR/issue-99265.rs:49:9
+warning: named argument `width` is not used by name
+  --> $DIR/issue-99265.rs:52:9
    |
 LL |         "{}, Hello {1:2$.3$} {4:5$.6$}! {1}",
-   |                     - this formatting argument uses named argument `f` by position
+   |                       -- this formatting argument uses named argument `width` by position
 ...
-LL |         f = 0.02f32,
-   |         ^ this named argument is referred to by position in formatting string
+LL |         width = 5,
+   |         ^^^^^ this named argument is referred to by position in formatting string
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |         "{}, Hello {f:2$.3$} {4:5$.6$}! {1}",
-   |                     ~
+LL |         "{}, Hello {1:width$.3$} {4:5$.6$}! {1}",
+   |                       ~~~~~~
 
-warning: named argument `width2` is not used by name
-  --> $DIR/issue-99265.rs:58:9
+warning: named argument `g` is not used by name
+  --> $DIR/issue-99265.rs:56:9
    |
 LL |         "{}, Hello {1:2$.3$} {4:5$.6$}! {1}",
-   |                                 -- this formatting argument uses named argument `width2` by position
+   |                              --------- this formatting argument uses named argument `g` by position
 ...
-LL |         width2 = 5,
-   |         ^^^^^^ this named argument is referred to by position in formatting string
+LL |         g = 0.02f32,
+   |         ^ this named argument is referred to by position in formatting string
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |         "{}, Hello {1:2$.3$} {4:width2$.6$}! {1}",
-   |                                 ~~~~~~~
+LL |         "{}, Hello {1:2$.3$} {g:5$.6$}! {1}",
+   |                               ~
 
 warning: named argument `precision2` is not used by name
   --> $DIR/issue-99265.rs:60:9
@@ -225,25 +225,25 @@ help: use the named argument by name to avoid ambiguity
 LL |         "{}, Hello {1:2$.3$} {4:5$.precision2$}! {1}",
    |                                    ~~~~~~~~~~~
 
-warning: named argument `g` is not used by name
-  --> $DIR/issue-99265.rs:56:9
+warning: named argument `width2` is not used by name
+  --> $DIR/issue-99265.rs:58:9
    |
 LL |         "{}, Hello {1:2$.3$} {4:5$.6$}! {1}",
-   |                               - this formatting argument uses named argument `g` by position
+   |                                 -- this formatting argument uses named argument `width2` by position
 ...
-LL |         g = 0.02f32,
-   |         ^ this named argument is referred to by position in formatting string
+LL |         width2 = 5,
+   |         ^^^^^^ this named argument is referred to by position in formatting string
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |         "{}, Hello {1:2$.3$} {g:5$.6$}! {1}",
-   |                               ~
+LL |         "{}, Hello {1:2$.3$} {4:width2$.6$}! {1}",
+   |                                 ~~~~~~~
 
 warning: named argument `f` is not used by name
   --> $DIR/issue-99265.rs:49:9
    |
 LL |         "{}, Hello {1:2$.3$} {4:5$.6$}! {1}",
-   |                                          - this formatting argument uses named argument `f` by position
+   |                                         --- this formatting argument uses named argument `f` by position
 ...
 LL |         f = 0.02f32,
    |         ^ this named argument is referred to by position in formatting string
@@ -257,7 +257,7 @@ warning: named argument `f` is not used by name
   --> $DIR/issue-99265.rs:64:31
    |
 LL |     println!("Hello {:0.1}!", f = 0.02f32);
-   |                     --        ^ this named argument is referred to by position in formatting string
+   |                     ------    ^ this named argument is referred to by position in formatting string
    |                     |
    |                     this formatting argument uses named argument `f` by position
    |
@@ -270,15 +270,28 @@ warning: named argument `f` is not used by name
   --> $DIR/issue-99265.rs:68:32
    |
 LL |     println!("Hello {0:0.1}!", f = 0.02f32);
-   |                      -         ^ this named argument is referred to by position in formatting string
-   |                      |
-   |                      this formatting argument uses named argument `f` by position
+   |                     -------    ^ this named argument is referred to by position in formatting string
+   |                     |
+   |                     this formatting argument uses named argument `f` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
 LL |     println!("Hello {f:0.1}!", f = 0.02f32);
    |                      ~
 
+warning: named argument `v` is not used by name
+  --> $DIR/issue-99265.rs:79:23
+   |
+LL |     println!("{:0$}", v = val);
+   |               -----   ^ this named argument is referred to by position in formatting string
+   |               |
+   |               this formatting argument uses named argument `v` by position
+   |
+help: use the named argument by name to avoid ambiguity
+   |
+LL |     println!("{v:0$}", v = val);
+   |                +
+
 warning: named argument `v` is not used by name
   --> $DIR/issue-99265.rs:79:23
    |
@@ -293,17 +306,17 @@ LL |     println!("{:v$}", v = val);
    |                 ~~
 
 warning: named argument `v` is not used by name
-  --> $DIR/issue-99265.rs:79:23
+  --> $DIR/issue-99265.rs:84:24
    |
-LL |     println!("{:0$}", v = val);
-   |               --      ^ this named argument is referred to by position in formatting string
+LL |     println!("{0:0$}", v = val);
+   |               ------   ^ this named argument is referred to by position in formatting string
    |               |
    |               this formatting argument uses named argument `v` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
 LL |     println!("{v:0$}", v = val);
-   |                +
+   |                ~
 
 warning: named argument `v` is not used by name
   --> $DIR/issue-99265.rs:84:24
@@ -318,31 +331,18 @@ help: use the named argument by name to avoid ambiguity
 LL |     println!("{0:v$}", v = val);
    |                  ~~
 
-warning: named argument `v` is not used by name
-  --> $DIR/issue-99265.rs:84:24
-   |
-LL |     println!("{0:0$}", v = val);
-   |                -       ^ this named argument is referred to by position in formatting string
-   |                |
-   |                this formatting argument uses named argument `v` by position
-   |
-help: use the named argument by name to avoid ambiguity
-   |
-LL |     println!("{v:0$}", v = val);
-   |                ~
-
 warning: named argument `v` is not used by name
   --> $DIR/issue-99265.rs:89:26
    |
 LL |     println!("{:0$.0$}", v = val);
-   |                 --       ^ this named argument is referred to by position in formatting string
-   |                 |
-   |                 this formatting argument uses named argument `v` by position
+   |               --------   ^ this named argument is referred to by position in formatting string
+   |               |
+   |               this formatting argument uses named argument `v` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |     println!("{:v$.0$}", v = val);
-   |                 ~~
+LL |     println!("{v:0$.0$}", v = val);
+   |                +
 
 warning: named argument `v` is not used by name
   --> $DIR/issue-99265.rs:89:26
@@ -361,27 +361,27 @@ warning: named argument `v` is not used by name
   --> $DIR/issue-99265.rs:89:26
    |
 LL |     println!("{:0$.0$}", v = val);
-   |               --         ^ this named argument is referred to by position in formatting string
-   |               |
-   |               this formatting argument uses named argument `v` by position
+   |                 --       ^ this named argument is referred to by position in formatting string
+   |                 |
+   |                 this formatting argument uses named argument `v` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |     println!("{v:0$.0$}", v = val);
-   |                +
+LL |     println!("{:v$.0$}", v = val);
+   |                 ~~
 
 warning: named argument `v` is not used by name
   --> $DIR/issue-99265.rs:96:27
    |
 LL |     println!("{0:0$.0$}", v = val);
-   |                  --       ^ this named argument is referred to by position in formatting string
-   |                  |
-   |                  this formatting argument uses named argument `v` by position
+   |               ---------   ^ this named argument is referred to by position in formatting string
+   |               |
+   |               this formatting argument uses named argument `v` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |     println!("{0:v$.0$}", v = val);
-   |                  ~~
+LL |     println!("{v:0$.0$}", v = val);
+   |                ~
 
 warning: named argument `v` is not used by name
   --> $DIR/issue-99265.rs:96:27
@@ -400,14 +400,14 @@ warning: named argument `v` is not used by name
   --> $DIR/issue-99265.rs:96:27
    |
 LL |     println!("{0:0$.0$}", v = val);
-   |                -          ^ this named argument is referred to by position in formatting string
-   |                |
-   |                this formatting argument uses named argument `v` by position
+   |                  --       ^ this named argument is referred to by position in formatting string
+   |                  |
+   |                  this formatting argument uses named argument `v` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |     println!("{v:0$.0$}", v = val);
-   |                ~
+LL |     println!("{0:v$.0$}", v = val);
+   |                  ~~
 
 warning: named argument `a` is not used by name
   --> $DIR/issue-99265.rs:104:28
@@ -426,28 +426,28 @@ warning: named argument `a` is not used by name
   --> $DIR/issue-99265.rs:104:28
    |
 LL |     println!("{} {a} {0}", a = 1);
-   |                       -    ^ this named argument is referred to by position in formatting string
-   |                       |
-   |                       this formatting argument uses named argument `a` by position
+   |                      ---   ^ this named argument is referred to by position in formatting string
+   |                      |
+   |                      this formatting argument uses named argument `a` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
 LL |     println!("{} {a} {a}", a = 1);
    |                       ~
 
-warning: named argument `b` is not used by name
-  --> $DIR/issue-99265.rs:115:23
+warning: named argument `a` is not used by name
+  --> $DIR/issue-99265.rs:115:14
    |
 LL |                 {:1$.2$}",
-   |                   -- this formatting argument uses named argument `b` by position
+   |                 -------- this formatting argument uses named argument `a` by position
 ...
 LL |              a = 1.0, b = 1, c = 2,
-   |                       ^ this named argument is referred to by position in formatting string
+   |              ^ this named argument is referred to by position in formatting string
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |                 {:b$.2$}",
-   |                   ~~
+LL |                 {a:1$.2$}",
+   |                  +
 
 warning: named argument `c` is not used by name
   --> $DIR/issue-99265.rs:115:30
@@ -463,33 +463,33 @@ help: use the named argument by name to avoid ambiguity
 LL |                 {:1$.c$}",
    |                      ~~
 
-warning: named argument `a` is not used by name
-  --> $DIR/issue-99265.rs:115:14
+warning: named argument `b` is not used by name
+  --> $DIR/issue-99265.rs:115:23
    |
 LL |                 {:1$.2$}",
-   |                 -- this formatting argument uses named argument `a` by position
+   |                   -- this formatting argument uses named argument `b` by position
 ...
 LL |              a = 1.0, b = 1, c = 2,
-   |              ^ this named argument is referred to by position in formatting string
+   |                       ^ this named argument is referred to by position in formatting string
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |                 {a:1$.2$}",
-   |                  +
+LL |                 {:b$.2$}",
+   |                   ~~
 
-warning: named argument `b` is not used by name
-  --> $DIR/issue-99265.rs:126:23
+warning: named argument `a` is not used by name
+  --> $DIR/issue-99265.rs:126:14
    |
 LL |                 {0:1$.2$}",
-   |                    -- this formatting argument uses named argument `b` by position
+   |                 --------- this formatting argument uses named argument `a` by position
 ...
 LL |              a = 1.0, b = 1, c = 2,
-   |                       ^ this named argument is referred to by position in formatting string
+   |              ^ this named argument is referred to by position in formatting string
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |                 {0:b$.2$}",
-   |                    ~~
+LL |                 {a:1$.2$}",
+   |                  ~
 
 warning: named argument `c` is not used by name
   --> $DIR/issue-99265.rs:126:30
@@ -505,32 +505,32 @@ help: use the named argument by name to avoid ambiguity
 LL |                 {0:1$.c$}",
    |                       ~~
 
-warning: named argument `a` is not used by name
-  --> $DIR/issue-99265.rs:126:14
+warning: named argument `b` is not used by name
+  --> $DIR/issue-99265.rs:126:23
    |
 LL |                 {0:1$.2$}",
-   |                  - this formatting argument uses named argument `a` by position
+   |                    -- this formatting argument uses named argument `b` by position
 ...
 LL |              a = 1.0, b = 1, c = 2,
-   |              ^ this named argument is referred to by position in formatting string
+   |                       ^ this named argument is referred to by position in formatting string
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |                 {a:1$.2$}",
-   |                  ~
+LL |                 {0:b$.2$}",
+   |                    ~~
 
-warning: named argument `width` is not used by name
-  --> $DIR/issue-99265.rs:132:39
+warning: named argument `x` is not used by name
+  --> $DIR/issue-99265.rs:132:30
    |
 LL |     println!("{{{:1$.2$}}}", x = 1.0, width = 3, precision = 2);
-   |                   --                  ^^^^^ this named argument is referred to by position in formatting string
-   |                   |
-   |                   this formatting argument uses named argument `width` by position
+   |                 --------     ^ this named argument is referred to by position in formatting string
+   |                 |
+   |                 this formatting argument uses named argument `x` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |     println!("{{{:width$.2$}}}", x = 1.0, width = 3, precision = 2);
-   |                   ~~~~~~
+LL |     println!("{{{x:1$.2$}}}", x = 1.0, width = 3, precision = 2);
+   |                  +
 
 warning: named argument `precision` is not used by name
   --> $DIR/issue-99265.rs:132:50
@@ -545,18 +545,18 @@ help: use the named argument by name to avoid ambiguity
 LL |     println!("{{{:1$.precision$}}}", x = 1.0, width = 3, precision = 2);
    |                      ~~~~~~~~~~
 
-warning: named argument `x` is not used by name
-  --> $DIR/issue-99265.rs:132:30
+warning: named argument `width` is not used by name
+  --> $DIR/issue-99265.rs:132:39
    |
 LL |     println!("{{{:1$.2$}}}", x = 1.0, width = 3, precision = 2);
-   |                 --           ^ this named argument is referred to by position in formatting string
-   |                 |
-   |                 this formatting argument uses named argument `x` by position
+   |                   --                  ^^^^^ this named argument is referred to by position in formatting string
+   |                   |
+   |                   this formatting argument uses named argument `width` by position
    |
 help: use the named argument by name to avoid ambiguity
    |
-LL |     println!("{{{x:1$.2$}}}", x = 1.0, width = 3, precision = 2);
-   |                  +
+LL |     println!("{{{:width$.2$}}}", x = 1.0, width = 3, precision = 2);
+   |                   ~~~~~~
 
 warning: 42 warnings emitted
 
diff --git a/src/test/ui/macros/issue-99907.stderr b/src/test/ui/macros/issue-99907.stderr
index 4786ce003b4c2..eefb28dee35b8 100644
--- a/src/test/ui/macros/issue-99907.stderr
+++ b/src/test/ui/macros/issue-99907.stderr
@@ -2,7 +2,7 @@ warning: named argument `f` is not used by name
   --> $DIR/issue-99907.rs:5:30
    |
 LL |     println!("Hello {:.1}!", f = 0.02f32);
-   |                     --       ^ this named argument is referred to by position in formatting string
+   |                     -----    ^ this named argument is referred to by position in formatting string
    |                     |
    |                     this formatting argument uses named argument `f` by position
    |
@@ -16,7 +16,7 @@ warning: named argument `f` is not used by name
   --> $DIR/issue-99907.rs:9:31
    |
 LL |     println!("Hello {:1.1}!", f = 0.02f32);
-   |                     --        ^ this named argument is referred to by position in formatting string
+   |                     ------    ^ this named argument is referred to by position in formatting string
    |                     |
    |                     this formatting argument uses named argument `f` by position
    |