1
1
use std:: collections:: BTreeMap ;
2
+ use std:: collections:: btree_map:: Entry ;
3
+ use std:: mem;
2
4
use std:: ops:: ControlFlow ;
3
5
4
6
use clippy_config:: Conf ;
@@ -20,15 +22,35 @@ use rustc_span::{DesugaringKind, Span, sym};
20
22
pub struct UselessVec {
21
23
too_large_for_stack : u64 ,
22
24
msrv : Msrv ,
23
- span_to_lint_map : BTreeMap < Span , Option < ( HirId , SuggestedType , String , Applicability ) > > ,
25
+ /// Maps from a `vec![]` source callsite invocation span to the "state" (i.e., whether we can
26
+ /// emit a warning there or not).
27
+ ///
28
+ /// The purpose of this is to buffer lints up until `check_expr_post` so that we can cancel a
29
+ /// lint while visiting, because a `vec![]` invocation span can appear multiple times when
30
+ /// it is passed as a macro argument, once in a context that doesn't require a `Vec<_>` and
31
+ /// another time that does. Consider:
32
+ /// ```
33
+ /// macro_rules! m {
34
+ /// ($v:expr) => {
35
+ /// let a = $v;
36
+ /// $v.push(3);
37
+ /// }
38
+ /// }
39
+ /// m!(vec![1, 2]);
40
+ /// ```
41
+ /// The macro invocation expands to two `vec![1, 2]` invocations. If we eagerly suggest changing
42
+ /// the first `vec![1, 2]` (which is shared with the other expn) to an array which indeed would work,
43
+ /// we get a false positive warning on the `$v.push(3)` which really requires `$v` to be a vector.
44
+ span_to_state : BTreeMap < Span , VecState > ,
24
45
allow_in_test : bool ,
25
46
}
47
+
26
48
impl UselessVec {
27
49
pub fn new ( conf : & ' static Conf ) -> Self {
28
50
Self {
29
51
too_large_for_stack : conf. too_large_for_stack ,
30
52
msrv : conf. msrv ,
31
- span_to_lint_map : BTreeMap :: new ( ) ,
53
+ span_to_state : BTreeMap :: new ( ) ,
32
54
allow_in_test : conf. allow_useless_vec_in_tests ,
33
55
}
34
56
}
@@ -62,17 +84,28 @@ declare_clippy_lint! {
62
84
63
85
impl_lint_pass ! ( UselessVec => [ USELESS_VEC ] ) ;
64
86
65
- impl < ' tcx > LateLintPass < ' tcx > for UselessVec {
66
- fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
67
- let Some ( vec_args) = higher:: VecArgs :: hir ( cx, expr. peel_borrows ( ) ) else {
68
- return ;
69
- } ;
70
- if self . allow_in_test && is_in_test ( cx. tcx , expr. hir_id ) {
71
- return ;
72
- }
73
- // the parent callsite of this `vec!` expression, or span to the borrowed one such as `&vec!`
74
- let callsite = expr. span . parent_callsite ( ) . unwrap_or ( expr. span ) ;
87
+ /// The "state" of a `vec![]` invocation, indicating whether it can or cannot be changed.
88
+ enum VecState {
89
+ Change {
90
+ suggest_ty : SuggestedType ,
91
+ vec_snippet : String ,
92
+ expr_hir_id : HirId ,
93
+ } ,
94
+ NoChange ,
95
+ }
75
96
97
+ enum VecToArray {
98
+ /// Expression does not need to be a `Vec<_>` and its type can be changed to an array (or
99
+ /// slice).
100
+ Possible ,
101
+ /// Expression must be a `Vec<_>`. Type cannot change.
102
+ Impossible ,
103
+ }
104
+
105
+ impl UselessVec {
106
+ /// Checks if the surrounding environment requires this expression to actually be of type
107
+ /// `Vec<_>`, or if it can be changed to `&[]`/`[]` without causing type errors.
108
+ fn expr_usage_requires_vec ( & mut self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> VecToArray {
76
109
match cx. tcx . parent_hir_node ( expr. hir_id ) {
77
110
// search for `let foo = vec![_]` expressions where all uses of `foo`
78
111
// adjust to slices or call a method that exist on slices (e.g. len)
@@ -100,110 +133,122 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
100
133
. is_continue ( ) ;
101
134
102
135
if only_slice_uses {
103
- self . check_vec_macro ( cx , & vec_args , callsite , expr . hir_id , SuggestedType :: Array ) ;
136
+ VecToArray :: Possible
104
137
} else {
105
- self . span_to_lint_map . insert ( callsite , None ) ;
138
+ VecToArray :: Impossible
106
139
}
107
140
} ,
108
141
// if the local pattern has a specified type, do not lint.
109
142
Node :: LetStmt ( LetStmt { ty : Some ( _) , .. } ) if higher:: VecArgs :: hir ( cx, expr) . is_some ( ) => {
110
- self . span_to_lint_map . insert ( callsite , None ) ;
143
+ VecToArray :: Impossible
111
144
} ,
112
145
// search for `for _ in vec![...]`
113
146
Node :: Expr ( Expr { span, .. } )
114
147
if span. is_desugaring ( DesugaringKind :: ForLoop ) && self . msrv . meets ( cx, msrvs:: ARRAY_INTO_ITERATOR ) =>
115
148
{
116
- let suggest_slice = suggest_type ( expr) ;
117
- self . check_vec_macro ( cx, & vec_args, callsite, expr. hir_id , suggest_slice) ;
149
+ VecToArray :: Possible
118
150
} ,
119
151
// search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]`
120
152
_ => {
121
- let suggest_slice = suggest_type ( expr) ;
122
-
123
153
if adjusts_to_slice ( cx, expr) {
124
- self . check_vec_macro ( cx , & vec_args , callsite , expr . hir_id , suggest_slice ) ;
154
+ VecToArray :: Possible
125
155
} else {
126
- self . span_to_lint_map . insert ( callsite , None ) ;
156
+ VecToArray :: Impossible
127
157
}
128
158
} ,
129
159
}
130
160
}
161
+ }
162
+
163
+ impl < ' tcx > LateLintPass < ' tcx > for UselessVec {
164
+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
165
+ if let Some ( vec_args) = higher:: VecArgs :: hir ( cx, expr. peel_borrows ( ) )
166
+ // The `vec![]` or `&vec![]` invocation span.
167
+ && let vec_span = expr. span . parent_callsite ( ) . unwrap_or ( expr. span )
168
+ && !vec_span. from_expansion ( )
169
+ {
170
+ if self . allow_in_test && is_in_test ( cx. tcx , expr. hir_id ) {
171
+ return ;
172
+ }
173
+
174
+ match self . expr_usage_requires_vec ( cx, expr) {
175
+ VecToArray :: Possible => {
176
+ let suggest_ty = suggest_type ( expr) ;
177
+
178
+ // Size and `Copy` checks don't depend on the enclosing usage of the expression
179
+ // and don't need to be inserted into the state map.
180
+ let vec_snippet = match vec_args {
181
+ higher:: VecArgs :: Repeat ( expr, len) => {
182
+ if is_copy ( cx, cx. typeck_results ( ) . expr_ty ( expr) )
183
+ && let Some ( Constant :: Int ( length) ) = ConstEvalCtxt :: new ( cx) . eval ( len)
184
+ && let Ok ( length) = u64:: try_from ( length)
185
+ && size_of ( cx, expr)
186
+ . checked_mul ( length)
187
+ . is_some_and ( |size| size <= self . too_large_for_stack )
188
+ {
189
+ suggest_ty. snippet ( cx, Some ( expr. span ) , Some ( len. span ) )
190
+ } else {
191
+ return ;
192
+ }
193
+ } ,
194
+ higher:: VecArgs :: Vec ( args) => {
195
+ if let Ok ( length) = u64:: try_from ( args. len ( ) )
196
+ && size_of ( cx, expr)
197
+ . checked_mul ( length)
198
+ . is_some_and ( |size| size <= self . too_large_for_stack )
199
+ {
200
+ suggest_ty. snippet (
201
+ cx,
202
+ args. first ( ) . zip ( args. last ( ) ) . map ( |( first, last) | {
203
+ first. span . source_callsite ( ) . to ( last. span . source_callsite ( ) )
204
+ } ) ,
205
+ None ,
206
+ )
207
+ } else {
208
+ return ;
209
+ }
210
+ } ,
211
+ } ;
212
+
213
+ if let Entry :: Vacant ( entry) = self . span_to_state . entry ( vec_span) {
214
+ entry. insert ( VecState :: Change {
215
+ suggest_ty,
216
+ vec_snippet,
217
+ expr_hir_id : expr. hir_id ,
218
+ } ) ;
219
+ }
220
+ } ,
221
+ VecToArray :: Impossible => {
222
+ self . span_to_state . insert ( vec_span, VecState :: NoChange ) ;
223
+ } ,
224
+ }
225
+ }
226
+ }
131
227
132
228
fn check_crate_post ( & mut self , cx : & LateContext < ' tcx > ) {
133
- for ( span, lint_opt) in & self . span_to_lint_map {
134
- if let Some ( ( hir_id, suggest_slice, snippet, applicability) ) = lint_opt {
135
- let help_msg = format ! ( "you can use {} directly" , suggest_slice. desc( ) ) ;
136
- span_lint_hir_and_then ( cx, USELESS_VEC , * hir_id, * span, "useless use of `vec!`" , |diag| {
137
- // If the `vec!` macro contains comment, better not make the suggestion machine
138
- // applicable as it would remove them.
139
- let applicability = if * applicability != Applicability :: Unspecified
140
- && let source_map = cx. tcx . sess . source_map ( )
141
- && span_contains_comment ( source_map, * span)
142
- {
229
+ for ( span, state) in mem:: take ( & mut self . span_to_state ) {
230
+ if let VecState :: Change {
231
+ suggest_ty,
232
+ vec_snippet,
233
+ expr_hir_id,
234
+ } = state
235
+ {
236
+ span_lint_hir_and_then ( cx, USELESS_VEC , expr_hir_id, span, "useless use of `vec!`" , |diag| {
237
+ let help_msg = format ! ( "you can use {} directly" , suggest_ty. desc( ) ) ;
238
+ // If the `vec!` macro contains comment, better not make the suggestion machine applicable as it
239
+ // would remove them.
240
+ let applicability = if span_contains_comment ( cx. tcx . sess . source_map ( ) , span) {
143
241
Applicability :: Unspecified
144
242
} else {
145
- * applicability
243
+ Applicability :: MachineApplicable
146
244
} ;
147
- diag. span_suggestion ( * span, help_msg, snippet , applicability) ;
245
+ diag. span_suggestion ( span, help_msg, vec_snippet , applicability) ;
148
246
} ) ;
149
247
}
150
248
}
151
249
}
152
250
}
153
251
154
- impl UselessVec {
155
- fn check_vec_macro < ' tcx > (
156
- & mut self ,
157
- cx : & LateContext < ' tcx > ,
158
- vec_args : & higher:: VecArgs < ' tcx > ,
159
- span : Span ,
160
- hir_id : HirId ,
161
- suggest_slice : SuggestedType ,
162
- ) {
163
- if span. from_expansion ( ) {
164
- return ;
165
- }
166
-
167
- let snippet = match * vec_args {
168
- higher:: VecArgs :: Repeat ( elem, len) => {
169
- if let Some ( Constant :: Int ( len_constant) ) = ConstEvalCtxt :: new ( cx) . eval ( len) {
170
- // vec![ty; N] works when ty is Clone, [ty; N] requires it to be Copy also
171
- if !is_copy ( cx, cx. typeck_results ( ) . expr_ty ( elem) ) {
172
- return ;
173
- }
174
-
175
- #[ expect( clippy:: cast_possible_truncation) ]
176
- if len_constant as u64 * size_of ( cx, elem) > self . too_large_for_stack {
177
- return ;
178
- }
179
-
180
- suggest_slice. snippet ( cx, Some ( elem. span ) , Some ( len. span ) )
181
- } else {
182
- return ;
183
- }
184
- } ,
185
- higher:: VecArgs :: Vec ( args) => {
186
- let args_span = if let Some ( last) = args. iter ( ) . last ( ) {
187
- if args. len ( ) as u64 * size_of ( cx, last) > self . too_large_for_stack {
188
- return ;
189
- }
190
- Some ( args[ 0 ] . span . source_callsite ( ) . to ( last. span . source_callsite ( ) ) )
191
- } else {
192
- None
193
- } ;
194
- suggest_slice. snippet ( cx, args_span, None )
195
- } ,
196
- } ;
197
-
198
- self . span_to_lint_map . entry ( span) . or_insert ( Some ( (
199
- hir_id,
200
- suggest_slice,
201
- snippet,
202
- Applicability :: MachineApplicable ,
203
- ) ) ) ;
204
- }
205
- }
206
-
207
252
#[ derive( Copy , Clone ) ]
208
253
pub ( crate ) enum SuggestedType {
209
254
/// Suggest using a slice `&[..]` / `&mut [..]`
0 commit comments