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