Skip to content

Commit 5ad10c8

Browse files
committed
Fix #7903 and to_string_in_format_args false negative
1 parent 310ecb0 commit 5ad10c8

File tree

5 files changed

+131
-39
lines changed

5 files changed

+131
-39
lines changed

clippy_lints/src/format_args.rs

+32-16
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,9 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
9595
if let Some(args) = format_args.args();
9696
then {
9797
for (i, arg) in args.iter().enumerate() {
98-
if !arg.is_display() {
99-
continue;
100-
}
101-
if arg.has_string_formatting() {
102-
continue;
103-
}
104-
if is_aliased(&args, i) {
105-
continue;
106-
}
107-
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg);
108-
check_to_string_in_format_args(cx, name, arg);
98+
let aliases = aliases(&args, i);
99+
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg, &aliases);
100+
check_to_string_in_format_args(cx, name, arg, &aliases);
109101
}
110102
}
111103
}
@@ -120,8 +112,17 @@ fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
120112
}
121113
}
122114

123-
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &FormatArgsArg<'_>) {
115+
fn check_format_in_format_args(
116+
cx: &LateContext<'_>,
117+
call_site: Span,
118+
name: Symbol,
119+
arg: &FormatArgsArg<'_>,
120+
aliases: &[&FormatArgsArg<'_>],
121+
) {
124122
if_chain! {
123+
if arg.is_display();
124+
if !arg.has_string_formatting();
125+
if aliases.is_empty();
125126
if FormatExpn::parse(arg.value).is_some();
126127
if !arg.value.span.ctxt().outer_expn_data().call_site.from_expansion();
127128
then {
@@ -142,9 +143,17 @@ fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symb
142143
}
143144
}
144145

145-
fn check_to_string_in_format_args<'tcx>(cx: &LateContext<'tcx>, name: Symbol, arg: &FormatArgsArg<'tcx>) {
146+
fn check_to_string_in_format_args<'tcx>(
147+
cx: &LateContext<'tcx>,
148+
name: Symbol,
149+
arg: &FormatArgsArg<'tcx>,
150+
aliases: &[&FormatArgsArg<'tcx>],
151+
) {
146152
let value = arg.value;
147153
if_chain! {
154+
if std::iter::once(&arg)
155+
.chain(aliases)
156+
.all(|arg| arg.is_display() && !arg.has_string_formatting());
148157
if !value.span.from_expansion();
149158
if let ExprKind::MethodCall(_, _, [receiver], _) = value.kind;
150159
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
@@ -184,12 +193,19 @@ fn check_to_string_in_format_args<'tcx>(cx: &LateContext<'tcx>, name: Symbol, ar
184193
}
185194
}
186195

187-
// Returns true if `args[i]` "refers to" or "is referred to by" another argument.
188-
fn is_aliased(args: &[FormatArgsArg<'_>], i: usize) -> bool {
196+
// Returns the `args[j]` that refer to value of `args[i]`, but that are not `args[i]`.
197+
fn aliases<'a, 'tcx>(args: &'a [FormatArgsArg<'tcx>], i: usize) -> Vec<&'a FormatArgsArg<'tcx>> {
189198
let value = args[i].value;
190199
args.iter()
191200
.enumerate()
192-
.any(|(j, arg)| i != j && std::ptr::eq(value, arg.value))
201+
.filter_map(|(j, arg)| {
202+
if i != j && std::ptr::eq(value, arg.value) {
203+
Some(arg)
204+
} else {
205+
None
206+
}
207+
})
208+
.collect()
193209
}
194210

195211
fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {

clippy_utils/src/higher.rs

+25-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use if_chain::if_chain;
88
use rustc_ast::ast::{self, LitKind};
99
use rustc_hir as hir;
1010
use rustc_hir::{
11-
Arm, Block, BorrowKind, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, QPath, StmtKind, UnOp,
11+
Arm, Block, BorrowKind, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, PatKind, QPath, StmtKind, UnOp,
1212
};
1313
use rustc_lint::LateContext;
1414
use rustc_span::{sym, symbol, ExpnKind, Span, Symbol};
@@ -513,6 +513,8 @@ pub struct FormatArgsExpn<'tcx> {
513513
pub format_string_parts: &'tcx [Expr<'tcx>],
514514
/// Symbols corresponding to [`Self::format_string_parts`]
515515
pub format_string_symbols: Vec<Symbol>,
516+
/// Match arm patterns, the `arg0`, etc. from the next field `args`
517+
pub arg_names: &'tcx [Pat<'tcx>],
516518
/// Expressions like `ArgumentV1::new(arg0, Debug::fmt)`
517519
pub args: &'tcx [Expr<'tcx>],
518520
/// The final argument passed to `Arguments::new_v1_formatted`, if applicable
@@ -557,13 +559,15 @@ impl FormatArgsExpn<'tcx> {
557559
_ => None,
558560
})
559561
.collect();
562+
if let PatKind::Tuple(arg_names, None) = arm.pat.kind;
560563
if let ExprKind::Array(args) = arm.body.kind;
561564
then {
562565
Some(FormatArgsExpn {
563566
format_string_span: strs_ref.span,
564567
value_args,
565568
format_string_parts,
566569
format_string_symbols,
570+
arg_names,
567571
args,
568572
fmt_expr,
569573
})
@@ -587,9 +591,12 @@ impl FormatArgsExpn<'tcx> {
587591
if let Some(position_field) = fields.iter().find(|f| f.ident.name == sym::position);
588592
if let ExprKind::Lit(lit) = &position_field.expr.kind;
589593
if let LitKind::Int(position, _) = lit.node;
594+
if let Ok(i) = usize::try_from(position);
595+
let arg = &self.args[i];
596+
if let ExprKind::Call(_, [arg_name, _]) = arg.kind;
597+
if let Some(j) = self.arg_names.iter().position(|pat| match_arg(pat, arg_name));
590598
then {
591-
let i = usize::try_from(position).unwrap();
592-
Some(FormatArgsArg { value: self.value_args[i], arg: &self.args[i], fmt: Some(fmt) })
599+
Some(FormatArgsArg { value: self.value_args[j], arg, fmt: Some(fmt) })
593600
} else {
594601
None
595602
}
@@ -611,6 +618,21 @@ impl FormatArgsExpn<'tcx> {
611618
}
612619
}
613620

