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