Skip to main content

rustc_builtin_macros/
format.rs

1use std::ops::Range;
2
3use parse::Position::ArgumentNamed;
4use rustc_ast::tokenstream::TokenStream;
5use rustc_ast::{
6    Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs,
7    FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount,
8    FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait, Recovered, StmtKind,
9    token,
10};
11use rustc_data_structures::fx::FxHashSet;
12use rustc_errors::{
13    Applicability, BufferedEarlyLint, DecorateDiagCompat, Diag, Diagnostic, MultiSpan, PResult,
14    SingleLabelManySpans, listify, pluralize,
15};
16use rustc_expand::base::*;
17use rustc_lint_defs::LintId;
18use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
19use rustc_parse::exp;
20use rustc_parse_format as parse;
21use rustc_span::{BytePos, ErrorGuaranteed, Ident, InnerSpan, Span, Symbol};
22
23use crate::diagnostics;
24use crate::util::{ExprToSpannedString, expr_to_spanned_string};
25
26// The format_args!() macro is expanded in three steps:
27//  1. First, `parse_args` will parse the `(literal, arg, arg, name=arg, name=arg)` syntax,
28//     but doesn't parse the template (the literal) itself.
29//  2. Second, `make_format_args` will parse the template, the format options, resolve argument references,
30//     produce diagnostics, and turn the whole thing into a `FormatArgs` AST node.
31//  3. Much later, in AST lowering (rustc_ast_lowering), that `FormatArgs` structure will be turned
32//     into the expression of type `core::fmt::Arguments`.
33
34// See rustc_ast/src/format.rs for the FormatArgs structure and glossary.
35
36// Only used in parse_args and report_invalid_references,
37// to indicate how a referred argument was used.
38#[derive(#[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
    for PositionUsedAs {
}
#[automatically_derived]impl ::core::clone::Clone for PositionUsedAs {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<Option<Span>>;
        *self
    }
}Clone, #[automatically_derived]impl ::core::marker::Copy for PositionUsedAs { }Copy, #[automatically_derived]impl ::core::fmt::Debug for PositionUsedAs {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Placeholder(__self_0) =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "Placeholder", &__self_0),
            Self::Precision =>
                ::core::fmt::Formatter::write_str(f, "Precision"),
            Self::Width => ::core::fmt::Formatter::write_str(f, "Width"),
        }
    }
}Debug, #[automatically_derived]impl ::core::marker::StructuralPartialEq for
    PositionUsedAs {
}
#[automatically_derived]impl ::core::cmp::PartialEq for PositionUsedAs {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
                ::core::intrinsics::discriminant_value(other) &&
            match (self, other) {
                (Self::Placeholder(__self_0), Self::Placeholder(__arg1_0)) =>
                    __self_0 == __arg1_0,
                _ => true,
            }
    }
}PartialEq, #[automatically_derived]impl ::core::cmp::Eq for PositionUsedAs {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<Option<Span>>;
    }
}Eq)]
39enum PositionUsedAs {
40    Placeholder(Option<Span>),
41    Precision,
42    Width,
43}
44use PositionUsedAs::*;
45
46#[derive(#[automatically_derived]impl ::core::fmt::Debug for MacroInput {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f,
            "MacroInput fmtstr args is_direct_literal", &self.fmtstr,
            &self.args, &&self.is_direct_literal)
    }
}Debug)]
47struct MacroInput {
48    fmtstr: Box<Expr>,
49    args: FormatArguments,
50    /// Whether the first argument was a string literal or a result from eager macro expansion.
51    /// If it's not a string literal, we disallow implicit argument capturing.
52    ///
53    /// This does not correspond to whether we can treat spans to the literal normally, as the whole
54    /// invocation might be the result of another macro expansion, in which case this flag may still be true.
55    ///
56    /// See [RFC 2795] for more information.
57    ///
58    /// [RFC 2795]: https://rust-lang.github.io/rfcs/2795-format-args-implicit-identifiers.html#macro-hygiene
59    is_direct_literal: bool,
60}
61
62/// Parses the arguments from the given list of tokens, returning the diagnostic
63/// if there's a parse error so we can continue parsing other format!
64/// expressions.
65///
66/// If parsing succeeds, the return value is:
67///
68/// ```text
69/// Ok((fmtstr, parsed arguments))
70/// ```
71fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> {
72    let mut p = ecx.new_parser_from_tts(tts);
73
74    // parse the format string
75    let fmtstr = match p.token.kind {
76        token::Eof => {
77            return Err(ecx.dcx().create_err(diagnostics::FormatRequiresString { span: sp }));
78        }
79        // This allows us to properly handle cases when the first comma
80        // after the format string is mistakenly replaced with any operator,
81        // which cause the expression parser to eat too much tokens.
82        token::Literal(token::Lit { kind: token::Str | token::StrRaw(_), .. }) => {
83            p.parse_literal_maybe_minus()?
84        }
85        // Otherwise, we fall back to the expression parser.
86        _ => p.parse_expr()?,
87    };
88
89    // parse comma FormatArgument pairs
90    let mut args = FormatArguments::new();
91    let mut first = true;
92    while p.token != token::Eof {
93        // parse a comma, or else report an error
94        if !p.eat(::rustc_parse::parser::token_type::ExpTokenPair {
    tok: rustc_ast::token::Comma,
    token_type: ::rustc_parse::parser::token_type::TokenType::Comma,
}exp!(Comma)) {
95            if first {
96                p.clear_expected_token_types();
97            }
98
99            match p.expect(::rustc_parse::parser::token_type::ExpTokenPair {
    tok: rustc_ast::token::Comma,
    token_type: ::rustc_parse::parser::token_type::TokenType::Comma,
}exp!(Comma)) {
100                Err(err) => {
101                    if token::TokenKind::Comma.similar_tokens().contains(&p.token.kind) {
102                        // If a similar token is found, then it may be a typo. We
103                        // consider it as a comma, and continue parsing.
104                        err.emit();
105                        p.bump();
106                    } else {
107                        // Otherwise stop the parsing and return the error.
108                        return Err(err);
109                    }
110                }
111                Ok(Recovered::Yes(_)) => (),
112                Ok(Recovered::No) => ::core::panicking::unreachable_default_msg()unreachable!(),
113            }
114        }
115        first = false;
116        // accept a trailing comma
117        if p.token == token::Eof {
118            break;
119        }
120        // parse a FormatArgument
121        match p.token.ident() {
122            Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => {
123                p.bump();
124                p.expect(::rustc_parse::parser::token_type::ExpTokenPair {
    tok: rustc_ast::token::Eq,
    token_type: ::rustc_parse::parser::token_type::TokenType::Eq,
}exp!(Eq))?;
125                let expr = p.parse_expr()?;
126                if let Some((_, prev)) = args.by_name(ident.name) {
127                    ecx.dcx().emit_err(diagnostics::FormatDuplicateArg {
128                        span: ident.span,
129                        prev: prev.kind.ident().unwrap().span,
130                        duplicate: ident.span,
131                        ident,
132                    });
133                    continue;
134                }
135                args.add(FormatArgument {
136                    original_span: ident.span.to(expr.span),
137                    kind: FormatArgumentKind::Named(ident),
138                    expr,
139                });
140            }
141            _ => {
142                let expr = p.parse_expr()?;
143                if !args.named_args().is_empty() {
144                    return Err(ecx.dcx().create_err(diagnostics::PositionalAfterNamed {
145                        span: expr.span,
146                        args: args
147                            .named_args()
148                            .iter()
149                            .filter_map(|a| a.kind.ident().map(|ident| (a, ident)))
150                            .map(|(arg, n)| n.span.to(arg.expr.span))
151                            .collect(),
152                    }));
153                }
154                args.add(FormatArgument {
155                    original_span: expr.span,
156                    kind: FormatArgumentKind::Normal,
157                    expr,
158                });
159            }
160        }
161    }
162
163    // Only allow implicit captures for direct literals
164    let is_direct_literal = #[allow(non_exhaustive_omitted_patterns)] match fmtstr.kind {
    ExprKind::Lit(_) => true,
    _ => false,
}matches!(fmtstr.kind, ExprKind::Lit(_));
165
166    Ok(MacroInput { fmtstr, args, is_direct_literal })
167}
168
169fn make_format_args(
170    ecx: &mut ExtCtxt<'_>,
171    input: MacroInput,
172    append_newline: bool,
173    macro_span: Span,
174) -> ExpandResult<Result<FormatArgs, ErrorGuaranteed>, ()> {
175    let unexpanded_fmt_span = input.fmtstr.span;
176
177    let MacroInput { fmtstr: efmt, mut args, is_direct_literal } = input;
178
179    let ExprToSpannedString {
180        symbol: fmt_str,
181        span: fmt_span,
182        style: fmt_style,
183        uncooked_symbol: uncooked_fmt_str,
184    } = {
185        // Extract snippet so that we can check cases `{}`, `{:?}` and `{:#?}` and emit help for
186        // them later.
187        let snippet = if let ExprKind::Block(b, None) = &efmt.kind
188            && b.stmts.len() <= 1
189        {
190            Some(ecx.sess.source_map().span_to_snippet(unexpanded_fmt_span))
191        } else {
192            None
193        };
194
195        let ExpandResult::Ready(mac) =
196            expr_to_spanned_string(ecx, efmt.clone(), "format argument must be a string literal")
197        else {
198            return ExpandResult::Retry(());
199        };
200        match mac {
201            Ok(mut fmt) if append_newline => {
202                fmt.symbol = Symbol::intern(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}\n", fmt.symbol))
    })format!("{}\n", fmt.symbol));
203                fmt
204            }
205            Ok(fmt) => fmt,
206            Err(err) => {
207                let guar = match err {
208                    Ok((mut err, suggested)) => {
209                        if !suggested {
210                            if let ExprKind::Block(block, None) = &efmt.kind
211                                && let [stmt] = block.stmts.as_slice()
212                                && let StmtKind::Expr(expr) = &stmt.kind
213                                && let ExprKind::Path(None, path) = &expr.kind
214                                && path.segments.len() == 1
215                                && path.segments[0].args.is_none()
216                            {
217                                err.multipart_suggestion(
218                                    "quote your inlined format argument to use as string literal",
219                                    ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [(unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()),
                (unexpanded_fmt_span.shrink_to_lo(), "\"".to_string())]))vec![
220                                        (unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()),
221                                        (unexpanded_fmt_span.shrink_to_lo(), "\"".to_string()),
222                                    ],
223                                    Applicability::MaybeIncorrect,
224                                );
225                            } else {
226                                // `{}` or `()`
227                                let should_suggest = |kind: &ExprKind| -> bool {
228                                    match kind {
229                                        ExprKind::Block(b, None) if b.stmts.is_empty() => true,
230                                        ExprKind::Tup(v) if v.is_empty() => true,
231                                        _ => false,
232                                    }
233                                };
234
235                                let mut sugg_fmt = String::new();
236                                for kind in std::iter::once(&efmt.kind)
237                                    .chain(args.explicit_args().iter().map(|a| &a.expr.kind))
238                                {
239                                    sugg_fmt.push_str(if should_suggest(kind) {
240                                        "{:?} "
241                                    } else {
242                                        "{} "
243                                    });
244                                }
245                                sugg_fmt = sugg_fmt.trim_end().to_string();
246                                err.span_suggestion_verbose(
247                                    unexpanded_fmt_span.shrink_to_lo(),
248                                    "you might be missing a string literal to format with",
249                                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\"{0}\", ", sugg_fmt))
    })format!("\"{sugg_fmt}\", "),