621+
/// Returns true if `pat` and `arg_name` are the same ident.
622+
fn match_arg(pat: &Pat<'_>, arg_name: &Expr<'_>) -> bool {
623+
if_chain! {
624+
if let PatKind::Binding(_, _, ident, _) = pat.kind;
625+
if let ExprKind::Path(QPath::Resolved(_, path)) = &arg_name.kind;
626+
if let [segment] = path.segments;
627+
if ident == segment.ident;
628+
then {
629+
true
630+
} else {
631+
false
632+
}
633+
}
634+
}
635+
614636
/// Type representing a `FormatArgsExpn`'s format arguments
615637
pub struct FormatArgsArg<'tcx> {
616638
/// An element of `value_args` according to `position`

tests/ui/format_args.fixed

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#![allow(unused_variables)]
66
#![allow(clippy::assertions_on_constants)]
77
#![allow(clippy::eq_op)]
8+
#![allow(clippy::print_literal)]
89
#![warn(clippy::to_string_in_format_args)]
910

1011
use std::io::{stdout, Write};
@@ -97,9 +98,17 @@ fn main() {
9798
println!("{}", Z(1));
9899
println!("{}", **x);
99100
println!("{}", ***x_ref);
101+
println!("{} and again {0}", **x);
102+
// https://github.com/rust-lang/rust-clippy/issues/7903
103+
println!("{foo}{foo}", foo = "foo");
104+
println!("{foo}{bar}", foo = "foo", bar = "bar");
105+
println!("{foo}{bar}", foo = "foo", bar = "bar");
106+
println!("{foo}{bar}", bar = "bar", foo = "foo");
107+
println!("{foo}{bar}", bar = "bar", foo = "foo");
100108

101109
println!("error: something failed at {}", Somewhere.to_string());
102-
println!("{} and again {0}", x.to_string());
103110
my_macro!();
104111
println!("error: something failed at {}", my_other_macro!());
112+
// https://github.com/rust-lang/rust-clippy/issues/7903
113+
println!("{foo}{foo:?}", foo = "foo".to_string());
105114
}

tests/ui/format_args.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#![allow(unused_variables)]
66
#![allow(clippy::assertions_on_constants)]
77
#![allow(clippy::eq_op)]
8+
#![allow(clippy::print_literal)]
89
#![warn(clippy::to_string_in_format_args)]
910

