Skip to content

Commit 93df9e6

Browse files
committed
Suggest removing redundant arguments in format!()
1 parent 90f3a6f commit 93df9e6

File tree

3 files changed

+180
-4
lines changed

3 files changed

+180
-4
lines changed

compiler/rustc_builtin_macros/src/format.rs

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use parse::Position::ArgumentNamed;
12
use rustc_ast::ptr::P;
23
use rustc_ast::tokenstream::TokenStream;
34
use rustc_ast::{token, StmtKind};
@@ -6,7 +7,7 @@ use rustc_ast::{
67
FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount,
78
FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait,
89
};
9-
use rustc_data_structures::fx::FxHashSet;
10+
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
1011
use rustc_errors::{Applicability, MultiSpan, PResult, SingleLabelManySpans};
1112
use rustc_expand::base::{self, *};
1213
use rustc_parse_format as parse;
@@ -357,8 +358,8 @@ fn make_format_args(
357358
let mut unfinished_literal = String::new();
358359
let mut placeholder_index = 0;
359360

360-
for piece in pieces {
361-
match piece {
361+
for piece in &pieces {
362+
match *piece {
362363
parse::Piece::String(s) => {
363364
unfinished_literal.push_str(s);
364365
}
@@ -506,7 +507,17 @@ fn make_format_args(
506507
// If there's a lot of unused arguments,
507508
// let's check if this format arguments looks like another syntax (printf / shell).
508509
let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2;
509-
report_missing_placeholders(ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span);
510+
report_missing_placeholders(
511+
ecx,
512+
unused,
513+
&used,
514+
&args,
515+
&pieces,
516+
detect_foreign_fmt,
517+
str_style,
518+
fmt_str,
519+
fmt_span,
520+
);
510521
}
511522

512523
// Only check for unused named argument names if there are no other errors to avoid causing
@@ -573,6 +584,9 @@ fn invalid_placeholder_type_error(
573584
fn report_missing_placeholders(
574585
ecx: &mut ExtCtxt<'_>,
575586
unused: Vec<(Span, bool)>,
587+
used: &[bool],
588+
args: &FormatArguments,
589+
pieces: &[parse::Piece<'_>],
576590
detect_foreign_fmt: bool,
577591
str_style: Option<usize>,
578592
fmt_str: &str,
@@ -591,6 +605,68 @@ fn report_missing_placeholders(
591605
})
592606
};
593607

608+
let placeholders = pieces
609+
.iter()
610+
.filter_map(|piece| {
611+
if let parse::Piece::NextArgument(argument) = piece && let ArgumentNamed(binding) = argument.position {
612+
let span = fmt_span.from_inner(InnerSpan::new(argument.position_span.start, argument.position_span.end));
613+
Some((span, binding))
614+
} else { None }
615+
})
616+
.collect::<Vec<_>>();
617+
618+
let mut args_spans = vec![];
619+
let mut fmt_spans = FxIndexSet::default();
620+
621+
for (i, unnamed_arg) in args.unnamed_args().iter().enumerate().rev() {
622+
let Some(ty) = unnamed_arg.expr.to_ty() else { continue };
623+
let Some(argument_binding) = ty.kind.is_simple_path() else { continue };
624+
let argument_binding = argument_binding.as_str();
625+
626+
if used[i] {
627+
continue;
628+
}
629+
630+
let matching_placeholders = placeholders
631+
.iter()
632+
.filter(|(_, inline_binding)| argument_binding == *inline_binding)
633+
.collect::<Vec<_>>();
634+
635+
if !matching_placeholders.is_empty() {
636+
args_spans.push(unnamed_arg.expr.span);
637+
for placeholder in &matching_placeholders {
638+
fmt_spans.insert(*placeholder);
639+
}
640+
}
641+
}
642+
643+
if !args_spans.is_empty() {
644+
let mut multispan = MultiSpan::from(args_spans.clone());
645+
646+
let msg = if fmt_spans.len() > 1 {
647+
"the formatting strings already captures the bindings \
648+
directly, they don't need to be included in the argument list"
649+
} else {
650+
"the formatting string already captures the binding \
651+
directly, it doesn't need to be included in the argument list"
652+
};
653+
654+
for (span, binding) in fmt_spans {
655+
multispan.push_span_label(
656+
*span,
657+
format!("this formatting specifier is referencing the `{binding}` binding"),
658+
);
659+
}
660+
661+
for span in &args_spans {
662+
multispan.push_span_label(*span, "this can be removed");
663+
}
664+
665+
diag.span_help(multispan, msg);
666+
diag.emit();
667+
return;
668+
}
669+
594670
// Used to ensure we only report translations for *one* kind of foreign format.
595671
let mut found_foreign = false;
596672

tests/ui/did_you_mean/issue-105225.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
fn main() {
2+
let x = 0;
3+
println!("{x}", x);
4+
//~^ ERROR: argument never used
5+
6+
println!("{x} {}", x, x);
7+
//~^ ERROR: argument never used
8+
9+
println!("{} {x}", x, x);
10+
//~^ ERROR: argument never used
11+
12+
let y = 0;
13+
println!("{x} {y}", x, y);
14+
//~^ ERROR: multiple unused formatting arguments
15+
16+
let y = 0;
17+
println!("{} {} {x} {y} {}", x, x, x, y, y);
18+
//~^ ERROR: multiple unused formatting arguments
19+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
error: argument never used
2+
--> $DIR/issue-105225.rs:3:21
3+
|
4+
LL | println!("{x}", x);
5+
| ^ argument never used
6+
|
7+
help: the formatting string already captures the binding directly, it doesn't need to be included in the argument list
8+
--> $DIR/issue-105225.rs:3:21
9+
|
10+
LL | println!("{x}", x);
11+
| - ^ this can be removed
12+
| |
13+
| this formatting specifier is referencing the `x` binding
14+
15+
error: argument never used
16+
--> $DIR/issue-105225.rs:6:27
17+
|
18+
LL | println!("{x} {}", x, x);
19+
| ^ argument never used
20+
|
21+
help: the formatting string already captures the binding directly, it doesn't need to be included in the argument list
22+
--> $DIR/issue-105225.rs:6:27
23+
|
24+
LL | println!("{x} {}", x, x);
25+
| - ^ this can be removed
26+
| |
27+
| this formatting specifier is referencing the `x` binding
28+
29+
error: argument never used
30+
--> $DIR/issue-105225.rs:9:27
31+
|
32+
LL | println!("{} {x}", x, x);
33+
| ^ argument never used
34+
|
35+
help: the formatting string already captures the binding directly, it doesn't need to be included in the argument list
36+
--> $DIR/issue-105225.rs:9:27
37+
|
38+
LL | println!("{} {x}", x, x);
39+
| - ^ this can be removed
40+
| |
41+
| this formatting specifier is referencing the `x` binding
42+
43+
error: multiple unused formatting arguments
44+
--> $DIR/issue-105225.rs:13:25
45+
|
46+
LL | println!("{x} {y}", x, y);
47+
| --------- ^ ^ argument never used
48+
| | |
49+
| | argument never used
50+
| multiple missing formatting specifiers
51+
|
52+
help: the formatting strings already captures the bindings directly, they don't need to be included in the argument list
53+
--> $DIR/issue-105225.rs:13:25
54+
|
55+
LL | println!("{x} {y}", x, y);
56+
| - - ^ ^ this can be removed
57+
| | | |
58+
| | | this can be removed
59+
| | this formatting specifier is referencing the `y` binding
60+
| this formatting specifier is referencing the `x` binding
61+
62+
error: multiple unused formatting arguments
63+
--> $DIR/issue-105225.rs:17:43
64+
|
65+
LL | println!("{} {} {x} {y} {}", x, x, x, y, y);
66+
| ------------------ ^ ^ argument never used
67+
| | |
68+
| | argument never used
69+
| multiple missing formatting specifiers
70+
|
71+
help: the formatting string already captures the binding directly, it doesn't need to be included in the argument list
72+
--> $DIR/issue-105225.rs:17:43
73+
|
74+
LL | println!("{} {} {x} {y} {}", x, x, x, y, y);
75+
| - ^ ^ this can be removed
76+
| | |
77+
| | this can be removed
78+
| this formatting specifier is referencing the `y` binding
79+
80+
error: aborting due to 5 previous errors
81+

0 commit comments

Comments
 (0)