@@ -2,7 +2,8 @@ use clippy_utils::diagnostics::span_lint_and_then;
2
2
use clippy_utils:: sugg:: Sugg ;
3
3
use clippy_utils:: ty:: is_type_diagnostic_item;
4
4
use clippy_utils:: {
5
- get_enclosing_block, is_integer_literal, is_path_diagnostic_item, path_to_local, path_to_local_id, SpanlessEq ,
5
+ get_enclosing_block, is_expr_path_def_path, is_integer_literal, is_path_diagnostic_item, path_to_local,
6
+ path_to_local_id, paths, SpanlessEq ,
6
7
} ;
7
8
use if_chain:: if_chain;
8
9
use rustc_errors:: Applicability ;
@@ -60,7 +61,11 @@ struct VecAllocation<'tcx> {
60
61
61
62
/// Reference to the expression used as argument on `with_capacity` call. This is used
62
63
/// to only match slow zero-filling idioms of the same length than vector initialization.
63
- len_expr : & ' tcx Expr < ' tcx > ,
64
+ ///
65
+ /// Initially set to `None` if initialized with `Vec::new()`, but will always be `Some(_)` by
66
+ /// the time the visitor has looked through the enclosing block and found a slow
67
+ /// initialization, so it is safe to unwrap later at lint time.
68
+ size_expr : Option < & ' tcx Expr < ' tcx > > ,
64
69
}
65
70
66
71
/// Type of slow initialization
@@ -77,18 +82,14 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
77
82
// Matches initialization on reassignments. For example: `vec = Vec::with_capacity(100)`
78
83
if_chain ! {
79
84
if let ExprKind :: Assign ( left, right, _) = expr. kind;
80
-
81
- // Extract variable
82
85
if let Some ( local_id) = path_to_local( left) ;
83
-
84
- // Extract len argument
85
- if let Some ( len_arg) = Self :: is_vec_with_capacity( cx, right) ;
86
+ if let Some ( size_expr) = Self :: as_vec_initializer( cx, right) ;
86
87
87
88
then {
88
89
let vi = VecAllocation {
89
90
local_id,
90
91
allocation_expr: right,
91
- len_expr : len_arg ,
92
+ size_expr ,
92
93
} ;
93
94
94
95
Self :: search_initialization( cx, vi, expr. hir_id) ;
@@ -98,17 +99,18 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
98
99
99
100
fn check_stmt ( & mut self , cx : & LateContext < ' tcx > , stmt : & ' tcx Stmt < ' _ > ) {
100
101
// Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)`
102
+ // or `Vec::new()`
101
103
if_chain ! {
102
104
if let StmtKind :: Local ( local) = stmt. kind;
103
105
if let PatKind :: Binding ( BindingAnnotation :: MUT , local_id, _, None ) = local. pat. kind;
104
106
if let Some ( init) = local. init;
105
- if let Some ( len_arg ) = Self :: is_vec_with_capacity ( cx, init) ;
107
+ if let Some ( size_expr ) = Self :: as_vec_initializer ( cx, init) ;
106
108
107
109
then {
108
110
let vi = VecAllocation {
109
111
local_id,
110
112
allocation_expr: init,
111
- len_expr : len_arg ,
113
+ size_expr ,
112
114
} ;
113
115
114
116
Self :: search_initialization( cx, vi, stmt. hir_id) ;
@@ -118,6 +120,25 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
118
120
}
119
121
120
122
impl SlowVectorInit {
123
+ /// Given an expression, returns:
124
+ /// - `Some(Some(size))` if it is a function call to `Vec::with_capacity(size)`
125
+ /// - `Some(None)` if it is a call to `Vec::new()`
126
+ /// - `None` if it is neither
127
+ #[ allow(
128
+ clippy:: option_option,
129
+ reason = "outer option is immediately removed at call site and the inner option is kept around, \
130
+ so extracting into a dedicated enum seems unnecessarily complicated"
131
+ ) ]
132
+ fn as_vec_initializer < ' tcx > ( cx : & LateContext < ' _ > , expr : & ' tcx Expr < ' tcx > ) -> Option < Option < & ' tcx Expr < ' tcx > > > {
133
+ if let Some ( len_expr) = Self :: is_vec_with_capacity ( cx, expr) {
134
+ Some ( Some ( len_expr) )
135
+ } else if Self :: is_vec_new ( cx, expr) {
136
+ Some ( None )
137
+ } else {
138
+ None
139
+ }
140
+ }
141
+
121
142
/// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression
122
143
/// of the first argument of `with_capacity` call if it matches or `None` if it does not.
123
144
fn is_vec_with_capacity < ' tcx > ( cx : & LateContext < ' _ > , expr : & Expr < ' tcx > ) -> Option < & ' tcx Expr < ' tcx > > {
@@ -134,6 +155,14 @@ impl SlowVectorInit {
134
155
}
135
156
}
136
157
158
+ /// Checks if the given expression is `Vec::new()`
159
+ fn is_vec_new ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
160
+ matches ! (
161
+ expr. kind,
162
+ ExprKind :: Call ( func, _) if is_expr_path_def_path( cx, func, & paths:: VEC_NEW )
163
+ )
164
+ }
165
+
137
166
/// Search initialization for the given vector
138
167
fn search_initialization < ' tcx > ( cx : & LateContext < ' tcx > , vec_alloc : VecAllocation < ' tcx > , parent_node : HirId ) {
139
168
let enclosing_body = get_enclosing_block ( cx, parent_node) ;
@@ -169,12 +198,18 @@ impl SlowVectorInit {
169
198
}
170
199
171
200
fn emit_lint ( cx : & LateContext < ' _ > , slow_fill : & Expr < ' _ > , vec_alloc : & VecAllocation < ' _ > , msg : & str ) {
172
- let len_expr = Sugg :: hir ( cx, vec_alloc. len_expr , "len" ) ;
201
+ let len_expr = Sugg :: hir (
202
+ cx,
203
+ vec_alloc
204
+ . size_expr
205
+ . expect ( "length expression must be set by this point" ) ,
206
+ "len" ,
207
+ ) ;
173
208
174
209
span_lint_and_then ( cx, SLOW_VECTOR_INITIALIZATION , slow_fill. span , msg, |diag| {
175
210
diag. span_suggestion (
176
211
vec_alloc. allocation_expr . span ,
177
- "consider replace allocation with" ,
212
+ "consider replacing this with" ,
178
213
format ! ( "vec![0; {len_expr}]" ) ,
179
214
Applicability :: Unspecified ,
180
215
) ;
@@ -214,36 +249,45 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
214
249
}
215
250
216
251
/// Checks if the given expression is resizing a vector with 0
217
- fn search_slow_resize_filling ( & mut self , expr : & ' tcx Expr < ' _ > ) {
252
+ fn search_slow_resize_filling ( & mut self , expr : & ' tcx Expr < ' tcx > ) {
218
253
if self . initialization_found
219
254
&& let ExprKind :: MethodCall ( path, self_arg, [ len_arg, fill_arg] , _) = expr. kind
220
255
&& path_to_local_id ( self_arg, self . vec_alloc . local_id )
221
256
&& path. ident . name == sym ! ( resize)
222
257
// Check that is filled with 0
223
- && is_integer_literal ( fill_arg, 0 ) {
224
- // Check that len expression is equals to `with_capacity` expression
225
- if SpanlessEq :: new ( self . cx ) . eq_expr ( len_arg, self . vec_alloc . len_expr ) {
226
- self . slow_expression = Some ( InitializationType :: Resize ( expr) ) ;
227
- } else if let ExprKind :: MethodCall ( path, ..) = len_arg. kind && path. ident . as_str ( ) == "capacity" {
228
- self . slow_expression = Some ( InitializationType :: Resize ( expr) ) ;
229
- }
258
+ && is_integer_literal ( fill_arg, 0 )
259
+ {
260
+ let is_matching_resize = if let Some ( size_expr) = self . vec_alloc . size_expr {
261
+ // If we have a size expression, check that it is equal to what's passed to `resize`
262
+ SpanlessEq :: new ( self . cx ) . eq_expr ( len_arg, size_expr)
263
+ || matches ! ( len_arg. kind, ExprKind :: MethodCall ( path, ..) if path. ident. as_str( ) == "capacity" )
264
+ } else {
265
+ self . vec_alloc . size_expr = Some ( len_arg) ;
266
+ true
267
+ } ;
268
+
269
+ if is_matching_resize {
270
+ self . slow_expression = Some ( InitializationType :: Resize ( expr) ) ;
230
271
}
272
+ }
231
273
}
232
274
233
275
/// Returns `true` if give expression is `repeat(0).take(...)`
234
- fn is_repeat_take ( & self , expr : & Expr < ' _ > ) -> bool {
276
+ fn is_repeat_take ( & mut self , expr : & ' tcx Expr < ' tcx > ) -> bool {
235
277
if_chain ! {
236
278
if let ExprKind :: MethodCall ( take_path, recv, [ len_arg, ..] , _) = expr. kind;
237
279
if take_path. ident. name == sym!( take) ;
238
280
// Check that take is applied to `repeat(0)`
239
281
if self . is_repeat_zero( recv) ;
240
282
then {
241
- // Check that len expression is equals to `with_capacity` expression
242
- if SpanlessEq :: new( self . cx) . eq_expr( len_arg, self . vec_alloc. len_expr) {
243
- return true ;
244
- } else if let ExprKind :: MethodCall ( path, ..) = len_arg. kind && path. ident. as_str( ) == "capacity" {
245
- return true ;
283
+ if let Some ( size_expr) = self . vec_alloc. size_expr {
284
+ // Check that len expression is equals to `with_capacity` expression
285
+ return SpanlessEq :: new( self . cx) . eq_expr( len_arg, size_expr)
286
+ || matches!( len_arg. kind, ExprKind :: MethodCall ( path, ..) if path. ident. as_str( ) == "capacity" )
246
287
}
288
+
289
+ self . vec_alloc. size_expr = Some ( len_arg) ;
290
+ return true ;
247
291
}
248
292
}
249
293
0 commit comments