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