250                                    Applicability::MaybeIncorrect,
251                                );
252
253                                if let Some(Ok(snippet)) = snippet.as_ref() {
254                                    match snippet.as_str() {
255                                        "{}" | "{:?}" | "{:#?}" => {
256                                            err.span_suggestion_verbose(
257                                                unexpanded_fmt_span,
258                                                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("you might want to enclose `{0}` with `\"\"`",
                snippet))
    })format!("you might want to enclose `{snippet}` with `\"\"`"),
259                                                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\"{0}\"", snippet))
    })format!("\"{snippet}\""),
260                                                Applicability::MaybeIncorrect,
261                                            );
262                                        }
263                                        _ => {}
264                                    };
265                                }
266                            }
267                        }
268                        err.emit_err()
269                    }
270                    Err(guar) => guar,
271                };
272                return ExpandResult::Ready(Err(guar));
273            }
274        }
275    };
276
277    let str_style = match fmt_style {
278        rustc_ast::StrStyle::Cooked => None,
279        rustc_ast::StrStyle::Raw(raw) => Some(raw as usize),
280    };
281
282    let fmt_str = fmt_str.as_str(); // for the suggestions below
283    let fmt_snippet = ecx.source_map().span_to_snippet(unexpanded_fmt_span).ok();
284    let mut parser = parse::Parser::new(
285        fmt_str,
286        str_style,
287        fmt_snippet,
288        append_newline,
289        parse::ParseMode::Format,
290    );
291
292    let mut pieces = Vec::new();
293    while let Some(piece) = parser.next() {
294        if !parser.errors.is_empty() {
295            break;
296        } else {
297            pieces.push(piece);
298        }
299    }
300
301    let is_source_literal = parser.is_source_literal;
302
303    if !parser.errors.is_empty() {
304        let err = parser.errors.remove(0);
305        let sp = if is_source_literal {
306            fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end))
307        } else {
308            // The format string could be another macro invocation, e.g.:
309            //     format!(concat!("abc", "{}"), 4);
310            // However, `err.span` is an inner span relative to the *result* of
311            // the macro invocation, which is why we would get a nonsensical
312            // result calling `fmt_span.from_inner(err.span)` as above, and
313            // might even end up inside a multibyte character (issue #86085).
314            // Therefore, we conservatively report the error for the entire
315            // argument span here.
316            fmt_span
317        };
318        let mut e = diagnostics::InvalidFormatString {
319            span: sp,
320            note_: None,
321            label_: None,
322            sugg_: None,
323            desc: err.description,
324            label1: err.label,
325        };
326        if let Some(note) = err.note {
327            e.note_ = Some(diagnostics::InvalidFormatStringNote { note });
328        }
329        if let Some((label, span)) = err.secondary_label
330            && is_source_literal
331        {
332            e.label_ = Some(diagnostics::InvalidFormatStringLabel {
333                span: fmt_span.from_inner(InnerSpan::new(span.start, span.end)),
334                label,
335            });
336        }
337        match err.suggestion {
338            parse::Suggestion::None => {}
339            parse::Suggestion::UsePositional => {
340                let captured_arg_span =
341                    fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
342                if is_source_literal
343                    && let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span)
344                {
345                    let span = match args.unnamed_args().last() {
346                        Some(arg) => arg.expr.span,
347                        None => fmt_span,
348                    };
349                    e.sugg_ = Some(diagnostics::InvalidFormatStringSuggestion::UsePositional {
350                        captured: captured_arg_span,
351                        len: args.unnamed_args().len().to_string(),
352                        span: span.shrink_to_hi(),
353                        arg,
354                    });
355                }
356            }
357            parse::Suggestion::RemoveRawIdent(span) => {
358                if is_source_literal {
359                    let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
360                    e.sugg_ =
361                        Some(diagnostics::InvalidFormatStringSuggestion::RemoveRawIdent { span })
362                }
363            }
364            parse::Suggestion::ReorderFormatParameter(span, replacement) => {
365                if is_source_literal {
366                    let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
367                    e.sugg_ =
368                        Some(diagnostics::InvalidFormatStringSuggestion::ReorderFormatParameter {
369                            span,
370                            replacement,
371                        });
372                }
373            }
374            parse::Suggestion::AddMissingColon(span) => {
375                if is_source_literal {
376                    let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
377                    e.sugg_ =
378                        Some(diagnostics::InvalidFormatStringSuggestion::AddMissingColon { span });
379                }
380            }
381            parse::Suggestion::UseRustDebugPrintingMacro => {
382                // This targets `println!("{=}", x);` and `println!("{0=}", x);`
383                if let [arg] = args.all_args() {
384                    let expr_span = arg.expr.span;
385                    if let Ok(expr_snippet) = ecx.source_map().span_to_snippet(expr_span) {
386                        let replacement = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}!({1})", "dbg", expr_snippet))
    })format!("{}!({})", "dbg", expr_snippet);
