1
+ use parse:: Position :: ArgumentNamed ;
1
2
use rustc_ast:: ptr:: P ;
2
3
use rustc_ast:: tokenstream:: TokenStream ;
3
4
use rustc_ast:: { token, StmtKind } ;
@@ -6,7 +7,7 @@ use rustc_ast::{
6
7
FormatArgsPiece , FormatArgument , FormatArgumentKind , FormatArguments , FormatCount ,
7
8
FormatDebugHex , FormatOptions , FormatPlaceholder , FormatSign , FormatTrait ,
8
9
} ;
9
- use rustc_data_structures:: fx:: FxHashSet ;
10
+ use rustc_data_structures:: fx:: { FxHashSet , FxIndexSet } ;
10
11
use rustc_errors:: { Applicability , MultiSpan , PResult , SingleLabelManySpans } ;
11
12
use rustc_expand:: base:: { self , * } ;
12
13
use rustc_parse_format as parse;
@@ -348,8 +349,8 @@ fn make_format_args(
348
349
let mut unfinished_literal = String :: new ( ) ;
349
350
let mut placeholder_index = 0 ;
350
351
351
- for piece in pieces {
352
- match piece {
352
+ for piece in & pieces {
353
+ match * piece {
353
354
parse:: Piece :: String ( s) => {
354
355
unfinished_literal. push_str ( s) ;
355
356
}
@@ -497,7 +498,17 @@ fn make_format_args(
497
498
// If there's a lot of unused arguments,
498
499
// let's check if this format arguments looks like another syntax (printf / shell).
499
500
let detect_foreign_fmt = unused. len ( ) > args. explicit_args ( ) . len ( ) / 2 ;
500
- report_missing_placeholders ( ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span) ;
501
+ report_missing_placeholders (
502
+ ecx,
503
+ unused,
504
+ & used,
505
+ & args,
506
+ & pieces,
507
+ detect_foreign_fmt,
508
+ str_style,
509
+ fmt_str,
510
+ fmt_span,
511
+ ) ;
501
512
}
502
513
503
514
// Only check for unused named argument names if there are no other errors to avoid causing
@@ -564,6 +575,9 @@ fn invalid_placeholder_type_error(
564
575
fn report_missing_placeholders (
565
576
ecx : & mut ExtCtxt < ' _ > ,
566
577
unused : Vec < ( Span , bool ) > ,
578
+ used : & [ bool ] ,
579
+ args : & FormatArguments ,
580
+ pieces : & [ parse:: Piece < ' _ > ] ,
567
581
detect_foreign_fmt : bool ,
568
582
str_style : Option < usize > ,
569
583
fmt_str : & str ,
@@ -582,6 +596,68 @@ fn report_missing_placeholders(
582
596
} )
583
597
} ;
584
598
599
+ let placeholders = pieces
600
+ . iter ( )
601
+ . filter_map ( |piece| {
602
+ if let parse:: Piece :: NextArgument ( argument) = piece && let ArgumentNamed ( binding) = argument. position {
603
+ let span = fmt_span. from_inner ( InnerSpan :: new ( argument. position_span . start , argument. position_span . end ) ) ;
604
+ Some ( ( span, binding) )
605
+ } else { None }
606
+ } )
607
+ . collect :: < Vec < _ > > ( ) ;
608
+
609
+ let mut args_spans = vec ! [ ] ;
610
+ let mut fmt_spans = FxIndexSet :: default ( ) ;
611
+
612
+ for ( i, unnamed_arg) in args. unnamed_args ( ) . iter ( ) . enumerate ( ) . rev ( ) {
613
+ let Some ( ty) = unnamed_arg. expr . to_ty ( ) else { continue } ;
614
+ let Some ( argument_binding) = ty. kind . is_simple_path ( ) else { continue } ;
615
+ let argument_binding = argument_binding. as_str ( ) ;
616
+
617
+ if used[ i] {
618
+ continue ;
619
+ }
620
+
621
+ let matching_placeholders = placeholders
622
+ . iter ( )
623
+ . filter ( |( _, inline_binding) | argument_binding == * inline_binding)
624
+ . collect :: < Vec < _ > > ( ) ;
625
+
626
+ if !matching_placeholders. is_empty ( ) {
627
+ args_spans. push ( unnamed_arg. expr . span ) ;
628
+ for placeholder in & matching_placeholders {
629
+ fmt_spans. insert ( * placeholder) ;
630
+ }
631
+ }
632
+ }
633
+
634
+ if !args_spans. is_empty ( ) {
635
+ let mut multispan = MultiSpan :: from ( args_spans. clone ( ) ) ;
636
+
637
+ let msg = if fmt_spans. len ( ) > 1 {
638
+ "the formatting strings already captures the bindings \
639
+ directly, they don't need to be included in the argument list"
640
+ } else {
641
+ "the formatting string already captures the binding \
642
+ directly, it doesn't need to be included in the argument list"
643
+ } ;
644
+
645
+ for ( span, binding) in fmt_spans {
646
+ multispan. push_span_label (
647
+ * span,
648
+ format ! ( "this formatting specifier is referencing the `{binding}` binding" ) ,
649
+ ) ;
650
+ }
651
+
652
+ for span in & args_spans {
653
+ multispan. push_span_label ( * span, "this can be removed" ) ;
654
+ }
655
+
656
+ diag. span_help ( multispan, msg) ;
657
+ diag. emit ( ) ;
658
+ return ;
659
+ }
660
+
585
661
// Used to ensure we only report translations for *one* kind of foreign format.
586
662
let mut found_foreign = false ;
587
663
0 commit comments