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