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