387
388                        let call_span = macro_span.source_callsite();
389                        e.sugg_ = Some(
390                            diagnostics::InvalidFormatStringSuggestion::UseRustDebugPrintingMacro {
391                                macro_span: call_span,
392                                replacement,
393                            },
394                        );
395                    }
396                }
397            }
398        }
399        let guar = ecx.dcx().emit_err(e);
400        return ExpandResult::Ready(Err(guar));
401    }
402
403    let to_span = |inner_span: Range<usize>| {
404        is_source_literal.then(|| {
405            fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end })
406        })
407    };
408
409    let mut used = ::alloc::vec::from_elem(false, args.explicit_args().len())vec![false; args.explicit_args().len()];
410    let mut invalid_refs = Vec::new();
411    let mut numeric_references_to_named_arg = Vec::new();
412
413    enum ArgRef<'a> {
414        Index(usize),
415        Name(&'a str, Option<Span>),
416    }
417    use ArgRef::*;
418
419    let mut unnamed_arg_after_named_arg = false;
420
421    let mut lookup_arg = |arg: ArgRef<'_>,
422                          span: Option<Span>,
423                          used_as: PositionUsedAs,
424                          kind: FormatArgPositionKind|
425     -> FormatArgPosition {
426        let index = match arg {
427            Index(index) => {
428                if let Some(arg) = args.by_index(index) {
429                    used[index] = true;
430                    if arg.kind.ident().is_some() {
431                        // This was a named argument, but it was used as a positional argument.
432                        numeric_references_to_named_arg.push((index, span, used_as));
433                    }
434                    Ok(index)
435                } else {
436                    // Doesn't exist as an explicit argument.
437                    invalid_refs.push((index, span, used_as, kind));
438                    Err(index)
439                }
440            }
441            Name(name, span) => {
442                let name = Symbol::intern(name);
443                if let Some((index, _)) = args.by_name(name) {
444                    // Name found in `args`, so we resolve it to its index.
445                    if index < args.explicit_args().len() {
446                        // Mark it as used, if it was an explicit argument.
447                        used[index] = true;
448                    }
449                    Ok(index)
450                } else {
451                    // Name not found in `args`, so we add it as an implicitly captured argument.
452                    let span = span.unwrap_or(fmt_span);
453                    let ident = Ident::new(name, span);
454                    let expr = if is_direct_literal {
455                        ecx.expr_ident(span, ident)
456                    } else {
457                        // For the moment capturing variables from format strings expanded from macros is
458                        // disabled (see RFC #2795)
459                        let guar = ecx.dcx().emit_err(diagnostics::FormatNoArgNamed { span, name });
460                        unnamed_arg_after_named_arg = true;
461                        DummyResult::raw_expr(span, Some(guar))
462                    };
463                    Ok(args.add(FormatArgument {
464                        original_span: span,
465                        kind: FormatArgumentKind::Captured(ident),
466                        expr,
467                    }))
468                }
469            }
470        };
471        FormatArgPosition { index, kind, span }
472    };
473
474    let mut template = Vec::new();
475    let mut unfinished_literal = String::new();
476    let mut placeholder_index = 0;
477
478    for piece in &pieces {
479        match piece.clone() {
480            parse::Piece::Lit(s) => {
481                unfinished_literal.push_str(s);
482            }
483            parse::Piece::NextArgument(parse::Argument { position, position_span, format }) => {
484                if !unfinished_literal.is_empty() {
485                    template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
486                    unfinished_literal.clear();
487                }
488
489                let span =
490                    parser.arg_places.get(placeholder_index).and_then(|s| to_span(s.clone()));
491                placeholder_index += 1;
492
493                let position_span = to_span(position_span);
494                let argument = match position {
495                    parse::ArgumentImplicitlyIs(i) => lookup_arg(
496                        Index(i),
497                        position_span,
498                        Placeholder(span),
499                        FormatArgPositionKind::Implicit,
500                    ),
501                    parse::ArgumentIs(i) => lookup_arg(
502                        Index(i),
503                        position_span,
504                        Placeholder(span),
505                        FormatArgPositionKind::Number,
506                    ),
507                    parse::ArgumentNamed(name) => lookup_arg(
508                        Name(name, position_span),
509                        position_span,
510                        Placeholder(span),
511                        FormatArgPositionKind::Named,
512                    ),
513                };
514
515                let alignment = match format.align {
516                    parse::AlignUnknown => None,
517                    parse::AlignLeft => Some(FormatAlignment::Left),
518                    parse::AlignRight => Some(FormatAlignment::Right),
519                    parse::AlignCenter => Some(FormatAlignment::Center),
520                };
521
522                let format_trait = match format.ty {
523                    "" => FormatTrait::Display,
524                    "?" => FormatTrait::Debug,
525                    "e" => FormatTrait::LowerExp,
526                    "E" => FormatTrait::UpperExp,
527                    "o" => FormatTrait::Octal,
528                    "p" => FormatTrait::Pointer,
529                    "b" => FormatTrait::Binary,
530                    "x" => FormatTrait::LowerHex,
531                    "X" => FormatTrait::UpperHex,
532                    _ => {
533                        invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span);
534                        FormatTrait::Display
535                    }
536                };
537
538                let precision_span = format.precision_span.and_then(to_span);
539                let precision = match format.precision {
540                    parse::CountIs(n) => Some(FormatCount::Literal(n)),
541                    parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
542                        Name(name, to_span(name_span)),
543                        precision_span,
544                        Precision,
545                        FormatArgPositionKind::Named,
546                    ))),
547                    parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
548                        Index(i),
549                        precision_span,
550                        Precision,
551                        FormatArgPositionKind::Number,
552                    ))),
553                    parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
554                        Index(i),
555                        precision_span,
556                        Precision,
557                        FormatArgPositionKind::Implicit,
558                    ))),
559                    parse::CountImplied => None,
560                };
561
562                let width_span = format.width_span.and_then(to_span);
563                let width = match format.width {
564                    parse::CountIs(n) => Some(FormatCount::Literal(n)),
565                    parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
566                        Name(name, to_span(name_span)),
567                        width_span,
568                        Width,
569                        FormatArgPositionKind::Named,
570                    ))),
571                    parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
572                        Index(i),
573                        width_span,
574                        Width,
575                        FormatArgPositionKind::Number,
576                    ))),
577                    parse::CountIsStar(_) => ::core::panicking::unreachable_default_msg()unreachable!(),
578                    parse::CountImplied => None,
579                };
580
581                template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
582                    argument,
583                    span,
584                    format_trait,
585                    format_options: FormatOptions {
586                        fill: format.fill,
587                        alignment,
588                        sign: format.sign.map(|s| match s {
589                            parse::Sign::Plus => FormatSign::Plus,
590                            parse::Sign::Minus => FormatSign::Minus,
591                        }),
592                        alternate: format.alternate,
593                        zero_pad: format.zero_pad,
594                        debug_hex: format.debug_hex.map(|s| match s {
595                            parse::DebugHex::Lower => FormatDebugHex::Lower,
596                            parse::DebugHex::Upper => FormatDebugHex::Upper,
597                        }),
598                        precision,
599                        width,
600                    },
601                }));
602            }
603        }
604    }
605
606    if !unfinished_literal.is_empty() {
607        template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
608    }
609
610    if !invalid_refs.is_empty() {
611        report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser);
612    }
613
614    let unused = used
615        .iter()
616        .enumerate()
617        .filter(|&(_, used)| !used)
618        .map(|(i, _)| {
619            let named = #[allow(non_exhaustive_omitted_patterns)] match args.explicit_args()[i].kind {
    FormatArgumentKind::Named(_) => true,
    _ => false,
}matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
620            (args.explicit_args()[i].expr.span, named)
621        })
622        .collect::<Vec<_>>();
623
624    let has_unused = !unused.is_empty();
625    if has_unused {
626        let foreign_fmt_str =
627            if append_newline { fmt_str.strip_suffix('\n').unwrap_or(fmt_str) } else { fmt_str };
628        report_missing_placeholders(
629            ecx,
630            unused,
631            &used,
632            &args,
633            &pieces,
634            &invalid_refs,
635            str_style,
636            foreign_fmt_str,
637            uncooked_fmt_str.1.as_str(),
638            fmt_span,
639            is_source_literal,
640        );
641    }
642
643    // Only check for unused named argument names if there are no other errors to avoid causing
644    // too much noise in output errors, such as when a named argument is entirely unused.
645    if invalid_refs.is_empty() && !has_unused && !unnamed_arg_after_named_arg {
646        for &(index, span, used_as) in &numeric_references_to_named_arg {
647            let (position_sp_to_replace, position_sp_for_msg) = match used_as {
648                Placeholder(pspan) => (span, pspan),
649                Precision => {
650                    // Strip the leading `.` for precision.
651                    let span = span.map(|span| span.with_lo(span.lo() + BytePos(1)));
652                    (span, span)
653                }
654                Width => (span, span),
655            };
656            let arg_name = args.explicit_args()[index].kind.ident().unwrap();
657            ecx.buffered_early_lint.push(BufferedEarlyLint {
658                span: Some(arg_name.span.into()),
659                node_id: rustc_ast::CRATE_NODE_ID,
660                lint_id: LintId::of(NAMED_ARGUMENTS_USED_POSITIONALLY),
661                diagnostic: DecorateDiagCompat(Box::new(move |dcx, level, sess| {
662                    let (suggestion, name) =
663                        if let Some(positional_arg_to_replace) = position_sp_to_replace {
664                            let mut name = arg_name.name.to_string();
665                            let is_formatting_arg = #[allow(non_exhaustive_omitted_patterns)] match used_as {
    Width | Precision => true,
    _ => false,
}matches!(used_as, Width | Precision);
666                            if is_formatting_arg {
667                                name.push('$')
668                            };
669                            let span_to_replace = if let Ok(positional_arg_content) = sess
670                                .downcast_ref::<rustc_session::Session>()
671                                .expect("expected a `Session`")
672                                .source_map()
673                                .span_to_snippet(positional_arg_to_replace)
674                                && positional_arg_content.starts_with(':')
675                            {
676                                positional_arg_to_replace.shrink_to_lo()
677                            } else {
678                                positional_arg_to_replace
679                            };
680                            (Some(span_to_replace), name)
681                        } else {
682                            (None, String::new())
683                        };
684
685                    diagnostics::NamedArgumentUsedPositionally {
686                        named_arg_sp: arg_name.span,
687                        position_label_sp: position_sp_for_msg,
688                        suggestion,
689                        name,
690                        named_arg_name: arg_name.name.to_string(),
691                    }
692                    .into_diag(dcx, level)
693                })),
694            });
695        }
696    }
697
698    ExpandResult::Ready(Ok(FormatArgs {
699        span: fmt_span,
700        template,
701        arguments: args,
702        uncooked_fmt_str,
703        is_source_literal,
704    }))
705}
706
707fn invalid_placeholder_type_error(
708    ecx: &ExtCtxt<'_>,
709    ty: &str,
710    ty_span: Option<Range<usize>>,
711    fmt_span: Span,
712) {
713    let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end)));
714    let suggs = if let Some(sp) = sp {
715        [
716            ("", "Display"),
717            ("?", "Debug"),
718            ("e", "LowerExp"),
719            ("E", "UpperExp"),
720            ("o", "Octal"),
721            ("p", "Pointer"),
722            ("b", "Binary"),
723            ("x", "LowerHex"),
724            ("X", "UpperHex"),
725        ]
726        .into_iter()
727        .map(|(fmt, trait_name)| diagnostics::FormatUnknownTraitSugg { span: sp, fmt, trait_name })
728        .collect()
729    } else {
730        ::alloc::vec::Vec::new()vec![]
731    };
732    ecx.dcx().emit_err(diagnostics::FormatUnknownTrait { span: sp.unwrap_or(fmt_span), ty, suggs });
733}
734
735fn report_missing_placeholders(
736    ecx: &ExtCtxt<'_>,
737    unused: Vec<(Span, bool)>,
738    used: &[bool],
739    args: &FormatArguments,
740    pieces: &[parse::Piece<'_>],
741    invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
742    str_style: Option<usize>,
743    fmt_str: &str,
744    uncooked_fmt_str: &str,
745    fmt_span: Span,
746    is_source_literal: bool,
747) {
748    let mut diag = if let &[(span, named)] = &unused[..] {
749        ecx.dcx().create_err(diagnostics::FormatUnusedArg { span, named })
750    } else {
751        let unused_labels = unused
752            .iter()
753            .map(|&(span, named)| diagnostics::FormatUnusedArg { span, named })
754            .collect();
755        let unused_spans = unused.iter().map(|&(span, _)| span).collect();
756        ecx.dcx().create_err(diagnostics::FormatUnusedArgs {
757            fmt: fmt_span,
758            unused: unused_spans,
759            unused_labels,
760        })
761    };
762
763    let placeholders = pieces
764        .iter()
765        .filter_map(|piece| {
766            if let parse::Piece::NextArgument(argument) = piece
767                && let ArgumentNamed(binding) = argument.position
768            {
769                let span = fmt_span.from_inner(InnerSpan::new(
770                    argument.position_span.start,
771                    argument.position_span.end,
772                ));
773                Some((span, binding))
774            } else {
775                None
776            }
777        })
778        .collect::<Vec<_>>();
779
780    if !placeholders.is_empty() {
781        if let Some(new_diag) = report_redundant_format_arguments(ecx, args, used, placeholders) {
782            diag.cancel();
783            new_diag.emit();
784            return;
785        }
786    }
787
788    // Used to ensure we only report translations for *one* kind of foreign format.
789    let mut found_foreign = false;
790
791    // If there's a lot of unused arguments,
792    // let's check if this format arguments looks like another syntax (printf / shell).
793    if unused.len() > args.explicit_args().len() / 2 {
794        use super::format_foreign as foreign;
795
796        // The set of foreign substitutions we've explained. This prevents spamming the user
797        // with `%d should be written as {}` over and over again.
798        let mut explained = FxHashSet::default();
799
800        macro_rules! check_foreign {
801            ($kind:ident) => {{
802                let mut show_doc_note = false;
803
804                let mut suggestions = vec![];
805                // account for `"` and account for raw strings `r#`
806                let padding = str_style.map(|i| i + 2).unwrap_or(1);
807                for sub in foreign::$kind::iter_subs(fmt_str, padding) {
808                    let (trn, success) = match sub.translate() {
809                        Ok(trn) => (trn, true),
810                        Err(Some(msg)) => (msg, false),
811
812                        // If it has no translation, don't call it out specifically.
813                        _ => continue,
814                    };
815
816                    let sub_str = sub.to_string();
817                    if !explained.insert(sub_str.clone()) {
818                        continue;
819                    }
820
821                    if !found_foreign {
822                        found_foreign = true;
823                        show_doc_note = true;
824                    }
825
826                    if is_source_literal {
827                        let sp = fmt_span.from_inner(sub.position());
828
829                        if success {
830                            suggestions.push((sp, trn));
831                        } else {
832                            diag.span_note(
833                                sp,
834                                format!("format specifiers use curly braces, and {}", trn),
835                            );
836                        }
837                    } else if success {
838                        // The offsets of `sub` are relative to the expanded format string, so
839                        // they cannot be mapped back to the source. Explain the translation in
840                        // text instead of suggesting a replacement at a bogus location.
841                        diag.note(format!(
842                            "format specifiers use curly braces: `{sub_str}` should be written as \
843                            `{trn}`"
844                        ));
845                    } else {
846                        diag.span_note(
847                            fmt_span,
848                            format!("format specifiers use curly braces, and {trn}"),
849                        );
850                    }
851                }
852
853                if show_doc_note {
854                    diag.note(concat!(
855                        stringify!($kind),
856                        " formatting is not supported; see the documentation for `std::fmt`",
857                    ));
858                }
859                if suggestions.len() > 0 {
860                    diag.multipart_suggestion(
861                        "format specifiers use curly braces",
862                        suggestions,
863                        Applicability::MachineApplicable,
864                    );
865                }
866            }};
867        }
868
869        {
    let mut show_doc_note = false;
    let mut suggestions = ::alloc::vec::Vec::new();
    let padding = str_style.map(|i| i + 2).unwrap_or(1);
    for sub in foreign::printf::iter_subs(fmt_str, padding) {
        let (trn, success) =
            match sub.translate() {
                Ok(trn) => (trn, true),
                Err(Some(msg)) => (msg, false),
                _ => continue,
            };
        let sub_str = sub.to_string();
        if !explained.insert(sub_str.clone()) { continue; }
        if !found_foreign { found_foreign = true; show_doc_note = true; }
        if is_source_literal {
            let sp = fmt_span.from_inner(sub.position());
            if success {
                suggestions.push((sp, trn));
            } else {
                diag.span_note(sp,
                    ::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!("format specifiers use curly braces, and {0}",
                                    trn))
                        }));
            }
        } else if success {
            diag.note(::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("format specifiers use curly braces: `{0}` should be written as `{1}`",
                                sub_str, trn))
                    }));
        } else {
            diag.span_note(fmt_span,
                ::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("format specifiers use curly braces, and {0}",
                                trn))
                    }));
        }
    }
    if show_doc_note {
        diag.note("printf formatting is not supported; see the documentation for `std::fmt`");
    }
    if suggestions.len() > 0 {
        diag.multipart_suggestion("format specifiers use curly braces",
            suggestions, Applicability::MachineApplicable);
    }
};check_foreign!(printf);
870        if !found_foreign {
871            {
    let mut show_doc_note = false;
    let mut suggestions = ::alloc::vec::Vec::new();
    let padding = str_style.map(|i| i + 2).unwrap_or(1);
    for sub in foreign::shell::iter_subs(fmt_str, padding) {
        let (trn, success) =
            match sub.translate() {
                Ok(trn) => (trn, true),
                Err(Some(msg)) => (msg, false),
                _ => continue,
            };
        let sub_str = sub.to_string();
        if !explained.insert(sub_str.clone()) { continue; }
        if !found_foreign { found_foreign = true; show_doc_note = true; }
        if is_source_literal {
            let sp = fmt_span.from_inner(sub.position());
            if success {
                suggestions.push((sp, trn));
            } else {
                diag.span_note(sp,
                    ::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!("format specifiers use curly braces, and {0}",
                                    trn))
                        }));
            }
        } else if success {
            diag.note(::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("format specifiers use curly braces: `{0}` should be written as `{1}`",
                                sub_str, trn))
                    }));
        } else {
            diag.span_note(fmt_span,
                ::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("format specifiers use curly braces, and {0}",
                                trn))
                    }));
        }
    }
    if show_doc_note {
        diag.note("shell formatting is not supported; see the documentation for `std::fmt`");
    }
    if suggestions.len() > 0 {
        diag.multipart_suggestion("format specifiers use curly braces",
            suggestions, Applicability::MachineApplicable);
    }
};check_foreign!(shell);
872        }
873    }
874    if !found_foreign && unused.len() == 1 {
875        diag.span_label(fmt_span, "formatting specifier missing");
876    }
877
878    if !found_foreign && invalid_refs.is_empty() {
879        // Show example if user didn't use any format specifiers
880        let show_example = !used.contains(&true);
881
882        if !show_example {
883            if unused.len() > 1 {
884                diag.note(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("consider adding {0} format specifiers",
                unused.len()))
    })format!("consider adding {} format specifiers", unused.len()));
