@@ -11,14 +11,14 @@ use rustc_errors::{pluralize, Applicability, MultiSpan, PResult};
1111use rustc_expand:: base:: { self , * } ;
1212use rustc_parse_format as parse;
1313use rustc_span:: symbol:: { sym, Ident , Symbol } ;
14- use rustc_span:: { InnerSpan , Span } ;
14+ use rustc_span:: { BytePos , InnerSpan , Span } ;
1515use smallvec:: SmallVec ;
1616
1717use rustc_lint_defs:: builtin:: NAMED_ARGUMENTS_USED_POSITIONALLY ;
1818use rustc_lint_defs:: { BufferedEarlyLint , BuiltinLintDiagnostics , LintId } ;
19- use rustc_parse_format:: Count ;
2019use std:: borrow:: Cow ;
2120use std:: collections:: hash_map:: Entry ;
21+ use std:: ops:: Deref ;
2222
2323#[ derive( PartialEq ) ]
2424enum ArgumentType {
@@ -32,6 +32,105 @@ enum Position {
3232 Named ( Symbol , InnerSpan ) ,
3333}
3434
35+ /// Indicates how positional named argument (i.e. an named argument which is used by position
36+ /// instead of by name) is used in format string
37+ /// * `Arg` is the actual argument to print
38+ /// * `Width` is width format argument
39+ /// * `Precision` is precion format argument
40+ /// Example: `{Arg:Width$.Precision$}
41+ #[ derive( Debug , Eq , PartialEq ) ]
42+ enum PositionalNamedArgType {
43+ Arg ,
44+ Width ,
45+ Precision ,
46+ }
47+
48+ /// Contains information necessary to create a lint for a positional named argument
49+ #[ derive( Debug ) ]
50+ struct PositionalNamedArg {
51+ ty : PositionalNamedArgType ,
52+ /// The piece of the using this argument (multiple pieces can use the same argument)
53+ cur_piece : usize ,
54+ /// The InnerSpan for in the string to be replaced with the named argument
55+ /// This will be None when the position is implicit
56+ inner_span_to_replace : Option < rustc_parse_format:: InnerSpan > ,
57+ /// The name to use instead of the position
58+ replacement : Symbol ,
59+ /// The span for the positional named argument (so the lint can point a message to it)
60+ positional_named_arg_span : Span ,
61+ }
62+
63+ impl PositionalNamedArg {
64+ /// Determines what span to replace with the name of the named argument
65+ fn get_span_to_replace ( & self , cx : & Context < ' _ , ' _ > ) -> Option < Span > {
66+ if let Some ( inner_span) = & self . inner_span_to_replace {
67+ return match self . ty {
68+ PositionalNamedArgType :: Arg | PositionalNamedArgType :: Width => Some ( Span :: new (
69+ cx. fmtsp . lo ( ) + BytePos ( inner_span. start . try_into ( ) . unwrap ( ) ) ,
70+ cx. fmtsp . lo ( ) + BytePos ( inner_span. end . try_into ( ) . unwrap ( ) ) ,
71+ self . positional_named_arg_span . ctxt ( ) ,
72+ self . positional_named_arg_span . parent ( ) ,
73+ ) ) ,
74+ PositionalNamedArgType :: Precision => Some ( Span :: new (
75+ cx. fmtsp . lo ( ) + BytePos ( inner_span. start . try_into ( ) . unwrap ( ) ) + BytePos ( 1 ) ,
76+ cx. fmtsp . lo ( ) + BytePos ( inner_span. end . try_into ( ) . unwrap ( ) ) ,
77+ self . positional_named_arg_span . ctxt ( ) ,
78+ self . positional_named_arg_span . parent ( ) ,
79+ ) ) ,
80+ } ;
81+ } else if self . ty == PositionalNamedArgType :: Arg {
82+ // In the case of a named argument whose position is implicit, there will not be a span
83+ // to replace. Instead, we insert the name after the `{`, which is the first character
84+ // of arg_span.
85+ if let Some ( arg_span) = cx. arg_spans . get ( self . cur_piece ) . copied ( ) {
86+ return Some ( Span :: new (
87+ arg_span. lo ( ) + BytePos ( 1 ) ,
88+ arg_span. lo ( ) + BytePos ( 1 ) ,
89+ self . positional_named_arg_span . ctxt ( ) ,
90+ self . positional_named_arg_span . parent ( ) ,
91+ ) ) ;
92+ }
93+ }
94+
95+ None
96+ }
97+ }
98+
99+ /// Encapsulates all the named arguments that have been used positionally
100+ #[ derive( Debug ) ]
101+ struct PositionalNamedArgsLint {
102+ positional_named_args : Vec < PositionalNamedArg > ,
103+ }
104+
105+ impl PositionalNamedArgsLint {
106+ /// Try constructing a PositionalNamedArg struct and pushing it into the vec of positional
107+ /// named arguments. If a named arg associated with `format_argument_index` cannot be found,
108+ /// a new item will not be added as the lint cannot be emitted in this case.
109+ fn maybe_push (
110+ & mut self ,
111+ format_argument_index : usize ,
112+ ty : PositionalNamedArgType ,
113+ cur_piece : usize ,
114+ inner_span : Option < rustc_parse_format:: InnerSpan > ,
115+ names : & FxHashMap < Symbol , ( usize , Span ) > ,
116+ ) {
117+ let named_arg = names
118+ . iter ( )
119+ . find ( |name| name. deref ( ) . 1 . 0 == format_argument_index)
120+ . map ( |found| found. clone ( ) ) ;
121+
122+ if let Some ( named_arg) = named_arg {
123+ self . positional_named_args . push ( PositionalNamedArg {
124+ ty,
125+ cur_piece,
126+ inner_span_to_replace : inner_span,
127+ replacement : named_arg. 0 . clone ( ) ,
128+ positional_named_arg_span : named_arg. 1 . 1 . clone ( ) ,
129+ } ) ;
130+ }
131+ }
132+ }
133+
35134struct Context < ' a , ' b > {
36135 ecx : & ' a mut ExtCtxt < ' b > ,
37136 /// The macro's call site. References to unstable formatting internals must
@@ -118,6 +217,7 @@ struct Context<'a, 'b> {
118217
119218 /// Whether this format string came from a string literal, as opposed to a macro.
120219 is_literal : bool ,
220+ unused_names_lint : PositionalNamedArgsLint ,
121221}
122222
123223/// Parses the arguments from the given list of tokens, returning the diagnostic
@@ -242,7 +342,7 @@ impl<'a, 'b> Context<'a, 'b> {
242342 self . args . len ( ) - self . num_captured_args
243343 }
244344
245- fn resolve_name_inplace ( & self , p : & mut parse:: Piece < ' _ > ) {
345+ fn resolve_name_inplace ( & mut self , p : & mut parse:: Piece < ' _ > ) {
246346 // NOTE: the `unwrap_or` branch is needed in case of invalid format
247347 // arguments, e.g., `format_args!("{foo}")`.
248348 let lookup =
@@ -252,7 +352,7 @@ impl<'a, 'b> Context<'a, 'b> {
252352 parse:: String ( _) => { }
253353 parse:: NextArgument ( ref mut arg) => {
254354 if let parse:: ArgumentNamed ( s, _) = arg. position {
255- arg. position = parse:: ArgumentIs ( lookup ( s) ) ;
355+ arg. position = parse:: ArgumentIs ( lookup ( s) , None ) ;
256356 }
257357 if let parse:: CountIsName ( s, _) = arg. format . width {
258358 arg. format . width = parse:: CountIsParam ( lookup ( s) ) ;
@@ -273,15 +373,50 @@ impl<'a, 'b> Context<'a, 'b> {
273373 parse:: NextArgument ( ref arg) => {
274374 // width/precision first, if they have implicit positional
275375 // parameters it makes more sense to consume them first.
276- self . verify_count ( arg. format . width ) ;
277- self . verify_count ( arg. format . precision ) ;
376+ self . verify_count (
377+ arg. format . width ,
378+ & arg. format . width_span ,
379+ PositionalNamedArgType :: Width ,
380+ ) ;
381+ self . verify_count (
382+ arg. format . precision ,
383+ & arg. format . precision_span ,
384+ PositionalNamedArgType :: Precision ,
385+ ) ;
278386
279387 // argument second, if it's an implicit positional parameter
280388 // it's written second, so it should come after width/precision.
281389 let pos = match arg. position {
282- parse:: ArgumentIs ( i) | parse:: ArgumentImplicitlyIs ( i) => Exact ( i) ,
390+ parse:: ArgumentIs ( i, arg_end) => {
391+ let start_of_named_args = self . args . len ( ) - self . names . len ( ) ;
392+ if self . curpiece >= start_of_named_args {
393+ self . unused_names_lint . maybe_push (
394+ i,
395+ PositionalNamedArgType :: Arg ,
396+ self . curpiece ,
397+ arg_end,
398+ & self . names ,
399+ ) ;
400+ }
401+
402+ Exact ( i)
403+ }
404+ parse:: ArgumentImplicitlyIs ( i) => {
405+ let start_of_named_args = self . args . len ( ) - self . names . len ( ) ;
406+ if self . curpiece >= start_of_named_args {
407+ self . unused_names_lint . maybe_push (
408+ i,
409+ PositionalNamedArgType :: Arg ,
410+ self . curpiece ,
411+ None ,
412+ & self . names ,
413+ ) ;
414+ }
415+ Exact ( i)
416+ }
283417 parse:: ArgumentNamed ( s, span) => {
284- Named ( Symbol :: intern ( s) , InnerSpan :: new ( span. start , span. end ) )
418+ let symbol = Symbol :: intern ( s) ;
419+ Named ( symbol, InnerSpan :: new ( span. start , span. end ) )
285420 }
286421 } ;
287422
@@ -349,10 +484,25 @@ impl<'a, 'b> Context<'a, 'b> {
349484 }
350485 }
351486
352- fn verify_count ( & mut self , c : parse:: Count < ' _ > ) {
487+ fn verify_count (
488+ & mut self ,
489+ c : parse:: Count < ' _ > ,
490+ inner_span : & Option < rustc_parse_format:: InnerSpan > ,
491+ named_arg_type : PositionalNamedArgType ,
492+ ) {
353493 match c {
354494 parse:: CountImplied | parse:: CountIs ( ..) => { }
355495 parse:: CountIsParam ( i) => {
496+ let start_of_named_args = self . args . len ( ) - self . names . len ( ) ;
497+ if i >= start_of_named_args {
498+ self . unused_names_lint . maybe_push (
499+ i,
500+ named_arg_type,
501+ self . curpiece ,
502+ inner_span. clone ( ) ,
503+ & self . names ,
504+ ) ;
505+ }
356506 self . verify_arg_type ( Exact ( i) , Count ) ;
357507 }
358508 parse:: CountIsName ( s, span) => {
@@ -673,7 +823,7 @@ impl<'a, 'b> Context<'a, 'b> {
673823 // Build the position
674824 let pos = {
675825 match arg. position {
676- parse:: ArgumentIs ( i) | parse:: ArgumentImplicitlyIs ( i) => {
826+ parse:: ArgumentIs ( i, .. ) | parse:: ArgumentImplicitlyIs ( i) => {
677827 // Map to index in final generated argument array
678828 // in case of multiple types specified
679829 let arg_idx = match arg_index_consumed. get_mut ( i) {
@@ -701,7 +851,7 @@ impl<'a, 'b> Context<'a, 'b> {
701851 // track the current argument ourselves.
702852 let i = self . curarg ;
703853 self . curarg += 1 ;
704- parse:: ArgumentIs ( i)
854+ parse:: ArgumentIs ( i, None )
705855 } ,
706856 format : parse:: FormatSpec {
707857 fill : arg. format . fill ,
@@ -971,43 +1121,27 @@ pub fn expand_format_args_nl<'cx>(
9711121 expand_format_args_impl ( ecx, sp, tts, true )
9721122}
9731123
974- fn lint_named_arguments_used_positionally (
975- names : FxHashMap < Symbol , ( usize , Span ) > ,
976- cx : & mut Context < ' _ , ' _ > ,
977- unverified_pieces : Vec < parse:: Piece < ' _ > > ,
978- ) {
979- let mut used_argument_names = FxHashSet :: < & str > :: default ( ) ;
980- for piece in unverified_pieces {
981- if let rustc_parse_format:: Piece :: NextArgument ( a) = piece {
982- match a. position {
983- rustc_parse_format:: Position :: ArgumentNamed ( arg_name, _) => {
984- used_argument_names. insert ( arg_name) ;
985- }
986- _ => { }
987- } ;
988- if let Count :: CountIsName ( s, _) = a. format . width {
989- used_argument_names. insert ( s) ;
990- }
991- if let Count :: CountIsName ( s, _) = a. format . precision {
992- used_argument_names. insert ( s) ;
993- }
994- }
995- }
1124+ fn create_lints_for_named_arguments_used_positionally ( cx : & mut Context < ' _ , ' _ > ) {
1125+ for named_arg in & cx. unused_names_lint . positional_named_args {
1126+ let arg_span = named_arg. get_span_to_replace ( cx) ;
9961127
997- for ( symbol, ( index, span) ) in names {
998- if !used_argument_names. contains ( symbol. as_str ( ) ) {
999- let msg = format ! ( "named argument `{}` is not used by name" , symbol. as_str( ) ) ;
1000- let arg_span = cx. arg_spans . get ( index) . copied ( ) ;
1001- cx. ecx . buffered_early_lint . push ( BufferedEarlyLint {
1002- span : MultiSpan :: from_span ( span) ,
1003- msg : msg. clone ( ) ,
1004- node_id : ast:: CRATE_NODE_ID ,
1005- lint_id : LintId :: of ( & NAMED_ARGUMENTS_USED_POSITIONALLY ) ,
1006- diagnostic : BuiltinLintDiagnostics :: NamedArgumentUsedPositionally (
1007- arg_span, span, symbol,
1008- ) ,
1009- } ) ;
1010- }
1128+ let msg = format ! ( "named argument `{}` is not used by name" , named_arg. replacement) ;
1129+ let replacement = match named_arg. ty {
1130+ PositionalNamedArgType :: Arg => named_arg. replacement . to_string ( ) ,
1131+ _ => named_arg. replacement . to_string ( ) + "$" ,
1132+ } ;
1133+
1134+ cx. ecx . buffered_early_lint . push ( BufferedEarlyLint {
1135+ span : MultiSpan :: from_span ( named_arg. positional_named_arg_span ) ,
1136+ msg : msg. clone ( ) ,
1137+ node_id : ast:: CRATE_NODE_ID ,
1138+ lint_id : LintId :: of ( & NAMED_ARGUMENTS_USED_POSITIONALLY ) ,
1139+ diagnostic : BuiltinLintDiagnostics :: NamedArgumentUsedPositionally (
1140+ arg_span,
1141+ named_arg. positional_named_arg_span ,
1142+ replacement,
1143+ ) ,
1144+ } ) ;
10111145 }
10121146}
10131147
@@ -1119,11 +1253,6 @@ pub fn expand_preparsed_format_args(
11191253
11201254 let named_pos: FxHashSet < usize > = names. values ( ) . cloned ( ) . map ( |( i, _) | i) . collect ( ) ;
11211255
1122- // Clone `names` because `names` in Context get updated by verify_piece, which includes usages
1123- // of the names of named arguments, resulting in incorrect errors if a name argument is used
1124- // but not declared, such as: `println!("x = {x}");`
1125- let named_arguments = names. clone ( ) ;
1126-
11271256 let mut cx = Context {
11281257 ecx,
11291258 args,
@@ -1148,13 +1277,12 @@ pub fn expand_preparsed_format_args(
11481277 arg_spans,
11491278 arg_with_formatting : Vec :: new ( ) ,
11501279 is_literal : parser. is_literal ,
1280+ unused_names_lint : PositionalNamedArgsLint { positional_named_args : vec ! [ ] } ,
11511281 } ;
11521282
1153- // This needs to happen *after* the Parser has consumed all pieces to create all the spans.
1154- // unverified_pieces is used later to check named argument names are used, so clone each piece.
1283+ // This needs to happen *after* the Parser has consumed all pieces to create all the spans
11551284 let pieces = unverified_pieces
1156- . iter ( )
1157- . cloned ( )
1285+ . into_iter ( )
11581286 . map ( |mut piece| {
11591287 cx. verify_piece ( & piece) ;
11601288 cx. resolve_name_inplace ( & mut piece) ;
@@ -1164,7 +1292,7 @@ pub fn expand_preparsed_format_args(
11641292
11651293 let numbered_position_args = pieces. iter ( ) . any ( |arg : & parse:: Piece < ' _ > | match * arg {
11661294 parse:: String ( _) => false ,
1167- parse:: NextArgument ( arg) => matches ! ( arg. position, parse:: Position :: ArgumentIs ( _ ) ) ,
1295+ parse:: NextArgument ( arg) => matches ! ( arg. position, parse:: Position :: ArgumentIs ( .. ) ) ,
11681296 } ) ;
11691297
11701298 cx. build_index_map ( ) ;
@@ -1316,11 +1444,10 @@ pub fn expand_preparsed_format_args(
13161444 }
13171445
13181446 diag. emit ( ) ;
1319- } else if cx. invalid_refs . is_empty ( ) && !named_arguments . is_empty ( ) {
1447+ } else if cx. invalid_refs . is_empty ( ) && cx . ecx . sess . err_count ( ) == 0 {
13201448 // Only check for unused named argument names if there are no other errors to avoid causing
13211449 // too much noise in output errors, such as when a named argument is entirely unused.
1322- // We also only need to perform this check if there are actually named arguments.
1323- lint_named_arguments_used_positionally ( named_arguments, & mut cx, unverified_pieces) ;
1450+ create_lints_for_named_arguments_used_positionally ( & mut cx) ;
13241451 }
13251452
13261453 cx. into_expr ( )
0 commit comments