@@ -11,14 +11,14 @@ use rustc_errors::{pluralize, Applicability, MultiSpan, PResult};
11
11
use rustc_expand:: base:: { self , * } ;
12
12
use rustc_parse_format as parse;
13
13
use rustc_span:: symbol:: { sym, Ident , Symbol } ;
14
- use rustc_span:: { InnerSpan , Span } ;
14
+ use rustc_span:: { BytePos , InnerSpan , Span } ;
15
15
use smallvec:: SmallVec ;
16
16
17
17
use rustc_lint_defs:: builtin:: NAMED_ARGUMENTS_USED_POSITIONALLY ;
18
18
use rustc_lint_defs:: { BufferedEarlyLint , BuiltinLintDiagnostics , LintId } ;
19
- use rustc_parse_format:: Count ;
20
19
use std:: borrow:: Cow ;
21
20
use std:: collections:: hash_map:: Entry ;
21
+ use std:: ops:: Deref ;
22
22
23
23
#[ derive( PartialEq ) ]
24
24
enum ArgumentType {
@@ -32,6 +32,105 @@ enum Position {
32
32
Named ( Symbol , InnerSpan ) ,
33
33
}
34
34
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
+
35
134
struct Context < ' a , ' b > {
36
135
ecx : & ' a mut ExtCtxt < ' b > ,
37
136
/// The macro's call site. References to unstable formatting internals must
@@ -118,6 +217,7 @@ struct Context<'a, 'b> {
118
217
119
218
/// Whether this format string came from a string literal, as opposed to a macro.
120
219
is_literal : bool ,
220
+ unused_names_lint : PositionalNamedArgsLint ,
121
221
}
122
222
123
223
/// Parses the arguments from the given list of tokens, returning the diagnostic
@@ -242,7 +342,7 @@ impl<'a, 'b> Context<'a, 'b> {
242
342
self . args . len ( ) - self . num_captured_args
243
343
}
244
344
245
- fn resolve_name_inplace ( & self , p : & mut parse:: Piece < ' _ > ) {
345
+ fn resolve_name_inplace ( & mut self , p : & mut parse:: Piece < ' _ > ) {
246
346
// NOTE: the `unwrap_or` branch is needed in case of invalid format
247
347
// arguments, e.g., `format_args!("{foo}")`.
248
348
let lookup =
@@ -252,7 +352,7 @@ impl<'a, 'b> Context<'a, 'b> {
252
352
parse:: String ( _) => { }
253
353
parse:: NextArgument ( ref mut arg) => {
254
354
if let parse:: ArgumentNamed ( s, _) = arg. position {
255
- arg. position = parse:: ArgumentIs ( lookup ( s) ) ;
355
+ arg. position = parse:: ArgumentIs ( lookup ( s) , None ) ;
256
356
}
257
357
if let parse:: CountIsName ( s, _) = arg. format . width {
258
358
arg. format . width = parse:: CountIsParam ( lookup ( s) ) ;
@@ -273,15 +373,50 @@ impl<'a, 'b> Context<'a, 'b> {
273
373
parse:: NextArgument ( ref arg) => {
274
374
// width/precision first, if they have implicit positional
275
375
// 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
+ ) ;
278
386
279
387
// argument second, if it's an implicit positional parameter
280
388
// it's written second, so it should come after width/precision.
281
389
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
+ }
283
417
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 ) )
285
420
}
286
421
} ;
287
422
@@ -349,10 +484,25 @@ impl<'a, 'b> Context<'a, 'b> {
349
484
}
350
485
}
351
486
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
+ ) {
353
493
match c {
354
494
parse:: CountImplied | parse:: CountIs ( ..) => { }
355
495
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
+ }
356
506
self . verify_arg_type ( Exact ( i) , Count ) ;
357
507
}
358
508
parse:: CountIsName ( s, span) => {
@@ -673,7 +823,7 @@ impl<'a, 'b> Context<'a, 'b> {
673
823
// Build the position
674
824
let pos = {
675
825
match arg. position {
676
- parse:: ArgumentIs ( i) | parse:: ArgumentImplicitlyIs ( i) => {
826
+ parse:: ArgumentIs ( i, .. ) | parse:: ArgumentImplicitlyIs ( i) => {
677
827
// Map to index in final generated argument array
678
828
// in case of multiple types specified
679
829
let arg_idx = match arg_index_consumed. get_mut ( i) {
@@ -701,7 +851,7 @@ impl<'a, 'b> Context<'a, 'b> {
701
851
// track the current argument ourselves.
702
852
let i = self . curarg ;
703
853
self . curarg += 1 ;
704
- parse:: ArgumentIs ( i)
854
+ parse:: ArgumentIs ( i, None )
705
855
} ,
706
856
format : parse:: FormatSpec {
707
857
fill : arg. format . fill ,
@@ -971,43 +1121,27 @@ pub fn expand_format_args_nl<'cx>(
971
1121
expand_format_args_impl ( ecx, sp, tts, true )
972
1122
}
973
1123
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) ;
996
1127
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
+ } ) ;
1011
1145
}
1012
1146
}
1013
1147
@@ -1119,11 +1253,6 @@ pub fn expand_preparsed_format_args(
1119
1253
1120
1254
let named_pos: FxHashSet < usize > = names. values ( ) . cloned ( ) . map ( |( i, _) | i) . collect ( ) ;
1121
1255
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
-
1127
1256
let mut cx = Context {
1128
1257
ecx,
1129
1258
args,
@@ -1148,13 +1277,12 @@ pub fn expand_preparsed_format_args(
1148
1277
arg_spans,
1149
1278
arg_with_formatting : Vec :: new ( ) ,
1150
1279
is_literal : parser. is_literal ,
1280
+ unused_names_lint : PositionalNamedArgsLint { positional_named_args : vec ! [ ] } ,
1151
1281
} ;
1152
1282
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
1155
1284
let pieces = unverified_pieces
1156
- . iter ( )
1157
- . cloned ( )
1285
+ . into_iter ( )
1158
1286
. map ( |mut piece| {
1159
1287
cx. verify_piece ( & piece) ;
1160
1288
cx. resolve_name_inplace ( & mut piece) ;
@@ -1164,7 +1292,7 @@ pub fn expand_preparsed_format_args(
1164
1292
1165
1293
let numbered_position_args = pieces. iter ( ) . any ( |arg : & parse:: Piece < ' _ > | match * arg {
1166
1294
parse:: String ( _) => false ,
1167
- parse:: NextArgument ( arg) => matches ! ( arg. position, parse:: Position :: ArgumentIs ( _ ) ) ,
1295
+ parse:: NextArgument ( arg) => matches ! ( arg. position, parse:: Position :: ArgumentIs ( .. ) ) ,
1168
1296
} ) ;
1169
1297
1170
1298
cx. build_index_map ( ) ;
@@ -1316,11 +1444,10 @@ pub fn expand_preparsed_format_args(
1316
1444
}
1317
1445
1318
1446
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 {
1320
1448
// Only check for unused named argument names if there are no other errors to avoid causing
1321
1449
// 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) ;
1324
1451
}
1325
1452
1326
1453
cx. into_expr ( )
0 commit comments