@@ -7,71 +7,120 @@ use rustc_errors::Applicability;
77use rustc_hir as hir;
88use rustc_hir:: PatKind ;
99use rustc_lint:: LateContext ;
10+ use rustc_middle:: ty;
1011use rustc_span:: { source_map:: Span , sym} ;
1112
1213use super :: UNNECESSARY_FOLD ;
1314
14- pub ( super ) fn check (
15+ /// Do we need to suggest turbofish when suggesting a replacement method?
16+ /// Changing `fold` to `sum` needs it sometimes when the return type can't be
17+ /// inferred. This checks for some common cases where it can be safely omitted
18+ fn needs_turbofish ( cx : & LateContext < ' _ > , expr : & hir:: Expr < ' _ > ) -> bool {
19+ let parent = cx. tcx . hir ( ) . get_parent ( expr. hir_id ) ;
20+
21+ // some common cases where turbofish isn't needed:
22+ // - assigned to a local variable with a type annotation
23+ if let hir:: Node :: Local ( local) = parent
24+ && local. ty . is_some ( )
25+ {
26+ return false ;
27+ }
28+
29+ // - part of a function call argument, can be inferred from the function signature (provided that
30+ // the parameter is not a generic type parameter)
31+ if let hir:: Node :: Expr ( parent_expr) = parent
32+ && let hir:: ExprKind :: Call ( recv, args) = parent_expr. kind
33+ && let hir:: ExprKind :: Path ( ref qpath) = recv. kind
34+ && let Some ( fn_def_id) = cx. qpath_res ( qpath, recv. hir_id ) . opt_def_id ( )
35+ && let fn_sig = cx. tcx . fn_sig ( fn_def_id) . skip_binder ( ) . skip_binder ( )
36+ && let Some ( arg_pos) = args. iter ( ) . position ( |arg| arg. hir_id == expr. hir_id )
37+ && let Some ( ty) = fn_sig. inputs ( ) . get ( arg_pos)
38+ && !matches ! ( ty. kind( ) , ty:: Param ( _) )
39+ {
40+ return false ;
41+ }
42+
43+ // if it's neither of those, stay on the safe side and suggest turbofish,
44+ // even if it could work!
45+ true
46+ }
47+
48+ #[ derive( Copy , Clone ) ]
49+ struct Replacement {
50+ method_name : & ' static str ,
51+ has_args : bool ,
52+ has_generic_return : bool ,
53+ }
54+
55+ fn check_fold_with_op (
1556 cx : & LateContext < ' _ > ,
1657 expr : & hir:: Expr < ' _ > ,
17- init : & hir:: Expr < ' _ > ,
1858 acc : & hir:: Expr < ' _ > ,
1959 fold_span : Span ,
60+ op : hir:: BinOpKind ,
61+ replacement : Replacement ,
2062) {
21- fn check_fold_with_op (
22- cx : & LateContext < ' _ > ,
23- expr : & hir:: Expr < ' _ > ,
24- acc : & hir:: Expr < ' _ > ,
25- fold_span : Span ,
26- op : hir:: BinOpKind ,
27- replacement_method_name : & str ,
28- replacement_has_args : bool ,
29- ) {
30- if_chain ! {
31- // Extract the body of the closure passed to fold
32- if let hir:: ExprKind :: Closure ( & hir:: Closure { body, .. } ) = acc. kind;
33- let closure_body = cx. tcx. hir( ) . body( body) ;
34- let closure_expr = peel_blocks( closure_body. value) ;
35-
36- // Check if the closure body is of the form `acc <op> some_expr(x)`
37- if let hir:: ExprKind :: Binary ( ref bin_op, left_expr, right_expr) = closure_expr. kind;
38- if bin_op. node == op;
39-
40- // Extract the names of the two arguments to the closure
41- if let [ param_a, param_b] = closure_body. params;
42- if let PatKind :: Binding ( _, first_arg_id, ..) = strip_pat_refs( param_a. pat) . kind;
43- if let PatKind :: Binding ( _, second_arg_id, second_arg_ident, _) = strip_pat_refs( param_b. pat) . kind;
44-
45- if path_to_local_id( left_expr, first_arg_id) ;
46- if replacement_has_args || path_to_local_id( right_expr, second_arg_id) ;
47-
48- then {
49- let mut applicability = Applicability :: MachineApplicable ;
50- let sugg = if replacement_has_args {
51- format!(
52- "{replacement_method_name}(|{second_arg_ident}| {r})" ,
53- r = snippet_with_applicability( cx, right_expr. span, "EXPR" , & mut applicability) ,
54- )
55- } else {
56- format!(
57- "{replacement_method_name}()" ,
58- )
59- } ;
60-
61- span_lint_and_sugg(
62- cx,
63- UNNECESSARY_FOLD ,
64- fold_span. with_hi( expr. span. hi( ) ) ,
65- // TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
66- "this `.fold` can be written more succinctly using another method" ,
67- "try" ,
68- sugg,
69- applicability,
70- ) ;
71- }
63+ if_chain ! {
64+ // Extract the body of the closure passed to fold
65+ if let hir:: ExprKind :: Closure ( & hir:: Closure { body, .. } ) = acc. kind;
66+ let closure_body = cx. tcx. hir( ) . body( body) ;
67+ let closure_expr = peel_blocks( closure_body. value) ;
68+
69+ // Check if the closure body is of the form `acc <op> some_expr(x)`
70+ if let hir:: ExprKind :: Binary ( ref bin_op, left_expr, right_expr) = closure_expr. kind;
71+ if bin_op. node == op;
72+
73+ // Extract the names of the two arguments to the closure
74+ if let [ param_a, param_b] = closure_body. params;
75+ if let PatKind :: Binding ( _, first_arg_id, ..) = strip_pat_refs( param_a. pat) . kind;
76+ if let PatKind :: Binding ( _, second_arg_id, second_arg_ident, _) = strip_pat_refs( param_b. pat) . kind;
77+
78+ if path_to_local_id( left_expr, first_arg_id) ;
79+ if replacement. has_args || path_to_local_id( right_expr, second_arg_id) ;
80+
81+ then {
82+ let mut applicability = Applicability :: MachineApplicable ;
83+
84+ let turbofish = if replacement. has_generic_return {
85+ format!( "::<{}>" , cx. typeck_results( ) . expr_ty_adjusted( right_expr) . peel_refs( ) )
86+ } else {
87+ String :: new( )
88+ } ;
89+
90+ let sugg = if replacement. has_args {
91+ format!(
92+ "{method}{turbofish}(|{second_arg_ident}| {r})" ,
93+ method = replacement. method_name,
94+ r = snippet_with_applicability( cx, right_expr. span, "EXPR" , & mut applicability) ,
95+ )
96+ } else {
97+ format!(
98+ "{method}{turbofish}()" ,
99+ method = replacement. method_name,
100+ )
101+ } ;
102+
103+ span_lint_and_sugg(
104+ cx,
105+ UNNECESSARY_FOLD ,
106+ fold_span. with_hi( expr. span. hi( ) ) ,
107+ // TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
108+ "this `.fold` can be written more succinctly using another method" ,
109+ "try" ,
110+ sugg,
111+ applicability,
112+ ) ;
72113 }
73114 }
115+ }
74116
117+ pub ( super ) fn check (
118+ cx : & LateContext < ' _ > ,
119+ expr : & hir:: Expr < ' _ > ,
120+ init : & hir:: Expr < ' _ > ,
121+ acc : & hir:: Expr < ' _ > ,
122+ fold_span : Span ,
123+ ) {
75124 // Check that this is a call to Iterator::fold rather than just some function called fold
76125 if !is_trait_method ( cx, expr, sym:: Iterator ) {
77126 return ;
@@ -80,11 +129,59 @@ pub(super) fn check(
80129 // Check if the first argument to .fold is a suitable literal
81130 if let hir:: ExprKind :: Lit ( lit) = init. kind {
82131 match lit. node {
83- ast:: LitKind :: Bool ( false ) => check_fold_with_op ( cx, expr, acc, fold_span, hir:: BinOpKind :: Or , "any" , true ) ,
84- ast:: LitKind :: Bool ( true ) => check_fold_with_op ( cx, expr, acc, fold_span, hir:: BinOpKind :: And , "all" , true ) ,
85- ast:: LitKind :: Int ( 0 , _) => check_fold_with_op ( cx, expr, acc, fold_span, hir:: BinOpKind :: Add , "sum" , false ) ,
132+ ast:: LitKind :: Bool ( false ) => {
133+ check_fold_with_op (
134+ cx,
135+ expr,
136+ acc,
137+ fold_span,
138+ hir:: BinOpKind :: Or ,
139+ Replacement {
140+ has_args : true ,
141+ has_generic_return : false ,
142+ method_name : "any" ,
143+ } ,
144+ ) ;
145+ } ,
146+ ast:: LitKind :: Bool ( true ) => {
147+ check_fold_with_op (
148+ cx,
149+ expr,
150+ acc,
151+ fold_span,
152+ hir:: BinOpKind :: And ,
153+ Replacement {
154+ has_args : true ,
155+ has_generic_return : false ,
156+ method_name : "all" ,
157+ } ,
158+ ) ;
159+ } ,
160+ ast:: LitKind :: Int ( 0 , _) => check_fold_with_op (
161+ cx,
162+ expr,
163+ acc,
164+ fold_span,
165+ hir:: BinOpKind :: Add ,
166+ Replacement {
167+ has_args : false ,
168+ has_generic_return : needs_turbofish ( cx, expr) ,
169+ method_name : "sum" ,
170+ } ,
171+ ) ,
86172 ast:: LitKind :: Int ( 1 , _) => {
87- check_fold_with_op ( cx, expr, acc, fold_span, hir:: BinOpKind :: Mul , "product" , false ) ;
173+ check_fold_with_op (
174+ cx,
175+ expr,
176+ acc,
177+ fold_span,
178+ hir:: BinOpKind :: Mul ,
179+ Replacement {
180+ has_args : false ,
181+ has_generic_return : needs_turbofish ( cx, expr) ,
182+ method_name : "product" ,
183+ } ,
184+ ) ;
88185 } ,
89186 _ => ( ) ,
90187 }
0 commit comments