@@ -4,11 +4,15 @@ use clippy_utils::source::snippet_opt;
4
4
use clippy_utils:: ty:: implements_trait;
5
5
use clippy_utils:: { get_trait_def_id, match_def_path, paths} ;
6
6
use if_chain:: if_chain;
7
+ use rustc_ast:: ast:: LitKind ;
7
8
use rustc_errors:: Applicability ;
9
+ use rustc_hir:: def_id:: DefId ;
8
10
use rustc_hir:: { Expr , ExprKind } ;
9
11
use rustc_lint:: { LateContext , LateLintPass } ;
12
+ use rustc_middle:: ty:: subst:: GenericArg ;
13
+ use rustc_middle:: ty:: Ty ;
10
14
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
11
- use rustc_span:: { BytePos , ExpnKind , Span , Symbol } ;
15
+ use rustc_span:: { sym , BytePos , ExpnKind , Span , Symbol } ;
12
16
13
17
declare_clippy_lint ! {
14
18
/// ### What it does
@@ -34,14 +38,6 @@ declare_clippy_lint! {
34
38
"`format!` used in a macro that does formatting"
35
39
}
36
40
37
- declare_lint_pass ! ( FormatInFormatArgs => [ FORMAT_IN_FORMAT_ARGS ] ) ;
38
-
39
- impl < ' tcx > LateLintPass < ' tcx > for FormatInFormatArgs {
40
- fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
41
- check_expr ( cx, expr, format_in_format_args) ;
42
- }
43
- }
44
-
45
41
declare_clippy_lint ! {
46
42
/// ### What it does
47
43
/// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
@@ -67,216 +63,115 @@ declare_clippy_lint! {
67
63
"`to_string` applied to a type that implements `Display` in format args"
68
64
}
69
65
70
- declare_lint_pass ! ( ToStringInFormatArgs => [ TO_STRING_IN_FORMAT_ARGS ] ) ;
71
-
72
- impl < ' tcx > LateLintPass < ' tcx > for ToStringInFormatArgs {
73
- fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
74
- check_expr ( cx, expr, to_string_in_format_args) ;
75
- }
76
- }
77
-
78
- fn check_expr < ' tcx , F > ( cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > , check_value : F )
79
- where
80
- F : Fn ( & LateContext < ' _ > , & FormatArgsExpn < ' _ > , Span , Symbol , usize , & FormatArgsArg < ' _ > ) -> bool ,
81
- {
82
- if_chain ! {
83
- if let Some ( format_args) = FormatArgsExpn :: parse( expr) ;
84
- let call_site = expr. span. ctxt( ) . outer_expn_data( ) . call_site;
85
- if call_site. from_expansion( ) ;
86
- let expn_data = call_site. ctxt( ) . outer_expn_data( ) ;
87
- if let ExpnKind :: Macro ( _, name) = expn_data. kind;
88
- if !format_args. has_string_formatting( ) ;
89
- then {
90
- for ( i, arg) in format_args. args( ) . enumerate( ) {
91
- if !arg. is_display( ) {
92
- continue ;
66
+ declare_lint_pass ! ( FormatArgs => [ FORMAT_IN_FORMAT_ARGS , TO_STRING_IN_FORMAT_ARGS ] ) ;
67
+
68
+ impl < ' tcx > LateLintPass < ' tcx > for FormatArgs {
69
+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
70
+ if_chain ! {
71
+ if let Some ( format_args) = FormatArgsExpn :: parse( expr) ;
72
+ let expn_data = {
73
+ let expn_data = expr. span. ctxt( ) . outer_expn_data( ) ;
74
+ if expn_data. call_site. from_expansion( ) {
75
+ expn_data. call_site. ctxt( ) . outer_expn_data( )
76
+ } else {
77
+ expn_data
93
78
}
94
- if check_value( cx, & format_args, expn_data. call_site, name, i, & arg) {
95
- break ;
79
+ } ;
80
+ if let ExpnKind :: Macro ( _, name) = expn_data. kind;
81
+ if let Some ( args) = format_args. args( ) ;
82
+ if !args. iter( ) . any( FormatArgsArg :: has_string_formatting) ;
83
+ then {
84
+ for ( i, arg) in args. iter( ) . enumerate( ) {
85
+ if !arg. is_display( ) {
86
+ continue ;
87
+ }
88
+ if is_aliased( & args, i) {
89
+ continue ;
90
+ }
91
+ check_format_in_format_args( cx, expn_data. call_site, name, arg) ;
92
+ check_to_string_in_format_args( cx, expn_data. call_site, name, arg) ;
96
93
}
97
94
}
98
95
}
99
96
}
100
97
}
101
98
102
- fn format_in_format_args (
103
- cx : & LateContext < ' _ > ,
104
- format_args : & FormatArgsExpn < ' _ > ,
105
- call_site : Span ,
106
- name : Symbol ,
107
- i : usize ,
108
- arg : & FormatArgsArg < ' _ > ,
109
- ) -> bool {
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) {
116
- span_lint_and_sugg (
117
- cx,
118
- FORMAT_IN_FORMAT_ARGS ,
119
- trim_semicolon ( cx, call_site) ,
120
- & format ! ( "`format!` in `{}!` args" , name) ,
121
- "inline the `format!` call" ,
122
- sugg,
123
- applicability,
124
- ) ;
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
- ) ;
134
- }
135
- // Report only the first instance.
136
- return true ;
99
+ fn check_format_in_format_args ( cx : & LateContext < ' _ > , call_site : Span , name : Symbol , arg : & FormatArgsArg < ' _ , ' _ > ) {
100
+ if FormatExpn :: parse ( arg. value ) . is_some ( ) {
101
+ span_lint_and_help (
102
+ cx,
103
+ FORMAT_IN_FORMAT_ARGS ,
104
+ trim_semicolon ( cx, call_site) ,
105
+ & format ! ( "`format!` in `{}!` args" , name) ,
106
+ None ,
107
+ "inline the `format!` call" ,
108
+ ) ;
137
109
}
138
- false
139
110
}
140
111
141
- fn to_string_in_format_args (
142
- cx : & LateContext < ' _ > ,
143
- _format_args : & FormatArgsExpn < ' _ > ,
112
+ fn check_to_string_in_format_args < ' tcx > (
113
+ cx : & LateContext < ' tcx > ,
144
114
_call_site : Span ,
145
115
name : Symbol ,
146
- _i : usize ,
147
- arg : & FormatArgsArg < ' _ > ,
148
- ) -> bool {
149
- let value = arg. value ( ) ;
116
+ arg : & FormatArgsArg < ' _ , ' tcx > ,
117
+ ) {
118
+ let value = arg. value ;
150
119
if_chain ! {
151
120
if let ExprKind :: MethodCall ( _, _, args, _) = value. kind;
152
121
if let Some ( method_def_id) = cx. typeck_results( ) . type_dependent_def_id( value. hir_id) ;
153
122
if match_def_path( cx, method_def_id, & paths:: TO_STRING_METHOD ) ;
154
123
if let Some ( receiver) = args. get( 0 ) ;
155
124
let ty = cx. typeck_results( ) . expr_ty( receiver) ;
156
125
if let Some ( display_trait_id) = get_trait_def_id( cx, & paths:: DISPLAY_TRAIT ) ;
157
- if implements_trait( cx, ty, display_trait_id, & [ ] ) ;
158
126
if let Some ( snippet) = snippet_opt( cx, value. span) ;
159
127
if let Some ( dot) = snippet. rfind( '.' ) ;
160
128
then {
161
129
let span = value. span. with_lo(
162
130
value. span. lo( ) + BytePos ( u32 :: try_from( dot) . unwrap( ) )
163
131
) ;
164
- span_lint_and_sugg(
165
- cx,
166
- TO_STRING_IN_FORMAT_ARGS ,
167
- span,
168
- & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
169
- "remove this" ,
170
- String :: new( ) ,
171
- Applicability :: MachineApplicable ,
172
- ) ;
173
- }
174
- }
175
- false
176
- }
177
-
178
- fn format_in_format_args_sugg (
179
- cx : & LateContext < ' _ > ,
180
- name : Symbol ,
181
- format_args : & FormatArgsExpn < ' _ > ,
182
- i : usize ,
183
- inner_format_args : & FormatArgsExpn < ' _ > ,
184
- ) -> Option < ( String , Applicability ) > {
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
- }
194
- // If the inner format string is raw, the user is on their own.
195
- let ( new_format_string, applicability) = if inner_format_string. starts_with ( 'r' ) {
196
- ( left + ".." + & right, Applicability :: HasPlaceholders )
197
- } else {
198
- (
199
- left + & trim_quotes ( & inner_format_string) + & right,
200
- Applicability :: MachineApplicable ,
201
- )
202
- } ;
203
- let values = format_args
204
- . value_args
205
- . iter ( )
206
- . map ( |value| snippet_opt ( cx, value. span ) )
207
- . collect :: < Option < Vec < _ > > > ( ) ?;
208
- let inner_values = inner_format_args
209
- . value_args
210
- . iter ( )
211
- . map ( |value| snippet_opt ( cx, value. span ) )
212
- . collect :: < Option < Vec < _ > > > ( ) ?;
213
- let new_values = itertools:: join (
214
- values
215
- . iter ( )
216
- . take ( i)
217
- . chain ( inner_values. iter ( ) )
218
- . chain ( values. iter ( ) . skip ( i + 1 ) ) ,
219
- ", " ,
220
- ) ;
221
- Some ( (
222
- format ! ( r#"{}!({}, {})"# , name, new_format_string, new_values) ,
223
- applicability,
224
- ) )
225
- }
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 ;
132
+ if implements_trait( cx, ty, display_trait_id, & [ ] ) {
133
+ span_lint_and_sugg(
134
+ cx,
135
+ TO_STRING_IN_FORMAT_ARGS ,
136
+ span,
137
+ & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
138
+ "remove this" ,
139
+ String :: new( ) ,
140
+ Applicability :: MachineApplicable ,
141
+ ) ;
142
+ } else if implements_trait_via_deref( cx, ty, display_trait_id, & [ ] ) {
143
+ span_lint_and_sugg(
144
+ cx,
145
+ TO_STRING_IN_FORMAT_ARGS ,
146
+ span,
147
+ & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
148
+ "use this" ,
149
+ String :: from( ".deref()" ) ,
150
+ Applicability :: MachineApplicable ,
151
+ ) ;
152
+ }
239
153
}
240
154
}
241
- false
242
- }
243
-
244
- fn split_format_string ( format_string : & str , i : usize ) -> ( String , String ) {
245
- let mut iter = format_string. chars ( ) ;
246
- for j in 0 ..=i {
247
- assert ! ( advance( & mut iter) == '}' || j < i) ;
248
- }
249
-
250
- let right = iter. collect :: < String > ( ) ;
251
-
252
- let size = format_string. len ( ) ;
253
- let right_size = right. len ( ) ;
254
- let left_size = size - right_size;
255
- assert ! ( left_size >= 2 ) ;
256
- let left = std:: str:: from_utf8 ( & format_string. as_bytes ( ) [ ..left_size - 2 ] )
257
- . unwrap ( )
258
- . to_owned ( ) ;
259
- ( left, right)
260
155
}
261
156
262
- fn advance ( iter : & mut std:: str:: Chars < ' _ > ) -> char {
263
- loop {
264
- let first_char = iter. next ( ) . unwrap ( ) ;
265
- if first_char != '{' {
266
- continue ;
267
- }
268
- let second_char = iter. next ( ) . unwrap ( ) ;
269
- if second_char == '{' {
270
- continue ;
157
+ // Returns true if `args[i]` "refers to" or "is referred to by" another argument.
158
+ fn is_aliased ( args : & [ FormatArgsArg < ' _ , ' _ > ] , i : usize ) -> bool {
159
+ args. iter ( ) . enumerate ( ) . any ( |( j, arg) | {
160
+ if_chain ! {
161
+ if let Some ( fmt) = arg. fmt;
162
+ // struct `core::fmt::rt::v1::Argument`
163
+ if let ExprKind :: Struct ( _, fields, _) = fmt. kind;
164
+ if let Some ( position_field) = fields. iter( ) . find( |f| f. ident. name == sym:: position) ;
165
+ if let ExprKind :: Lit ( lit) = & position_field. expr. kind;
166
+ if let LitKind :: Int ( position, _) = lit. node;
167
+ then {
168
+ let position = usize :: try_from( position) . unwrap( ) ;
169
+ ( j == i && position != i) || ( j != i && position == i)
170
+ } else {
171
+ false
172
+ }
271
173
}
272
- return second_char;
273
- }
274
- }
275
-
276
- fn trim_quotes ( string_literal : & str ) -> String {
277
- let len = string_literal. chars ( ) . count ( ) ;
278
- assert ! ( len >= 2 ) ;
279
- string_literal. chars ( ) . skip ( 1 ) . take ( len - 2 ) . collect ( )
174
+ } )
280
175
}
281
176
282
177
fn trim_semicolon ( cx : & LateContext < ' _ > , span : Span ) -> Span {
@@ -285,3 +180,24 @@ fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
285
180
span. with_hi ( span. lo ( ) + BytePos ( u32:: try_from ( snippet. len ( ) ) . unwrap ( ) ) )
286
181
} )
287
182
}
183
+
184
+ fn implements_trait_via_deref < ' tcx > (
185
+ cx : & LateContext < ' tcx > ,
186
+ ty : Ty < ' tcx > ,
187
+ trait_id : DefId ,
188
+ ty_params : & [ GenericArg < ' tcx > ] ,
189
+ ) -> bool {
190
+ if_chain ! {
191
+ if let Some ( deref_trait_id) = cx. tcx. lang_items( ) . deref_trait( ) ;
192
+ if implements_trait( cx, ty, deref_trait_id, & [ ] ) ;
193
+ if let Some ( deref_target_id) = cx. tcx. lang_items( ) . deref_target( ) ;
194
+ then {
195
+ let substs = cx. tcx. mk_substs_trait( ty, & [ ] ) ;
196
+ let projection_ty = cx. tcx. mk_projection( deref_target_id, substs) ;
197
+ let target_ty = cx. tcx. normalize_erasing_regions( cx. param_env, projection_ty) ;
198
+ implements_trait( cx, target_ty, trait_id, ty_params)
199
+ } else {
200
+ false
201
+ }
202
+ }
203
+ }
0 commit comments