@@ -9,8 +9,8 @@ use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Saf
9
9
use rustc_infer:: infer:: TyCtxtInferExt ;
10
10
use rustc_lint:: { LateContext , LateLintPass } ;
11
11
use rustc_middle:: ty:: {
12
- self , Binder , ClosureArgs , ClosureKind , FnSig , GenericArg , GenericArgKind , List , Region , RegionKind , Ty , TyCtxt ,
13
- TypeVisitableExt , TypeckResults ,
12
+ self , Binder , ClosureKind , FnSig , GenericArg , GenericArgKind , List , Region , RegionKind , Ty , TypeVisitableExt ,
13
+ TypeckResults ,
14
14
} ;
15
15
use rustc_session:: declare_lint_pass;
16
16
use rustc_span:: symbol:: sym;
@@ -74,159 +74,173 @@ declare_clippy_lint! {
74
74
declare_lint_pass ! ( EtaReduction => [ REDUNDANT_CLOSURE , REDUNDANT_CLOSURE_FOR_METHOD_CALLS ] ) ;
75
75
76
76
impl < ' tcx > LateLintPass < ' tcx > for EtaReduction {
77
- #[ allow( clippy:: too_many_lines) ]
78
- fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
79
- let body = if let ExprKind :: Closure ( c) = expr. kind
80
- && c. fn_decl . inputs . iter ( ) . all ( |ty| matches ! ( ty. kind, TyKind :: Infer ) )
81
- && matches ! ( c. fn_decl. output, FnRetTy :: DefaultReturn ( _) )
82
- && !expr. span . from_expansion ( )
83
- {
84
- cx. tcx . hir ( ) . body ( c. body )
85
- } else {
86
- return ;
87
- } ;
88
-
89
- if body. value . span . from_expansion ( ) {
90
- if body. params . is_empty ( ) {
91
- if let Some ( VecArgs :: Vec ( & [ ] ) ) = VecArgs :: hir ( cx, body. value ) {
92
- // replace `|| vec![]` with `Vec::new`
93
- span_lint_and_sugg (
94
- cx,
95
- REDUNDANT_CLOSURE ,
96
- expr. span ,
97
- "redundant closure" ,
98
- "replace the closure with `Vec::new`" ,
99
- "std::vec::Vec::new" . into ( ) ,
100
- Applicability :: MachineApplicable ,
101
- ) ;
102
- }
77
+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
78
+ if let ExprKind :: MethodCall ( _method, receiver, args, _) = expr. kind {
79
+ for arg in args {
80
+ check_clousure ( cx, Some ( receiver) , arg) ;
81
+ }
82
+ }
83
+ if let ExprKind :: Call ( func, args) = expr. kind {
84
+ check_clousure ( cx, None , func) ;
85
+ for arg in args {
86
+ check_clousure ( cx, None , arg) ;
103
87
}
104
- // skip `foo(|| macro!())`
105
- return ;
106
88
}
89
+ }
90
+ }
107
91
108
- let typeck = cx. typeck_results ( ) ;
109
- let closure = if let ty:: Closure ( _, closure_subs) = typeck. expr_ty ( expr) . kind ( ) {
110
- closure_subs. as_closure ( )
111
- } else {
112
- return ;
113
- } ;
92
+ #[ allow( clippy:: too_many_lines) ]
93
+ fn check_clousure < ' tcx > ( cx : & LateContext < ' tcx > , outer_receiver : Option < & Expr < ' tcx > > , expr : & Expr < ' tcx > ) {
94
+ let body = if let ExprKind :: Closure ( c) = expr. kind
95
+ && c. fn_decl . inputs . iter ( ) . all ( |ty| matches ! ( ty. kind, TyKind :: Infer ) )
96
+ && matches ! ( c. fn_decl. output, FnRetTy :: DefaultReturn ( _) )
97
+ && !expr. span . from_expansion ( )
98
+ {
99
+ cx. tcx . hir ( ) . body ( c. body )
100
+ } else {
101
+ return ;
102
+ } ;
114
103
115
- if is_adjusted ( cx, body. value ) {
116
- return ;
104
+ if body. value . span . from_expansion ( ) {
105
+ if body. params . is_empty ( ) {
106
+ if let Some ( VecArgs :: Vec ( & [ ] ) ) = VecArgs :: hir ( cx, body. value ) {
107
+ // replace `|| vec![]` with `Vec::new`
108
+ span_lint_and_sugg (
109
+ cx,
110
+ REDUNDANT_CLOSURE ,
111
+ expr. span ,
112
+ "redundant closure" ,
113
+ "replace the closure with `Vec::new`" ,
114
+ "std::vec::Vec::new" . into ( ) ,
115
+ Applicability :: MachineApplicable ,
116
+ ) ;
117
+ }
117
118
}
119
+ // skip `foo(|| macro!())`
120
+ return ;
121
+ }
118
122
119
- match body. value . kind {
120
- ExprKind :: Call ( callee, args)
121
- if matches ! (
122
- callee. kind,
123
- ExprKind :: Path ( QPath :: Resolved ( ..) | QPath :: TypeRelative ( ..) )
124
- ) =>
123
+ if is_adjusted ( cx, body. value ) {
124
+ return ;
125
+ }
126
+
127
+ let typeck = cx. typeck_results ( ) ;
128
+ let closure = if let ty:: Closure ( _, closure_subs) = typeck. expr_ty ( expr) . kind ( ) {
129
+ closure_subs. as_closure ( )
130
+ } else {
131
+ return ;
132
+ } ;
133
+ let closure_sig = cx. tcx . signature_unclosure ( closure. sig ( ) , Safety :: Safe ) . skip_binder ( ) ;
134
+ match body. value . kind {
135
+ ExprKind :: Call ( callee, args)
136
+ if matches ! (
137
+ callee. kind,
138
+ ExprKind :: Path ( QPath :: Resolved ( ..) | QPath :: TypeRelative ( ..) )
139
+ ) =>
140
+ {
141
+ let callee_ty_raw = typeck. expr_ty ( callee) ;
142
+ let callee_ty = callee_ty_raw. peel_refs ( ) ;
143
+ if matches ! ( type_diagnostic_name( cx, callee_ty) , Some ( sym:: Arc | sym:: Rc ) )
144
+ || !check_inputs ( typeck, body. params , None , args)
125
145
{
126
- let callee_ty_raw = typeck. expr_ty ( callee) ;
127
- let callee_ty = callee_ty_raw. peel_refs ( ) ;
128
- if matches ! ( type_diagnostic_name( cx, callee_ty) , Some ( sym:: Arc | sym:: Rc ) )
129
- || !check_inputs ( typeck, body. params , None , args)
130
- {
131
- return ;
132
- }
133
- let callee_ty_adjusted = typeck
134
- . expr_adjustments ( callee)
135
- . last ( )
136
- . map_or ( callee_ty, |a| a. target . peel_refs ( ) ) ;
146
+ return ;
147
+ }
148
+ let callee_ty_adjusted = typeck
149
+ . expr_adjustments ( callee)
150
+ . last ( )
151
+ . map_or ( callee_ty, |a| a. target . peel_refs ( ) ) ;
137
152
138
- let sig = match callee_ty_adjusted. kind ( ) {
139
- ty:: FnDef ( def, _) => {
140
- // Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location`
141
- if cx. tcx . has_attr ( * def, sym:: track_caller) {
142
- return ;
143
- }
153
+ let sig = match callee_ty_adjusted. kind ( ) {
154
+ ty:: FnDef ( def, _) => {
155
+ // Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location`
156
+ if cx. tcx . has_attr ( * def, sym:: track_caller) {
157
+ return ;
158
+ }
144
159
145
- cx. tcx . fn_sig ( def) . skip_binder ( ) . skip_binder ( )
146
- } ,
147
- ty:: FnPtr ( sig) => sig. skip_binder ( ) ,
148
- ty:: Closure ( _, subs) => cx
149
- . tcx
150
- . signature_unclosure ( subs. as_closure ( ) . sig ( ) , Safety :: Safe )
151
- . skip_binder ( ) ,
152
- _ => {
153
- if typeck. type_dependent_def_id ( body. value . hir_id ) . is_some ( )
154
- && let subs = typeck. node_args ( body. value . hir_id )
155
- && let output = typeck. expr_ty ( body. value )
156
- && let ty:: Tuple ( tys) = * subs. type_at ( 1 ) . kind ( )
157
- {
158
- cx. tcx . mk_fn_sig ( tys, output, false , Safety :: Safe , Abi :: Rust )
159
- } else {
160
- return ;
161
- }
162
- } ,
163
- } ;
164
- if check_sig ( cx, closure, sig)
165
- && let generic_args = typeck. node_args ( callee. hir_id )
166
- // Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not
167
- // `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result
168
- // in a type which is `'static`.
169
- // For now ignore all callee types which reference a type parameter.
170
- && !generic_args. types ( ) . any ( |t| matches ! ( t. kind( ) , ty:: Param ( _) ) )
171
- {
172
- span_lint_and_then ( cx, REDUNDANT_CLOSURE , expr. span , "redundant closure" , |diag| {
173
- if let Some ( mut snippet) = snippet_opt ( cx, callee. span ) {
174
- if path_to_local ( callee) . map_or ( false , |l| {
175
- // FIXME: Do we really need this `local_used_in` check?
176
- // Isn't it checking something like... `callee(callee)`?
177
- // If somehow this check is needed, add some test for it,
178
- // 'cuz currently nothing changes after deleting this check.
179
- local_used_in ( cx, l, args) || local_used_after_expr ( cx, l, expr)
180
- } ) {
181
- match cx. tcx . infer_ctxt ( ) . build ( ) . err_ctxt ( ) . type_implements_fn_trait (
182
- cx. param_env ,
183
- Binder :: bind_with_vars ( callee_ty_adjusted, List :: empty ( ) ) ,
184
- ty:: PredicatePolarity :: Positive ,
185
- ) {
186
- // Mutable closure is used after current expr; we cannot consume it.
187
- Ok ( ( ClosureKind :: FnMut , _) ) => snippet = format ! ( "&mut {snippet}" ) ,
188
- Ok ( ( ClosureKind :: Fn , _) ) if !callee_ty_raw. is_ref ( ) => {
189
- snippet = format ! ( "&{snippet}" ) ;
190
- } ,
191
- _ => ( ) ,
192
- }
160
+ cx. tcx . fn_sig ( def) . skip_binder ( ) . skip_binder ( )
161
+ } ,
162
+ ty:: FnPtr ( sig) => sig. skip_binder ( ) ,
163
+ ty:: Closure ( _, subs) => cx
164
+ . tcx
165
+ . signature_unclosure ( subs. as_closure ( ) . sig ( ) , Safety :: Safe )
166
+ . skip_binder ( ) ,
167
+ _ => {
168
+ if typeck. type_dependent_def_id ( body. value . hir_id ) . is_some ( )
169
+ && let subs = typeck. node_args ( body. value . hir_id )
170
+ && let output = typeck. expr_ty ( body. value )
171
+ && let ty:: Tuple ( tys) = * subs. type_at ( 1 ) . kind ( )
172
+ {
173
+ cx. tcx . mk_fn_sig ( tys, output, false , Safety :: Safe , Abi :: Rust )
174
+ } else {
175
+ return ;
176
+ }
177
+ } ,
178
+ } ;
179
+ if check_sig ( closure_sig, sig)
180
+ && let generic_args = typeck. node_args ( callee. hir_id )
181
+ // Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not
182
+ // `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result
183
+ // in a type which is `'static`.
184
+ // For now ignore all callee types which reference a type parameter.
185
+ && !generic_args. types ( ) . any ( |t| matches ! ( t. kind( ) , ty:: Param ( _) ) )
186
+ {
187
+ span_lint_and_then ( cx, REDUNDANT_CLOSURE , expr. span , "redundant closure" , |diag| {
188
+ if let Some ( mut snippet) = snippet_opt ( cx, callee. span ) {
189
+ if path_to_local ( callee) . map_or ( false , |l| {
190
+ // FIXME: Do we really need this `local_used_in` check?
191
+ // Isn't it checking something like... `callee(callee)`?
192
+ // If somehow this check is needed, add some test for it,
193
+ // 'cuz currently nothing changes after deleting this check.
194
+ local_used_in ( cx, l, args) || local_used_after_expr ( cx, l, expr)
195
+ } ) {
196
+ match cx. tcx . infer_ctxt ( ) . build ( ) . err_ctxt ( ) . type_implements_fn_trait (
197
+ cx. param_env ,
198
+ Binder :: bind_with_vars ( callee_ty_adjusted, List :: empty ( ) ) ,
199
+ ty:: PredicatePolarity :: Positive ,
200
+ ) {
201
+ // Mutable closure is used after current expr; we cannot consume it.
202
+ Ok ( ( ClosureKind :: FnMut , _) ) => snippet = format ! ( "&mut {snippet}" ) ,
203
+ Ok ( ( ClosureKind :: Fn , _) ) if !callee_ty_raw. is_ref ( ) => {
204
+ snippet = format ! ( "&{snippet}" ) ;
205
+ } ,
206
+ _ => ( ) ,
193
207
}
194
- diag. span_suggestion (
195
- expr. span ,
196
- "replace the closure with the function itself" ,
197
- snippet,
198
- Applicability :: MachineApplicable ,
199
- ) ;
200
208
}
201
- } ) ;
202
- }
203
- } ,
204
- ExprKind :: MethodCall ( path, self_, args, _) if check_inputs ( typeck, body. params , Some ( self_) , args) => {
205
- if let Some ( method_def_id) = typeck. type_dependent_def_id ( body. value . hir_id )
206
- && !cx. tcx . has_attr ( method_def_id, sym:: track_caller)
207
- && check_sig ( cx, closure, cx. tcx . fn_sig ( method_def_id) . skip_binder ( ) . skip_binder ( ) )
208
- {
209
- span_lint_and_then (
210
- cx,
211
- REDUNDANT_CLOSURE_FOR_METHOD_CALLS ,
212
- expr. span ,
213
- "redundant closure" ,
214
- |diag| {
215
- let args = typeck. node_args ( body. value . hir_id ) ;
216
- let caller = self_. hir_id . owner . def_id ;
217
- let type_name = get_path_from_caller_to_method_type ( cx. tcx , caller, method_def_id, args) ;
218
- diag. span_suggestion (
219
- expr. span ,
220
- "replace the closure with the method itself" ,
221
- format ! ( "{}::{}" , type_name, path. ident. name) ,
222
- Applicability :: MachineApplicable ,
223
- ) ;
224
- } ,
225
- ) ;
226
- }
227
- } ,
228
- _ => ( ) ,
229
- }
209
+ diag. span_suggestion (
210
+ expr. span ,
211
+ "replace the closure with the function itself" ,
212
+ snippet,
213
+ Applicability :: MachineApplicable ,
214
+ ) ;
215
+ }
216
+ } ) ;
217
+ }
218
+ } ,
219
+ ExprKind :: MethodCall ( path, self_, args, _) if check_inputs ( typeck, body. params , Some ( self_) , args) => {
220
+ if let Some ( method_def_id) = typeck. type_dependent_def_id ( body. value . hir_id )
221
+ && !cx. tcx . has_attr ( method_def_id, sym:: track_caller)
222
+ && check_sig ( closure_sig, cx. tcx . fn_sig ( method_def_id) . skip_binder ( ) . skip_binder ( ) )
223
+ {
224
+ span_lint_and_then (
225
+ cx,
226
+ REDUNDANT_CLOSURE_FOR_METHOD_CALLS ,
227
+ expr. span ,
228
+ "redundant closure" ,
229
+ |diag| {
230
+ let args = typeck. node_args ( body. value . hir_id ) ;
231
+ let caller = self_. hir_id . owner . def_id ;
232
+ let type_name = get_path_from_caller_to_method_type ( cx. tcx , caller, method_def_id, args) ;
233
+ diag. span_suggestion (
234
+ expr. span ,
235
+ "replace the closure with the method itself" ,
236
+ format ! ( "{}::{}" , type_name, path. ident. name) ,
237
+ Applicability :: MachineApplicable ,
238
+ ) ;
239
+ } ,
240
+ ) ;
241
+ }
242
+ } ,
243
+ _ => ( ) ,
230
244
}
231
245
}
232
246
@@ -251,12 +265,8 @@ fn check_inputs(
251
265
} )
252
266
}
253
267
254
- fn check_sig < ' tcx > ( cx : & LateContext < ' tcx > , closure : ClosureArgs < TyCtxt < ' tcx > > , call_sig : FnSig < ' _ > ) -> bool {
255
- call_sig. safety == Safety :: Safe
256
- && !has_late_bound_to_non_late_bound_regions (
257
- cx. tcx . signature_unclosure ( closure. sig ( ) , Safety :: Safe ) . skip_binder ( ) ,
258
- call_sig,
259
- )
268
+ fn check_sig < ' tcx > ( closure_sig : FnSig < ' tcx > , call_sig : FnSig < ' tcx > ) -> bool {
269
+ call_sig. safety == Safety :: Safe && !has_late_bound_to_non_late_bound_regions ( closure_sig, call_sig)
260
270
}
261
271
262
272
/// This walks through both signatures and checks for any time a late-bound region is expected by an
0 commit comments