rustc_builtin_macros/
format.rs

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