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;
@@ -357,8 +358,8 @@ fn make_format_args(
357
358
let mut unfinished_literal = String :: new ( ) ;
358
359
let mut placeholder_index = 0 ;
359
360
360
- for piece in pieces {
361
- match piece {
361
+ for piece in & pieces {
362
+ match * piece {
362
363
parse:: Piece :: String ( s) => {
363
364
unfinished_literal. push_str ( s) ;
364
365
}
@@ -506,7 +507,17 @@ fn make_format_args(
506
507
// If there's a lot of unused arguments,
507
508
// let's check if this format arguments looks like another syntax (printf / shell).
508
509
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
+ ) ;
510
521
}
511
522
512
523
// 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(
573
584
fn report_missing_placeholders (
574
585
ecx : & mut ExtCtxt < ' _ > ,
575
586
unused : Vec < ( Span , bool ) > ,
587
+ used : & [ bool ] ,
588
+ args : & FormatArguments ,
589
+ pieces : & [ parse:: Piece < ' _ > ] ,
576
590
detect_foreign_fmt : bool ,
577
591
str_style : Option < usize > ,
578
592
fmt_str : & str ,
@@ -591,6 +605,68 @@ fn report_missing_placeholders(
591
605
} )
592
606
} ;
593
607
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
+
594
670
// Used to ensure we only report translations for *one* kind of foreign format.
595
671
let mut found_foreign = false ;
596
672
0 commit comments