885            }
886        } else {
887            let msg = if unused.len() == 1 {
888                "a format specifier".to_string()
889            } else {
890                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} format specifiers",
                unused.len()))
    })format!("{} format specifiers", unused.len())
891            };
892
893            let sugg = match str_style {
894                None => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\"{0}{1}\"", uncooked_fmt_str,
                "{}".repeat(unused.len())))
    })format!("\"{}{}\"", uncooked_fmt_str, "{}".repeat(unused.len())),
895                Some(n_hashes) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("r{0}\"{2}{1}\"{0}",
                "#".repeat(n_hashes), "{}".repeat(unused.len()),
                uncooked_fmt_str))
    })format!(
896                    "r{hashes}\"{uncooked_fmt_str}{fmt_specifiers}\"{hashes}",
897                    hashes = "#".repeat(n_hashes),
898                    fmt_specifiers = "{}".repeat(unused.len())
899                ),
900            };
901            let msg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("format specifiers use curly braces, consider adding {0}",
                msg))
    })format!("format specifiers use curly braces, consider adding {msg}");
902
903            diag.span_suggestion_verbose(fmt_span, msg, sugg, Applicability::MaybeIncorrect);
904        }
905    }
906
907    diag.emit();
908}
909
910/// This function detects and reports unused format!() arguments that are
911/// redundant due to implicit captures (e.g. `format!("{x}", x)`).
912fn report_redundant_format_arguments<'a>(
913    ecx: &ExtCtxt<'a>,
914    args: &FormatArguments,
915    used: &[bool],
916    placeholders: Vec<(Span, &str)>,
917) -> Option<Diag<'a>> {
918    let mut fmt_arg_indices = ::alloc::vec::Vec::new()vec![];
919    let mut args_spans = ::alloc::vec::Vec::new()vec![];
920    let mut fmt_spans = ::alloc::vec::Vec::new()vec![];
921
922    for (i, unnamed_arg) in args.unnamed_args().iter().enumerate().rev() {
923        let Some(ty) = unnamed_arg.expr.to_ty() else { continue };
924        let Some(argument_binding) = ty.kind.is_simple_path() else { continue };
925        let argument_binding = argument_binding.as_str();
926
927        if used[i] {
928            continue;
929        }
930
931        let matching_placeholders = placeholders
932            .iter()
933            .filter(|(_, inline_binding)| argument_binding == *inline_binding)
934            .map(|(span, _)| span)
935            .collect::<Vec<_>>();
936
937        if !matching_placeholders.is_empty() {
938            fmt_arg_indices.push(i);
939            args_spans.push(unnamed_arg.expr.span);
940            for span in &matching_placeholders {
941                if fmt_spans.contains(*span) {
942                    continue;
943                }
944                fmt_spans.push(**span);
945            }
946        }
947    }
948
949    if !args_spans.is_empty() {
950        let multispan = MultiSpan::from(fmt_spans);
951        let mut suggestion_spans = ::alloc::vec::Vec::new()vec![];
952
953        for (arg_span, fmt_arg_idx) in args_spans.iter().zip(fmt_arg_indices.iter()) {
954            let span = if fmt_arg_idx + 1 == args.explicit_args().len() {
955                *arg_span
956            } else {
957                arg_span.until(args.explicit_args()[*fmt_arg_idx + 1].expr.span)
958            };
959
960            suggestion_spans.push(span);
961        }
962
963        let sugg = if args.named_args().is_empty() {
964            Some(diagnostics::FormatRedundantArgsSugg { spans: suggestion_spans })
965        } else {
966            None
967        };
968
969        return Some(ecx.dcx().create_err(diagnostics::FormatRedundantArgs {
970            n: args_spans.len(),
971            span: MultiSpan::from(args_spans),
972            note: multispan,
973            sugg,
974        }));
975    }
976
977    None
978}
979
980/// Handle invalid references to positional arguments. Output different
981/// errors for the case where all arguments are positional and for when
982/// there are named arguments or numbered positional arguments in the
983/// format string.
984fn report_invalid_references(
985    ecx: &ExtCtxt<'_>,
986    invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
987    template: &[FormatArgsPiece],
988    fmt_span: Span,
989    args: &FormatArguments,
990    parser: parse::Parser<'_>,
991) {
992    let num_args_desc = match args.explicit_args().len() {
993        0 => "no arguments were given".to_string(),
994        1 => "there is 1 argument".to_string(),
995        n => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("there are {0} arguments", n))
    })format!("there are {n} arguments"),
