Skip to main content

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