@@ -2,7 +2,8 @@ use clippy_utils::diagnostics::span_lint_and_then;
22use clippy_utils:: sugg:: Sugg ;
33use clippy_utils:: ty:: is_type_diagnostic_item;
44use 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 ,
67} ;
78use if_chain:: if_chain;
89use rustc_errors:: Applicability ;
@@ -60,7 +61,11 @@ struct VecAllocation<'tcx> {
6061
6162 /// Reference to the expression used as argument on `with_capacity` call. This is used
6263 /// 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 > > ,
6469}
6570
6671/// Type of slow initialization
@@ -77,18 +82,14 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
7782 // Matches initialization on reassignments. For example: `vec = Vec::with_capacity(100)`
7883 if_chain ! {
7984 if let ExprKind :: Assign ( left, right, _) = expr. kind;
80-
81- // Extract variable
8285 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) ;
8687
8788 then {
8889 let vi = VecAllocation {
8990 local_id,
9091 allocation_expr: right,
91- len_expr : len_arg ,
92+ size_expr ,
9293 } ;
9394
9495 Self :: search_initialization( cx, vi, expr. hir_id) ;
@@ -98,17 +99,18 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
9899
99100 fn check_stmt ( & mut self , cx : & LateContext < ' tcx > , stmt : & ' tcx Stmt < ' _ > ) {
100101 // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)`
102+ // or `Vec::new()`
101103 if_chain ! {
102104 if let StmtKind :: Local ( local) = stmt. kind;
103105 if let PatKind :: Binding ( BindingAnnotation :: MUT , local_id, _, None ) = local. pat. kind;
104106 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) ;
106108
107109 then {
108110 let vi = VecAllocation {
109111 local_id,
110112 allocation_expr: init,
111- len_expr : len_arg ,
113+ size_expr ,
112114 } ;
113115
114116 Self :: search_initialization( cx, vi, stmt. hir_id) ;
@@ -118,6 +120,25 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
118120}
119121
120122impl 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+
121142 /// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression
122143 /// of the first argument of `with_capacity` call if it matches or `None` if it does not.
123144 fn is_vec_with_capacity < ' tcx > ( cx : & LateContext < ' _ > , expr : & Expr < ' tcx > ) -> Option < & ' tcx Expr < ' tcx > > {
@@ -134,6 +155,14 @@ impl SlowVectorInit {
134155 }
135156 }
136157
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+
137166 /// Search initialization for the given vector
138167 fn search_initialization < ' tcx > ( cx : & LateContext < ' tcx > , vec_alloc : VecAllocation < ' tcx > , parent_node : HirId ) {
139168 let enclosing_body = get_enclosing_block ( cx, parent_node) ;
@@ -169,12 +198,18 @@ impl SlowVectorInit {
169198 }
170199
171200 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+ ) ;
173208
174209 span_lint_and_then ( cx, SLOW_VECTOR_INITIALIZATION , slow_fill. span , msg, |diag| {
175210 diag. span_suggestion (
176211 vec_alloc. allocation_expr . span ,
177- "consider replace allocation with" ,
212+ "consider replacing this with" ,
178213 format ! ( "vec![0; {len_expr}]" ) ,
179214 Applicability :: Unspecified ,
180215 ) ;
@@ -214,36 +249,45 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
214249 }
215250
216251 /// 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 > ) {
218253 if self . initialization_found
219254 && let ExprKind :: MethodCall ( path, self_arg, [ len_arg, fill_arg] , _) = expr. kind
220255 && path_to_local_id ( self_arg, self . vec_alloc . local_id )
221256 && path. ident . name == sym ! ( resize)
222257 // 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) ) ;
230271 }
272+ }
231273 }
232274
233275 /// 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 {
235277 if_chain ! {
236278 if let ExprKind :: MethodCall ( take_path, recv, [ len_arg, ..] , _) = expr. kind;
237279 if take_path. ident. name == sym!( take) ;
238280 // Check that take is applied to `repeat(0)`
239281 if self . is_repeat_zero( recv) ;
240282 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" )
246287 }
288+
289+ self . vec_alloc. size_expr = Some ( len_arg) ;
290+ return true ;
247291 }
248292 }
249293
0 commit comments