rustc_builtin_macros/
format.rs

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