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