1
- use clippy_utils:: diagnostics:: { span_lint_and_help , span_lint_and_sugg } ;
1
+ use clippy_utils:: diagnostics:: { span_lint_and_sugg , span_lint_and_then } ;
2
2
use clippy_utils:: higher:: { FormatArgsArg , FormatArgsExpn , FormatExpn } ;
3
3
use clippy_utils:: source:: snippet_opt;
4
4
use clippy_utils:: ty:: implements_trait;
5
- use clippy_utils:: { get_trait_def_id, match_def_path, paths} ;
5
+ use clippy_utils:: { get_trait_def_id, is_diag_trait_item , match_def_path, paths} ;
6
6
use if_chain:: if_chain;
7
- use rustc_ast:: ast:: LitKind ;
8
7
use rustc_errors:: Applicability ;
9
- use rustc_hir:: def_id:: DefId ;
10
8
use rustc_hir:: { Expr , ExprKind } ;
11
9
use rustc_lint:: { LateContext , LateLintPass } ;
12
- use rustc_middle:: ty:: subst :: GenericArg ;
10
+ use rustc_middle:: ty:: adjustment :: { Adjust , Adjustment } ;
13
11
use rustc_middle:: ty:: Ty ;
14
12
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
15
- use rustc_span:: { sym, BytePos , ExpnKind , Span , Symbol } ;
13
+ use rustc_span:: { sym, BytePos , ExpnData , ExpnKind , Span , Symbol } ;
16
14
17
15
declare_clippy_lint ! {
18
16
/// ### What it does
@@ -65,112 +63,129 @@ declare_clippy_lint! {
65
63
66
64
declare_lint_pass ! ( FormatArgs => [ FORMAT_IN_FORMAT_ARGS , TO_STRING_IN_FORMAT_ARGS ] ) ;
67
65
66
+ const FORMAT_MACROS : & [ & [ & str ] ] = & [
67
+ & [ "alloc" , "macros" , "format" ] ,
68
+ & [ "core" , "macros" , "assert_eq" ] ,
69
+ & [ "core" , "macros" , "assert_ne" ] ,
70
+ & [ "core" , "macros" , "write" ] ,
71
+ & [ "core" , "macros" , "writeln" ] ,
72
+ & [ "core" , "macros" , "builtin" , "assert" ] ,
73
+ & [ "core" , "macros" , "builtin" , "format_args" ] ,
74
+ & [ "std" , "macros" , "eprint" ] ,
75
+ & [ "std" , "macros" , "eprintln" ] ,
76
+ & [ "std" , "macros" , "panic" ] ,
77
+ & [ "std" , "macros" , "print" ] ,
78
+ & [ "std" , "macros" , "println" ] ,
79
+ ] ;
80
+
68
81
impl < ' tcx > LateLintPass < ' tcx > for FormatArgs {
69
82
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
70
83
if_chain ! {
71
84
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
78
- }
79
- } ;
80
- if let ExpnKind :: Macro ( _, name) = expn_data. kind;
85
+ let expr_expn_data = expr. span. ctxt( ) . outer_expn_data( ) ;
86
+ let outermost_expn_data = outermost_expn_data( expr_expn_data) ;
87
+ if let Some ( macro_def_id) = outermost_expn_data. macro_def_id;
88
+ if FORMAT_MACROS . iter( ) . any( |path| match_def_path( cx, macro_def_id, path) ) ;
89
+ if let ExpnKind :: Macro ( _, name) = outermost_expn_data. kind;
81
90
if let Some ( args) = format_args. args( ) ;
82
- if !args. iter( ) . any( FormatArgsArg :: has_string_formatting) ;
83
91
then {
84
92
for ( i, arg) in args. iter( ) . enumerate( ) {
85
93
if !arg. is_display( ) {
86
94
continue ;
87
95
}
96
+ if arg. has_string_formatting( ) {
97
+ continue ;
98
+ }
88
99
if is_aliased( & args, i) {
89
100
continue ;
90
101
}
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) ;
102
+ check_format_in_format_args( cx, outermost_expn_data . call_site, name, arg) ;
103
+ check_to_string_in_format_args( cx, name, arg) ;
93
104
}
94
105
}
95
106
}
96
107
}
97
108
}
98
109
99
- fn check_format_in_format_args ( cx : & LateContext < ' _ > , call_site : Span , name : Symbol , arg : & FormatArgsArg < ' _ , ' _ > ) {
110
+ fn outermost_expn_data ( expn_data : ExpnData ) -> ExpnData {
111
+ if expn_data. call_site . from_expansion ( ) {
112
+ outermost_expn_data ( expn_data. call_site . ctxt ( ) . outer_expn_data ( ) )
113
+ } else {
114
+ expn_data
115
+ }
116
+ }
117
+
118
+ fn check_format_in_format_args ( cx : & LateContext < ' _ > , call_site : Span , name : Symbol , arg : & FormatArgsArg < ' _ > ) {
100
119
if FormatExpn :: parse ( arg. value ) . is_some ( ) {
101
- span_lint_and_help (
120
+ span_lint_and_then (
102
121
cx,
103
122
FORMAT_IN_FORMAT_ARGS ,
104
123
trim_semicolon ( cx, call_site) ,
105
124
& format ! ( "`format!` in `{}!` args" , name) ,
106
- None ,
107
- "inline the `format!` call" ,
125
+ |diag| {
126
+ diag. help ( & format ! (
127
+ "combine the `format!(..)` arguments with the outer `{}!(..)` call" ,
128
+ name
129
+ ) ) ;
130
+ diag. help ( "or consider changing `format!` to `format_args!`" ) ;
131
+ } ,
108
132
) ;
109
133
}
110
134
}
111
135
112
- fn check_to_string_in_format_args < ' tcx > (
113
- cx : & LateContext < ' tcx > ,
114
- name : Symbol ,
115
- arg : & FormatArgsArg < ' _ , ' tcx > ,
116
- ) {
136
+ fn check_to_string_in_format_args < ' tcx > ( cx : & LateContext < ' tcx > , name : Symbol , arg : & FormatArgsArg < ' tcx > ) {
117
137
let value = arg. value ;
118
138
if_chain ! {
119
- if let ExprKind :: MethodCall ( _, _, [ receiver] , span ) = value. kind;
139
+ if let ExprKind :: MethodCall ( _, _, [ receiver] , _ ) = value. kind;
120
140
if let Some ( method_def_id) = cx. typeck_results( ) . type_dependent_def_id( value. hir_id) ;
121
141
if is_diag_trait_item( cx, method_def_id, sym:: ToString ) ;
122
- if let Some ( receiver) = args. get( 0 ) ;
123
- let ty = cx. typeck_results( ) . expr_ty( receiver) ;
142
+ let receiver_ty = cx. typeck_results( ) . expr_ty( receiver) ;
124
143
if let Some ( display_trait_id) = get_trait_def_id( cx, & paths:: DISPLAY_TRAIT ) ;
125
- if let Some ( snippet) = snippet_opt( cx, value. span) ;
126
- if let Some ( dot) = snippet. rfind( '.' ) ;
144
+ if let Some ( value_snippet) = snippet_opt( cx, value. span) ;
145
+ if let Some ( dot) = value_snippet. rfind( '.' ) ;
146
+ if let Some ( receiver_snippet) = snippet_opt( cx, receiver. span) ;
127
147
then {
128
- let span = value. span. with_lo(
129
- value. span. lo( ) + BytePos ( u32 :: try_from( dot) . unwrap( ) )
148
+ let ( n_derefs, target) = count_derefs(
149
+ 0 ,
150
+ receiver_ty,
151
+ & mut cx. typeck_results( ) . expr_adjustments( receiver) . iter( ) ,
130
152
) ;
131
- if implements_trait( cx, ty, display_trait_id, & [ ] ) {
132
- span_lint_and_sugg(
133
- cx,
134
- TO_STRING_IN_FORMAT_ARGS ,
135
- span,
136
- & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
137
- "remove this" ,
138
- String :: new( ) ,
139
- Applicability :: MachineApplicable ,
140
- ) ;
141
- } else if implements_trait_via_deref( cx, ty, display_trait_id, & [ ] ) {
142
- span_lint_and_sugg(
143
- cx,
144
- TO_STRING_IN_FORMAT_ARGS ,
145
- span,
146
- & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
147
- "use this" ,
148
- String :: from( ".deref()" ) ,
149
- Applicability :: MachineApplicable ,
150
- ) ;
153
+ if implements_trait( cx, target, display_trait_id, & [ ] ) {
154
+ if n_derefs == 0 {
155
+ let span = value. span. with_lo(
156
+ value. span. lo( ) + BytePos ( u32 :: try_from( dot) . unwrap( ) )
157
+ ) ;
158
+ span_lint_and_sugg(
159
+ cx,
160
+ TO_STRING_IN_FORMAT_ARGS ,
161
+ span,
162
+ & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
163
+ "remove this" ,
164
+ String :: new( ) ,
165
+ Applicability :: MachineApplicable ,
166
+ ) ;
167
+ } else {
168
+ span_lint_and_sugg(
169
+ cx,
170
+ TO_STRING_IN_FORMAT_ARGS ,
171
+ value. span,
172
+ & format!( "`to_string` applied to a type that implements `Display` in `{}!` args" , name) ,
173
+ "use this" ,
174
+ format!( "{:*>width$}{}" , "" , receiver_snippet, width = n_derefs) ,
175
+ Applicability :: MachineApplicable ,
176
+ ) ;
177
+ }
151
178
}
152
179
}
153
180
}
154
181
}
155
182
156
183
// Returns true if `args[i]` "refers to" or "is referred to by" another argument.
157
- fn is_aliased ( args : & [ FormatArgsArg < ' _ , ' _ > ] , i : usize ) -> bool {
158
- args. iter ( ) . enumerate ( ) . any ( |( j, arg) | {
159
- if_chain ! {
160
- if let Some ( fmt) = arg. fmt;
161
- // struct `core::fmt::rt::v1::Argument`
162
- if let ExprKind :: Struct ( _, fields, _) = fmt. kind;
163
- if let Some ( position_field) = fields. iter( ) . find( |f| f. ident. name == sym:: position) ;
164
- if let ExprKind :: Lit ( lit) = & position_field. expr. kind;
165
- if let LitKind :: Int ( position, _) = lit. node;
166
- then {
167
- let position = usize :: try_from( position) . unwrap( ) ;
168
- ( j == i && position != i) || ( j != i && position == i)
169
- } else {
170
- false
171
- }
172
- }
173
- } )
184
+ fn is_aliased ( args : & [ FormatArgsArg < ' _ > ] , i : usize ) -> bool {
185
+ let value = args[ i] . value ;
186
+ args. iter ( )
187
+ . enumerate ( )
188
+ . any ( |( j, arg) | i != j && std:: ptr:: eq ( value, arg. value ) )
174
189
}
175
190
176
191
fn trim_semicolon ( cx : & LateContext < ' _ > , span : Span ) -> Span {
@@ -180,23 +195,17 @@ fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
180
195
} )
181
196
}
182
197
183
- fn implements_trait_via_deref < ' tcx > (
184
- cx : & LateContext < ' tcx > ,
185
- ty : Ty < ' tcx > ,
186
- trait_id : DefId ,
187
- ty_params : & [ GenericArg < ' tcx > ] ,
188
- ) -> bool {
189
- if_chain ! {
190
- if let Some ( deref_trait_id) = cx. tcx. lang_items( ) . deref_trait( ) ;
191
- if implements_trait( cx, ty, deref_trait_id, & [ ] ) ;
192
- if let Some ( deref_target_id) = cx. tcx. lang_items( ) . deref_target( ) ;
193
- then {
194
- let substs = cx. tcx. mk_substs_trait( ty, & [ ] ) ;
195
- let projection_ty = cx. tcx. mk_projection( deref_target_id, substs) ;
196
- let target_ty = cx. tcx. normalize_erasing_regions( cx. param_env, projection_ty) ;
197
- implements_trait( cx, target_ty, trait_id, ty_params)
198
- } else {
199
- false
200
- }
198
+ fn count_derefs < ' tcx , I > ( n : usize , ty : Ty < ' tcx > , iter : & mut I ) -> ( usize , Ty < ' tcx > )
199
+ where
200
+ I : Iterator < Item = & ' tcx Adjustment < ' tcx > > ,
201
+ {
202
+ if let Some ( Adjustment {
203
+ kind : Adjust :: Deref ( Some ( _) ) ,
204
+ target,
205
+ } ) = iter. next ( )
206
+ {
207
+ count_derefs ( n + 1 , target, iter)
208
+ } else {
209
+ ( n, ty)
201
210
}
202
211
}
0 commit comments