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