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