1use parse::Position::ArgumentNamed;
2use rustc_ast::ptr::P;
3use rustc_ast::tokenstream::TokenStream;
4use rustc_ast::{
5 Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs,
6 FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount,
7 FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait, Recovered, StmtKind,
8 token,
9};
10use rustc_data_structures::fx::FxHashSet;
11use rustc_errors::{
12 Applicability, Diag, MultiSpan, PResult, SingleLabelManySpans, listify, pluralize,
13};
14use rustc_expand::base::*;
15use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
16use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiag, LintId};
17use rustc_parse::exp;
18use rustc_parse_format as parse;
19use rustc_span::{BytePos, ErrorGuaranteed, Ident, InnerSpan, Span, Symbol};
20
21use crate::errors;
22use crate::util::{ExprToSpannedString, expr_to_spanned_string};
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37enum PositionUsedAs {
38 Placeholder(Option<Span>),
39 Precision,
40 Width,
41}
42use PositionUsedAs::*;
43
44#[derive(Debug)]
45struct MacroInput {
46 fmtstr: P<Expr>,
47 args: FormatArguments,
48 is_direct_literal: bool,
58}
59
60fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> {
70 let mut args = FormatArguments::new();
71
72 let mut p = ecx.new_parser_from_tts(tts);
73
74 if p.token == token::Eof {
75 return Err(ecx.dcx().create_err(errors::FormatRequiresString { span: sp }));
76 }
77
78 let first_token = &p.token;
79
80 let fmtstr = if let token::Literal(lit) = first_token.kind
81 && matches!(lit.kind, token::Str | token::StrRaw(_))
82 {
83 p.parse_literal_maybe_minus()?
87 } else {
88 p.parse_expr()?
90 };
91
92 let is_direct_literal = matches!(fmtstr.kind, ExprKind::Lit(_));
95
96 let mut first = true;
97
98 while p.token != token::Eof {
99 if !p.eat(exp!(Comma)) {
100 if first {
101 p.clear_expected_token_types();
102 }
103
104 match p.expect(exp!(Comma)) {
105 Err(err) => {
106 if token::TokenKind::Comma.similar_tokens().contains(&p.token.kind) {
107 err.emit();
110 p.bump();
111 } else {
112 return Err(err);
114 }
115 }
116 Ok(Recovered::Yes(_)) => (),
117 Ok(Recovered::No) => unreachable!(),
118 }
119 }
120 first = false;
121 if p.token == token::Eof {
122 break;
123 } match p.token.ident() {
125 Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => {
126 p.bump();
127 p.expect(exp!(Eq))?;
128 let expr = p.parse_expr()?;
129 if let Some((_, prev)) = args.by_name(ident.name) {
130 ecx.dcx().emit_err(errors::FormatDuplicateArg {
131 span: ident.span,
132 prev: prev.kind.ident().unwrap().span,
133 duplicate: ident.span,
134 ident,
135 });
136 continue;
137 }
138 args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr });
139 }
140 _ => {
141 let expr = p.parse_expr()?;
142 if !args.named_args().is_empty() {
143 return Err(ecx.dcx().create_err(errors::PositionalAfterNamed {
144 span: expr.span,
145 args: args
146 .named_args()
147 .iter()
148 .filter_map(|a| a.kind.ident().map(|ident| (a, ident)))
149 .map(|(arg, n)| n.span.to(arg.expr.span))
150 .collect(),
151 }));
152 }
153 args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr });
154 }
155 }
156 }
157 Ok(MacroInput { fmtstr, args, is_direct_literal })
158}
159
160fn make_format_args(
161 ecx: &mut ExtCtxt<'_>,
162 input: MacroInput,
163 append_newline: bool,
164) -> ExpandResult<Result<FormatArgs, ErrorGuaranteed>, ()> {
165 let msg = "format argument must be a string literal";
166 let unexpanded_fmt_span = input.fmtstr.span;
167
168 let MacroInput { fmtstr: efmt, mut args, is_direct_literal } = input;
169
170 let ExprToSpannedString {
171 symbol: fmt_str,
172 span: fmt_span,
173 style: fmt_style,
174 uncooked_symbol: uncooked_fmt_str,
175 } = {
176 let ExpandResult::Ready(mac) = expr_to_spanned_string(ecx, efmt.clone(), msg) else {
177 return ExpandResult::Retry(());
178 };
179 match mac {
180 Ok(mut fmt) if append_newline => {
181 fmt.symbol = Symbol::intern(&format!("{}\n", fmt.symbol));
182 fmt
183 }
184 Ok(fmt) => fmt,
185 Err(err) => {
186 let guar = match err {
187 Ok((mut err, suggested)) => {
188 if !suggested {
189 if let ExprKind::Block(block, None) = &efmt.kind
190 && let [stmt] = block.stmts.as_slice()
191 && let StmtKind::Expr(expr) = &stmt.kind
192 && let ExprKind::Path(None, path) = &expr.kind
193 && path.segments.len() == 1
194 && path.segments[0].args.is_none()
195 {
196 err.multipart_suggestion(
197 "quote your inlined format argument to use as string literal",
198 vec![
199 (unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()),
200 (unexpanded_fmt_span.shrink_to_lo(), "\"".to_string()),
201 ],
202 Applicability::MaybeIncorrect,
203 );
204 } else {
205 let should_suggest = |kind: &ExprKind| -> bool {
207 match kind {
208 ExprKind::Block(b, None) if b.stmts.is_empty() => true,
209 ExprKind::Tup(v) if v.is_empty() => true,
210 _ => false,
211 }
212 };
213
214 let mut sugg_fmt = String::new();
215 for kind in std::iter::once(&efmt.kind)
216 .chain(args.explicit_args().into_iter().map(|a| &a.expr.kind))
217 {
218 sugg_fmt.push_str(if should_suggest(kind) {
219 "{:?} "
220 } else {
221 "{} "
222 });
223 }
224 sugg_fmt = sugg_fmt.trim_end().to_string();
225 err.span_suggestion(
226 unexpanded_fmt_span.shrink_to_lo(),
227 "you might be missing a string literal to format with",
228 format!("\"{sugg_fmt}\", "),
229 Applicability::MaybeIncorrect,
230 );
231 }
232 }
233 err.emit()
234 }
235 Err(guar) => guar,
236 };
237 return ExpandResult::Ready(Err(guar));
238 }
239 }
240 };
241
242 let str_style = match fmt_style {
243 rustc_ast::StrStyle::Cooked => None,
244 rustc_ast::StrStyle::Raw(raw) => Some(raw as usize),
245 };
246
247 let fmt_str = fmt_str.as_str(); let fmt_snippet = ecx.source_map().span_to_snippet(unexpanded_fmt_span).ok();
249 let mut parser = parse::Parser::new(
250 fmt_str,
251 str_style,
252 fmt_snippet,
253 append_newline,
254 parse::ParseMode::Format,
255 );
256
257 let mut pieces = Vec::new();
258 while let Some(piece) = parser.next() {
259 if !parser.errors.is_empty() {
260 break;
261 } else {
262 pieces.push(piece);
263 }
264 }
265
266 let is_source_literal = parser.is_source_literal;
267
268 if !parser.errors.is_empty() {
269 let err = parser.errors.remove(0);
270 let sp = if is_source_literal {
271 fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end))
272 } else {
273 fmt_span
282 };
283 let mut e = errors::InvalidFormatString {
284 span: sp,
285 note_: None,
286 label_: None,
287 sugg_: None,
288 desc: err.description,
289 label1: err.label,
290 };
291 if let Some(note) = err.note {
292 e.note_ = Some(errors::InvalidFormatStringNote { note });
293 }
294 if let Some((label, span)) = err.secondary_label
295 && is_source_literal
296 {
297 e.label_ = Some(errors::InvalidFormatStringLabel {
298 span: fmt_span.from_inner(InnerSpan::new(span.start, span.end)),
299 label,
300 });
301 }
302 match err.suggestion {
303 parse::Suggestion::None => {}
304 parse::Suggestion::UsePositional => {
305 let captured_arg_span =
306 fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
307 if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) {
308 let span = match args.unnamed_args().last() {
309 Some(arg) => arg.expr.span,
310 None => fmt_span,
311 };
312 e.sugg_ = Some(errors::InvalidFormatStringSuggestion::UsePositional {
313 captured: captured_arg_span,
314 len: args.unnamed_args().len().to_string(),
315 span: span.shrink_to_hi(),
316 arg,
317 });
318 }
319 }
320 parse::Suggestion::RemoveRawIdent(span) => {
321 if is_source_literal {
322 let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
323 e.sugg_ = Some(errors::InvalidFormatStringSuggestion::RemoveRawIdent { span })
324 }
325 }
326 parse::Suggestion::ReorderFormatParameter(span, replacement) => {
327 let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
328 e.sugg_ = Some(errors::InvalidFormatStringSuggestion::ReorderFormatParameter {
329 span,
330 replacement,
331 });
332 }
333 }
334 let guar = ecx.dcx().emit_err(e);
335 return ExpandResult::Ready(Err(guar));
336 }
337
338 let to_span = |inner_span: parse::InnerSpan| {
339 is_source_literal.then(|| {
340 fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end })
341 })
342 };
343
344 let mut used = vec![false; args.explicit_args().len()];
345 let mut invalid_refs = Vec::new();
346 let mut numeric_references_to_named_arg = Vec::new();
347
348 enum ArgRef<'a> {
349 Index(usize),
350 Name(&'a str, Option<Span>),
351 }
352 use ArgRef::*;
353
354 let mut unnamed_arg_after_named_arg = false;
355
356 let mut lookup_arg = |arg: ArgRef<'_>,
357 span: Option<Span>,
358 used_as: PositionUsedAs,
359 kind: FormatArgPositionKind|
360 -> FormatArgPosition {
361 let index = match arg {
362 Index(index) => {
363 if let Some(arg) = args.by_index(index) {
364 used[index] = true;
365 if arg.kind.ident().is_some() {
366 numeric_references_to_named_arg.push((index, span, used_as));
368 }
369 Ok(index)
370 } else {
371 invalid_refs.push((index, span, used_as, kind));
373 Err(index)
374 }
375 }
376 Name(name, span) => {
377 let name = Symbol::intern(name);
378 if let Some((index, _)) = args.by_name(name) {
379 if index < args.explicit_args().len() {
381 used[index] = true;
383 }
384 Ok(index)
385 } else {
386 let span = span.unwrap_or(fmt_span);
388 let ident = Ident::new(name, span);
389 let expr = if is_direct_literal {
390 ecx.expr_ident(span, ident)
391 } else {
392 let guar = ecx.dcx().emit_err(errors::FormatNoArgNamed { span, name });
395 unnamed_arg_after_named_arg = true;
396 DummyResult::raw_expr(span, Some(guar))
397 };
398 Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }))
399 }
400 }
401 };
402 FormatArgPosition { index, kind, span }
403 };
404
405 let mut template = Vec::new();
406 let mut unfinished_literal = String::new();
407 let mut placeholder_index = 0;
408
409 for piece in &pieces {
410 match *piece {
411 parse::Piece::Lit(s) => {
412 unfinished_literal.push_str(s);
413 }
414 parse::Piece::NextArgument(box parse::Argument { position, position_span, format }) => {
415 if !unfinished_literal.is_empty() {
416 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
417 unfinished_literal.clear();
418 }
419
420 let span = parser.arg_places.get(placeholder_index).and_then(|&s| to_span(s));
421 placeholder_index += 1;
422
423 let position_span = to_span(position_span);
424 let argument = match position {
425 parse::ArgumentImplicitlyIs(i) => lookup_arg(
426 Index(i),
427 position_span,
428 Placeholder(span),
429 FormatArgPositionKind::Implicit,
430 ),
431 parse::ArgumentIs(i) => lookup_arg(
432 Index(i),
433 position_span,
434 Placeholder(span),
435 FormatArgPositionKind::Number,
436 ),
437 parse::ArgumentNamed(name) => lookup_arg(
438 Name(name, position_span),
439 position_span,
440 Placeholder(span),
441 FormatArgPositionKind::Named,
442 ),
443 };
444
445 let alignment = match format.align {
446 parse::AlignUnknown => None,
447 parse::AlignLeft => Some(FormatAlignment::Left),
448 parse::AlignRight => Some(FormatAlignment::Right),
449 parse::AlignCenter => Some(FormatAlignment::Center),
450 };
451
452 let format_trait = match format.ty {
453 "" => FormatTrait::Display,
454 "?" => FormatTrait::Debug,
455 "e" => FormatTrait::LowerExp,
456 "E" => FormatTrait::UpperExp,
457 "o" => FormatTrait::Octal,
458 "p" => FormatTrait::Pointer,
459 "b" => FormatTrait::Binary,
460 "x" => FormatTrait::LowerHex,
461 "X" => FormatTrait::UpperHex,
462 _ => {
463 invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span);
464 FormatTrait::Display
465 }
466 };
467
468 let precision_span = format.precision_span.and_then(to_span);
469 let precision = match format.precision {
470 parse::CountIs(n) => Some(FormatCount::Literal(n)),
471 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
472 Name(name, to_span(name_span)),
473 precision_span,
474 Precision,
475 FormatArgPositionKind::Named,
476 ))),
477 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
478 Index(i),
479 precision_span,
480 Precision,
481 FormatArgPositionKind::Number,
482 ))),
483 parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
484 Index(i),
485 precision_span,
486 Precision,
487 FormatArgPositionKind::Implicit,
488 ))),
489 parse::CountImplied => None,
490 };
491
492 let width_span = format.width_span.and_then(to_span);
493 let width = match format.width {
494 parse::CountIs(n) => Some(FormatCount::Literal(n)),
495 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
496 Name(name, to_span(name_span)),
497 width_span,
498 Width,
499 FormatArgPositionKind::Named,
500 ))),
501 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
502 Index(i),
503 width_span,
504 Width,
505 FormatArgPositionKind::Number,
506 ))),
507 parse::CountIsStar(_) => unreachable!(),
508 parse::CountImplied => None,
509 };
510
511 template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
512 argument,
513 span,
514 format_trait,
515 format_options: FormatOptions {
516 fill: format.fill,
517 alignment,
518 sign: format.sign.map(|s| match s {
519 parse::Sign::Plus => FormatSign::Plus,
520 parse::Sign::Minus => FormatSign::Minus,
521 }),
522 alternate: format.alternate,
523 zero_pad: format.zero_pad,
524 debug_hex: format.debug_hex.map(|s| match s {
525 parse::DebugHex::Lower => FormatDebugHex::Lower,
526 parse::DebugHex::Upper => FormatDebugHex::Upper,
527 }),
528 precision,
529 width,
530 },
531 }));
532 }
533 }
534 }
535
536 if !unfinished_literal.is_empty() {
537 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
538 }
539
540 if !invalid_refs.is_empty() {
541 report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser);
542 }
543
544 let unused = used
545 .iter()
546 .enumerate()
547 .filter(|&(_, used)| !used)
548 .map(|(i, _)| {
549 let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
550 (args.explicit_args()[i].expr.span, named)
551 })
552 .collect::<Vec<_>>();
553
554 let has_unused = !unused.is_empty();
555 if has_unused {
556 let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2;
559 report_missing_placeholders(
560 ecx,
561 unused,
562 &used,
563 &args,
564 &pieces,
565 detect_foreign_fmt,
566 str_style,
567 fmt_str,
568 fmt_span,
569 );
570 }
571
572 if invalid_refs.is_empty() && !has_unused && !unnamed_arg_after_named_arg {
575 for &(index, span, used_as) in &numeric_references_to_named_arg {
576 let (position_sp_to_replace, position_sp_for_msg) = match used_as {
577 Placeholder(pspan) => (span, pspan),
578 Precision => {
579 let span = span.map(|span| span.with_lo(span.lo() + BytePos(1)));
581 (span, span)
582 }
583 Width => (span, span),
584 };
585 let arg_name = args.explicit_args()[index].kind.ident().unwrap();
586 ecx.buffered_early_lint.push(BufferedEarlyLint {
587 span: Some(arg_name.span.into()),
588 node_id: rustc_ast::CRATE_NODE_ID,
589 lint_id: LintId::of(NAMED_ARGUMENTS_USED_POSITIONALLY),
590 diagnostic: BuiltinLintDiag::NamedArgumentUsedPositionally {
591 position_sp_to_replace,
592 position_sp_for_msg,
593 named_arg_sp: arg_name.span,
594 named_arg_name: arg_name.name.to_string(),
595 is_formatting_arg: matches!(used_as, Width | Precision),
596 },
597 });
598 }
599 }
600
601 ExpandResult::Ready(Ok(FormatArgs {
602 span: fmt_span,
603 template,
604 arguments: args,
605 uncooked_fmt_str,
606 }))
607}
608
609fn invalid_placeholder_type_error(
610 ecx: &ExtCtxt<'_>,
611 ty: &str,
612 ty_span: Option<parse::InnerSpan>,
613 fmt_span: Span,
614) {
615 let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end)));
616 let suggs = if let Some(sp) = sp {
617 [
618 ("", "Display"),
619 ("?", "Debug"),
620 ("e", "LowerExp"),
621 ("E", "UpperExp"),
622 ("o", "Octal"),
623 ("p", "Pointer"),
624 ("b", "Binary"),
625 ("x", "LowerHex"),
626 ("X", "UpperHex"),
627 ]
628 .into_iter()
629 .map(|(fmt, trait_name)| errors::FormatUnknownTraitSugg { span: sp, fmt, trait_name })
630 .collect()
631 } else {
632 vec![]
633 };
634 ecx.dcx().emit_err(errors::FormatUnknownTrait { span: sp.unwrap_or(fmt_span), ty, suggs });
635}
636
637fn report_missing_placeholders(
638 ecx: &ExtCtxt<'_>,
639 unused: Vec<(Span, bool)>,
640 used: &[bool],
641 args: &FormatArguments,
642 pieces: &[parse::Piece<'_>],
643 detect_foreign_fmt: bool,
644 str_style: Option<usize>,
645 fmt_str: &str,
646 fmt_span: Span,
647) {
648 let mut diag = if let &[(span, named)] = &unused[..] {
649 ecx.dcx().create_err(errors::FormatUnusedArg { span, named })
650 } else {
651 let unused_labels =
652 unused.iter().map(|&(span, named)| errors::FormatUnusedArg { span, named }).collect();
653 let unused_spans = unused.iter().map(|&(span, _)| span).collect();
654 ecx.dcx().create_err(errors::FormatUnusedArgs {
655 fmt: fmt_span,
656 unused: unused_spans,
657 unused_labels,
658 })
659 };
660
661 let placeholders = pieces
662 .iter()
663 .filter_map(|piece| {
664 if let parse::Piece::NextArgument(argument) = piece
665 && let ArgumentNamed(binding) = argument.position
666 {
667 let span = fmt_span.from_inner(InnerSpan::new(
668 argument.position_span.start,
669 argument.position_span.end,
670 ));
671 Some((span, binding))
672 } else {
673 None
674 }
675 })
676 .collect::<Vec<_>>();
677
678 if !placeholders.is_empty() {
679 if let Some(new_diag) = report_redundant_format_arguments(ecx, args, used, placeholders) {
680 diag.cancel();
681 new_diag.emit();
682 return;
683 }
684 }
685
686 let mut found_foreign = false;
688
689 if detect_foreign_fmt {
691 use super::format_foreign as foreign;
692
693 let mut explained = FxHashSet::default();
696
697 macro_rules! check_foreign {
698 ($kind:ident) => {{
699 let mut show_doc_note = false;
700
701 let mut suggestions = vec![];
702 let padding = str_style.map(|i| i + 2).unwrap_or(1);
704 for sub in foreign::$kind::iter_subs(fmt_str, padding) {
705 let (trn, success) = match sub.translate() {
706 Ok(trn) => (trn, true),
707 Err(Some(msg)) => (msg, false),
708
709 _ => continue,
711 };
712
713 let pos = sub.position();
714 if !explained.insert(sub.to_string()) {
715 continue;
716 }
717
718 if !found_foreign {
719 found_foreign = true;
720 show_doc_note = true;
721 }
722
723 let sp = fmt_span.from_inner(pos);
724
725 if success {
726 suggestions.push((sp, trn));
727 } else {
728 diag.span_note(
729 sp,
730 format!("format specifiers use curly braces, and {}", trn),
731 );
732 }
733 }
734
735 if show_doc_note {
736 diag.note(concat!(
737 stringify!($kind),
738 " formatting is not supported; see the documentation for `std::fmt`",
739 ));
740 }
741 if suggestions.len() > 0 {
742 diag.multipart_suggestion(
743 "format specifiers use curly braces",
744 suggestions,
745 Applicability::MachineApplicable,
746 );
747 }
748 }};
749 }
750
751 check_foreign!(printf);
752 if !found_foreign {
753 check_foreign!(shell);
754 }
755 }
756 if !found_foreign && unused.len() == 1 {
757 diag.span_label(fmt_span, "formatting specifier missing");
758 }
759
760 diag.emit();
761}
762
763fn report_redundant_format_arguments<'a>(
766 ecx: &ExtCtxt<'a>,
767 args: &FormatArguments,
768 used: &[bool],
769 placeholders: Vec<(Span, &str)>,
770) -> Option<Diag<'a>> {
771 let mut fmt_arg_indices = vec![];
772 let mut args_spans = vec![];
773 let mut fmt_spans = vec![];
774
775 for (i, unnamed_arg) in args.unnamed_args().iter().enumerate().rev() {
776 let Some(ty) = unnamed_arg.expr.to_ty() else { continue };
777 let Some(argument_binding) = ty.kind.is_simple_path() else { continue };
778 let argument_binding = argument_binding.as_str();
779
780 if used[i] {
781 continue;
782 }
783
784 let matching_placeholders = placeholders
785 .iter()
786 .filter(|(_, inline_binding)| argument_binding == *inline_binding)
787 .map(|(span, _)| span)
788 .collect::<Vec<_>>();
789
790 if !matching_placeholders.is_empty() {
791 fmt_arg_indices.push(i);
792 args_spans.push(unnamed_arg.expr.span);
793 for span in &matching_placeholders {
794 if fmt_spans.contains(*span) {
795 continue;
796 }
797 fmt_spans.push(**span);
798 }
799 }
800 }
801
802 if !args_spans.is_empty() {
803 let multispan = MultiSpan::from(fmt_spans);
804 let mut suggestion_spans = vec![];
805
806 for (arg_span, fmt_arg_idx) in args_spans.iter().zip(fmt_arg_indices.iter()) {
807 let span = if fmt_arg_idx + 1 == args.explicit_args().len() {
808 *arg_span
809 } else {
810 arg_span.until(args.explicit_args()[*fmt_arg_idx + 1].expr.span)
811 };
812
813 suggestion_spans.push(span);
814 }
815
816 let sugg = if args.named_args().len() == 0 {
817 Some(errors::FormatRedundantArgsSugg { spans: suggestion_spans })
818 } else {
819 None
820 };
821
822 return Some(ecx.dcx().create_err(errors::FormatRedundantArgs {
823 n: args_spans.len(),
824 span: MultiSpan::from(args_spans),
825 note: multispan,
826 sugg,
827 }));
828 }
829
830 None
831}
832
833fn report_invalid_references(
838 ecx: &ExtCtxt<'_>,
839 invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
840 template: &[FormatArgsPiece],
841 fmt_span: Span,
842 args: &FormatArguments,
843 parser: parse::Parser<'_>,
844) {
845 let num_args_desc = match args.explicit_args().len() {
846 0 => "no arguments were given".to_string(),
847 1 => "there is 1 argument".to_string(),
848 n => format!("there are {n} arguments"),
849 };
850
851 let mut e;
852
853 if template.iter().all(|piece| match piece {
854 FormatArgsPiece::Placeholder(FormatPlaceholder {
855 argument: FormatArgPosition { kind: FormatArgPositionKind::Number, .. },
856 ..
857 }) => false,
858 FormatArgsPiece::Placeholder(FormatPlaceholder {
859 format_options:
860 FormatOptions {
861 precision:
862 Some(FormatCount::Argument(FormatArgPosition {
863 kind: FormatArgPositionKind::Number,
864 ..
865 })),
866 ..
867 }
868 | FormatOptions {
869 width:
870 Some(FormatCount::Argument(FormatArgPosition {
871 kind: FormatArgPositionKind::Number,
872 ..
873 })),
874 ..
875 },
876 ..
877 }) => false,
878 _ => true,
879 }) {
880 let mut spans = Vec::new();
883 let mut num_placeholders = 0;
884 for piece in template {
885 let mut placeholder = None;
886 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
888 format_options:
889 FormatOptions {
890 precision:
891 Some(FormatCount::Argument(FormatArgPosition {
892 span,
893 kind: FormatArgPositionKind::Implicit,
894 ..
895 })),
896 ..
897 },
898 ..
899 }) = piece
900 {
901 placeholder = *span;
902 num_placeholders += 1;
903 }
904 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
906 argument: FormatArgPosition { kind: FormatArgPositionKind::Implicit, .. },
907 span,
908 ..
909 }) = piece
910 {
911 placeholder = *span;
912 num_placeholders += 1;
913 }
914 spans.extend(placeholder);
916 }
917 let span = if spans.is_empty() {
918 MultiSpan::from_span(fmt_span)
919 } else {
920 MultiSpan::from_spans(spans)
921 };
922 e = ecx.dcx().create_err(errors::FormatPositionalMismatch {
923 span,
924 n: num_placeholders,
925 desc: num_args_desc,
926 highlight: SingleLabelManySpans {
927 spans: args.explicit_args().iter().map(|arg| arg.expr.span).collect(),
928 label: "",
929 },
930 });
931 let mut has_precision_star = false;
933 for piece in template {
934 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
935 format_options:
936 FormatOptions {
937 precision:
938 Some(FormatCount::Argument(FormatArgPosition {
939 index,
940 span: Some(span),
941 kind: FormatArgPositionKind::Implicit,
942 ..
943 })),
944 ..
945 },
946 ..
947 }) = piece
948 {
949 let (Ok(index) | Err(index)) = index;
950 has_precision_star = true;
951 e.span_label(
952 *span,
953 format!(
954 "this precision flag adds an extra required argument at position {}, which is why there {} expected",
955 index,
956 if num_placeholders == 1 {
957 "is 1 argument".to_string()
958 } else {
959 format!("are {num_placeholders} arguments")
960 },
961 ),
962 );
963 }
964 }
965 if has_precision_star {
966 e.note("positional arguments are zero-based");
967 }
968 } else {
969 let mut indexes: Vec<_> = invalid_refs.iter().map(|&(index, _, _, _)| index).collect();
970 indexes.sort();
973 indexes.dedup();
974 let span: MultiSpan = if !parser.is_source_literal || parser.arg_places.is_empty() {
975 MultiSpan::from_span(fmt_span)
976 } else {
977 MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect())
978 };
979 let arg_list = format!(
980 "argument{} {}",
981 pluralize!(indexes.len()),
982 listify(&indexes, |i: &usize| i.to_string()).unwrap_or_default()
983 );
984 e = ecx.dcx().struct_span_err(
985 span,
986 format!("invalid reference to positional {arg_list} ({num_args_desc})"),
987 );
988 e.note("positional arguments are zero-based");
989 }
990
991 if template.iter().any(|piece| match piece {
992 FormatArgsPiece::Placeholder(FormatPlaceholder { format_options: f, .. }) => {
993 *f != FormatOptions::default()
994 }
995 _ => false,
996 }) {
997 e.note("for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html");
998 }
999
1000 e.emit();
1001}
1002
1003fn expand_format_args_impl<'cx>(
1004 ecx: &'cx mut ExtCtxt<'_>,
1005 mut sp: Span,
1006 tts: TokenStream,
1007 nl: bool,
1008) -> MacroExpanderResult<'cx> {
1009 sp = ecx.with_def_site_ctxt(sp);
1010 ExpandResult::Ready(match parse_args(ecx, sp, tts) {
1011 Ok(input) => {
1012 let ExpandResult::Ready(mac) = make_format_args(ecx, input, nl) else {
1013 return ExpandResult::Retry(());
1014 };
1015 match mac {
1016 Ok(format_args) => {
1017 MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args))))
1018 }
1019 Err(guar) => MacEager::expr(DummyResult::raw_expr(sp, Some(guar))),
1020 }
1021 }
1022 Err(err) => {
1023 let guar = err.emit();
1024 DummyResult::any(sp, guar)
1025 }
1026 })
1027}
1028
1029pub(crate) fn expand_format_args<'cx>(
1030 ecx: &'cx mut ExtCtxt<'_>,
1031 sp: Span,
1032 tts: TokenStream,
1033) -> MacroExpanderResult<'cx> {
1034 expand_format_args_impl(ecx, sp, tts, false)
1035}
1036
1037pub(crate) fn expand_format_args_nl<'cx>(
1038 ecx: &'cx mut ExtCtxt<'_>,
1039 sp: Span,
1040 tts: TokenStream,
1041) -> MacroExpanderResult<'cx> {
1042 expand_format_args_impl(ecx, sp, tts, true)
1043}