996    };
997
998    let mut e;
999
1000    if template.iter().all(|piece| match piece {
1001        FormatArgsPiece::Placeholder(FormatPlaceholder {
1002            argument: FormatArgPosition { kind: FormatArgPositionKind::Number, .. },
1003            ..
1004        }) => false,
1005        FormatArgsPiece::Placeholder(FormatPlaceholder {
1006            format_options:
1007                FormatOptions {
1008                    precision:
1009                        Some(FormatCount::Argument(FormatArgPosition {
1010                            kind: FormatArgPositionKind::Number,
1011                            ..
1012                        })),
1013                    ..
1014                }
1015                | FormatOptions {
1016                    width:
1017                        Some(FormatCount::Argument(FormatArgPosition {
1018                            kind: FormatArgPositionKind::Number,
1019                            ..
1020                        })),
1021                    ..
1022                },
1023            ..
1024        }) => false,
1025        _ => true,
1026    }) {
1027        // There are no numeric positions.
1028        // Collect all the implicit positions:
1029        let mut spans = Vec::new();
1030        let mut num_placeholders = 0;
1031        let mut has_white_space_only_missing_arg = false;
1032        for piece in template {
1033            let mut placeholder = None;
1034            // `{arg:.*}`
1035            if let FormatArgsPiece::Placeholder(FormatPlaceholder {
1036                format_options:
1037                    FormatOptions {
1038                        precision:
1039                            Some(FormatCount::Argument(FormatArgPosition {
1040                                span,
1041                                kind: FormatArgPositionKind::Implicit,
1042                                ..
1043                            })),
1044                        ..
1045                    },
1046                ..
1047            }) = piece
1048            {
1049                placeholder = *span;
1050                num_placeholders += 1;
1051            }
1052            // `{}`
1053            if let FormatArgsPiece::Placeholder(FormatPlaceholder {
1054                argument: FormatArgPosition { kind: FormatArgPositionKind::Implicit, index, .. },
1055                span,
1056                ..
1057            }) = piece
1058            {
1059                placeholder = *span;
1060                num_placeholders += 1;
1061                //  Check whether there's any non-space whitespace in the placeholder. If so, we should emit a note suggesting an escaping `{`.
1062                if index.is_err()
1063                    && let Some(span) = span
1064                    && let Ok(snippet) = ecx.source_map().span_to_snippet(*span)
1065                    && snippet.chars().any(|c| c.is_whitespace() && c != ' ')
1066                {
1067                    has_white_space_only_missing_arg = true;
1068                }
1069            }
1070            // For `{:.*}`, we only push one span.
1071            spans.extend(placeholder);
1072        }
1073        let span = if spans.is_empty() {
1074            MultiSpan::from_span(fmt_span)
1075        } else {
1076            MultiSpan::from_spans(spans)
1077        };
1078        e = ecx.dcx().create_err(diagnostics::FormatPositionalMismatch {
1079            span,
1080            n: num_placeholders,
1081            desc: num_args_desc,
1082            highlight: SingleLabelManySpans {
1083                spans: args.explicit_args().iter().map(|arg| arg.expr.span).collect(),
1084                label: "",
1085            },
1086        });
1087        // Point out `{:.*}` placeholders: those take an extra argument.
1088        let mut has_precision_star = false;
1089        for piece in template {
1090            if let FormatArgsPiece::Placeholder(FormatPlaceholder {
1091                format_options:
1092                    FormatOptions {
1093                        precision:
1094                            Some(FormatCount::Argument(FormatArgPosition {
1095                                index,
1096                                span: Some(span),
1097                                kind: FormatArgPositionKind::Implicit,
1098                                ..
1099                            })),
1100                        ..
1101                    },
1102                ..
1103            }) = piece
1104            {
1105                let (Ok(index) | Err(index)) = index;
1106                has_precision_star = true;
1107                e.span_label(
1108                    *span,
1109                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("this precision flag adds an extra required argument at position {0}, which is why there {1} expected",
                index,
                if num_placeholders == 1 {
                    "is 1 argument".to_string()
                } else {
                    ::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!("are {0} arguments",
                                    num_placeholders))
                        })
                }))
    })format!(
1110                        "this precision flag adds an extra required argument at position {}, which is why there {} expected",
1111                        index,
1112                        if num_placeholders == 1 {
1113                            "is 1 argument".to_string()
1114                        } else {
1115                            format!("are {num_placeholders} arguments")
1116                        },
1117                    ),
1118                );
1119            }
1120        }
1121        if has_precision_star {
1122            e.note("positional arguments are zero-based");
1123        }
1124        if has_white_space_only_missing_arg {
1125            e.note("if you intended to print `{`, you can escape it with `{{`");
1126        }
1127    } else {
1128        let mut indexes: Vec<_> = invalid_refs.iter().map(|&(index, _, _, _)| index).collect();
1129        // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)`
1130        // for `println!("{7:7$}", 1);`
1131        indexes.sort();
1132        indexes.dedup();
1133        let span = if !parser.is_source_literal || parser.arg_places.is_empty() {
1134            MultiSpan::from_span(fmt_span)
1135        } else {
1136            MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect())
1137        };
1138        let arg_list = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("argument{0} {1}",
                if indexes.len() == 1 { "" } else { "s" },
                listify(&indexes,
                        |i: &usize| i.to_string()).unwrap_or_default()))
    })format!(
1139            "argument{} {}",
1140            pluralize!(indexes.len()),
1141            listify(&indexes, |i: &usize| i.to_string()).unwrap_or_default()
1142        );
1143        e = ecx.dcx().struct_span_err(
1144            span,
1145            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("invalid reference to positional {0} ({1})",
                arg_list, num_args_desc))
    })format!("invalid reference to positional {arg_list} ({num_args_desc})"),
