1+ use parse:: Position :: ArgumentNamed ;
12use rustc_ast:: ptr:: P ;
23use rustc_ast:: tokenstream:: TokenStream ;
34use rustc_ast:: { token, StmtKind } ;
@@ -6,7 +7,7 @@ use rustc_ast::{
67 FormatArgsPiece , FormatArgument , FormatArgumentKind , FormatArguments , FormatCount ,
78 FormatDebugHex , FormatOptions , FormatPlaceholder , FormatSign , FormatTrait ,
89} ;
9- use rustc_data_structures:: fx:: FxHashSet ;
10+ use rustc_data_structures:: fx:: { FxHashSet , FxIndexSet } ;
1011use rustc_errors:: { Applicability , MultiSpan , PResult , SingleLabelManySpans } ;
1112use rustc_expand:: base:: { self , * } ;
1213use rustc_parse_format as parse;
@@ -357,8 +358,8 @@ fn make_format_args(
357358 let mut unfinished_literal = String :: new ( ) ;
358359 let mut placeholder_index = 0 ;
359360
360- for piece in pieces {
361- match piece {
361+ for piece in & pieces {
362+ match * piece {
362363 parse:: Piece :: String ( s) => {
363364 unfinished_literal. push_str ( s) ;
364365 }
@@ -506,7 +507,17 @@ fn make_format_args(
506507 // If there's a lot of unused arguments,
507508 // let's check if this format arguments looks like another syntax (printf / shell).
508509 let detect_foreign_fmt = unused. len ( ) > args. explicit_args ( ) . len ( ) / 2 ;
509- report_missing_placeholders ( ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span) ;
510+ report_missing_placeholders (
511+ ecx,
512+ unused,
513+ & used,
514+ & args,
515+ & pieces,
516+ detect_foreign_fmt,
517+ str_style,
518+ fmt_str,
519+ fmt_span,
520+ ) ;
510521 }
511522
512523 // Only check for unused named argument names if there are no other errors to avoid causing
@@ -573,6 +584,9 @@ fn invalid_placeholder_type_error(
573584fn report_missing_placeholders (
574585 ecx : & mut ExtCtxt < ' _ > ,
575586 unused : Vec < ( Span , bool ) > ,
587+ used : & [ bool ] ,
588+ args : & FormatArguments ,
589+ pieces : & [ parse:: Piece < ' _ > ] ,
576590 detect_foreign_fmt : bool ,
577591 str_style : Option < usize > ,
578592 fmt_str : & str ,
@@ -591,6 +605,68 @@ fn report_missing_placeholders(
591605 } )
592606 } ;
593607
608+ let placeholders = pieces
609+ . iter ( )
610+ . filter_map ( |piece| {
611+ if let parse:: Piece :: NextArgument ( argument) = piece && let ArgumentNamed ( binding) = argument. position {
612+ let span = fmt_span. from_inner ( InnerSpan :: new ( argument. position_span . start , argument. position_span . end ) ) ;
613+ Some ( ( span, binding) )
614+ } else { None }
615+ } )
616+ . collect :: < Vec < _ > > ( ) ;
617+
618+ let mut args_spans = vec ! [ ] ;
619+ let mut fmt_spans = FxIndexSet :: default ( ) ;
620+
621+ for ( i, unnamed_arg) in args. unnamed_args ( ) . iter ( ) . enumerate ( ) . rev ( ) {
622+ let Some ( ty) = unnamed_arg. expr . to_ty ( ) else { continue } ;
623+ let Some ( argument_binding) = ty. kind . is_simple_path ( ) else { continue } ;
624+ let argument_binding = argument_binding. as_str ( ) ;
625+
626+ if used[ i] {
627+ continue ;
628+ }
629+
630+ let matching_placeholders = placeholders
631+ . iter ( )
632+ . filter ( |( _, inline_binding) | argument_binding == * inline_binding)
633+ . collect :: < Vec < _ > > ( ) ;
634+
635+ if !matching_placeholders. is_empty ( ) {
636+ args_spans. push ( unnamed_arg. expr . span ) ;
637+ for placeholder in & matching_placeholders {
638+ fmt_spans. insert ( * placeholder) ;
639+ }
640+ }
641+ }
642+
643+ if !args_spans. is_empty ( ) {
644+ let mut multispan = MultiSpan :: from ( args_spans. clone ( ) ) ;
645+
646+ let msg = if fmt_spans. len ( ) > 1 {
647+ "the formatting strings already captures the bindings \
648+ directly, they don't need to be included in the argument list"
649+ } else {
650+ "the formatting string already captures the binding \
651+ directly, it doesn't need to be included in the argument list"
652+ } ;
653+
654+ for ( span, binding) in fmt_spans {
655+ multispan. push_span_label (
656+ * span,
657+ format ! ( "this formatting specifier is referencing the `{binding}` binding" ) ,
658+ ) ;
659+ }
660+
661+ for span in & args_spans {
662+ multispan. push_span_label ( * span, "this can be removed" ) ;
663+ }
664+
665+ diag. span_help ( multispan, msg) ;
666+ diag. emit ( ) ;
667+ return ;
668+ }
669+
594670 // Used to ensure we only report translations for *one* kind of foreign format.
595671 let mut found_foreign = false ;
596672
0 commit comments