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, BufferedEarlyLint, Diag, MultiSpan, PResult, SingleLabelManySpans, listify,
14 pluralize,
15};
16use rustc_expand::base::*;
17use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
18use rustc_lint_defs::{BuiltinLintDiag, LintId};
19use rustc_parse::exp;
20use rustc_parse_format as parse;
21use rustc_span::{BytePos, ErrorGuaranteed, Ident, InnerSpan, Span, Symbol};
22
23use crate::errors;
24use crate::util::{ExprToSpannedString, expr_to_spanned_string};
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39enum PositionUsedAs {
40 Placeholder(Option<Span>),
41 Precision,
42 Width,
43}
44use PositionUsedAs::*;
45
46#[derive(Debug)]
47struct MacroInput {
48 fmtstr: Box<Expr>,
49 args: FormatArguments,
50 is_direct_literal: bool,
60}
61
62fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> {
72 let mut p = ecx.new_parser_from_tts(tts);
73
74 let fmtstr = match p.token.kind {
76 token::Eof => return Err(ecx.dcx().create_err(errors::FormatRequiresString { span: sp })),
77 token::Literal(token::Lit { kind: token::Str | token::StrRaw(_), .. }) => {
81 p.parse_literal_maybe_minus()?
82 }
83 _ => p.parse_expr()?,
85 };
86
87 let mut args = FormatArguments::new();
89 let mut first = true;
90 while p.token != token::Eof {
91 if !p.eat(exp!(Comma)) {
93 if first {
94 p.clear_expected_token_types();
95 }
96
97 match p.expect(exp!(Comma)) {
98 Err(err) => {
99 if token::TokenKind::Comma.similar_tokens().contains(&p.token.kind) {
100 err.emit();
103 p.bump();
104 } else {
105 return Err(err);
107 }
108 }
109 Ok(Recovered::Yes(_)) => (),
110 Ok(Recovered::No) => unreachable!(),
111 }
112 }
113 first = false;
114 if p.token == token::Eof {
116 break;
117 }
118 match p.token.ident() {
120 Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => {
121 p.bump();
122 p.expect(exp!(Eq))?;
123 let expr = p.parse_expr()?;
124 if let Some((_, prev)) = args.by_name(ident.name) {
125 ecx.dcx().emit_err(errors::FormatDuplicateArg {
126 span: ident.span,
127 prev: prev.kind.ident().unwrap().span,
128 duplicate: ident.span,
129 ident,
130 });
131 continue;
132 }
133 args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr });
134 }
135 _ => {
136 let expr = p.parse_expr()?;
137 if !args.named_args().is_empty() {
138 return Err(ecx.dcx().create_err(errors::PositionalAfterNamed {
139 span: expr.span,
140 args: args
141 .named_args()
142 .iter()
143 .filter_map(|a| a.kind.ident().map(|ident| (a, ident)))
144 .map(|(arg, n)| n.span.to(arg.expr.span))
145 .collect(),
146 }));
147 }
148 args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr });
149 }
150 }
151 }
152
153 let is_direct_literal = matches!(fmtstr.kind, ExprKind::Lit(_));
155
156 Ok(MacroInput { fmtstr, args, is_direct_literal })
157}
158
159fn make_format_args(
160 ecx: &mut ExtCtxt<'_>,
161 input: MacroInput,
162 append_newline: bool,
163) -> ExpandResult<Result<FormatArgs, ErrorGuaranteed>, ()> {
164 let msg = "format argument must be a string literal";
165 let unexpanded_fmt_span = input.fmtstr.span;
166
167 let MacroInput { fmtstr: efmt, mut args, is_direct_literal } = input;
168
169 let ExprToSpannedString {
170 symbol: fmt_str,
171 span: fmt_span,
172 style: fmt_style,
173 uncooked_symbol: uncooked_fmt_str,
174 } = {
175 let ExpandResult::Ready(mac) = expr_to_spanned_string(ecx, efmt.clone(), msg) else {
176 return ExpandResult::Retry(());
177 };
178 match mac {
179 Ok(mut fmt) if append_newline => {
180 fmt.symbol = Symbol::intern(&format!("{}\n", fmt.symbol));
181 fmt
182 }
183 Ok(fmt) => fmt,
184 Err(err) => {
185 let guar = match err {
186 Ok((mut err, suggested)) => {
187 if !suggested {
188 if let ExprKind::Block(block, None) = &efmt.kind
189 && let [stmt] = block.stmts.as_slice()
190 && let StmtKind::Expr(expr) = &stmt.kind
191 && let ExprKind::Path(None, path) = &expr.kind
192 && path.segments.len() == 1
193 && path.segments[0].args.is_none()
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 parse::Suggestion::AddMissingColon(span) => {
333 let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
334 e.sugg_ = Some(errors::InvalidFormatStringSuggestion::AddMissingColon { span });
335 }
336 }
337 let guar = ecx.dcx().emit_err(e);
338 return ExpandResult::Ready(Err(guar));
339 }
340
341 let to_span = |inner_span: Range<usize>| {
342 is_source_literal.then(|| {
343 fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end })
344 })
345 };
346
347 let mut used = vec![false; args.explicit_args().len()];
348 let mut invalid_refs = Vec::new();
349 let mut numeric_references_to_named_arg = Vec::new();
350
351 enum ArgRef<'a> {
352 Index(usize),
353 Name(&'a str, Option<Span>),
354 }
355 use ArgRef::*;
356
357 let mut unnamed_arg_after_named_arg = false;
358
359 let mut lookup_arg = |arg: ArgRef<'_>,
360 span: Option<Span>,
361 used_as: PositionUsedAs,
362 kind: FormatArgPositionKind|
363 -> FormatArgPosition {
364 let index = match arg {
365 Index(index) => {
366 if let Some(arg) = args.by_index(index) {
367 used[index] = true;
368 if arg.kind.ident().is_some() {
369 numeric_references_to_named_arg.push((index, span, used_as));
371 }
372 Ok(index)
373 } else {
374 invalid_refs.push((index, span, used_as, kind));
376 Err(index)
377 }
378 }
379 Name(name, span) => {
380 let name = Symbol::intern(name);
381 if let Some((index, _)) = args.by_name(name) {
382 if index < args.explicit_args().len() {
384 used[index] = true;
386 }
387 Ok(index)
388 } else {
389 let span = span.unwrap_or(fmt_span);
391 let ident = Ident::new(name, span);
392 let expr = if is_direct_literal {
393 ecx.expr_ident(span, ident)
394 } else {
395 let guar = ecx.dcx().emit_err(errors::FormatNoArgNamed { span, name });
398 unnamed_arg_after_named_arg = true;
399 DummyResult::raw_expr(span, Some(guar))
400 };
401 Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }))
402 }
403 }
404 };
405 FormatArgPosition { index, kind, span }
406 };
407
408 let mut template = Vec::new();
409 let mut unfinished_literal = String::new();
410 let mut placeholder_index = 0;
411
412 for piece in &pieces {
413 match piece.clone() {
414 parse::Piece::Lit(s) => {
415 unfinished_literal.push_str(s);
416 }
417 parse::Piece::NextArgument(box parse::Argument { position, position_span, format }) => {
418 if !unfinished_literal.is_empty() {
419 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
420 unfinished_literal.clear();
421 }
422
423 let span =
424 parser.arg_places.get(placeholder_index).and_then(|s| to_span(s.clone()));
425 placeholder_index += 1;
426
427 let position_span = to_span(position_span);
428 let argument = match position {
429 parse::ArgumentImplicitlyIs(i) => lookup_arg(
430 Index(i),
431 position_span,
432 Placeholder(span),
433 FormatArgPositionKind::Implicit,
434 ),
435 parse::ArgumentIs(i) => lookup_arg(
436 Index(i),
437 position_span,
438 Placeholder(span),
439 FormatArgPositionKind::Number,
440 ),
441 parse::ArgumentNamed(name) => lookup_arg(
442 Name(name, position_span),
443 position_span,
444 Placeholder(span),
445 FormatArgPositionKind::Named,
446 ),
447 };
448
449 let alignment = match format.align {
450 parse::AlignUnknown => None,
451 parse::AlignLeft => Some(FormatAlignment::Left),
452 parse::AlignRight => Some(FormatAlignment::Right),
453 parse::AlignCenter => Some(FormatAlignment::Center),
454 };
455
456 let format_trait = match format.ty {
457 "" => FormatTrait::Display,
458 "?" => FormatTrait::Debug,
459 "e" => FormatTrait::LowerExp,
460 "E" => FormatTrait::UpperExp,
461 "o" => FormatTrait::Octal,
462 "p" => FormatTrait::Pointer,
463 "b" => FormatTrait::Binary,
464 "x" => FormatTrait::LowerHex,
465 "X" => FormatTrait::UpperHex,
466 _ => {
467 invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span);
468 FormatTrait::Display
469 }
470 };
471
472 let precision_span = format.precision_span.and_then(to_span);
473 let precision = match format.precision {
474 parse::CountIs(n) => Some(FormatCount::Literal(n)),
475 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
476 Name(name, to_span(name_span)),
477 precision_span,
478 Precision,
479 FormatArgPositionKind::Named,
480 ))),
481 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
482 Index(i),
483 precision_span,
484 Precision,
485 FormatArgPositionKind::Number,
486 ))),
487 parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
488 Index(i),
489 precision_span,
490 Precision,
491 FormatArgPositionKind::Implicit,
492 ))),
493 parse::CountImplied => None,
494 };
495
496 let width_span = format.width_span.and_then(to_span);
497 let width = match format.width {
498 parse::CountIs(n) => Some(FormatCount::Literal(n)),
499 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
500 Name(name, to_span(name_span)),
501 width_span,
502 Width,
503 FormatArgPositionKind::Named,
504 ))),
505 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
506 Index(i),
507 width_span,
508 Width,
509 FormatArgPositionKind::Number,
510 ))),
511 parse::CountIsStar(_) => unreachable!(),
512 parse::CountImplied => None,
513 };
514
515 template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
516 argument,
517 span,
518 format_trait,
519 format_options: FormatOptions {
520 fill: format.fill,
521 alignment,
522 sign: format.sign.map(|s| match s {
523 parse::Sign::Plus => FormatSign::Plus,
524 parse::Sign::Minus => FormatSign::Minus,
525 }),
526 alternate: format.alternate,
527 zero_pad: format.zero_pad,
528 debug_hex: format.debug_hex.map(|s| match s {
529 parse::DebugHex::Lower => FormatDebugHex::Lower,
530 parse::DebugHex::Upper => FormatDebugHex::Upper,
531 }),
532 precision,
533 width,
534 },
535 }));
536 }
537 }
538 }
539
540 if !unfinished_literal.is_empty() {
541 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
542 }
543
544 if !invalid_refs.is_empty() {
545 report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser);
546 }
547
548 let unused = used
549 .iter()
550 .enumerate()
551 .filter(|&(_, used)| !used)
552 .map(|(i, _)| {
553 let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
554 (args.explicit_args()[i].expr.span, named)
555 })
556 .collect::<Vec<_>>();
557
558 let has_unused = !unused.is_empty();
559 if has_unused {
560 let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2;
563 report_missing_placeholders(
564 ecx,
565 unused,
566 &used,
567 &args,
568 &pieces,
569 &invalid_refs,
570 detect_foreign_fmt,
571 str_style,
572 fmt_str,
573 uncooked_fmt_str.1.as_str(),
574 fmt_span,
575 );
576 }
577
578 if invalid_refs.is_empty() && !has_unused && !unnamed_arg_after_named_arg {
581 for &(index, span, used_as) in &numeric_references_to_named_arg {
582 let (position_sp_to_replace, position_sp_for_msg) = match used_as {
583 Placeholder(pspan) => (span, pspan),
584 Precision => {
585 let span = span.map(|span| span.with_lo(span.lo() + BytePos(1)));
587 (span, span)
588 }
589 Width => (span, span),
590 };
591 let arg_name = args.explicit_args()[index].kind.ident().unwrap();
592 ecx.buffered_early_lint.push(BufferedEarlyLint {
593 span: Some(arg_name.span.into()),
594 node_id: rustc_ast::CRATE_NODE_ID,
595 lint_id: LintId::of(NAMED_ARGUMENTS_USED_POSITIONALLY),
596 diagnostic: BuiltinLintDiag::NamedArgumentUsedPositionally {
597 position_sp_to_replace,
598 position_sp_for_msg,
599 named_arg_sp: arg_name.span,
600 named_arg_name: arg_name.name.to_string(),
601 is_formatting_arg: matches!(used_as, Width | Precision),
602 }
603 .into(),
604 });
605 }
606 }
607
608 ExpandResult::Ready(Ok(FormatArgs {
609 span: fmt_span,
610 template,
611 arguments: args,
612 uncooked_fmt_str,
613 is_source_literal,
614 }))
615}
616
617fn invalid_placeholder_type_error(
618 ecx: &ExtCtxt<'_>,
619 ty: &str,
620 ty_span: Option<Range<usize>>,
621 fmt_span: Span,
622) {
623 let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end)));
624 let suggs = if let Some(sp) = sp {
625 [
626 ("", "Display"),
627 ("?", "Debug"),
628 ("e", "LowerExp"),
629 ("E", "UpperExp"),
630 ("o", "Octal"),
631 ("p", "Pointer"),
632 ("b", "Binary"),
633 ("x", "LowerHex"),
634 ("X", "UpperHex"),
635 ]
636 .into_iter()
637 .map(|(fmt, trait_name)| errors::FormatUnknownTraitSugg { span: sp, fmt, trait_name })
638 .collect()
639 } else {
640 vec![]
641 };
642 ecx.dcx().emit_err(errors::FormatUnknownTrait { span: sp.unwrap_or(fmt_span), ty, suggs });
643}
644
645fn report_missing_placeholders(
646 ecx: &ExtCtxt<'_>,
647 unused: Vec<(Span, bool)>,
648 used: &[bool],
649 args: &FormatArguments,
650 pieces: &[parse::Piece<'_>],
651 invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
652 detect_foreign_fmt: bool,
653 str_style: Option<usize>,
654 fmt_str: &str,
655 uncooked_fmt_str: &str,
656 fmt_span: Span,
657) {
658 let mut diag = if let &[(span, named)] = &unused[..] {
659 ecx.dcx().create_err(errors::FormatUnusedArg { span, named })
660 } else {
661 let unused_labels =
662 unused.iter().map(|&(span, named)| errors::FormatUnusedArg { span, named }).collect();
663 let unused_spans = unused.iter().map(|&(span, _)| span).collect();
664 ecx.dcx().create_err(errors::FormatUnusedArgs {
665 fmt: fmt_span,
666 unused: unused_spans,
667 unused_labels,
668 })
669 };
670
671 let placeholders = pieces
672 .iter()
673 .filter_map(|piece| {
674 if let parse::Piece::NextArgument(argument) = piece
675 && let ArgumentNamed(binding) = argument.position
676 {
677 let span = fmt_span.from_inner(InnerSpan::new(
678 argument.position_span.start,
679 argument.position_span.end,
680 ));
681 Some((span, binding))
682 } else {
683 None
684 }
685 })
686 .collect::<Vec<_>>();
687
688 if !placeholders.is_empty() {
689 if let Some(new_diag) = report_redundant_format_arguments(ecx, args, used, placeholders) {
690 diag.cancel();
691 new_diag.emit();
692 return;
693 }
694 }
695
696 let mut found_foreign = false;
698
699 if detect_foreign_fmt {
701 use super::format_foreign as foreign;
702
703 let mut explained = FxHashSet::default();
706
707 macro_rules! check_foreign {
708 ($kind:ident) => {{
709 let mut show_doc_note = false;
710
711 let mut suggestions = vec![];
712 let padding = str_style.map(|i| i + 2).unwrap_or(1);
714 for sub in foreign::$kind::iter_subs(fmt_str, padding) {
715 let (trn, success) = match sub.translate() {
716 Ok(trn) => (trn, true),
717 Err(Some(msg)) => (msg, false),
718
719 _ => continue,
721 };
722
723 let pos = sub.position();
724 if !explained.insert(sub.to_string()) {
725 continue;
726 }
727
728 if !found_foreign {
729 found_foreign = true;
730 show_doc_note = true;
731 }
732
733 let sp = fmt_span.from_inner(pos);
734
735 if success {
736 suggestions.push((sp, trn));
737 } else {
738 diag.span_note(
739 sp,
740 format!("format specifiers use curly braces, and {}", trn),
741 );
742 }
743 }
744
745 if show_doc_note {
746 diag.note(concat!(
747 stringify!($kind),
748 " formatting is not supported; see the documentation for `std::fmt`",
749 ));
750 }
751 if suggestions.len() > 0 {
752 diag.multipart_suggestion(
753 "format specifiers use curly braces",
754 suggestions,
755 Applicability::MachineApplicable,
756 );
757 }
758 }};
759 }
760
761 check_foreign!(printf);
762 if !found_foreign {
763 check_foreign!(shell);
764 }
765 }
766 if !found_foreign && unused.len() == 1 {
767 diag.span_label(fmt_span, "formatting specifier missing");
768 }
769
770 if !found_foreign && invalid_refs.is_empty() {
771 let show_example = !used.contains(&true);
773
774 if !show_example {
775 if unused.len() > 1 {
776 diag.note(format!("consider adding {} format specifiers", unused.len()));
777 }
778 } else {
779 let msg = if unused.len() == 1 {
780 "a format specifier".to_string()
781 } else {
782 format!("{} format specifiers", unused.len())
783 };
784
785 let sugg = match str_style {
786 None => format!("\"{}{}\"", uncooked_fmt_str, "{}".repeat(unused.len())),
787 Some(n_hashes) => format!(
788 "r{hashes}\"{uncooked_fmt_str}{fmt_specifiers}\"{hashes}",
789 hashes = "#".repeat(n_hashes),
790 fmt_specifiers = "{}".repeat(unused.len())
791 ),
792 };
793 let msg = format!("format specifiers use curly braces, consider adding {msg}");
794
795 diag.span_suggestion_verbose(fmt_span, msg, sugg, Applicability::MaybeIncorrect);
796 }
797 }
798
799 diag.emit();
800}
801
802fn report_redundant_format_arguments<'a>(
805 ecx: &ExtCtxt<'a>,
806 args: &FormatArguments,
807 used: &[bool],
808 placeholders: Vec<(Span, &str)>,
809) -> Option<Diag<'a>> {
810 let mut fmt_arg_indices = vec![];
811 let mut args_spans = vec![];
812 let mut fmt_spans = vec![];
813
814 for (i, unnamed_arg) in args.unnamed_args().iter().enumerate().rev() {
815 let Some(ty) = unnamed_arg.expr.to_ty() else { continue };
816 let Some(argument_binding) = ty.kind.is_simple_path() else { continue };
817 let argument_binding = argument_binding.as_str();
818
819 if used[i] {
820 continue;
821 }
822
823 let matching_placeholders = placeholders
824 .iter()
825 .filter(|(_, inline_binding)| argument_binding == *inline_binding)
826 .map(|(span, _)| span)
827 .collect::<Vec<_>>();
828
829 if !matching_placeholders.is_empty() {
830 fmt_arg_indices.push(i);
831 args_spans.push(unnamed_arg.expr.span);
832 for span in &matching_placeholders {
833 if fmt_spans.contains(*span) {
834 continue;
835 }
836 fmt_spans.push(**span);
837 }
838 }
839 }
840
841 if !args_spans.is_empty() {
842 let multispan = MultiSpan::from(fmt_spans);
843 let mut suggestion_spans = vec![];
844
845 for (arg_span, fmt_arg_idx) in args_spans.iter().zip(fmt_arg_indices.iter()) {
846 let span = if fmt_arg_idx + 1 == args.explicit_args().len() {
847 *arg_span
848 } else {
849 arg_span.until(args.explicit_args()[*fmt_arg_idx + 1].expr.span)
850 };
851
852 suggestion_spans.push(span);
853 }
854
855 let sugg = if args.named_args().len() == 0 {
856 Some(errors::FormatRedundantArgsSugg { spans: suggestion_spans })
857 } else {
858 None
859 };
860
861 return Some(ecx.dcx().create_err(errors::FormatRedundantArgs {
862 n: args_spans.len(),
863 span: MultiSpan::from(args_spans),
864 note: multispan,
865 sugg,
866 }));
867 }
868
869 None
870}
871
872fn report_invalid_references(
877 ecx: &ExtCtxt<'_>,
878 invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
879 template: &[FormatArgsPiece],
880 fmt_span: Span,
881 args: &FormatArguments,
882 parser: parse::Parser<'_>,
883) {
884 let num_args_desc = match args.explicit_args().len() {
885 0 => "no arguments were given".to_string(),
886 1 => "there is 1 argument".to_string(),
887 n => format!("there are {n} arguments"),
888 };
889
890 let mut e;
891
892 if template.iter().all(|piece| match piece {
893 FormatArgsPiece::Placeholder(FormatPlaceholder {
894 argument: FormatArgPosition { kind: FormatArgPositionKind::Number, .. },
895 ..
896 }) => false,
897 FormatArgsPiece::Placeholder(FormatPlaceholder {
898 format_options:
899 FormatOptions {
900 precision:
901 Some(FormatCount::Argument(FormatArgPosition {
902 kind: FormatArgPositionKind::Number,
903 ..
904 })),
905 ..
906 }
907 | FormatOptions {
908 width:
909 Some(FormatCount::Argument(FormatArgPosition {
910 kind: FormatArgPositionKind::Number,
911 ..
912 })),
913 ..
914 },
915 ..
916 }) => false,
917 _ => true,
918 }) {
919 let mut spans = Vec::new();
922 let mut num_placeholders = 0;
923 for piece in template {
924 let mut placeholder = None;
925 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
927 format_options:
928 FormatOptions {
929 precision:
930 Some(FormatCount::Argument(FormatArgPosition {
931 span,
932 kind: FormatArgPositionKind::Implicit,
933 ..
934 })),
935 ..
936 },
937 ..
938 }) = piece
939 {
940 placeholder = *span;
941 num_placeholders += 1;
942 }
943 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
945 argument: FormatArgPosition { kind: FormatArgPositionKind::Implicit, .. },
946 span,
947 ..
948 }) = piece
949 {
950 placeholder = *span;
951 num_placeholders += 1;
952 }
953 spans.extend(placeholder);
955 }
956 let span = if spans.is_empty() {
957 MultiSpan::from_span(fmt_span)
958 } else {
959 MultiSpan::from_spans(spans)
960 };
961 e = ecx.dcx().create_err(errors::FormatPositionalMismatch {
962 span,
963 n: num_placeholders,
964 desc: num_args_desc,
965 highlight: SingleLabelManySpans {
966 spans: args.explicit_args().iter().map(|arg| arg.expr.span).collect(),
967 label: "",
968 },
969 });
970 let mut has_precision_star = false;
972 for piece in template {
973 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
974 format_options:
975 FormatOptions {
976 precision:
977 Some(FormatCount::Argument(FormatArgPosition {
978 index,
979 span: Some(span),
980 kind: FormatArgPositionKind::Implicit,
981 ..
982 })),
983 ..
984 },
985 ..
986 }) = piece
987 {
988 let (Ok(index) | Err(index)) = index;
989 has_precision_star = true;
990 e.span_label(
991 *span,
992 format!(
993 "this precision flag adds an extra required argument at position {}, which is why there {} expected",
994 index,
995 if num_placeholders == 1 {
996 "is 1 argument".to_string()
997 } else {
998 format!("are {num_placeholders} arguments")
999 },
1000 ),
1001 );
1002 }
1003 }
1004 if has_precision_star {
1005 e.note("positional arguments are zero-based");
1006 }
1007 } else {
1008 let mut indexes: Vec<_> = invalid_refs.iter().map(|&(index, _, _, _)| index).collect();
1009 indexes.sort();
1012 indexes.dedup();
1013 let span: MultiSpan = if !parser.is_source_literal || parser.arg_places.is_empty() {
1014 MultiSpan::from_span(fmt_span)
1015 } else {
1016 MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect())
1017 };
1018 let arg_list = format!(
1019 "argument{} {}",
1020 pluralize!(indexes.len()),
1021 listify(&indexes, |i: &usize| i.to_string()).unwrap_or_default()
1022 );
1023 e = ecx.dcx().struct_span_err(
1024 span,
1025 format!("invalid reference to positional {arg_list} ({num_args_desc})"),
1026 );
1027 e.note("positional arguments are zero-based");
1028 }
1029
1030 if template.iter().any(|piece| match piece {
1031 FormatArgsPiece::Placeholder(FormatPlaceholder { format_options: f, .. }) => {
1032 *f != FormatOptions::default()
1033 }
1034 _ => false,
1035 }) {
1036 e.note("for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html");
1037 }
1038
1039 e.emit();
1040}
1041
1042fn expand_format_args_impl<'cx>(
1043 ecx: &'cx mut ExtCtxt<'_>,
1044 mut sp: Span,
1045 tts: TokenStream,
1046 nl: bool,
1047) -> MacroExpanderResult<'cx> {
1048 sp = ecx.with_def_site_ctxt(sp);
1049 ExpandResult::Ready(match parse_args(ecx, sp, tts) {
1050 Ok(input) => {
1051 let ExpandResult::Ready(mac) = make_format_args(ecx, input, nl) else {
1052 return ExpandResult::Retry(());
1053 };
1054 match mac {
1055 Ok(format_args) => {
1056 MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(Box::new(format_args))))
1057 }
1058 Err(guar) => MacEager::expr(DummyResult::raw_expr(sp, Some(guar))),
1059 }
1060 }
1061 Err(err) => {
1062 let guar = err.emit();
1063 DummyResult::any(sp, guar)
1064 }
1065 })
1066}
1067
1068pub(crate) fn expand_format_args<'cx>(
1069 ecx: &'cx mut ExtCtxt<'_>,
1070 sp: Span,
1071 tts: TokenStream,
1072) -> MacroExpanderResult<'cx> {
1073 expand_format_args_impl(ecx, sp, tts, false)
1074}
1075
1076pub(crate) fn expand_format_args_nl<'cx>(
1077 ecx: &'cx mut ExtCtxt<'_>,
1078 sp: Span,
1079 tts: TokenStream,
1080) -> MacroExpanderResult<'cx> {
1081 expand_format_args_impl(ecx, sp, tts, true)
1082}