1011
use std::io::{stdout, Write};
@@ -97,9 +98,17 @@ fn main() {
9798
println!("{}", Z(1).to_string());
9899
println!("{}", x.to_string());
99100
println!("{}", x_ref.to_string());
101+
println!("{} and again {0}", x.to_string());
102+
// https://github.com/rust-lang/rust-clippy/issues/7903
103+
println!("{foo}{foo}", foo = "foo".to_string());
104+
println!("{foo}{bar}", foo = "foo".to_string(), bar = "bar");
105+
println!("{foo}{bar}", foo = "foo", bar = "bar".to_string());
106+
println!("{foo}{bar}", bar = "bar".to_string(), foo = "foo");
107+
println!("{foo}{bar}", bar = "bar", foo = "foo".to_string());
100108

101109
println!("error: something failed at {}", Somewhere.to_string());
102-
println!("{} and again {0}", x.to_string());
103110
my_macro!();
104111
println!("error: something failed at {}", my_other_macro!());
112+
// https://github.com/rust-lang/rust-clippy/issues/7903
113+
println!("{foo}{foo:?}", foo = "foo".to_string());
105114
}

tests/ui/format_args.stderr

+54-18
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,142 @@
11
error: `to_string` applied to a type that implements `Display` in `format!` args
2-
--> $DIR/format_args.rs:75:72
2+
--> $DIR/format_args.rs:76:72
33
|
44
LL | let _ = format!("error: something failed at {}", Location::caller().to_string());
55
| ^^^^^^^^^^^^ help: remove this
66
|
77
= note: `-D clippy::to-string-in-format-args` implied by `-D warnings`
88

99
error: `to_string` applied to a type that implements `Display` in `write!` args
10-
--> $DIR/format_args.rs:79:27
10+
--> $DIR/format_args.rs:80:27
1111
|
1212
LL | Location::caller().to_string()
1313
| ^^^^^^^^^^^^ help: remove this
1414

1515
error: `to_string` applied to a type that implements `Display` in `writeln!` args
16-
--> $DIR/format_args.rs:84:27
16+
--> $DIR/format_args.rs:85:27
1717
|
1818
LL | Location::caller().to_string()
1919
| ^^^^^^^^^^^^ help: remove this
2020

2121
error: `to_string` applied to a type that implements `Display` in `print!` args
22-
--> $DIR/format_args.rs:86:63
22+
--> $DIR/format_args.rs:87:63
2323
|
2424
LL | print!("error: something failed at {}", Location::caller().to_string());
2525
| ^^^^^^^^^^^^ help: remove this
2626

2727
error: `to_string` applied to a type that implements `Display` in `println!` args
28-
--> $DIR/format_args.rs:87:65
28+
--> $DIR/format_args.rs:88:65
2929
|
3030
LL | println!("error: something failed at {}", Location::caller().to_string());
3131
| ^^^^^^^^^^^^ help: remove this
3232

3333
error: `to_string` applied to a type that implements `Display` in `eprint!` args
34-
--> $DIR/format_args.rs:88:64
34+
--> $DIR/format_args.rs:89:64
3535
|
3636
LL | eprint!("error: something failed at {}", Location::caller().to_string());
3737
| ^^^^^^^^^^^^ help: remove this
3838

3939
error: `to_string` applied to a type that implements `Display` in `eprintln!` args
40-
--> $DIR/format_args.rs:89:66
40+
--> $DIR/format_args.rs:90:66
4141
|
4242
LL | eprintln!("error: something failed at {}", Location::caller().to_string());
4343
| ^^^^^^^^^^^^ help: remove this
4444

4545
error: `to_string` applied to a type that implements `Display` in `format_args!` args
46-
--> $DIR/format_args.rs:90:77
46+
--> $DIR/format_args.rs:91:77
4747
|
4848
LL | let _ = format_args!("error: something failed at {}", Location::caller().to_string());
4949
| ^^^^^^^^^^^^ help: remove this
5050

5151
error: `to_string` applied to a type that implements `Display` in `assert!` args
52-
--> $DIR/format_args.rs:91:70
52+
--> $DIR/format_args.rs:92:70
5353
|
5454
LL | assert!(true, "error: something failed at {}", Location::caller().to_string());
5555
| ^^^^^^^^^^^^ help: remove this
5656

5757
error: `to_string` applied to a type that implements `Display` in `assert_eq!` args
58-
--> $DIR/format_args.rs:92:73
58+
--> $DIR/format_args.rs:93:73
5959
|
6060
LL | assert_eq!(0, 0, "error: something failed at {}", Location::caller().to_string());
6161
| ^^^^^^^^^^^^ help: remove this
6262

