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