Skip to content

Commit 177c6fe

Browse files
committed
Auto merge of #10901 - y21:smarter-useless-vec, r=Manishearth,Centri3
[`useless_vec`]: lint `vec!` invocations when a slice or an array would do First off, sorry for that large diff in tests. *A lot* of tests seem to trigger the lint with this new change, so I decided to `#![allow()]` the lint in the affected tests to make reviewing this easier, and also split the commits up so that the first commit is the actual logic of the lint and the second commit contains all the test changes. The stuff that changed in the tests is mostly just line numbers now. So, as large as the diff looks, it's not actually that bad. 😅 I manually went through all of these to find out about edge cases and decided to put them in `tests/ui/vec.rs`. For more context, I wrote about the idea of this PR here: #2262 (comment) (that explains the logic) Basically, it now also considers the case where a `Vec` is put in a local variable and the user only ever does things with it that one could also do with a slice or an array. This should catch a lot more cases, and (at least from looking at the tests) it does. changelog: [`useless_vec`]: lint `vec!` invocations when a slice or an array would do (also considering local variables now)
2 parents 9ca1344 + 7af77f7 commit 177c6fe

File tree

140 files changed

+848
-565
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

140 files changed

+848
-565
lines changed

clippy_lints/src/vec.rs

+97-14
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1+
use std::ops::ControlFlow;
2+
13
use clippy_utils::consts::{constant, Constant};
24
use clippy_utils::diagnostics::span_lint_and_sugg;
3-
use clippy_utils::higher;
45
use clippy_utils::source::snippet_with_applicability;
56
use clippy_utils::ty::is_copy;
7+
use clippy_utils::visitors::for_each_local_use_after_expr;
8+
use clippy_utils::{get_parent_expr, higher, is_trait_method};
69
use if_chain::if_chain;
10+
use rustc_ast::BindingAnnotation;
711
use rustc_errors::Applicability;
8-
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability};
12+
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Node, PatKind};
913
use rustc_lint::{LateContext, LateLintPass};
1014
use rustc_middle::ty::layout::LayoutOf;
1115
use rustc_middle::ty::{self, Ty};
1216
use rustc_session::{declare_tool_lint, impl_lint_pass};
1317
use rustc_span::source_map::Span;
18+
use rustc_span::sym;
1419