6363
error: `to_string` applied to a type that implements `Display` in `assert_ne!` args
64-
--> $DIR/format_args.rs:93:73
64+
--> $DIR/format_args.rs:94:73
6565
|
6666
LL | assert_ne!(0, 0, "error: something failed at {}", Location::caller().to_string());
6767
| ^^^^^^^^^^^^ help: remove this
6868

6969
error: `to_string` applied to a type that implements `Display` in `panic!` args
70-
--> $DIR/format_args.rs:94:63
70+
--> $DIR/format_args.rs:95:63
7171
|
7272
LL | panic!("error: something failed at {}", Location::caller().to_string());
7373
| ^^^^^^^^^^^^ help: remove this
7474

7575
error: `to_string` applied to a type that implements `Display` in `println!` args
76-
--> $DIR/format_args.rs:95:20
76+
--> $DIR/format_args.rs:96:20
7777
|
7878
LL | println!("{}", X(1).to_string());
7979
| ^^^^^^^^^^^^^^^^ help: use this: `*X(1)`
8080

8181
error: `to_string` applied to a type that implements `Display` in `println!` args
82-
--> $DIR/format_args.rs:96:20
82+
--> $DIR/format_args.rs:97:20
8383
|
8484
LL | println!("{}", Y(&X(1)).to_string());
8585
| ^^^^^^^^^^^^^^^^^^^^ help: use this: `***Y(&X(1))`
8686

8787
error: `to_string` applied to a type that implements `Display` in `println!` args
88-
--> $DIR/format_args.rs:97:24
88+
--> $DIR/format_args.rs:98:24
8989
|
9090
LL | println!("{}", Z(1).to_string());
9191
| ^^^^^^^^^^^^ help: remove this
9292

9393
error: `to_string` applied to a type that implements `Display` in `println!` args
94-
--> $DIR/format_args.rs:98:20
94+
--> $DIR/format_args.rs:99:20
9595
|
9696
LL | println!("{}", x.to_string());
9797
| ^^^^^^^^^^^^^ help: use this: `**x`
9898

9999
error: `to_string` applied to a type that implements `Display` in `println!` args
100-
--> $DIR/format_args.rs:99:20
100+
--> $DIR/format_args.rs:100:20
101101
|
102102
LL | println!("{}", x_ref.to_string());
103103
| ^^^^^^^^^^^^^^^^^ help: use this: `***x_ref`
104104

105-
error: aborting due to 17 previous errors
105+
error: `to_string` applied to a type that implements `Display` in `println!` args
106+
--> $DIR/format_args.rs:101:34
107+
|
108+
LL | println!("{} and again {0}", x.to_string());
109+
| ^^^^^^^^^^^^^ help: use this: `**x`
110+
111+
error: `to_string` applied to a type that implements `Display` in `println!` args
112+
--> $DIR/format_args.rs:103:39
113+
|
114+
LL | println!("{foo}{foo}", foo = "foo".to_string());
115+
| ^^^^^^^^^^^^ help: remove this
116+
117+
error: `to_string` applied to a type that implements `Display` in `println!` args
118+
--> $DIR/format_args.rs:104:39
119+
|
120+
LL | println!("{foo}{bar}", foo = "foo".to_string(), bar = "bar");
121+
| ^^^^^^^^^^^^ help: remove this
122+
123+
error: `to_string` applied to a type that implements `Display` in `println!` args
124+
--> $DIR/format_args.rs:105:52
125+
|
126+
LL | println!("{foo}{bar}", foo = "foo", bar = "bar".to_string());
127+
| ^^^^^^^^^^^^ help: remove this
128+
129+
error: `to_string` applied to a type that implements `Display` in `println!` args
130+
--> $DIR/format_args.rs:106:39
131+
|
132+
LL | println!("{foo}{bar}", bar = "bar".to_string(), foo = "foo");
133+
| ^^^^^^^^^^^^ help: remove this
134+
135+
error: `to_string` applied to a type that implements `Display` in `println!` args
136+
--> $DIR/format_args.rs:107:52
137+
|
138+
LL | println!("{foo}{bar}", bar = "bar", foo = "foo".to_string());
139+
| ^^^^^^^^^^^^ help: remove this
140+
141+
error: aborting due to 23 previous errors
106142

0 commit comments

Comments
 (0)