1
1
use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
2
use clippy_utils:: higher:: { get_vec_init_kind, VecInitKind } ;
3
3
use clippy_utils:: source:: snippet;
4
- use clippy_utils:: { path_to_local, path_to_local_id} ;
5
- use if_chain:: if_chain;
4
+ use clippy_utils:: visitors:: for_each_local_use_after_expr;
5
+ use clippy_utils:: { get_parent_expr, path_to_local_id} ;
6
+ use core:: ops:: ControlFlow ;
6
7
use rustc_errors:: Applicability ;
7
- use rustc_hir:: { BindingAnnotation , Block , Expr , ExprKind , HirId , Local , PatKind , Stmt , StmtKind } ;
8
+ use rustc_hir:: def:: Res ;
9
+ use rustc_hir:: {
10
+ BindingAnnotation , Block , Expr , ExprKind , HirId , Local , Mutability , PatKind , QPath , Stmt , StmtKind , UnOp ,
11
+ } ;
8
12
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
9
13
use rustc_middle:: lint:: in_external_macro;
10
14
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
11
- use rustc_span:: Span ;
15
+ use rustc_span:: { Span , Symbol } ;
12
16
13
17
declare_clippy_lint ! {
14
18
/// ### What it does
15
19
/// Checks for calls to `push` immediately after creating a new `Vec`.
16
20
///
21
+ /// If the `Vec` is created using `with_capacity` this will only lint if the capacity is a
22
+ /// constant and the number of pushes is greater than or equal to the initial capacity.
23
+ ///
24
+ /// If the `Vec` is extended after the initial sequence of pushes and it was default initialized
25
+ /// then this will only lint after there were at least four pushes. This number may change in
26
+ /// the future.
27
+ ///
17
28
/// ### Why is this bad?
18
29
/// The `vec![]` macro is both more performant and easier to read than
19
30
/// multiple `push` calls.
@@ -43,26 +54,88 @@ pub struct VecInitThenPush {
43
54
struct VecPushSearcher {
44
55
local_id : HirId ,
45
56
init : VecInitKind ,
46
- lhs_is_local : bool ,
47
- lhs_span : Span ,
57
+ lhs_is_let : bool ,
58
+ let_ty_span : Option < Span > ,
59
+ name : Symbol ,
48
60
err_span : Span ,
49
- found : u64 ,
61
+ found : u128 ,
62
+ last_push_expr : HirId ,
50
63
}
51
64
impl VecPushSearcher {
52
65
fn display_err ( & self , cx : & LateContext < ' _ > ) {
53
- match self . init {
66
+ let required_pushes_before_extension = match self . init {
54
67
_ if self . found == 0 => return ,
55
- VecInitKind :: WithLiteralCapacity ( x) if x > self . found => return ,
68
+ VecInitKind :: WithConstCapacity ( x) if x > self . found => return ,
69
+ VecInitKind :: WithConstCapacity ( x) => x,
56
70
VecInitKind :: WithExprCapacity ( _) => return ,
57
- _ => ( ) ,
71
+ _ => 3 ,
58
72
} ;
59
73
60
- let mut s = if self . lhs_is_local {
74
+ let mut needs_mut = false ;
75
+ let res = for_each_local_use_after_expr ( cx, self . local_id , self . last_push_expr , |e| {
76
+ let Some ( parent) = get_parent_expr ( cx, e) else {
77
+ return ControlFlow :: Continue ( ( ) )
78
+ } ;
79
+ let adjusted_ty = cx. typeck_results ( ) . expr_ty_adjusted ( e) ;
80
+ let adjusted_mut = adjusted_ty. ref_mutability ( ) . unwrap_or ( Mutability :: Not ) ;
81
+ needs_mut |= adjusted_mut == Mutability :: Mut ;
82
+ match parent. kind {
83
+ ExprKind :: AddrOf ( _, Mutability :: Mut , _) => {
84
+ needs_mut = true ;
85
+ return ControlFlow :: Break ( true ) ;
86
+ } ,
87
+ ExprKind :: Unary ( UnOp :: Deref , _) | ExprKind :: Index ( ..) if !needs_mut => {
88
+ let mut last_place = parent;
89
+ while let Some ( parent) = get_parent_expr ( cx, parent) {
90
+ if matches ! ( parent. kind, ExprKind :: Unary ( UnOp :: Deref , _) | ExprKind :: Field ( ..) )
91
+ || matches ! ( parent. kind, ExprKind :: Index ( e, _) if e. hir_id == last_place. hir_id)
92
+ {
93
+ last_place = parent;
94
+ } else {
95
+ break ;
96
+ }
97
+ }
98
+ needs_mut |= cx. typeck_results ( ) . expr_ty_adjusted ( last_place) . ref_mutability ( )
99
+ == Some ( Mutability :: Mut )
100
+ || get_parent_expr ( cx, last_place)
101
+ . map_or ( false , |e| matches ! ( e. kind, ExprKind :: AddrOf ( _, Mutability :: Mut , _) ) ) ;
102
+ } ,
103
+ ExprKind :: MethodCall ( _, [ recv, ..] , _)
104
+ if recv. hir_id == e. hir_id
105
+ && adjusted_mut == Mutability :: Mut
106
+ && !adjusted_ty. peel_refs ( ) . is_slice ( ) =>
107
+ {
108
+ // No need to set `needs_mut` to true. The receiver will be either explicitly borrowed, or it will
109
+ // be implicitly borrowed via an adjustment. Both of these cases are already handled by this point.
110
+ return ControlFlow :: Break ( true ) ;
111
+ } ,
112
+ ExprKind :: Assign ( lhs, ..) if e. hir_id == lhs. hir_id => {
113
+ needs_mut = true ;
114
+ return ControlFlow :: Break ( false ) ;
115
+ } ,
116
+ _ => ( ) ,
117
+ }
118
+ ControlFlow :: Continue ( ( ) )
119
+ } ) ;
120
+
121
+ // Avoid allocating small `Vec`s when they'll be extended right after.
122
+ if res == ControlFlow :: Break ( true ) && self . found <= required_pushes_before_extension {
123
+ return ;
124
+ }
125
+
126
+ let mut s = if self . lhs_is_let {
61
127
String :: from ( "let " )
62
128
} else {
63
129
String :: new ( )
64
130
} ;
65
- s. push_str ( & snippet ( cx, self . lhs_span , ".." ) ) ;
131
+ if needs_mut {
132
+ s. push_str ( "mut " ) ;
133
+ }
134
+ s. push_str ( self . name . as_str ( ) ) ;
135
+ if let Some ( span) = self . let_ty_span {
136
+ s. push_str ( ": " ) ;
137
+ s. push_str ( & snippet ( cx, span, "_" ) ) ;
138
+ }
66
139
s. push_str ( " = vec![..];" ) ;
67
140
68
141
span_lint_and_sugg (
@@ -83,60 +156,63 @@ impl<'tcx> LateLintPass<'tcx> for VecInitThenPush {
83
156
}
84
157
85
158
fn check_local ( & mut self , cx : & LateContext < ' tcx > , local : & ' tcx Local < ' tcx > ) {
86
- if_chain ! {
87
- if !in_external_macro( cx. sess( ) , local. span) ;
88
- if let Some ( init) = local. init;
89
- if let PatKind :: Binding ( BindingAnnotation :: Mutable , id, _, None ) = local. pat. kind;
90
- if let Some ( init_kind) = get_vec_init_kind( cx, init) ;
91
- then {
92
- self . searcher = Some ( VecPushSearcher {
93
- local_id: id,
94
- init: init_kind,
95
- lhs_is_local: true ,
96
- lhs_span: local. ty. map_or( local. pat. span, |t| local. pat. span. to( t. span) ) ,
97
- err_span: local. span,
98
- found: 0 ,
99
- } ) ;
100
- }
159
+ if let Some ( init_expr) = local. init
160
+ && let PatKind :: Binding ( BindingAnnotation :: Mutable , id, name, None ) = local. pat . kind
161
+ && !in_external_macro ( cx. sess ( ) , local. span )
162
+ && let Some ( init) = get_vec_init_kind ( cx, init_expr)
163
+ && !matches ! ( init, VecInitKind :: WithExprCapacity ( _) )
164
+ {
165
+ self . searcher = Some ( VecPushSearcher {
166
+ local_id : id,
167
+ init,
168
+ lhs_is_let : true ,
169
+ name : name. name ,
170
+ let_ty_span : local. ty . map ( |ty| ty. span ) ,
171
+ err_span : local. span ,
172
+ found : 0 ,
173
+ last_push_expr : init_expr. hir_id ,
174
+ } ) ;
101
175
}
102
176
}
103
177
104
178
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
105
- if_chain ! {
106
- if self . searcher. is_none( ) ;
107
- if !in_external_macro( cx. sess( ) , expr. span) ;
108
- if let ExprKind :: Assign ( left, right, _) = expr. kind;
109
- if let Some ( id) = path_to_local( left) ;
110
- if let Some ( init_kind) = get_vec_init_kind( cx, right) ;
111
- then {
112
- self . searcher = Some ( VecPushSearcher {
113
- local_id: id,
114
- init: init_kind,
115
- lhs_is_local: false ,
116
- lhs_span: left. span,
117
- err_span: expr. span,
118
- found: 0 ,
119
- } ) ;
120
- }
179
+ if self . searcher . is_none ( )
180
+ && let ExprKind :: Assign ( left, right, _) = expr. kind
181
+ && let ExprKind :: Path ( QPath :: Resolved ( None , path) ) = left. kind
182
+ && let [ name] = & path. segments
183
+ && let Res :: Local ( id) = path. res
184
+ && !in_external_macro ( cx. sess ( ) , expr. span )
185
+ && let Some ( init) = get_vec_init_kind ( cx, right)
186
+ && !matches ! ( init, VecInitKind :: WithExprCapacity ( _) )
187
+ {
188
+ self . searcher = Some ( VecPushSearcher {
189
+ local_id : id,
190
+ init,
191
+ lhs_is_let : false ,
192
+ let_ty_span : None ,
193
+ name : name. ident . name ,
194
+ err_span : expr. span ,
195
+ found : 0 ,
196
+ last_push_expr : expr. hir_id ,
197
+ } ) ;
121
198
}
122
199
}
123
200
124
201
fn check_stmt ( & mut self , cx : & LateContext < ' tcx > , stmt : & ' tcx Stmt < ' _ > ) {
125
202
if let Some ( searcher) = self . searcher . take ( ) {
126
- if_chain ! {
127
- if let StmtKind :: Expr ( expr) | StmtKind :: Semi ( expr) = stmt. kind;
128
- if let ExprKind :: MethodCall ( path, [ self_arg, _] , _) = expr. kind;
129
- if path_to_local_id( self_arg, searcher. local_id) ;
130
- if path. ident. name. as_str( ) == "push" ;
131
- then {
132
- self . searcher = Some ( VecPushSearcher {
133
- found: searcher. found + 1 ,
134
- err_span: searcher. err_span. to( stmt. span) ,
135
- .. searcher
136
- } ) ;
137
- } else {
138
- searcher. display_err( cx) ;
139
- }
203
+ if let StmtKind :: Expr ( expr) | StmtKind :: Semi ( expr) = stmt. kind
204
+ && let ExprKind :: MethodCall ( name, [ self_arg, _] , _) = expr. kind
205
+ && path_to_local_id ( self_arg, searcher. local_id )
206
+ && name. ident . as_str ( ) == "push"
207
+ {
208
+ self . searcher = Some ( VecPushSearcher {
209
+ found : searcher. found + 1 ,
210
+ err_span : searcher. err_span . to ( stmt. span ) ,
211
+ last_push_expr : expr. hir_id ,
212
+ .. searcher
213
+ } ) ;
214
+ } else {
215
+ searcher. display_err ( cx) ;
140
216
}
141
217
}
142
218
}
0 commit comments