1+ use parse:: Position :: ArgumentNamed ;
12use rustc_ast:: ptr:: P ;
23use rustc_ast:: tokenstream:: TokenStream ;
34use rustc_ast:: { token, StmtKind } ;
@@ -7,7 +8,9 @@ use rustc_ast::{
78 FormatDebugHex , FormatOptions , FormatPlaceholder , FormatSign , FormatTrait ,
89} ;
910use rustc_data_structures:: fx:: FxHashSet ;
10- use rustc_errors:: { Applicability , MultiSpan , PResult , SingleLabelManySpans } ;
11+ use rustc_errors:: {
12+ Applicability , DiagnosticBuilder , ErrorGuaranteed , MultiSpan , PResult , SingleLabelManySpans ,
13+ } ;
1114use rustc_expand:: base:: { self , * } ;
1215use rustc_parse_format as parse;
1316use rustc_span:: symbol:: { Ident , Symbol } ;
@@ -364,8 +367,8 @@ fn make_format_args(
364367 let mut unfinished_literal = String :: new ( ) ;
365368 let mut placeholder_index = 0 ;
366369
367- for piece in pieces {
368- match piece {
370+ for piece in & pieces {
371+ match * piece {
369372 parse:: Piece :: String ( s) => {
370373 unfinished_literal. push_str ( s) ;
371374 }
@@ -513,7 +516,17 @@ fn make_format_args(
513516 // If there's a lot of unused arguments,
514517 // let's check if this format arguments looks like another syntax (printf / shell).
515518 let detect_foreign_fmt = unused. len ( ) > args. explicit_args ( ) . len ( ) / 2 ;
516- report_missing_placeholders ( ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span) ;
519+ report_missing_placeholders (
520+ ecx,
521+ unused,
522+ & used,
523+ & args,
524+ & pieces,
525+ detect_foreign_fmt,
526+ str_style,
527+ fmt_str,
528+ fmt_span,
529+ ) ;
517530 }
518531
519532 // Only check for unused named argument names if there are no other errors to avoid causing
@@ -580,6 +593,9 @@ fn invalid_placeholder_type_error(
580593fn report_missing_placeholders (
581594 ecx : & mut ExtCtxt < ' _ > ,
582595 unused : Vec < ( Span , bool ) > ,
596+ used : & [ bool ] ,
597+ args : & FormatArguments ,
598+ pieces : & [ parse:: Piece < ' _ > ] ,
583599 detect_foreign_fmt : bool ,
584600 str_style : Option < usize > ,
585601 fmt_str : & str ,
@@ -598,6 +614,26 @@ fn report_missing_placeholders(
598614 } )
599615 } ;
600616
617+ let placeholders = pieces
618+ . iter ( )
619+ . filter_map ( |piece| {
620+ if let parse:: Piece :: NextArgument ( argument) = piece && let ArgumentNamed ( binding) = argument. position {
621+ let span = fmt_span. from_inner ( InnerSpan :: new ( argument. position_span . start , argument. position_span . end ) ) ;
622+ Some ( ( span, binding) )
623+ } else { None }
624+ } )
625+ . collect :: < Vec < _ > > ( ) ;
626+
627+ if !placeholders. is_empty ( ) {
628+ if let Some ( mut new_diag) =
629+ report_redundant_format_arguments ( ecx, & args, used, placeholders)
630+ {
631+ diag. cancel ( ) ;
632+ new_diag. emit ( ) ;
633+ return ;
634+ }
635+ }
636+
601637 // Used to ensure we only report translations for *one* kind of foreign format.
602638 let mut found_foreign = false ;
603639
@@ -685,6 +721,76 @@ fn report_missing_placeholders(
685721 diag. emit ( ) ;
686722}
687723
724+ /// This function detects and reports unused format!() arguments that are
725+ /// redundant due to implicit captures (e.g. `format!("{x}", x)`).
726+ fn report_redundant_format_arguments < ' a > (
727+ ecx : & mut ExtCtxt < ' a > ,
728+ args : & FormatArguments ,
729+ used : & [ bool ] ,
730+ placeholders : Vec < ( Span , & str ) > ,
731+ ) -> Option < DiagnosticBuilder < ' a , ErrorGuaranteed > > {
732+ let mut fmt_arg_indices = vec ! [ ] ;
733+ let mut args_spans = vec ! [ ] ;
734+ let mut fmt_spans = vec ! [ ] ;
735+
736+ for ( i, unnamed_arg) in args. unnamed_args ( ) . iter ( ) . enumerate ( ) . rev ( ) {
737+ let Some ( ty) = unnamed_arg. expr . to_ty ( ) else { continue } ;
738+ let Some ( argument_binding) = ty. kind . is_simple_path ( ) else { continue } ;
739+ let argument_binding = argument_binding. as_str ( ) ;
740+
741+ if used[ i] {
742+ continue ;
743+ }
744+
745+ let matching_placeholders = placeholders
746+ . iter ( )
747+ . filter ( |( _, inline_binding) | argument_binding == * inline_binding)
748+ . map ( |( span, _) | span)
749+ . collect :: < Vec < _ > > ( ) ;
750+
751+ if !matching_placeholders. is_empty ( ) {
752+ fmt_arg_indices. push ( i) ;
753+ args_spans. push ( unnamed_arg. expr . span ) ;
754+ for span in & matching_placeholders {
755+ if fmt_spans. contains ( * span) {
756+ continue ;
757+ }
758+ fmt_spans. push ( * * span) ;
759+ }
760+ }
761+ }
762+
763+ if !args_spans. is_empty ( ) {
764+ let multispan = MultiSpan :: from ( fmt_spans) ;
765+ let mut suggestion_spans = vec ! [ ] ;
766+
767+ for ( arg_span, fmt_arg_idx) in args_spans. iter ( ) . zip ( fmt_arg_indices. iter ( ) ) {
768+ let span = if fmt_arg_idx + 1 == args. explicit_args ( ) . len ( ) {
769+ * arg_span
770+ } else {
771+ arg_span. until ( args. explicit_args ( ) [ * fmt_arg_idx + 1 ] . expr . span )
772+ } ;
773+
774+ suggestion_spans. push ( span) ;
775+ }
776+
777+ let sugg = if args. named_args ( ) . len ( ) == 0 {
778+ Some ( errors:: FormatRedundantArgsSugg { spans : suggestion_spans } )
779+ } else {
780+ None
781+ } ;
782+
783+ return Some ( ecx. create_err ( errors:: FormatRedundantArgs {
784+ n : args_spans. len ( ) ,
785+ span : MultiSpan :: from ( args_spans) ,
786+ note : multispan,
787+ sugg,
788+ } ) ) ;
789+ }
790+
791+ None
792+ }
793+
688794/// Handle invalid references to positional arguments. Output different
689795/// errors for the case where all arguments are positional and for when
690796/// there are named arguments or numbered positional arguments in the
0 commit comments