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 } ;
4
3
use clippy_utils:: source:: snippet_opt;
5
4
use clippy_utils:: ty:: implements_trait;
6
5
use clippy_utils:: { get_trait_def_id, match_def_path, paths} ;
@@ -78,22 +77,21 @@ impl<'tcx> LateLintPass<'tcx> for ToStringInFormatArgs {
78
77
79
78
fn check_expr < ' tcx , F > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > , check_value : F )
80
79
where
81
- F : Fn ( & LateContext < ' _ > , & FormatArgsExpn < ' _ > , Span , Symbol , usize , & Expr < ' _ > ) -> bool ,
80
+ F : Fn ( & LateContext < ' _ > , & FormatArgsExpn < ' _ > , Span , Symbol , usize , & FormatArgsArg < ' _ > ) -> bool ,
82
81
{
83
82
if_chain ! {
84
83
if let Some ( format_args) = FormatArgsExpn :: parse( expr) ;
85
84
let call_site = expr. span. ctxt( ) . outer_expn_data( ) . call_site;
86
85
if call_site. from_expansion( ) ;
87
86
let expn_data = call_site. ctxt( ) . outer_expn_data( ) ;
88
87
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 ( ) ) ;
90
89
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( ) {
94
92
continue ;
95
93
}
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 ) {
97
95
break ;
98
96
}
99
97
}
@@ -107,22 +105,14 @@ fn format_in_format_args(
107
105
call_site : Span ,
108
106
name : Symbol ,
109
107
i : usize ,
110
- value : & Expr < ' _ > ,
108
+ arg : & FormatArgsArg < ' _ > ,
111
109
) -> 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) {
126
116
span_lint_and_sugg (
127
117
cx,
128
118
FORMAT_IN_FORMAT_ARGS ,
@@ -132,9 +122,18 @@ fn format_in_format_args(
132
122
sugg,
133
123
applicability,
134
124
) ;
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
+ ) ;
137
134
}
135
+ // Report only the first instance.
136
+ return true ;
138
137
}
139
138
false
140
139
}
@@ -145,8 +144,9 @@ fn to_string_in_format_args(
145
144
_call_site : Span ,
146
145
name : Symbol ,
147
146
_i : usize ,
148
- value : & Expr < ' _ > ,
147
+ arg : & FormatArgsArg < ' _ > ,
149
148
) -> bool {
149
+ let value = arg. value ( ) ;
150
150
if_chain ! {
151
151
if let ExprKind :: MethodCall ( _, _, args, _) = value. kind;
152
152
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(
178
178
fn format_in_format_args_sugg (
179
179
cx : & LateContext < ' _ > ,
180
180
name : Symbol ,
181
- format_string : & str ,
182
- values : & [ & Expr < ' _ > ] ,
181
+ format_args : & FormatArgsExpn < ' _ > ,
183
182
i : usize ,
184
- inner_format_string : & str ,
185
- inner_values : & [ & Expr < ' _ > ] ,
183
+ inner_format_args : & FormatArgsExpn < ' _ > ,
186
184
) -> 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
+ }
188
194
// If the inner format string is raw, the user is on their own.
189
195
let ( new_format_string, applicability) = if inner_format_string. starts_with ( 'r' ) {
190
196
( left + ".." + & right, Applicability :: HasPlaceholders )
191
197
} else {
192
198
(
193
- left + & trim_quotes ( inner_format_string) + & right,
199
+ left + & trim_quotes ( & inner_format_string) + & right,
194
200
Applicability :: MachineApplicable ,
195
201
)
196
202
} ;
197
- let values = values
203
+ let values = format_args
204
+ . value_args
198
205
. iter ( )
199
206
. map ( |value| snippet_opt ( cx, value. span ) )
200
207
. collect :: < Option < Vec < _ > > > ( ) ?;
201
- let inner_values = inner_values
208
+ let inner_values = inner_format_args
209
+ . value_args
202
210
. iter ( )
203
211
. map ( |value| snippet_opt ( cx, value. span ) )
204
212
. collect :: < Option < Vec < _ > > > ( ) ?;
@@ -216,6 +224,23 @@ fn format_in_format_args_sugg(
216
224
) )
217
225
}
218
226
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
+
219
244
fn split_format_string ( format_string : & str , i : usize ) -> ( String , String ) {
220
245
let mut iter = format_string. chars ( ) ;
221
246
for j in 0 ..=i {
0 commit comments