@@ -14,6 +14,9 @@ use rustc_span::symbol::{sym, Ident, Symbol};
14
14
use rustc_span:: { InnerSpan , Span } ;
15
15
use smallvec:: SmallVec ;
16
16
17
+ use rustc_lint_defs:: builtin:: NAMED_ARGUMENTS_USED_POSITIONALLY ;
18
+ use rustc_lint_defs:: { BufferedEarlyLint , BuiltinLintDiagnostics , LintId } ;
19
+ use rustc_parse_format:: { Count , FormatSpec } ;
17
20
use std:: borrow:: Cow ;
18
21
use std:: collections:: hash_map:: Entry ;
19
22
@@ -57,7 +60,7 @@ struct Context<'a, 'b> {
57
60
/// Unique format specs seen for each argument.
58
61
arg_unique_types : Vec < Vec < ArgumentType > > ,
59
62
/// Map from named arguments to their resolved indices.
60
- names : FxHashMap < Symbol , usize > ,
63
+ names : FxHashMap < Symbol , ( usize , Span ) > ,
61
64
62
65
/// The latest consecutive literal strings, or empty if there weren't any.
63
66
literal : String ,
@@ -130,9 +133,9 @@ fn parse_args<'a>(
130
133
ecx : & mut ExtCtxt < ' a > ,
131
134
sp : Span ,
132
135
tts : TokenStream ,
133
- ) -> PResult < ' a , ( P < ast:: Expr > , Vec < P < ast:: Expr > > , FxHashMap < Symbol , usize > ) > {
136
+ ) -> PResult < ' a , ( P < ast:: Expr > , Vec < P < ast:: Expr > > , FxHashMap < Symbol , ( usize , Span ) > ) > {
134
137
let mut args = Vec :: < P < ast:: Expr > > :: new ( ) ;
135
- let mut names = FxHashMap :: < Symbol , usize > :: default ( ) ;
138
+ let mut names = FxHashMap :: < Symbol , ( usize , Span ) > :: default ( ) ;
136
139
137
140
let mut p = ecx. new_parser_from_tts ( tts) ;
138
141
@@ -197,7 +200,7 @@ fn parse_args<'a>(
197
200
p. bump ( ) ;
198
201
p. expect ( & token:: Eq ) ?;
199
202
let e = p. parse_expr ( ) ?;
200
- if let Some ( prev) = names. get ( & ident. name ) {
203
+ if let Some ( ( prev, _ ) ) = names. get ( & ident. name ) {
201
204
ecx. struct_span_err ( e. span , & format ! ( "duplicate argument named `{}`" , ident) )
202
205
. span_label ( args[ * prev] . span , "previously here" )
203
206
. span_label ( e. span , "duplicate argument" )
@@ -210,7 +213,7 @@ fn parse_args<'a>(
210
213
// if the input is valid, we can simply append to the positional
211
214
// args. And remember the names.
212
215
let slot = args. len ( ) ;
213
- names. insert ( ident. name , slot) ;
216
+ names. insert ( ident. name , ( slot, ident . span ) ) ;
214
217
args. push ( e) ;
215
218
}
216
219
_ => {
@@ -222,7 +225,7 @@ fn parse_args<'a>(
222
225
) ;
223
226
err. span_label ( e. span , "positional arguments must be before named arguments" ) ;
224
227
for pos in names. values ( ) {
225
- err. span_label ( args[ * pos] . span , "named argument" ) ;
228
+ err. span_label ( args[ pos. 0 ] . span , "named argument" ) ;
226
229
}
227
230
err. emit ( ) ;
228
231
}
@@ -242,7 +245,8 @@ impl<'a, 'b> Context<'a, 'b> {
242
245
fn resolve_name_inplace ( & self , p : & mut parse:: Piece < ' _ > ) {
243
246
// NOTE: the `unwrap_or` branch is needed in case of invalid format
244
247
// arguments, e.g., `format_args!("{foo}")`.
245
- let lookup = |s : & str | * self . names . get ( & Symbol :: intern ( s) ) . unwrap_or ( & 0 ) ;
248
+ let lookup =
249
+ |s : & str | self . names . get ( & Symbol :: intern ( s) ) . unwrap_or ( & ( 0 , Span :: default ( ) ) ) . 0 ;
246
250
247
251
match * p {
248
252
parse:: String ( _) => { }
@@ -548,7 +552,7 @@ impl<'a, 'b> Context<'a, 'b> {
548
552
match self . names . get ( & name) {
549
553
Some ( & idx) => {
550
554
// Treat as positional arg.
551
- self . verify_arg_type ( Capture ( idx) , ty)
555
+ self . verify_arg_type ( Capture ( idx. 0 ) , ty)
552
556
}
553
557
None => {
554
558
// For the moment capturing variables from format strings expanded from macros is
@@ -565,7 +569,7 @@ impl<'a, 'b> Context<'a, 'b> {
565
569
} ;
566
570
self . num_captured_args += 1 ;
567
571
self . args . push ( self . ecx . expr_ident ( span, Ident :: new ( name, span) ) ) ;
568
- self . names . insert ( name, idx) ;
572
+ self . names . insert ( name, ( idx, span ) ) ;
569
573
self . verify_arg_type ( Capture ( idx) , ty)
570
574
} else {
571
575
let msg = format ! ( "there is no argument named `{}`" , name) ;
@@ -967,14 +971,57 @@ pub fn expand_format_args_nl<'cx>(
967
971
expand_format_args_impl ( ecx, sp, tts, true )
968
972
}
969
973
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
+ match a. format {
989
+ FormatSpec { width : Count :: CountIsName ( s, _) , .. }
990
+ | FormatSpec { precision : Count :: CountIsName ( s, _) , .. } => {
991
+ used_argument_names. insert ( s) ;
992
+ }
993
+ _ => { }
994
+ } ;
995
+ }
996
+ }
997
+
998
+ for ( symbol, ( index, span) ) in names {
999
+ if !used_argument_names. contains ( symbol. as_str ( ) ) {
1000
+ let msg = format ! ( "named argument `{}` is not used by name" , symbol. as_str( ) ) ;
1001
+ let arg_span = cx. arg_spans [ index] ;
1002
+ cx. ecx . buffered_early_lint . push ( BufferedEarlyLint {
1003
+ span : MultiSpan :: from_span ( span) ,
1004
+ msg : msg. clone ( ) ,
1005
+ node_id : ast:: CRATE_NODE_ID ,
1006
+ lint_id : LintId :: of ( & NAMED_ARGUMENTS_USED_POSITIONALLY ) ,
1007
+ diagnostic : BuiltinLintDiagnostics :: NamedArgumentUsedPositionally (
1008
+ arg_span,
1009
+ span,
1010
+ symbol. to_string ( ) ,
1011
+ ) ,
1012
+ } ) ;
1013
+ }
1014
+ }
1015
+ }
1016
+
970
1017
/// Take the various parts of `format_args!(efmt, args..., name=names...)`
971
1018
/// and construct the appropriate formatting expression.
972
1019
pub fn expand_preparsed_format_args (
973
1020
ecx : & mut ExtCtxt < ' _ > ,
974
1021
sp : Span ,
975
1022
efmt : P < ast:: Expr > ,
976
1023
args : Vec < P < ast:: Expr > > ,
977
- names : FxHashMap < Symbol , usize > ,
1024
+ names : FxHashMap < Symbol , ( usize , Span ) > ,
978
1025
append_newline : bool ,
979
1026
) -> P < ast:: Expr > {
980
1027
// NOTE: this verbose way of initializing `Vec<Vec<ArgumentType>>` is because
@@ -1073,7 +1120,12 @@ pub fn expand_preparsed_format_args(
1073
1120
. map ( |span| fmt_span. from_inner ( InnerSpan :: new ( span. start , span. end ) ) )
1074
1121
. collect ( ) ;
1075
1122
1076
- let named_pos: FxHashSet < usize > = names. values ( ) . cloned ( ) . collect ( ) ;
1123
+ let named_pos: FxHashSet < usize > = names. values ( ) . cloned ( ) . map ( |( i, _) | i) . collect ( ) ;
1124
+
1125
+ // Clone `names` because `names` in Context get updated by verify_piece, which includes usages
1126
+ // of the names of named arguments, resulting in incorrect errors if a name argument is used
1127
+ // but not declared, such as: `println!("x = {x}");`
1128
+ let named_arguments = names. clone ( ) ;
1077
1129
1078
1130
let mut cx = Context {
1079
1131
ecx,
@@ -1101,9 +1153,11 @@ pub fn expand_preparsed_format_args(
1101
1153
is_literal : parser. is_literal ,
1102
1154
} ;
1103
1155
1104
- // This needs to happen *after* the Parser has consumed all pieces to create all the spans
1156
+ // This needs to happen *after* the Parser has consumed all pieces to create all the spans.
1157
+ // unverified_pieces is used later to check named argument names are used, so clone each piece.
1105
1158
let pieces = unverified_pieces
1106
- . into_iter ( )
1159
+ . iter ( )
1160
+ . cloned ( )
1107
1161
. map ( |mut piece| {
1108
1162
cx. verify_piece ( & piece) ;
1109
1163
cx. resolve_name_inplace ( & mut piece) ;
@@ -1265,6 +1319,11 @@ pub fn expand_preparsed_format_args(
1265
1319
}
1266
1320
1267
1321
diag. emit ( ) ;
1322
+ } else if cx. invalid_refs . is_empty ( ) && !named_arguments. is_empty ( ) {
1323
+ // Only check for unused named argument names if there are no other errors to avoid causing
1324
+ // too much noise in output errors, such as when a named argument is entirely unused.
1325
+ // We also only need to perform this check if there are actually named arguments.
1326
+ lint_named_arguments_used_positionally ( named_arguments, & mut cx, unverified_pieces) ;
1268
1327
}
1269
1328
1270
1329
cx. into_expr ( )
0 commit comments