11use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
22use clippy_utils:: higher:: VecArgs ;
33use clippy_utils:: source:: snippet_opt;
4- use clippy_utils:: ty:: is_type_diagnostic_item ;
4+ use clippy_utils:: ty:: type_diagnostic_name ;
55use clippy_utils:: usage:: local_used_after_expr;
66use clippy_utils:: { get_enclosing_loop_or_closure, higher, path_to_local, path_to_local_id} ;
7- use if_chain:: if_chain;
87use rustc_errors:: Applicability ;
98use rustc_hir:: def_id:: DefId ;
10- use rustc_hir:: { Expr , ExprKind , Param , PatKind , Unsafety } ;
9+ use rustc_hir:: { BindingAnnotation , Expr , ExprKind , FnRetTy , Param , PatKind , TyKind , Unsafety } ;
1110use rustc_lint:: { LateContext , LateLintPass } ;
12- use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment , AutoBorrow } ;
13- use rustc_middle:: ty:: binding:: BindingMode ;
14- use rustc_middle:: ty:: subst:: Subst ;
15- use rustc_middle:: ty:: { self , ClosureKind , Ty , TypeFoldable } ;
11+ use rustc_middle:: ty:: subst:: { GenericArg , GenericArgKind } ;
12+ use rustc_middle:: ty:: { self , ClosureKind , ClosureSubsts , FnSig , Region , RegionKind , Ty , TypeFoldable , TypeckResults } ;
1613use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1714use rustc_span:: symbol:: sym;
15+ use rustc_target:: spec:: abi:: Abi ;
1816
1917declare_clippy_lint ! {
2018 /// ### What it does
@@ -73,14 +71,18 @@ declare_clippy_lint! {
7371declare_lint_pass ! ( EtaReduction => [ REDUNDANT_CLOSURE , REDUNDANT_CLOSURE_FOR_METHOD_CALLS ] ) ;
7472
7573impl < ' tcx > LateLintPass < ' tcx > for EtaReduction {
74+ #[ allow( clippy:: too_many_lines) ]
7675 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
77- if expr. span . from_expansion ( ) {
78- return ;
79- }
80- let body = match expr. kind {
81- ExprKind :: Closure ( _, _, id, _, _) => cx. tcx . hir ( ) . body ( id) ,
82- _ => return ,
76+ let body = if let ExprKind :: Closure ( _, decl, id, _, _) = expr. kind
77+ && decl. inputs . iter ( ) . all ( |ty| matches ! ( ty. kind, TyKind :: Infer ) )
78+ && matches ! ( decl. output, FnRetTy :: DefaultReturn ( _) )
79+ && !expr. span . from_expansion ( )
80+ {
81+ cx. tcx . hir ( ) . body ( id)
82+ } else {
83+ return
8384 } ;
85+
8486 if body. value . span . from_expansion ( ) {
8587 if body. params . is_empty ( ) {
8688 if let Some ( VecArgs :: Vec ( & [ ] ) ) = higher:: VecArgs :: hir ( cx, & body. value ) {
@@ -100,122 +102,187 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
100102 return ;
101103 }
102104
103- let closure_ty = cx. typeck_results ( ) . expr_ty ( expr) ;
104-
105- if_chain ! (
106- if let ExprKind :: Call ( callee, args) = body. value. kind;
107- if let ExprKind :: Path ( _) = callee. kind;
108- if check_inputs( cx, body. params, args) ;
109- let callee_ty = cx. typeck_results( ) . expr_ty_adjusted( callee) ;
110- let call_ty = cx. typeck_results( ) . type_dependent_def_id( body. value. hir_id)
111- . map_or( callee_ty, |id| cx. tcx. type_of( id) ) ;
112- if check_sig( cx, closure_ty, call_ty) ;
113- let substs = cx. typeck_results( ) . node_substs( callee. hir_id) ;
114- // This fixes some false positives that I don't entirely understand
115- if substs. is_empty( ) || !cx. typeck_results( ) . expr_ty( expr) . has_late_bound_regions( ) ;
116- // A type param function ref like `T::f` is not 'static, however
117- // it is if cast like `T::f as fn()`. This seems like a rustc bug.
118- if !substs. types( ) . any( |t| matches!( t. kind( ) , ty:: Param ( _) ) ) ;
119- let callee_ty_unadjusted = cx. typeck_results( ) . expr_ty( callee) . peel_refs( ) ;
120- if !is_type_diagnostic_item( cx, callee_ty_unadjusted, sym:: Arc ) ;
121- if !is_type_diagnostic_item( cx, callee_ty_unadjusted, sym:: Rc ) ;
122- then {
123- span_lint_and_then( cx, REDUNDANT_CLOSURE , expr. span, "redundant closure" , |diag| {
124- if let Some ( mut snippet) = snippet_opt( cx, callee. span) {
125- if_chain! {
126- if let ty:: Closure ( _, substs) = callee_ty. peel_refs( ) . kind( ) ;
127- if substs. as_closure( ) . kind( ) == ClosureKind :: FnMut ;
128- if get_enclosing_loop_or_closure( cx. tcx, expr) . is_some( )
129- || path_to_local( callee) . map_or( false , |l| local_used_after_expr( cx, l, callee) ) ;
105+ let typeck = cx. typeck_results ( ) ;
106+ let closure = if let ty:: Closure ( _, closure_subs) = typeck. expr_ty ( expr) . kind ( ) {
107+ closure_subs. as_closure ( )
108+ } else {
109+ return ;
110+ } ;
130111
131- then {
132- // Mutable closure is used after current expr; we cannot consume it.
133- snippet = format!( "&mut {}" , snippet) ;
112+ match body. value . kind {
113+ ExprKind :: Call ( callee, args)
114+ if matches ! ( callee. kind, ExprKind :: Path ( ..) )
115+ && !matches ! (
116+ type_diagnostic_name( cx, typeck. expr_ty( callee) . peel_refs( ) ) ,
117+ Some ( sym:: Arc | sym:: Rc )
118+ )
119+ && check_inputs ( typeck, body. params , args) =>
120+ {
121+ let sig = match * typeck. expr_ty_adjusted ( callee) . kind ( ) {
122+ ty:: FnDef ( def, _) => cx. tcx . fn_sig ( def) . skip_binder ( ) ,
123+ ty:: FnPtr ( sig) => sig. skip_binder ( ) ,
124+ ty:: Closure ( _, subs) => cx
125+ . tcx
126+ . signature_unclosure ( subs. as_closure ( ) . sig ( ) , Unsafety :: Normal )
127+ . skip_binder ( ) ,
128+ _ => {
129+ if typeck. type_dependent_def_id ( body. value . hir_id ) . is_some ( )
130+ && let subs = typeck. node_substs ( body. value . hir_id )
131+ && let output = typeck. expr_ty ( & body. value )
132+ && let ty:: Tuple ( tys) = * subs. type_at ( 1 ) . kind ( )
133+ {
134+ cx. tcx . mk_fn_sig ( tys. into_iter ( ) , output, false , Unsafety :: Normal , Abi :: Rust )
135+ } else {
136+ return ;
137+ }
138+ } ,
139+ } ;
140+ if check_sig ( cx, closure, sig)
141+ // Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not
142+ // `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result
143+ // in a type which is `'static`.
144+ // For now ignore all callee types which reference a type parameter.
145+ && !typeck. node_substs ( callee. hir_id ) . types ( ) . any ( |t| matches ! ( t. kind( ) , ty:: Param ( _) ) )
146+ {
147+ span_lint_and_then (
148+ cx,
149+ REDUNDANT_CLOSURE ,
150+ expr. span ,
151+ "redundant closure" ,
152+ |diag| {
153+ if let Some ( mut snippet) = snippet_opt ( cx, callee. span ) {
154+ if let ty:: Closure ( _, substs) = * typeck. expr_ty ( callee) . peel_refs ( ) . kind ( )
155+ && substs. as_closure ( ) . kind ( ) == ClosureKind :: FnMut
156+ && (
157+ get_enclosing_loop_or_closure ( cx. tcx , expr) . is_some ( )
158+ || path_to_local ( callee)
159+ . map_or ( false , |l| local_used_after_expr ( cx, l, callee) )
160+ )
161+ {
162+ // Mutable closure is used after current expr; we cannot consume it.
163+ snippet = format ! ( "&mut {}" , snippet) ;
164+ }
165+ diag. span_suggestion (
166+ expr. span ,
167+ "replace the closure with the function itself" ,
168+ snippet,
169+ Applicability :: MachineApplicable ,
170+ ) ;
134171 }
135172 }
136- diag. span_suggestion(
137- expr. span,
138- "replace the closure with the function itself" ,
139- snippet,
140- Applicability :: MachineApplicable ,
141- ) ;
142- }
143- } ) ;
144- }
145- ) ;
146-
147- if_chain ! (
148- if let ExprKind :: MethodCall ( path, args, _) = body. value. kind;
149- if check_inputs( cx, body. params, args) ;
150- let method_def_id = cx. typeck_results( ) . type_dependent_def_id( body. value. hir_id) . unwrap( ) ;
151- let substs = cx. typeck_results( ) . node_substs( body. value. hir_id) ;
152- let call_ty = cx. tcx. type_of( method_def_id) . subst( cx. tcx, substs) ;
153- if check_sig( cx, closure_ty, call_ty) ;
154- then {
155- span_lint_and_then( cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS , expr. span, "redundant closure" , |diag| {
156- let name = get_ufcs_type_name( cx, method_def_id) ;
157- diag. span_suggestion(
173+ ) ;
174+ }
175+ } ,
176+ ExprKind :: MethodCall ( path, args, _) if check_inputs ( typeck, body. params , args) => {
177+ if let Some ( method_def_id) = typeck. type_dependent_def_id ( body. value . hir_id )
178+ && check_sig ( cx, closure, cx. tcx . fn_sig ( method_def_id) . skip_binder ( ) )
179+ {
180+ span_lint_and_then (
181+ cx,
182+ REDUNDANT_CLOSURE_FOR_METHOD_CALLS ,
158183 expr. span ,
159- "replace the closure with the method itself" ,
160- format!( "{}::{}" , name, path. ident. name) ,
161- Applicability :: MachineApplicable ,
184+ "redundant closure" ,
185+ |diag| {
186+ let name = get_ufcs_type_name ( cx, method_def_id) ;
187+ diag. span_suggestion (
188+ expr. span ,
189+ "replace the closure with the method itself" ,
190+ format ! ( "{}::{}" , name, path. ident. name) ,
191+ Applicability :: MachineApplicable ,
192+ ) ;
193+ } ,
162194 ) ;
163- } )
164- }
165- ) ;
195+ }
196+ } ,
197+ _ => ( ) ,
198+ }
166199 }
167200}
168201
169- fn check_inputs ( cx : & LateContext < ' _ > , params : & [ Param < ' _ > ] , call_args : & [ Expr < ' _ > ] ) -> bool {
170- if params. len ( ) != call_args. len ( ) {
171- return false ;
202+ fn check_inputs ( typeck : & TypeckResults < ' _ > , params : & [ Param < ' _ > ] , call_args : & [ Expr < ' _ > ] ) -> bool {
203+ params. len ( ) == call_args. len ( )
204+ && params. iter ( ) . zip ( call_args) . all ( |( p, arg) | {
205+ matches ! (
206+ p. pat. kind, PatKind :: Binding ( BindingAnnotation :: Unannotated , id, _, None )
207+ if path_to_local_id( arg, id)
208+ )
209+ // Only allow adjustments which change regions (i.e. re-borrowing).
210+ && typeck
211+ . expr_adjustments ( arg)
212+ . last ( )
213+ . map_or ( true , |a| a. target == typeck. expr_ty ( arg) )
214+ } )
215+ }
216+
217+ fn check_sig < ' tcx > ( cx : & LateContext < ' tcx > , closure : ClosureSubsts < ' tcx > , call_sig : FnSig < ' _ > ) -> bool {
218+ call_sig. unsafety == Unsafety :: Normal
219+ && !has_late_bound_to_non_late_bound_regions (
220+ cx. tcx
221+ . signature_unclosure ( closure. sig ( ) , Unsafety :: Normal )
222+ . skip_binder ( ) ,
223+ call_sig,
224+ )
225+ }
226+
227+ /// This walks through both signatures and checks for any time a late-bound region is expected by an
228+ /// `impl Fn` type, but the target signature does not have a late-bound region in the same position.
229+ ///
230+ /// This is needed because rustc is unable to late bind early-bound regions in a function signature.
231+ fn has_late_bound_to_non_late_bound_regions ( from_sig : FnSig < ' _ > , to_sig : FnSig < ' _ > ) -> bool {
232+ fn check_region ( from_region : Region < ' _ > , to_region : Region < ' _ > ) -> bool {
233+ matches ! ( from_region. kind( ) , RegionKind :: ReLateBound ( ..) )
234+ && !matches ! ( to_region. kind( ) , RegionKind :: ReLateBound ( ..) )
172235 }
173- let binding_modes = cx. typeck_results ( ) . pat_binding_modes ( ) ;
174- std:: iter:: zip ( params, call_args) . all ( |( param, arg) | {
175- match param. pat . kind {
176- PatKind :: Binding ( _, id, ..) if path_to_local_id ( arg, id) => { } ,
177- _ => return false ,
178- }
179- // checks that parameters are not bound as `ref` or `ref mut`
180- if let Some ( BindingMode :: BindByReference ( _) ) = binding_modes. get ( param. pat . hir_id ) {
181- return false ;
182- }
183236
184- match * cx. typeck_results ( ) . expr_adjustments ( arg) {
185- [ ] => true ,
186- [
187- Adjustment {
188- kind : Adjust :: Deref ( None ) ,
189- ..
237+ fn check_subs ( from_subs : & [ GenericArg < ' _ > ] , to_subs : & [ GenericArg < ' _ > ] ) -> bool {
238+ if from_subs. len ( ) != to_subs. len ( ) {
239+ return true ;
240+ }
241+ for ( from_arg, to_arg) in to_subs. iter ( ) . zip ( from_subs) {
242+ match ( from_arg. unpack ( ) , to_arg. unpack ( ) ) {
243+ ( GenericArgKind :: Lifetime ( from_region) , GenericArgKind :: Lifetime ( to_region) ) => {
244+ if check_region ( from_region, to_region) {
245+ return true ;
246+ }
190247 } ,
191- Adjustment {
192- kind : Adjust :: Borrow ( AutoBorrow :: Ref ( _, mu2) ) ,
193- ..
248+ ( GenericArgKind :: Type ( from_ty) , GenericArgKind :: Type ( to_ty) ) => {
249+ if check_ty ( from_ty, to_ty) {
250+ return true ;
251+ }
194252 } ,
195- ] => {
196- // re-borrow with the same mutability is allowed
197- let ty = cx. typeck_results ( ) . expr_ty ( arg) ;
198- matches ! ( * ty. kind( ) , ty:: Ref ( .., mu1) if mu1 == mu2. into( ) )
199- } ,
200- _ => false ,
253+ ( GenericArgKind :: Const ( _) , GenericArgKind :: Const ( _) ) => ( ) ,
254+ _ => return true ,
255+ }
201256 }
202- } )
203- }
204-
205- fn check_sig < ' tcx > ( cx : & LateContext < ' tcx > , closure_ty : Ty < ' tcx > , call_ty : Ty < ' tcx > ) -> bool {
206- let call_sig = call_ty. fn_sig ( cx. tcx ) ;
207- if call_sig. unsafety ( ) == Unsafety :: Unsafe {
208- return false ;
257+ false
209258 }
210- if !closure_ty. has_late_bound_regions ( ) {
211- return true ;
259+
260+ fn check_ty ( from_ty : Ty < ' _ > , to_ty : Ty < ' _ > ) -> bool {
261+ match ( from_ty. kind ( ) , to_ty. kind ( ) ) {
262+ ( & ty:: Adt ( _, from_subs) , & ty:: Adt ( _, to_subs) ) => check_subs ( from_subs, to_subs) ,
263+ ( & ty:: Array ( from_ty, _) , & ty:: Array ( to_ty, _) ) | ( & ty:: Slice ( from_ty) , & ty:: Slice ( to_ty) ) => {
264+ check_ty ( from_ty, to_ty)
265+ } ,
266+ ( & ty:: Ref ( from_region, from_ty, _) , & ty:: Ref ( to_region, to_ty, _) ) => {
267+ check_region ( from_region, to_region) || check_ty ( from_ty, to_ty)
268+ } ,
269+ ( & ty:: Tuple ( from_tys) , & ty:: Tuple ( to_tys) ) => {
270+ from_tys. len ( ) != to_tys. len ( )
271+ || from_tys
272+ . iter ( )
273+ . zip ( to_tys)
274+ . any ( |( from_ty, to_ty) | check_ty ( from_ty, to_ty) )
275+ } ,
276+ _ => from_ty. has_late_bound_regions ( ) ,
277+ }
212278 }
213- let substs = match closure_ty. kind ( ) {
214- ty:: Closure ( _, substs) => substs,
215- _ => return false ,
216- } ;
217- let closure_sig = cx. tcx . signature_unclosure ( substs. as_closure ( ) . sig ( ) , Unsafety :: Normal ) ;
218- cx. tcx . erase_late_bound_regions ( closure_sig) == cx. tcx . erase_late_bound_regions ( call_sig)
279+
280+ assert ! ( from_sig. inputs_and_output. len( ) == to_sig. inputs_and_output. len( ) ) ;
281+ from_sig
282+ . inputs_and_output
283+ . iter ( )
284+ . zip ( to_sig. inputs_and_output )
285+ . any ( |( from_ty, to_ty) | check_ty ( from_ty, to_ty) )
219286}
220287
221288fn get_ufcs_type_name ( cx : & LateContext < ' _ > , method_def_id : DefId ) -> String {
0 commit comments