Skip to content

Commit de5ba31

Browse files
committed
Properly handle positional arguments
1 parent 1d8e177 commit de5ba31

File tree

8 files changed

+262
-94
lines changed

8 files changed

+262
-94
lines changed

clippy_lints/src/format.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::format::{check_unformatted, is_display_arg};
3-
use clippy_utils::higher::FormatExpn;
2+
use clippy_utils::higher::{FormatExpn, Formatting};
43
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
54
use clippy_utils::sugg::Sugg;
65
use if_chain::if_chain;
@@ -69,8 +68,8 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
6968
ty::Str => true,
7069
_ => false,
7170
};
72-
if format_args.args.iter().all(is_display_arg);
73-
if format_args.fmt_expr.map_or(true, check_unformatted);
71+
if format_args.args().all(|arg| arg.is_display());
72+
if !format_args.has_formatting(Formatting::PRECISION | Formatting::WIDTH);
7473
then {
7574
let is_new_string = match value.kind {
7675
ExprKind::Binary(..) => true,

clippy_lints/src/format_args.rs

Lines changed: 60 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::format::{check_unformatted, is_display_arg};
3-
use clippy_utils::higher::{FormatArgsExpn, FormatExpn};
1+
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
2+
use clippy_utils::higher::{FormatArgsArg, FormatArgsExpn, FormatExpn, Formatting};
43
use clippy_utils::source::snippet_opt;
54
use clippy_utils::ty::implements_trait;
65
use clippy_utils::{get_trait_def_id, match_def_path, paths};
@@ -78,22 +77,21 @@ impl<'tcx> LateLintPass<'tcx> for ToStringInFormatArgs {
7877

7978
fn check_expr<'tcx, F>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, check_value: F)
8079
where
81-
F: Fn(&LateContext<'_>, &FormatArgsExpn<'_>, Span, Symbol, usize, &Expr<'_>) -> bool,
80+
F: Fn(&LateContext<'_>, &FormatArgsExpn<'_>, Span, Symbol, usize, &FormatArgsArg<'_>) -> bool,
8281
{
8382
if_chain! {
8483
if let Some(format_args) = FormatArgsExpn::parse(expr);
8584
let call_site = expr.span.ctxt().outer_expn_data().call_site;
8685
if call_site.from_expansion();
8786
let expn_data = call_site.ctxt().outer_expn_data();
8887
if let ExpnKind::Macro(_, name) = expn_data.kind;
89-
if format_args.fmt_expr.map_or(true, check_unformatted);
88+
if !format_args.has_formatting(Formatting::all());
9089
then {
91-
assert_eq!(format_args.args.len(), format_args.value_args.len());
92-
for (i, (arg, value)) in format_args.args.iter().zip(format_args.value_args.iter()).enumerate() {
93-
if !is_display_arg(arg) {
90+
for (i, arg) in format_args.args().enumerate() {
91+
if !arg.is_display() {
9492
continue;
9593
}
96-
if check_value(cx, &format_args, expn_data.call_site, name, i, value) {
94+
if check_value(cx, &format_args, expn_data.call_site, name, i, &arg) {
9795
break;
9896
}
9997
}
@@ -107,22 +105,14 @@ fn format_in_format_args(
107105
call_site: Span,
108106
name: Symbol,
109107
i: usize,
110-
value: &Expr<'_>,
108+
arg: &FormatArgsArg<'_>,
111109
) -> bool {
112-
if_chain! {
113-
if let Some(FormatExpn{ format_args: inner_format_args, .. }) = FormatExpn::parse(value);
114-
if let Some(format_string) = snippet_opt(cx, format_args.format_string_span);
115-
if let Some(inner_format_string) = snippet_opt(cx, inner_format_args.format_string_span);
116-
if let Some((sugg, applicability)) = format_in_format_args_sugg(
117-
cx,
118-
name,
119-
&format_string,
120-
&format_args.value_args,
121-
i,
122-
&inner_format_string,
123-
&inner_format_args.value_args
124-
);
125-
then {
110+
if let Some(FormatExpn {
111+
format_args: inner_format_args,
112+
..
113+
}) = FormatExpn::parse(arg.value())
114+
{
115+
if let Some((sugg, applicability)) = format_in_format_args_sugg(cx, name, format_args, i, &inner_format_args) {
126116
span_lint_and_sugg(
127117
cx,
128118
FORMAT_IN_FORMAT_ARGS,
@@ -132,9 +122,18 @@ fn format_in_format_args(
132122
sugg,
133123
applicability,
134124
);
135-
// Report only the first instance.
136-
return true;
125+
} else {
126+
span_lint_and_help(
127+
cx,
128+
FORMAT_IN_FORMAT_ARGS,
129+
trim_semicolon(cx, call_site),
130+
&format!("`format!` in `{}!` args", name),
131+
None,
132+
"inline the `format!` call",
133+
);
137134
}
135+
// Report only the first instance.
136+
return true;
138137
}
139138
false
140139
}
@@ -145,8 +144,9 @@ fn to_string_in_format_args(
145144
_call_site: Span,
146145
name: Symbol,
147146
_i: usize,
148-
value: &Expr<'_>,
147+
arg: &FormatArgsArg<'_>,
149148
) -> bool {
149+
let value = arg.value();
150150
if_chain! {
151151
if let ExprKind::MethodCall(_, _, args, _) = value.kind;
152152
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
@@ -178,27 +178,35 @@ fn to_string_in_format_args(
178178
fn format_in_format_args_sugg(
179179
cx: &LateContext<'_>,
180180
name: Symbol,
181-
format_string: &str,
182-
values: &[&Expr<'_>],
181+
format_args: &FormatArgsExpn<'_>,
183182
i: usize,
184-
inner_format_string: &str,
185-
inner_values: &[&Expr<'_>],
183+
inner_format_args: &FormatArgsExpn<'_>,
186184
) -> Option<(String, Applicability)> {
187-
let (left, right) = split_format_string(format_string, i);
185+
let format_string = snippet_opt(cx, format_args.format_string_span)?;
186+
if is_positional(&format_string) {
187+
return None;
188+
}
189+
let (left, right) = split_format_string(&format_string, i);
190+
let inner_format_string = snippet_opt(cx, inner_format_args.format_string_span)?;
191+
if is_positional(&inner_format_string) {
192+
return None;
193+
}
188194
// If the inner format string is raw, the user is on their own.
189195
let (new_format_string, applicability) = if inner_format_string.starts_with('r') {
190196
(left + ".." + &right, Applicability::HasPlaceholders)
191197
} else {
192198
(
193-
left + &trim_quotes(inner_format_string) + &right,
199+
left + &trim_quotes(&inner_format_string) + &right,
194200
Applicability::MachineApplicable,
195201
)
196202
};
197-
let values = values
203+
let values = format_args
204+
.value_args
198205
.iter()
199206
.map(|value| snippet_opt(cx, value.span))
200207
.collect::<Option<Vec<_>>>()?;
201-
let inner_values = inner_values
208+
let inner_values = inner_format_args
209+
.value_args
202210
.iter()
203211
.map(|value| snippet_opt(cx, value.span))
204212
.collect::<Option<Vec<_>>>()?;
@@ -216,6 +224,23 @@ fn format_in_format_args_sugg(
216224
))
217225
}
218226

227+
// Checking the position fields of the `core::fmt::rt::v1::Argument` would not be sufficient
228+
// because, for example, "{}{}" and "{}{1:}" could not be distinguished. Note that the `{}` could be
229+
// replaced in the former, but not the latter.
230+
fn is_positional(format_string: &str) -> bool {
231+
let mut iter = format_string.chars();
232+
while let Some(first_char) = iter.next() {
233+
if first_char != '{' {
234+
continue;
235+
}
236+
let second_char = iter.next().unwrap();
237+
if second_char.is_digit(10) {
238+
return true;
239+
}
240+
}
241+
false
242+
}
243+
219244
fn split_format_string(format_string: &str, i: usize) -> (String, String) {
220245
let mut iter = format_string.chars();
221246
for j in 0..=i {

clippy_utils/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edition = "2021"
55
publish = false
66

77
[dependencies]
8+
bitflags = "1.2"
89
if_chain = "1.0"
910
rustc-semver = "1.1"
1011

clippy_utils/src/format.rs

Lines changed: 0 additions & 49 deletions
This file was deleted.

0 commit comments

Comments
 (0)