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