11use std:: collections:: BTreeMap ;
2+ use std:: collections:: btree_map:: Entry ;
3+ use std:: mem;
24use std:: ops:: ControlFlow ;
35
46use clippy_config:: Conf ;
@@ -17,23 +19,6 @@ use rustc_middle::ty::layout::LayoutOf;
1719use rustc_session:: impl_lint_pass;
1820use rustc_span:: { DesugaringKind , Span , sym} ;
1921
20- pub struct UselessVec {
21- too_large_for_stack : u64 ,
22- msrv : Msrv ,
23- span_to_lint_map : BTreeMap < Span , Option < ( HirId , SuggestedType , String , Applicability ) > > ,
24- allow_in_test : bool ,
25- }
26- impl UselessVec {
27- pub fn new ( conf : & ' static Conf ) -> Self {
28- Self {
29- too_large_for_stack : conf. too_large_for_stack ,
30- msrv : conf. msrv ,
31- span_to_lint_map : BTreeMap :: new ( ) ,
32- allow_in_test : conf. allow_useless_vec_in_tests ,
33- }
34- }
35- }
36-
3722declare_clippy_lint ! {
3823 /// ### What it does
3924 /// Checks for usage of `vec![..]` when using `[..]` would
@@ -62,17 +47,62 @@ declare_clippy_lint! {
6247
6348impl_lint_pass ! ( UselessVec => [ USELESS_VEC ] ) ;
6449
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 ;
50+ /// The "state" of a `vec![]` invocation, indicating whether it can or cannot be changed.
51+ #[ derive( Debug ) ]
52+ enum VecState {
53+ Change {
54+ suggest_ty : SuggestedType ,
55+ vec_snippet : String ,
56+ expr_hir_id : HirId ,
57+ } ,
58+ NoChange ,
59+ }
60+
61+ pub struct UselessVec {
62+ too_large_for_stack : u64 ,
63+ msrv : Msrv ,
64+ /// Maps from a `vec![]` source callsite invocation span to the "state" (i.e., whether we can emit a warning there or not).
65+ ///
66+ /// The purpose of this is to buffer lints up until `check_expr_post` so that we can cancel a lint while visiting,
67+ /// because a `vec![]` invocation span can appear multiple times when it is passed as a macro argument,
68+ /// once in a context that doesn't require a `Vec<_>` and another time that does. Consider:
69+ /// ```
70+ /// macro_rules! m {
71+ /// ($v:expr) => {
72+ /// let a = $v;
73+ /// $v.push(3);
74+ /// }
75+ /// }
76+ /// m!(vec![1, 2]);
77+ /// ```
78+ /// The macro invocation expands to two `vec![1, 2]` invocations. If we eagerly suggest changing it to an array,
79+ /// we get a false positive warning on the `$v.push(3)` which really requires `$v` to be a vector.
80+ span_to_state : BTreeMap < Span , VecState > ,
81+ allow_in_test : bool ,
82+ }
83+
84+ impl UselessVec {
85+ pub fn new ( conf : & ' static Conf ) -> Self {
86+ Self {
87+ too_large_for_stack : conf. too_large_for_stack ,
88+ msrv : conf. msrv ,
89+ span_to_state : BTreeMap :: new ( ) ,
90+ allow_in_test : conf. allow_useless_vec_in_tests ,
7291 }
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 ) ;
92+ }
93+ }
7594
95+ enum VecToArray {
96+ /// Expression does not need to be a `Vec<_>` and its type can be changed to an array (or slice).
97+ Possible ,
98+ /// Expression must be a `Vec<_>`. Type cannot change.
99+ Impossible ,
100+ }
101+
102+ impl UselessVec {
103+ /// Checks if the surrounding environment requires this expression to actually be of type
104+ /// `Vec<_>`, or if it can be changed to `&[]`/`[]` without causing type errors.
105+ fn expr_usage_requires_vec < ' tcx > ( & mut self , cx : & LateContext < ' _ > , expr : & ' tcx Expr < ' _ > ) -> VecToArray {
76106 match cx. tcx . parent_hir_node ( expr. hir_id ) {
77107 // search for `let foo = vec![_]` expressions where all uses of `foo`
78108 // adjust to slices or call a method that exist on slices (e.g. len)
@@ -100,111 +130,124 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
100130 . is_continue ( ) ;
101131
102132 if only_slice_uses {
103- self . check_vec_macro ( cx , & vec_args , callsite , expr . hir_id , SuggestedType :: Array ) ;
133+ VecToArray :: Possible
104134 } else {
105- self . span_to_lint_map . insert ( callsite , None ) ;
135+ VecToArray :: Impossible
106136 }
107137 } ,
108138 // if the local pattern has a specified type, do not lint.
109139 Node :: LetStmt ( LetStmt { ty : Some ( _) , .. } ) if higher:: VecArgs :: hir ( cx, expr) . is_some ( ) => {
110- self . span_to_lint_map . insert ( callsite, None ) ;
140+ // FIXME: we can still lint here, it just needs to also change the type annotation
141+ VecToArray :: Impossible
111142 } ,
112143 // search for `for _ in vec![...]`
113144 Node :: Expr ( Expr { span, .. } )
114145 if span. is_desugaring ( DesugaringKind :: ForLoop ) && self . msrv . meets ( cx, msrvs:: ARRAY_INTO_ITERATOR ) =>
115146 {
116- let suggest_slice = suggest_type ( expr) ;
117- self . check_vec_macro ( cx, & vec_args, callsite, expr. hir_id , suggest_slice) ;
147+ VecToArray :: Possible
118148 } ,
119149 // search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]`
120150 _ => {
121- let suggest_slice = suggest_type ( expr) ;
122-
123151 if adjusts_to_slice ( cx, expr) {
124- self . check_vec_macro ( cx , & vec_args , callsite , expr . hir_id , suggest_slice ) ;
152+ VecToArray :: Possible
125153 } else {
126- self . span_to_lint_map . insert ( callsite , None ) ;
154+ VecToArray :: Impossible
127155 }
128156 } ,
129157 }
130158 }
159+ }
160+
161+ impl < ' tcx > LateLintPass < ' tcx > for UselessVec {
162+ fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' _ > ) {
163+ if let Some ( vec_args) = higher:: VecArgs :: hir ( cx, expr. peel_borrows ( ) )
164+ // The `vec![]` or `&vec![]` invocation span.
165+ && let vec_span = expr. span . parent_callsite ( ) . unwrap_or ( expr. span )
166+ && !vec_span. from_expansion ( )
167+ {
168+ if self . allow_in_test && is_in_test ( cx. tcx , expr. hir_id ) {
169+ return ;
170+ }
171+
172+ match self . expr_usage_requires_vec ( cx, expr) {
173+ VecToArray :: Possible => {
174+ let suggest_ty = suggest_type ( expr) ;
175+
176+ // Size and `Copy` checks don't depend on the enclosing usage of the expression
177+ // and don't need to be inserted into the state map.
178+ let vec_snippet = match vec_args {
179+ higher:: VecArgs :: Repeat ( expr, len) => {
180+ if is_copy ( cx, cx. typeck_results ( ) . expr_ty ( expr) )
181+ && let Some ( Constant :: Int ( length) ) = ConstEvalCtxt :: new ( cx) . eval ( len)
182+ && let Ok ( length) = u64:: try_from ( length)
183+ && size_of ( cx, expr)
184+ . checked_mul ( length)
185+ . is_some_and ( |size| size <= self . too_large_for_stack )
186+ {
187+ suggest_ty. snippet ( cx, Some ( expr. span ) , Some ( len. span ) )
188+ } else {
189+ return ;
190+ }
191+ } ,
192+ higher:: VecArgs :: Vec ( args) => {
193+ if let Ok ( length) = u64:: try_from ( args. len ( ) )
194+ && size_of ( cx, expr)
195+ . checked_mul ( length)
196+ . is_some_and ( |size| size <= self . too_large_for_stack )
197+ {
198+ suggest_ty. snippet (
199+ cx,
200+ args. first ( ) . zip ( args. last ( ) ) . map ( |( first, last) | {
201+ first. span . source_callsite ( ) . to ( last. span . source_callsite ( ) )
202+ } ) ,
203+ None ,
204+ )
205+ } else {
206+ return ;
207+ }
208+ } ,
209+ } ;
210+
211+ if let Entry :: Vacant ( entry) = self . span_to_state . entry ( vec_span) {
212+ entry. insert ( VecState :: Change {
213+ suggest_ty,
214+ vec_snippet,
215+ expr_hir_id : expr. hir_id ,
216+ } ) ;
217+ }
218+ } ,
219+ VecToArray :: Impossible => {
220+ self . span_to_state . insert ( vec_span, VecState :: NoChange ) ;
221+ } ,
222+ }
223+ }
224+ }
131225
132226 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- {
227+ for ( span, state) in mem:: take ( & mut self . span_to_state ) {
228+ if let VecState :: Change {
229+ suggest_ty,
230+ vec_snippet,
231+ expr_hir_id,
232+ } = state
233+ {
234+ let help_msg = format ! ( "you can use {} directly" , suggest_ty. desc( ) ) ;
235+ span_lint_hir_and_then ( cx, USELESS_VEC , expr_hir_id, span, "useless use of `vec!`" , |diag| {
236+ // If the `vec!` macro contains comment, better not make the suggestion machine applicable as it
237+ // would remove them.
238+ let applicability = if span_contains_comment ( cx. tcx . sess . source_map ( ) , span) {
143239 Applicability :: Unspecified
144240 } else {
145- * applicability
241+ Applicability :: MachineApplicable
146242 } ;
147- diag. span_suggestion ( * span, help_msg, snippet , applicability) ;
243+ diag. span_suggestion ( span, help_msg, vec_snippet , applicability) ;
148244 } ) ;
149245 }
150246 }
151247 }
152248}
153249
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- #[ derive( Copy , Clone ) ]
250+ #[ derive( Copy , Clone , Debug ) ]
208251pub ( crate ) enum SuggestedType {
209252 /// Suggest using a slice `&[..]` / `&mut [..]`
210253 SliceRef ( Mutability ) ,
0 commit comments