1520
#[expect(clippy::module_name_repetitions)]
1621
#[derive(Copy, Clone)]
@@ -20,7 +25,7 @@ pub struct UselessVec {
2025

2126
declare_clippy_lint! {
2227
/// ### What it does
23-
/// Checks for usage of `&vec![..]` when using `&[..]` would
28+
/// Checks for usage of `vec![..]` when using `[..]` would
2429
/// be possible.
2530
///
2631
/// ### Why is this bad?
@@ -46,16 +51,70 @@ declare_clippy_lint! {
4651

4752
impl_lint_pass!(UselessVec => [USELESS_VEC]);
4853

54+
fn adjusts_to_slice(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
55+
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(e).kind() {
56+
ty.is_slice()
57+
} else {
58+
false
59+
}
60+
}
61+
62+
/// Checks if the given expression is a method call to a `Vec` method
63+
/// that also exists on slices. If this returns true, it means that
64+
/// this expression does not actually require a `Vec` and could just work with an array.
65+
fn is_allowed_vec_method(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
66+
const ALLOWED_METHOD_NAMES: &[&str] = &["len", "as_ptr", "is_empty"];
67+
68+
if let ExprKind::MethodCall(path, ..) = e.kind {
69+
ALLOWED_METHOD_NAMES.contains(&path.ident.name.as_str())
70+
} else {
71+
is_trait_method(cx, e, sym::IntoIterator)
72+
}
73+
}
74+
4975
impl<'tcx> LateLintPass<'tcx> for UselessVec {
5076
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
5177
// search for `&vec![_]` expressions where the adjusted type is `&[_]`
5278
if_chain! {
53-
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(expr).kind();
54-
if let ty::Slice(..) = ty.kind();
79+
if adjusts_to_slice(cx, expr);
5580
if let ExprKind::AddrOf(BorrowKind::Ref, mutability, addressee) = expr.kind;
5681
if let Some(vec_args) = higher::VecArgs::hir(cx, addressee);
5782
then {
58-
self.check_vec_macro(cx, &vec_args, mutability, expr.span);
83+
self.check_vec_macro(cx, &vec_args, mutability, expr.span, SuggestSlice::Yes);
84+
}
85+
}
86+
87+
// search for `let foo = vec![_]` expressions where all uses of `foo`
88+
// adjust to slices or call a method that exist on slices (e.g. len)
89+
if let Some(vec_args) = higher::VecArgs::hir(cx, expr)
90+
&& let Node::Local(local) = cx.tcx.hir().get_parent(expr.hir_id)
91+
// for now ignore locals with type annotations.
92+
// this is to avoid compile errors when doing the suggestion here: let _: Vec<_> = vec![..];
93+
&& local.ty.is_none()
94+
&& let PatKind::Binding(BindingAnnotation(_, mutbl), id, ..) = local.pat.kind
95+
&& is_copy(cx, vec_type(cx.typeck_results().expr_ty_adjusted(expr)))
96+
{
97+
let only_slice_uses = for_each_local_use_after_expr(cx, id, expr.hir_id, |expr| {
98+
// allow indexing into a vec and some set of allowed method calls that exist on slices, too
99+
if let Some(parent) = get_parent_expr(cx, expr)
100+
&& (adjusts_to_slice(cx, expr)
101+
|| matches!(parent.kind, ExprKind::Index(..))
102+
|| is_allowed_vec_method(cx, parent))
103+
{
104+
ControlFlow::Continue(())
105+
} else {
106+
ControlFlow::Break(())
107+
}
108+
}).is_continue();
109+
110+
if only_slice_uses {
111+
self.check_vec_macro(
112+
cx,
113+
&vec_args,
114+
mutbl,
115+
expr.span.ctxt().outer_expn_data().call_site,
116+
SuggestSlice::No
117+
);
59118
}
60119
}
61120

@@ -67,21 +126,36 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
67126
then {
68127
// report the error around the `vec!` not inside `<std macros>:`
69128
let span = arg.span.ctxt().outer_expn_data().call_site;
70-
self.check_vec_macro(cx, &vec_args, Mutability::Not, span);
129+
self.check_vec_macro(cx, &vec_args, Mutability::Not, span, SuggestSlice::Yes);
71130
}
72131
}
73132
}
74133
}
75134

135+
#[derive(Copy, Clone)]
136+
enum SuggestSlice {
137+
/// Suggest using a slice `&[..]` / `&mut [..]`
138+
Yes,
139+
/// Suggest using an array: `[..]`
140+
No,
141+
}
142+
76143
impl UselessVec {
77144
fn check_vec_macro<'tcx>(
78145
self,
79146
cx: &LateContext<'tcx>,
80147
vec_args: &higher::VecArgs<'tcx>,
81148
mutability: Mutability,
82149
span: Span,
150+
suggest_slice: SuggestSlice,
83151
) {
84152
let mut applicability = Applicability::MachineApplicable;
153+
154+
let (borrow_prefix_mut, borrow_prefix) = match suggest_slice {
155+
SuggestSlice::Yes => ("&mut ", "&"),
156+
SuggestSlice::No => ("", ""),
157+
};
158+
85159
let snippet = match *vec_args {
86160
higher::VecArgs::Repeat(elem, len) => {
87161
if let Some(Constant::Int(len_constant)) = constant(cx, cx.typeck_results(), len) {
@@ -93,14 +167,14 @@ impl UselessVec {
93167
match mutability {
94168
Mutability::Mut => {
95169
format!(
96-
"&mut [{}; {}]",
170+
"{borrow_prefix_mut}[{}; {}]",
97171
snippet_with_applicability(cx, elem.span, "elem", &mut applicability),
98172
snippet_with_applicability(cx, len.span, "len", &mut applicability)
99173
)
100174
},
101175
Mutability::Not => {
102176
format!(
103-
"&[{}; {}]",
177+
"{borrow_prefix}[{}; {}]",
104178
snippet_with_applicability(cx, elem.span, "elem", &mut applicability),
105179
snippet_with_applicability(cx, len.span, "len", &mut applicability)
106180
)
@@ -120,18 +194,21 @@ impl UselessVec {
120194
match mutability {
121195
Mutability::Mut => {
122196
format!(
123-
"&mut [{}]",
197+
"{borrow_prefix_mut}[{}]",
124198
snippet_with_applicability(cx, span, "..", &mut applicability)
125199
)
126200
},
127201
Mutability::Not => {
128-
format!("&[{}]", snippet_with_applicability(cx, span, "..", &mut applicability))
202+
format!(
203+
"{borrow_prefix}[{}]",
204+
snippet_with_applicability(cx, span, "..", &mut applicability)
205+
)
129206
},
130207
}
131208
} else {
132209
match mutability {
133-
Mutability::Mut => "&mut []".into(),
134-
Mutability::Not => "&[]".into(),
210+
Mutability::Mut => format!("{borrow_prefix_mut}[]"),
211+
Mutability::Not => format!("{borrow_prefix}[]"),
135212
}
136213
}
137214
},
@@ -142,7 +219,13 @@ impl UselessVec {
142219
USELESS_VEC,
143220
span,
144221
"useless use of `vec!`",
145-
"you can use a slice directly",
222+
&format!(
223+
"you can use {} directly",
224+
match suggest_slice {
225+
SuggestSlice::Yes => "a slice",
226+
SuggestSlice::No => "an array",
227+
}
228+
),
146229
snippet,
147230
applicability,
148231
);

tests/ui-toml/suppress_lint_in_const/test.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
// We also check the out_of_bounds_indexing lint here, because it lints similar things and
44
// we want to avoid false positives.
55
#![warn(clippy::out_of_bounds_indexing)]
6-
#![allow(unconditional_panic, clippy::no_effect, clippy::unnecessary_operation)]
6+
#![allow(
7+
unconditional_panic,
8+
clippy::no_effect,
9+
clippy::unnecessary_operation,
10+
clippy::useless_vec
11+
)]
712

813
const ARR: [i32; 2] = [1, 2];
914
const REF: &i32 = &ARR[idx()]; // Ok, should not produce stderr, since `suppress-restriction-lint-in-const` is set true.

tests/ui-toml/suppress_lint_in_const/test.stderr

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
error[E0080]: evaluation of `main::{constant#3}` failed
2-
--> $DIR/test.rs:31:14
2+
--> $DIR/test.rs:36:14
33
|
44
LL | const { &ARR[idx4()] }; // Ok, should not produce stderr, since `suppress-restriction-lint-in-const` is set true.
55
| ^^^^^^^^^^^ index out of bounds: the length is 2 but the index is 4
66

77
note: erroneous constant used
8-
--> $DIR/test.rs:31:5
8+
--> $DIR/test.rs:36:5
99
|
1010
LL | const { &ARR[idx4()] }; // Ok, should not produce stderr, since `suppress-restriction-lint-in-const` is set true.
1111
| ^^^^^^^^^^^^^^^^^^^^^^
1212

1313
error: indexing may panic
14-
--> $DIR/test.rs:22:5
14+
--> $DIR/test.rs:27:5
1515
|
1616
LL | x[index];
1717
| ^^^^^^^^
@@ -20,47 +20,47 @@ LL | x[index];
2020
= note: `-D clippy::indexing-slicing` implied by `-D warnings`
2121

2222
error: indexing may panic
23-
--> $DIR/test.rs:38:5
23+
--> $DIR/test.rs:43:5
2424
|
2525
LL | v[0];
2626
| ^^^^
2727
|
2828
= help: consider using `.get(n)` or `.get_mut(n)` instead
2929

3030
error: indexing may panic
31-
--> $DIR/test.rs:39:5
31+
--> $DIR/test.rs:44:5
3232
|
3333
LL | v[10];
3434
| ^^^^^
3535
|
3636
= help: consider using `.get(n)` or `.get_mut(n)` instead
3737

3838
error: indexing may panic
39-
--> $DIR/test.rs:40:5
39+
--> $DIR/test.rs:45:5
4040
|
4141
LL | v[1 << 3];
4242
| ^^^^^^^^^
4343
|
4444
= help: consider using `.get(n)` or `.get_mut(n)` instead
4545

4646
error: indexing may panic
47-
--> $DIR/test.rs:46:5
47+
--> $DIR/test.rs:51:5
4848
|
4949
LL | v[N];
5050
| ^^^^
5151
|
5252
= help: consider using `.get(n)` or `.get_mut(n)` instead
5353

5454
error: indexing may panic
55-
--> $DIR/test.rs:47:5
55+
--> $DIR/test.rs:52:5
5656
|
5757
LL | v[M];
5858
| ^^^^
5959
|
6060
= help: consider using `.get(n)` or `.get_mut(n)` instead
6161

6262
error[E0080]: evaluation of constant value failed
63-
--> $DIR/test.rs:10:24
63+
--> $DIR/test.rs:15:24
6464
|
6565
LL | const REF_ERR: &i32 = &ARR[idx4()]; // Ok, let rustc handle const contexts.
6666
| ^^^^^^^^^^^ index out of bounds: the length is 2 but the index is 4

tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//@compile-flags: --crate-name conf_disallowed_methods
22

33
#![warn(clippy::disallowed_methods)]
4+
#![allow(clippy::useless_vec)]
45

56
extern crate futures;
67
extern crate regex;

0 commit comments

Comments
 (0)