@@ -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