]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_builtin_macros/src/format.rs
Auto merge of #105416 - nnethercote:more-linting-tweaks, r=cjgillot
[rust.git] / compiler / rustc_builtin_macros / src / format.rs
1 use rustc_ast::ptr::P;
2 use rustc_ast::token;
3 use rustc_ast::tokenstream::TokenStream;
4 use rustc_ast::Expr;
5 use rustc_data_structures::fx::FxHashSet;
6 use rustc_errors::{pluralize, Applicability, MultiSpan, PResult};
7 use rustc_expand::base::{self, *};
8 use rustc_parse_format as parse;
9 use rustc_span::symbol::{Ident, Symbol};
10 use rustc_span::{BytePos, InnerSpan, Span};
11
12 use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
13 use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId};
14
15 mod ast;
16 use ast::*;
17
18 mod expand;
19 use expand::expand_parsed_format_args;
20
21 // The format_args!() macro is expanded in three steps:
22 //  1. First, `parse_args` will parse the `(literal, arg, arg, name=arg, name=arg)` syntax,
23 //     but doesn't parse the template (the literal) itself.
24 //  2. Second, `make_format_args` will parse the template, the format options, resolve argument references,
25 //     produce diagnostics, and turn the whole thing into a `FormatArgs` structure.
26 //  3. Finally, `expand_parsed_format_args` will turn that `FormatArgs` structure
27 //     into the expression that the macro expands to.
28
29 // See format/ast.rs for the FormatArgs structure and glossary.
30
31 // Only used in parse_args and report_invalid_references,
32 // to indicate how a referred argument was used.
33 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
34 enum PositionUsedAs {
35     Placeholder(Option<Span>),
36     Precision,
37     Width,
38 }
39 use PositionUsedAs::*;
40
41 /// Parses the arguments from the given list of tokens, returning the diagnostic
42 /// if there's a parse error so we can continue parsing other format!
43 /// expressions.
44 ///
45 /// If parsing succeeds, the return value is:
46 ///
47 /// ```text
48 /// Ok((fmtstr, parsed arguments))
49 /// ```
50 fn parse_args<'a>(
51     ecx: &mut ExtCtxt<'a>,
52     sp: Span,
53     tts: TokenStream,
54 ) -> PResult<'a, (P<Expr>, FormatArguments)> {
55     let mut args = FormatArguments::new();
56
57     let mut p = ecx.new_parser_from_tts(tts);
58
59     if p.token == token::Eof {
60         return Err(ecx.struct_span_err(sp, "requires at least a format string argument"));
61     }
62
63     let first_token = &p.token;
64     let fmtstr = match first_token.kind {
65         token::TokenKind::Literal(token::Lit {
66             kind: token::LitKind::Str | token::LitKind::StrRaw(_),
67             ..
68         }) => {
69             // If the first token is a string literal, then a format expression
70             // is constructed from it.
71             //
72             // This allows us to properly handle cases when the first comma
73             // after the format string is mistakenly replaced with any operator,
74             // which cause the expression parser to eat too much tokens.
75             p.parse_literal_maybe_minus()?
76         }
77         _ => {
78             // Otherwise, we fall back to the expression parser.
79             p.parse_expr()?
80         }
81     };
82
83     let mut first = true;
84
85     while p.token != token::Eof {
86         if !p.eat(&token::Comma) {
87             if first {
88                 p.clear_expected_tokens();
89             }
90
91             match p.expect(&token::Comma) {
92                 Err(mut err) => {
93                     match token::TokenKind::Comma.similar_tokens() {
94                         Some(tks) if tks.contains(&p.token.kind) => {
95                             // If a similar token is found, then it may be a typo. We
96                             // consider it as a comma, and continue parsing.
97                             err.emit();
98                             p.bump();
99                         }
100                         // Otherwise stop the parsing and return the error.
101                         _ => return Err(err),
102                     }
103                 }
104                 Ok(recovered) => {
105                     assert!(recovered);
106                 }
107             }
108         }
109         first = false;
110         if p.token == token::Eof {
111             break;
112         } // accept trailing commas
113         match p.token.ident() {
114             Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => {
115                 p.bump();
116                 p.expect(&token::Eq)?;
117                 let expr = p.parse_expr()?;
118                 if let Some((_, prev)) = args.by_name(ident.name) {
119                     ecx.struct_span_err(
120                         ident.span,
121                         &format!("duplicate argument named `{}`", ident),
122                     )
123                     .span_label(prev.kind.ident().unwrap().span, "previously here")
124                     .span_label(ident.span, "duplicate argument")
125                     .emit();
126                     continue;
127                 }
128                 args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr });
129             }
130             _ => {
131                 let expr = p.parse_expr()?;
132                 if !args.named_args().is_empty() {
133                     let mut err = ecx.struct_span_err(
134                         expr.span,
135                         "positional arguments cannot follow named arguments",
136                     );
137                     err.span_label(
138                         expr.span,
139                         "positional arguments must be before named arguments",
140                     );
141                     for arg in args.named_args() {
142                         if let Some(name) = arg.kind.ident() {
143                             err.span_label(name.span.to(arg.expr.span), "named argument");
144                         }
145                     }
146                     err.emit();
147                 }
148                 args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr });
149             }
150         }
151     }
152     Ok((fmtstr, args))
153 }
154
155 pub fn make_format_args(
156     ecx: &mut ExtCtxt<'_>,
157     efmt: P<Expr>,
158     mut args: FormatArguments,
159     append_newline: bool,
160 ) -> Result<FormatArgs, ()> {
161     let msg = "format argument must be a string literal";
162     let unexpanded_fmt_span = efmt.span;
163     let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) {
164         Ok(mut fmt) if append_newline => {
165             fmt.0 = Symbol::intern(&format!("{}\n", fmt.0));
166             fmt
167         }
168         Ok(fmt) => fmt,
169         Err(err) => {
170             if let Some((mut err, suggested)) = err {
171                 let sugg_fmt = match args.explicit_args().len() {
172                     0 => "{}".to_string(),
173                     _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())),
174                 };
175                 if !suggested {
176                     err.span_suggestion(
177                         unexpanded_fmt_span.shrink_to_lo(),
178                         "you might be missing a string literal to format with",
179                         format!("\"{}\", ", sugg_fmt),
180                         Applicability::MaybeIncorrect,
181                     );
182                 }
183                 err.emit();
184             }
185             return Err(());
186         }
187     };
188
189     let str_style = match fmt_style {
190         rustc_ast::StrStyle::Cooked => None,
191         rustc_ast::StrStyle::Raw(raw) => Some(raw as usize),
192     };
193
194     let fmt_str = fmt_str.as_str(); // for the suggestions below
195     let fmt_snippet = ecx.source_map().span_to_snippet(unexpanded_fmt_span).ok();
196     let mut parser = parse::Parser::new(
197         fmt_str,
198         str_style,
199         fmt_snippet,
200         append_newline,
201         parse::ParseMode::Format,
202     );
203
204     let mut pieces = Vec::new();
205     while let Some(piece) = parser.next() {
206         if !parser.errors.is_empty() {
207             break;
208         } else {
209             pieces.push(piece);
210         }
211     }
212
213     let is_literal = parser.is_literal;
214
215     if !parser.errors.is_empty() {
216         let err = parser.errors.remove(0);
217         let sp = if is_literal {
218             fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end))
219         } else {
220             // The format string could be another macro invocation, e.g.:
221             //     format!(concat!("abc", "{}"), 4);
222             // However, `err.span` is an inner span relative to the *result* of
223             // the macro invocation, which is why we would get a nonsensical
224             // result calling `fmt_span.from_inner(err.span)` as above, and
225             // might even end up inside a multibyte character (issue #86085).
226             // Therefore, we conservatively report the error for the entire
227             // argument span here.
228             fmt_span
229         };
230         let mut e = ecx.struct_span_err(sp, &format!("invalid format string: {}", err.description));
231         e.span_label(sp, err.label + " in format string");
232         if let Some(note) = err.note {
233             e.note(&note);
234         }
235         if let Some((label, span)) = err.secondary_label && is_literal {
236             e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label);
237         }
238         if err.should_be_replaced_with_positional_argument {
239             let captured_arg_span =
240                 fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
241             if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) {
242                 let span = match args.unnamed_args().last() {
243                     Some(arg) => arg.expr.span,
244                     None => fmt_span,
245                 };
246                 e.multipart_suggestion_verbose(
247                     "consider using a positional formatting argument instead",
248                     vec![
249                         (captured_arg_span, args.unnamed_args().len().to_string()),
250                         (span.shrink_to_hi(), format!(", {}", arg)),
251                     ],
252                     Applicability::MachineApplicable,
253                 );
254             }
255         }
256         e.emit();
257         return Err(());
258     }
259
260     let to_span = |inner_span: rustc_parse_format::InnerSpan| {
261         is_literal.then(|| {
262             fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end })
263         })
264     };
265
266     let mut used = vec![false; args.explicit_args().len()];
267     let mut invalid_refs = Vec::new();
268     let mut numeric_refences_to_named_arg = Vec::new();
269
270     enum ArgRef<'a> {
271         Index(usize),
272         Name(&'a str, Option<Span>),
273     }
274     use ArgRef::*;
275
276     let mut lookup_arg = |arg: ArgRef<'_>,
277                           span: Option<Span>,
278                           used_as: PositionUsedAs,
279                           kind: FormatArgPositionKind|
280      -> FormatArgPosition {
281         let index = match arg {
282             Index(index) => {
283                 if let Some(arg) = args.by_index(index) {
284                     used[index] = true;
285                     if arg.kind.ident().is_some() {
286                         // This was a named argument, but it was used as a positional argument.
287                         numeric_refences_to_named_arg.push((index, span, used_as));
288                     }
289                     Ok(index)
290                 } else {
291                     // Doesn't exist as an explicit argument.
292                     invalid_refs.push((index, span, used_as, kind));
293                     Err(index)
294                 }
295             }
296             Name(name, span) => {
297                 let name = Symbol::intern(name);
298                 if let Some((index, _)) = args.by_name(name) {
299                     // Name found in `args`, so we resolve it to its index.
300                     if index < args.explicit_args().len() {
301                         // Mark it as used, if it was an explicit argument.
302                         used[index] = true;
303                     }
304                     Ok(index)
305                 } else {
306                     // Name not found in `args`, so we add it as an implicitly captured argument.
307                     let span = span.unwrap_or(fmt_span);
308                     let ident = Ident::new(name, span);
309                     let expr = if is_literal {
310                         ecx.expr_ident(span, ident)
311                     } else {
312                         // For the moment capturing variables from format strings expanded from macros is
313                         // disabled (see RFC #2795)
314                         ecx.struct_span_err(span, &format!("there is no argument named `{name}`"))
315                             .note(format!("did you intend to capture a variable `{name}` from the surrounding scope?"))
316                             .note("to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro")
317                             .emit();
318                         DummyResult::raw_expr(span, true)
319                     };
320                     Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }))
321                 }
322             }
323         };
324         FormatArgPosition { index, kind, span }
325     };
326
327     let mut template = Vec::new();
328     let mut unfinished_literal = String::new();
329     let mut placeholder_index = 0;
330
331     for piece in pieces {
332         match piece {
333             parse::Piece::String(s) => {
334                 unfinished_literal.push_str(s);
335             }
336             parse::Piece::NextArgument(box parse::Argument { position, position_span, format }) => {
337                 if !unfinished_literal.is_empty() {
338                     template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
339                     unfinished_literal.clear();
340                 }
341
342                 let span = parser.arg_places.get(placeholder_index).and_then(|&s| to_span(s));
343                 placeholder_index += 1;
344
345                 let position_span = to_span(position_span);
346                 let argument = match position {
347                     parse::ArgumentImplicitlyIs(i) => lookup_arg(
348                         Index(i),
349                         position_span,
350                         Placeholder(span),
351                         FormatArgPositionKind::Implicit,
352                     ),
353                     parse::ArgumentIs(i) => lookup_arg(
354                         Index(i),
355                         position_span,
356                         Placeholder(span),
357                         FormatArgPositionKind::Number,
358                     ),
359                     parse::ArgumentNamed(name) => lookup_arg(
360                         Name(name, position_span),
361                         position_span,
362                         Placeholder(span),
363                         FormatArgPositionKind::Named,
364                     ),
365                 };
366
367                 let alignment = match format.align {
368                     parse::AlignUnknown => None,
369                     parse::AlignLeft => Some(FormatAlignment::Left),
370                     parse::AlignRight => Some(FormatAlignment::Right),
371                     parse::AlignCenter => Some(FormatAlignment::Center),
372                 };
373
374                 let format_trait = match format.ty {
375                     "" => FormatTrait::Display,
376                     "?" => FormatTrait::Debug,
377                     "e" => FormatTrait::LowerExp,
378                     "E" => FormatTrait::UpperExp,
379                     "o" => FormatTrait::Octal,
380                     "p" => FormatTrait::Pointer,
381                     "b" => FormatTrait::Binary,
382                     "x" => FormatTrait::LowerHex,
383                     "X" => FormatTrait::UpperHex,
384                     _ => {
385                         invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span);
386                         FormatTrait::Display
387                     }
388                 };
389
390                 let precision_span = format.precision_span.and_then(to_span);
391                 let precision = match format.precision {
392                     parse::CountIs(n) => Some(FormatCount::Literal(n)),
393                     parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
394                         Name(name, to_span(name_span)),
395                         precision_span,
396                         Precision,
397                         FormatArgPositionKind::Named,
398                     ))),
399                     parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
400                         Index(i),
401                         precision_span,
402                         Precision,
403                         FormatArgPositionKind::Number,
404                     ))),
405                     parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
406                         Index(i),
407                         precision_span,
408                         Precision,
409                         FormatArgPositionKind::Implicit,
410                     ))),
411                     parse::CountImplied => None,
412                 };
413
414                 let width_span = format.width_span.and_then(to_span);
415                 let width = match format.width {
416                     parse::CountIs(n) => Some(FormatCount::Literal(n)),
417                     parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
418                         Name(name, to_span(name_span)),
419                         width_span,
420                         Width,
421                         FormatArgPositionKind::Named,
422                     ))),
423                     parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
424                         Index(i),
425                         width_span,
426                         Width,
427                         FormatArgPositionKind::Number,
428                     ))),
429                     parse::CountIsStar(_) => unreachable!(),
430                     parse::CountImplied => None,
431                 };
432
433                 template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
434                     argument,
435                     span,
436                     format_trait,
437                     format_options: FormatOptions {
438                         fill: format.fill,
439                         alignment,
440                         flags: format.flags,
441                         precision,
442                         width,
443                     },
444                 }));
445             }
446         }
447     }
448
449     if !unfinished_literal.is_empty() {
450         template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
451     }
452
453     if !invalid_refs.is_empty() {
454         report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser);
455     }
456
457     let unused = used
458         .iter()
459         .enumerate()
460         .filter(|&(_, used)| !used)
461         .map(|(i, _)| {
462             let msg = if let FormatArgumentKind::Named(_) = args.explicit_args()[i].kind {
463                 "named argument never used"
464             } else {
465                 "argument never used"
466             };
467             (args.explicit_args()[i].expr.span, msg)
468         })
469         .collect::<Vec<_>>();
470
471     if !unused.is_empty() {
472         // If there's a lot of unused arguments,
473         // let's check if this format arguments looks like another syntax (printf / shell).
474         let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2;
475         report_missing_placeholders(ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span);
476     }
477
478     // Only check for unused named argument names if there are no other errors to avoid causing
479     // too much noise in output errors, such as when a named argument is entirely unused.
480     if invalid_refs.is_empty() && ecx.sess.err_count() == 0 {
481         for &(index, span, used_as) in &numeric_refences_to_named_arg {
482             let (position_sp_to_replace, position_sp_for_msg) = match used_as {
483                 Placeholder(pspan) => (span, pspan),
484                 Precision => {
485                     // Strip the leading `.` for precision.
486                     let span = span.map(|span| span.with_lo(span.lo() + BytePos(1)));
487                     (span, span)
488                 }
489                 Width => (span, span),
490             };
491             let arg_name = args.explicit_args()[index].kind.ident().unwrap();
492             ecx.buffered_early_lint.push(BufferedEarlyLint {
493                 span: arg_name.span.into(),
494                 msg: format!("named argument `{}` is not used by name", arg_name.name).into(),
495                 node_id: rustc_ast::CRATE_NODE_ID,
496                 lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY),
497                 diagnostic: BuiltinLintDiagnostics::NamedArgumentUsedPositionally {
498                     position_sp_to_replace,
499                     position_sp_for_msg,
500                     named_arg_sp: arg_name.span,
501                     named_arg_name: arg_name.name.to_string(),
502                     is_formatting_arg: matches!(used_as, Width | Precision),
503                 },
504             });
505         }
506     }
507
508     Ok(FormatArgs { span: fmt_span, template, arguments: args })
509 }
510
511 fn invalid_placeholder_type_error(
512     ecx: &ExtCtxt<'_>,
513     ty: &str,
514     ty_span: Option<rustc_parse_format::InnerSpan>,
515     fmt_span: Span,
516 ) {
517     let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end)));
518     let mut err =
519         ecx.struct_span_err(sp.unwrap_or(fmt_span), &format!("unknown format trait `{}`", ty));
520     err.note(
521         "the only appropriate formatting traits are:\n\
522                                 - ``, which uses the `Display` trait\n\
523                                 - `?`, which uses the `Debug` trait\n\
524                                 - `e`, which uses the `LowerExp` trait\n\
525                                 - `E`, which uses the `UpperExp` trait\n\
526                                 - `o`, which uses the `Octal` trait\n\
527                                 - `p`, which uses the `Pointer` trait\n\
528                                 - `b`, which uses the `Binary` trait\n\
529                                 - `x`, which uses the `LowerHex` trait\n\
530                                 - `X`, which uses the `UpperHex` trait",
531     );
532     if let Some(sp) = sp {
533         for (fmt, name) in &[
534             ("", "Display"),
535             ("?", "Debug"),
536             ("e", "LowerExp"),
537             ("E", "UpperExp"),
538             ("o", "Octal"),
539             ("p", "Pointer"),
540             ("b", "Binary"),
541             ("x", "LowerHex"),
542             ("X", "UpperHex"),
543         ] {
544             err.tool_only_span_suggestion(
545                 sp,
546                 &format!("use the `{}` trait", name),
547                 *fmt,
548                 Applicability::MaybeIncorrect,
549             );
550         }
551     }
552     err.emit();
553 }
554
555 fn report_missing_placeholders(
556     ecx: &mut ExtCtxt<'_>,
557     unused: Vec<(Span, &str)>,
558     detect_foreign_fmt: bool,
559     str_style: Option<usize>,
560     fmt_str: &str,
561     fmt_span: Span,
562 ) {
563     let mut diag = if let &[(span, msg)] = &unused[..] {
564         let mut diag = ecx.struct_span_err(span, msg);
565         diag.span_label(span, msg);
566         diag
567     } else {
568         let mut diag = ecx.struct_span_err(
569             unused.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
570             "multiple unused formatting arguments",
571         );
572         diag.span_label(fmt_span, "multiple missing formatting specifiers");
573         for &(span, msg) in &unused {
574             diag.span_label(span, msg);
575         }
576         diag
577     };
578
579     // Used to ensure we only report translations for *one* kind of foreign format.
580     let mut found_foreign = false;
581
582     // Decide if we want to look for foreign formatting directives.
583     if detect_foreign_fmt {
584         use super::format_foreign as foreign;
585
586         // The set of foreign substitutions we've explained.  This prevents spamming the user
587         // with `%d should be written as {}` over and over again.
588         let mut explained = FxHashSet::default();
589
590         macro_rules! check_foreign {
591             ($kind:ident) => {{
592                 let mut show_doc_note = false;
593
594                 let mut suggestions = vec![];
595                 // account for `"` and account for raw strings `r#`
596                 let padding = str_style.map(|i| i + 2).unwrap_or(1);
597                 for sub in foreign::$kind::iter_subs(fmt_str, padding) {
598                     let (trn, success) = match sub.translate() {
599                         Ok(trn) => (trn, true),
600                         Err(Some(msg)) => (msg, false),
601
602                         // If it has no translation, don't call it out specifically.
603                         _ => continue,
604                     };
605
606                     let pos = sub.position();
607                     let sub = String::from(sub.as_str());
608                     if explained.contains(&sub) {
609                         continue;
610                     }
611                     explained.insert(sub.clone());
612
613                     if !found_foreign {
614                         found_foreign = true;
615                         show_doc_note = true;
616                     }
617
618                     if let Some(inner_sp) = pos {
619                         let sp = fmt_span.from_inner(inner_sp);
620
621                         if success {
622                             suggestions.push((sp, trn));
623                         } else {
624                             diag.span_note(
625                                 sp,
626                                 &format!("format specifiers use curly braces, and {}", trn),
627                             );
628                         }
629                     } else {
630                         if success {
631                             diag.help(&format!("`{}` should be written as `{}`", sub, trn));
632                         } else {
633                             diag.note(&format!("`{}` should use curly braces, and {}", sub, trn));
634                         }
635                     }
636                 }
637
638                 if show_doc_note {
639                     diag.note(concat!(
640                         stringify!($kind),
641                         " formatting not supported; see the documentation for `std::fmt`",
642                     ));
643                 }
644                 if suggestions.len() > 0 {
645                     diag.multipart_suggestion(
646                         "format specifiers use curly braces",
647                         suggestions,
648                         Applicability::MachineApplicable,
649                     );
650                 }
651             }};
652         }
653
654         check_foreign!(printf);
655         if !found_foreign {
656             check_foreign!(shell);
657         }
658     }
659     if !found_foreign && unused.len() == 1 {
660         diag.span_label(fmt_span, "formatting specifier missing");
661     }
662
663     diag.emit();
664 }
665
666 /// Handle invalid references to positional arguments. Output different
667 /// errors for the case where all arguments are positional and for when
668 /// there are named arguments or numbered positional arguments in the
669 /// format string.
670 fn report_invalid_references(
671     ecx: &mut ExtCtxt<'_>,
672     invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
673     template: &[FormatArgsPiece],
674     fmt_span: Span,
675     args: &FormatArguments,
676     parser: parse::Parser<'_>,
677 ) {
678     let num_args_desc = match args.explicit_args().len() {
679         0 => "no arguments were given".to_string(),
680         1 => "there is 1 argument".to_string(),
681         n => format!("there are {} arguments", n),
682     };
683
684     let mut e;
685
686     if template.iter().all(|piece| match piece {
687         FormatArgsPiece::Placeholder(FormatPlaceholder {
688             argument: FormatArgPosition { kind: FormatArgPositionKind::Number, .. },
689             ..
690         }) => false,
691         FormatArgsPiece::Placeholder(FormatPlaceholder {
692             format_options:
693                 FormatOptions {
694                     precision:
695                         Some(FormatCount::Argument(FormatArgPosition {
696                             kind: FormatArgPositionKind::Number,
697                             ..
698                         })),
699                     ..
700                 }
701                 | FormatOptions {
702                     width:
703                         Some(FormatCount::Argument(FormatArgPosition {
704                             kind: FormatArgPositionKind::Number,
705                             ..
706                         })),
707                     ..
708                 },
709             ..
710         }) => false,
711         _ => true,
712     }) {
713         // There are no numeric positions.
714         // Collect all the implicit positions:
715         let mut spans = Vec::new();
716         let mut num_placeholders = 0;
717         for piece in template {
718             let mut placeholder = None;
719             // `{arg:.*}`
720             if let FormatArgsPiece::Placeholder(FormatPlaceholder {
721                 format_options:
722                     FormatOptions {
723                         precision:
724                             Some(FormatCount::Argument(FormatArgPosition {
725                                 span,
726                                 kind: FormatArgPositionKind::Implicit,
727                                 ..
728                             })),
729                         ..
730                     },
731                 ..
732             }) = piece
733             {
734                 placeholder = *span;
735                 num_placeholders += 1;
736             }
737             // `{}`
738             if let FormatArgsPiece::Placeholder(FormatPlaceholder {
739                 argument: FormatArgPosition { kind: FormatArgPositionKind::Implicit, .. },
740                 span,
741                 ..
742             }) = piece
743             {
744                 placeholder = *span;
745                 num_placeholders += 1;
746             }
747             // For `{:.*}`, we only push one span.
748             spans.extend(placeholder);
749         }
750         let span = if spans.is_empty() {
751             MultiSpan::from_span(fmt_span)
752         } else {
753             MultiSpan::from_spans(spans)
754         };
755         e = ecx.struct_span_err(
756             span,
757             &format!(
758                 "{} positional argument{} in format string, but {}",
759                 num_placeholders,
760                 pluralize!(num_placeholders),
761                 num_args_desc,
762             ),
763         );
764         for arg in args.explicit_args() {
765             e.span_label(arg.expr.span, "");
766         }
767         // Point out `{:.*}` placeholders: those take an extra argument.
768         let mut has_precision_star = false;
769         for piece in template {
770             if let FormatArgsPiece::Placeholder(FormatPlaceholder {
771                 format_options:
772                     FormatOptions {
773                         precision:
774                             Some(FormatCount::Argument(FormatArgPosition {
775                                 index,
776                                 span: Some(span),
777                                 kind: FormatArgPositionKind::Implicit,
778                                 ..
779                             })),
780                         ..
781                     },
782                 ..
783             }) = piece
784             {
785                 let (Ok(index) | Err(index)) = index;
786                 has_precision_star = true;
787                 e.span_label(
788                     *span,
789                     &format!(
790                         "this precision flag adds an extra required argument at position {}, which is why there {} expected",
791                         index,
792                         if num_placeholders == 1 {
793                             "is 1 argument".to_string()
794                         } else {
795                             format!("are {} arguments", num_placeholders)
796                         },
797                     ),
798                 );
799             }
800         }
801         if has_precision_star {
802             e.note("positional arguments are zero-based");
803         }
804     } else {
805         let mut indexes: Vec<_> = invalid_refs.iter().map(|&(index, _, _, _)| index).collect();
806         // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)`
807         // for `println!("{7:7$}", 1);`
808         indexes.sort();
809         indexes.dedup();
810         let span: MultiSpan = if !parser.is_literal || parser.arg_places.is_empty() {
811             MultiSpan::from_span(fmt_span)
812         } else {
813             MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect())
814         };
815         let arg_list = if let &[index] = &indexes[..] {
816             format!("argument {index}")
817         } else {
818             let tail = indexes.pop().unwrap();
819             format!(
820                 "arguments {head} and {tail}",
821                 head = indexes.into_iter().map(|i| i.to_string()).collect::<Vec<_>>().join(", ")
822             )
823         };
824         e = ecx.struct_span_err(
825             span,
826             &format!("invalid reference to positional {} ({})", arg_list, num_args_desc),
827         );
828         e.note("positional arguments are zero-based");
829     }
830
831     if template.iter().any(|piece| match piece {
832         FormatArgsPiece::Placeholder(FormatPlaceholder { format_options: f, .. }) => {
833             *f != FormatOptions::default()
834         }
835         _ => false,
836     }) {
837         e.note("for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html");
838     }
839
840     e.emit();
841 }
842
843 fn expand_format_args_impl<'cx>(
844     ecx: &'cx mut ExtCtxt<'_>,
845     mut sp: Span,
846     tts: TokenStream,
847     nl: bool,
848 ) -> Box<dyn base::MacResult + 'cx> {
849     sp = ecx.with_def_site_ctxt(sp);
850     match parse_args(ecx, sp, tts) {
851         Ok((efmt, args)) => {
852             if let Ok(format_args) = make_format_args(ecx, efmt, args, nl) {
853                 MacEager::expr(expand_parsed_format_args(ecx, format_args))
854             } else {
855                 MacEager::expr(DummyResult::raw_expr(sp, true))
856             }
857         }
858         Err(mut err) => {
859             err.emit();
860             DummyResult::any(sp)
861         }
862     }
863 }
864
865 pub fn expand_format_args<'cx>(
866     ecx: &'cx mut ExtCtxt<'_>,
867     sp: Span,
868     tts: TokenStream,
869 ) -> Box<dyn base::MacResult + 'cx> {
870     expand_format_args_impl(ecx, sp, tts, false)
871 }
872
873 pub fn expand_format_args_nl<'cx>(
874     ecx: &'cx mut ExtCtxt<'_>,
875     sp: Span,
876     tts: TokenStream,
877 ) -> Box<dyn base::MacResult + 'cx> {
878     expand_format_args_impl(ecx, sp, tts, true)
879 }