11use clippy_utils:: diagnostics:: span_lint_and_then;
22use clippy_utils:: sugg:: Sugg ;
3- use clippy_utils:: ty:: is_type_diagnostic_item;
43use 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 ,
66} ;
77use if_chain:: if_chain;
88use rustc_errors:: Applicability ;
99use 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 } ;
1111use rustc_lint:: { LateContext , LateLintPass } ;
1212use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1313use rustc_span:: symbol:: sym;
@@ -60,7 +60,24 @@ struct VecAllocation<'tcx> {
6060
6161 /// Reference to the expression used as argument on `with_capacity` call. This is used
6262 /// 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 ,
6481}
6582
6683/// Type of slow initialization
@@ -77,18 +94,14 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
7794 // Matches initialization on reassignments. For example: `vec = Vec::with_capacity(100)`
7895 if_chain ! {
7996 if let ExprKind :: Assign ( left, right, _) = expr. kind;
80-
81- // Extract variable
8297 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) ;
8699
87100 then {
88101 let vi = VecAllocation {
89102 local_id,
90103 allocation_expr: right,
91- len_expr : len_arg ,
104+ size_expr ,
92105 } ;
93106
94107 Self :: search_initialization( cx, vi, expr. hir_id) ;
@@ -98,17 +111,18 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
98111
99112 fn check_stmt ( & mut self , cx : & LateContext < ' tcx > , stmt : & ' tcx Stmt < ' _ > ) {
100113 // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)`
114+ // or `Vec::new()`
101115 if_chain ! {
102116 if let StmtKind :: Local ( local) = stmt. kind;
103117 if let PatKind :: Binding ( BindingAnnotation :: MUT , local_id, _, None ) = local. pat. kind;
104118 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) ;
106120
107121 then {
108122 let vi = VecAllocation {
109123 local_id,
110124 allocation_expr: init,
111- len_expr : len_arg ,
125+ size_expr ,
112126 } ;
113127
114128 Self :: search_initialization( cx, vi, stmt. hir_id) ;
@@ -118,19 +132,20 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
118132}
119133
120134impl 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
134149 }
135150 }
136151
@@ -169,12 +184,19 @@ impl SlowVectorInit {
169184 }
170185
171186 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+ ) ;
173195
174196 span_lint_and_then ( cx, SLOW_VECTOR_INITIALIZATION , slow_fill. span , msg, |diag| {
175197 diag. span_suggestion (
176198 vec_alloc. allocation_expr . span ,
177- "consider replace allocation with" ,
199+ "consider replacing this with" ,
178200 format ! ( "vec![0; {len_expr}]" ) ,
179201 Applicability :: Unspecified ,
180202 ) ;
@@ -214,36 +236,45 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
214236 }
215237
216238 /// 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 > ) {
218240 if self . initialization_found
219241 && let ExprKind :: MethodCall ( path, self_arg, [ len_arg, fill_arg] , _) = expr. kind
220242 && path_to_local_id ( self_arg, self . vec_alloc . local_id )
221243 && path. ident . name == sym ! ( resize)
222244 // 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) ) ;
230258 }
259+ }
231260 }
232261
233262 /// 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 {
235264 if_chain ! {
236265 if let ExprKind :: MethodCall ( take_path, recv, [ len_arg, ..] , _) = expr. kind;
237266 if take_path. ident. name == sym!( take) ;
238267 // Check that take is applied to `repeat(0)`
239268 if self . is_repeat_zero( recv) ;
240269 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" )
246274 }
275+
276+ self . vec_alloc. size_expr = InitializedSize :: Initialized ( len_arg) ;
277+ return true ;
247278 }
248279 }
249280
0 commit comments