1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
2
use clippy_utils:: sugg:: Sugg ;
3
- use clippy_utils:: ty:: is_type_diagnostic_item;
4
3
use clippy_utils:: {
5
- get_enclosing_block, is_integer_literal, is_path_diagnostic_item, path_to_local, path_to_local_id, SpanlessEq ,
4
+ get_enclosing_block, is_expr_path_def_path, is_integer_literal, is_path_diagnostic_item, path_to_local,
5
+ path_to_local_id, paths, SpanlessEq ,
6
6
} ;
7
7
use if_chain:: if_chain;
8
8
use rustc_errors:: Applicability ;
9
9
use rustc_hir:: intravisit:: { walk_block, walk_expr, walk_stmt, Visitor } ;
10
- use rustc_hir:: { BindingAnnotation , Block , Expr , ExprKind , HirId , PatKind , QPath , Stmt , StmtKind } ;
10
+ use rustc_hir:: { BindingAnnotation , Block , Expr , ExprKind , HirId , PatKind , Stmt , StmtKind } ;
11
11
use rustc_lint:: { LateContext , LateLintPass } ;
12
12
use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
13
13
use rustc_span:: symbol:: sym;
@@ -60,7 +60,24 @@ struct VecAllocation<'tcx> {
60
60
61
61
/// Reference to the expression used as argument on `with_capacity` call. This is used
62
62
/// to only match slow zero-filling idioms of the same length than vector initialization.
63
- len_expr : & ' tcx Expr < ' tcx > ,
63
+ size_expr : InitializedSize < ' tcx > ,
64
+ }
65
+
66
+ /// Initializer for the creation of the vector.
67
+ ///
68
+ /// When `Vec::with_capacity(size)` is found, the `size` expression will be in
69
+ /// `InitializedSize::Initialized`.
70
+ ///
71
+ /// Otherwise, for `Vec::new()` calls, there is no allocation initializer yet, so
72
+ /// `InitializedSize::Uninitialized` is used.
73
+ /// Later, when a call to `.resize(size, 0)` or similar is found, it's set
74
+ /// to `InitializedSize::Initialized(size)`.
75
+ ///
76
+ /// Since it will be set to `InitializedSize::Initialized(size)` when a slow initialization is
77
+ /// found, it is always safe to "unwrap" it at lint time.
78
+ enum InitializedSize < ' tcx > {
79
+ Initialized ( & ' tcx Expr < ' tcx > ) ,
80
+ Uninitialized ,
64
81
}
65
82
66
83
/// Type of slow initialization
@@ -77,18 +94,14 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
77
94
// Matches initialization on reassignments. For example: `vec = Vec::with_capacity(100)`
78
95
if_chain ! {
79
96
if let ExprKind :: Assign ( left, right, _) = expr. kind;
80
-
81
- // Extract variable
82
97
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) ;
98
+ if let Some ( size_expr) = Self :: as_vec_initializer( cx, right) ;
86
99
87
100
then {
88
101
let vi = VecAllocation {
89
102
local_id,
90
103
allocation_expr: right,
91
- len_expr : len_arg ,
104
+ size_expr ,
92
105
} ;
93
106
94
107
Self :: search_initialization( cx, vi, expr. hir_id) ;
@@ -98,17 +111,18 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
98
111
99
112
fn check_stmt ( & mut self , cx : & LateContext < ' tcx > , stmt : & ' tcx Stmt < ' _ > ) {
100
113
// Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)`
114
+ // or `Vec::new()`
101
115
if_chain ! {
102
116
if let StmtKind :: Local ( local) = stmt. kind;
103
117
if let PatKind :: Binding ( BindingAnnotation :: MUT , local_id, _, None ) = local. pat. kind;
104
118
if let Some ( init) = local. init;
105
- if let Some ( len_arg ) = Self :: is_vec_with_capacity ( cx, init) ;
119
+ if let Some ( size_expr ) = Self :: as_vec_initializer ( cx, init) ;
106
120
107
121
then {
108
122
let vi = VecAllocation {
109
123
local_id,
110
124
allocation_expr: init,
111
- len_expr : len_arg ,
125
+ size_expr ,
112
126
} ;
113
127
114
128
Self :: search_initialization( cx, vi, stmt. hir_id) ;
@@ -118,19 +132,20 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
118
132
}
119
133
120
134
impl SlowVectorInit {
121
- /// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression
122
- /// of the first argument of `with_capacity` call if it matches or `None` if it does not.
123
- fn is_vec_with_capacity < ' tcx > ( cx : & LateContext < ' _ > , expr : & Expr < ' tcx > ) -> Option < & ' tcx Expr < ' tcx > > {
124
- if_chain ! {
125
- if let ExprKind :: Call ( func, [ arg] ) = expr. kind;
126
- if let ExprKind :: Path ( QPath :: TypeRelative ( ty, name) ) = func. kind;
127
- if name. ident. as_str( ) == "with_capacity" ;
128
- if is_type_diagnostic_item( cx, cx. typeck_results( ) . node_type( ty. hir_id) , sym:: Vec ) ;
129
- then {
130
- Some ( arg)
131
- } else {
132
- None
133
- }
135
+ /// Looks for `Vec::with_capacity(size)` or `Vec::new()` calls and returns the initialized size,
136
+ /// if any. More specifically, it returns:
137
+ /// - `Some(InitializedSize::Initialized(size))` for `Vec::with_capacity(size)`
138
+ /// - `Some(InitializedSize::Uninitialized)` for `Vec::new()`
139
+ /// - `None` for other, unrelated kinds of expressions
140
+ fn as_vec_initializer < ' tcx > ( cx : & LateContext < ' _ > , expr : & ' tcx Expr < ' tcx > ) -> Option < InitializedSize < ' tcx > > {
141
+ if let ExprKind :: Call ( func, [ len_expr] ) = expr. kind
142
+ && is_expr_path_def_path ( cx, func, & paths:: VEC_WITH_CAPACITY )
143
+ {
144
+ Some ( InitializedSize :: Initialized ( len_expr) )
145
+ } else if matches ! ( expr. kind, ExprKind :: Call ( func, _) if is_expr_path_def_path( cx, func, & paths:: VEC_NEW ) ) {
146
+ Some ( InitializedSize :: Uninitialized )
147
+ } else {
148
+ None
134
149
}
135
150
}
136
151
@@ -169,12 +184,19 @@ impl SlowVectorInit {
169
184
}
170
185
171
186
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" ) ;
187
+ let len_expr = Sugg :: hir (
188
+ cx,
189
+ match vec_alloc. size_expr {
190
+ InitializedSize :: Initialized ( expr) => expr,
191
+ InitializedSize :: Uninitialized => unreachable ! ( "size expression must be set by this point" ) ,
192
+ } ,
193
+ "len" ,
194
+ ) ;
173
195
174
196
span_lint_and_then ( cx, SLOW_VECTOR_INITIALIZATION , slow_fill. span , msg, |diag| {
175
197
diag. span_suggestion (
176
198
vec_alloc. allocation_expr . span ,
177
- "consider replace allocation with" ,
199
+ "consider replacing this with" ,
178
200
format ! ( "vec![0; {len_expr}]" ) ,
179
201
Applicability :: Unspecified ,
180
202
) ;
@@ -214,36 +236,45 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
214
236
}
215
237
216
238
/// Checks if the given expression is resizing a vector with 0
217
- fn search_slow_resize_filling ( & mut self , expr : & ' tcx Expr < ' _ > ) {
239
+ fn search_slow_resize_filling ( & mut self , expr : & ' tcx Expr < ' tcx > ) {
218
240
if self . initialization_found
219
241
&& let ExprKind :: MethodCall ( path, self_arg, [ len_arg, fill_arg] , _) = expr. kind
220
242
&& path_to_local_id ( self_arg, self . vec_alloc . local_id )
221
243
&& path. ident . name == sym ! ( resize)
222
244
// 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
- }
245
+ && is_integer_literal ( fill_arg, 0 )
246
+ {
247
+ let is_matching_resize = if let InitializedSize :: Initialized ( size_expr) = self . vec_alloc . size_expr {
248
+ // If we have a size expression, check that it is equal to what's passed to `resize`
249
+ SpanlessEq :: new ( self . cx ) . eq_expr ( len_arg, size_expr)
250
+ || matches ! ( len_arg. kind, ExprKind :: MethodCall ( path, ..) if path. ident. as_str( ) == "capacity" )
251
+ } else {
252
+ self . vec_alloc . size_expr = InitializedSize :: Initialized ( len_arg) ;
253
+ true
254
+ } ;
255
+
256
+ if is_matching_resize {
257
+ self . slow_expression = Some ( InitializationType :: Resize ( expr) ) ;
230
258
}
259
+ }
231
260
}
232
261
233
262
/// Returns `true` if give expression is `repeat(0).take(...)`
234
- fn is_repeat_take ( & self , expr : & Expr < ' _ > ) -> bool {
263
+ fn is_repeat_take ( & mut self , expr : & ' tcx Expr < ' tcx > ) -> bool {
235
264
if_chain ! {
236
265
if let ExprKind :: MethodCall ( take_path, recv, [ len_arg, ..] , _) = expr. kind;
237
266
if take_path. ident. name == sym!( take) ;
238
267
// Check that take is applied to `repeat(0)`
239
268
if self . is_repeat_zero( recv) ;
240
269
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 ;
270
+ if let InitializedSize :: Initialized ( size_expr) = self . vec_alloc. size_expr {
271
+ // Check that len expression is equals to `with_capacity` expression
272
+ return SpanlessEq :: new( self . cx) . eq_expr( len_arg, size_expr)
273
+ || matches!( len_arg. kind, ExprKind :: MethodCall ( path, ..) if path. ident. as_str( ) == "capacity" )
246
274
}
275
+
276
+ self . vec_alloc. size_expr = InitializedSize :: Initialized ( len_arg) ;
277
+ return true ;
247
278
}
248
279
}
249
280
0 commit comments