1146        );
1147        e.note("positional arguments are zero-based");
1148    }
1149
1150    if template.iter().any(|piece| match piece {
1151        FormatArgsPiece::Placeholder(FormatPlaceholder { format_options: f, .. }) => {
1152            *f != FormatOptions::default()
1153        }
1154        _ => false,
1155    }) {
1156        e.note("for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html");
1157    }
1158
1159    e.emit();
1160}
1161
1162fn expand_format_args_impl<'cx>(
1163    ecx: &'cx mut ExtCtxt<'_>,
1164    mut sp: Span,
1165    tts: TokenStream,
1166    nl: bool,
1167) -> MacroExpanderResult<'cx> {
1168    sp = ecx.with_def_site_ctxt(sp);
1169    ExpandResult::Ready(match parse_args(ecx, sp, tts) {
1170        Ok(input) => {
1171            let ExpandResult::Ready(mac) = make_format_args(ecx, input, nl, sp) else {
1172                return ExpandResult::Retry(());
1173            };
1174            match mac {
1175                Ok(format_args) => {
1176                    MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(Box::new(format_args))))
1177                }
1178                Err(guar) => MacEager::expr(DummyResult::raw_expr(sp, Some(guar))),
1179            }
1180        }
1181        Err(err) => {
1182            let guar = err.emit_err();
1183            DummyResult::any(sp, guar)
1184        }
1185    })
1186}
1187
1188pub(crate) fn expand_format_args<'cx>(
1189    ecx: &'cx mut ExtCtxt<'_>,
1190    sp: Span,
1191    tts: TokenStream,
1192) -> MacroExpanderResult<'cx> {
1193    expand_format_args_impl(ecx, sp, tts, false)
1194}
1195
1196pub(crate) fn expand_format_args_nl<'cx>(
1197    ecx: &'cx mut ExtCtxt<'_>,
1198    sp: Span,
1199    tts: TokenStream,
1200) -> MacroExpanderResult<'cx> {
1201    expand_format_args_impl(ecx, sp, tts, true)
1202}