Skip to content

Commit 6702c7a

Browse files
committed
Add lint [single_range_in_vec_init]
1 parent b095247 commit 6702c7a

12 files changed

+399
-33
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5164,6 +5164,7 @@ Released 2018-09-13
51645164
[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
51655165
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
51665166
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
5167+
[`single_range_in_vec_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_range_in_vec_init
51675168
[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
51685169
[`size_of_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_ref
51695170
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
569569
crate::significant_drop_tightening::SIGNIFICANT_DROP_TIGHTENING_INFO,
570570
crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO,
571571
crate::single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS_INFO,
572+
crate::single_range_in_vec_init::SINGLE_RANGE_IN_VEC_INIT_INFO,
572573
crate::size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT_INFO,
573574
crate::size_of_ref::SIZE_OF_REF_INFO,
574575
crate::slow_vector_initialization::SLOW_VECTOR_INITIALIZATION_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ mod shadow;
288288
mod significant_drop_tightening;
289289
mod single_char_lifetime_names;
290290
mod single_component_path_imports;
291+
mod single_range_in_vec_init;
291292
mod size_of_in_element_count;
292293
mod size_of_ref;
293294
mod slow_vector_initialization;
@@ -1045,6 +1046,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10451046
});
10461047
let stack_size_threshold = conf.stack_size_threshold;
10471048
store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(stack_size_threshold)));
1049+
store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit));
10481050
// add lints here, do not remove this comment, it's used in `new_lint`
10491051
}
10501052

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
use clippy_utils::{
2+
diagnostics::span_lint_and_then,
3+
get_trait_def_id,
4+
higher::VecArgs,
5+
macros::root_macro_call_first_node,
6+
source::{snippet_opt, snippet_with_applicability},
7+
ty::implements_trait,
8+
};
9+
use rustc_ast::{LitIntType, LitKind, UintTy};
10+
use rustc_errors::Applicability;
11+
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
12+
use rustc_lint::{LateContext, LateLintPass};
13+
use rustc_session::{declare_lint_pass, declare_tool_lint};
14+
use std::fmt::{self, Display, Formatter};
15+
16+
declare_clippy_lint! {
17+
/// ### What it does
18+
/// Checks for `Vec` or array initializations that contain only one range.
19+
///
20+
/// ### Why is this bad?
21+
/// This is almost always incorrect, as it will result in a `Vec` that has only element. Almost
22+
/// always, the programmer intended for it to include all elements in the range or for the end
23+
/// of the range to be the length instead.
24+
///
25+
/// ### Example
26+
/// ```rust
27+
/// let x = [0..200];
28+
/// ```
29+
/// Use instead:
30+
/// ```rust
31+
/// // If it was intended to include every element in the range...
32+
/// let x = (0..200).collect::<Vec<i32>>();
33+
/// // ...Or if 200 was meant to be the len
34+
/// let x = [0; 200];
35+
/// ```
36+
#[clippy::version = "1.72.0"]
37+
pub SINGLE_RANGE_IN_VEC_INIT,
38+
suspicious,
39+
"checks for initialization of `Vec` or arrays which consist of a single range"
40+
}
41+
declare_lint_pass!(SingleRangeInVecInit => [SINGLE_RANGE_IN_VEC_INIT]);
42+
43+
enum SuggestedType {
44+
Vec,
45+
Array,
46+
}
47+
48+
impl SuggestedType {
49+
fn starts_with(&self) -> &'static str {
50+
if matches!(self, SuggestedType::Vec) {
51+
"vec!"
52+
} else {
53+
"["
54+
}
55+
}
56+
57+
fn ends_with(&self) -> &'static str {
58+
if matches!(self, SuggestedType::Vec) { "" } else { "]" }
59+
}
60+
}
61+
62+
impl Display for SuggestedType {
63+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
64+
if matches!(&self, SuggestedType::Vec) {
65+
write!(f, "a `Vec`")
66+
} else {
67+
write!(f, "an array")
68+
}
69+
}
70+
}
71+
72+
impl LateLintPass<'_> for SingleRangeInVecInit {
73+
fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
74+
// inner_expr: `vec![0..200]` or `[0..200]`
75+
// ^^^^^^ ^^^^^^^
76+
// span: `vec![0..200]` or `[0..200]`
77+
// ^^^^^^^^^^^^ ^^^^^^^^
78+
// kind: What to print, an array or a `Vec`
79+
let (inner_expr, span, kind) = if let ExprKind::Array([inner_expr]) = expr.kind
80+
&& !expr.span.from_expansion()
81+
{
82+
(inner_expr, expr.span, SuggestedType::Array)
83+
} else if let Some(macro_call) = root_macro_call_first_node(cx, expr)
84+
&& let Some(VecArgs::Vec([expr])) = VecArgs::hir(cx, expr)
85+
{
86+
(expr, macro_call.span, SuggestedType::Vec)
87+
} else {
88+
return;
89+
};
90+
91+
let ExprKind::Struct(QPath::LangItem(lang_item, ..), [start, end], None) = inner_expr.kind else {
92+
return;
93+
};
94+
95+
if matches!(lang_item, LangItem::Range)
96+
&& let ty = cx.typeck_results().expr_ty(start.expr)
97+
&& let Some(snippet) = snippet_opt(cx, span)
98+
// `is_from_proc_macro` will skip any `vec![]`. Let's not!
99+
&& snippet.starts_with(kind.starts_with())
100+
&& snippet.ends_with(kind.ends_with())
101+
{
102+
let mut app = Applicability::MaybeIncorrect;
103+
let start_snippet = snippet_with_applicability(cx, start.span, "...", &mut app);
104+
let end_snippet = snippet_with_applicability(cx, end.span, "...", &mut app);
105+
106+
let should_emit_every_value = if let Some(step_def_id) = get_trait_def_id(cx, &["core", "iter", "Step"])
107+
&& implements_trait(cx, ty, step_def_id, &[])
108+
{
109+
true
110+
} else {
111+
false
112+
};
113+
let should_emit_of_len = if let Some(copy_def_id) = cx.tcx.lang_items().copy_trait()
114+
&& implements_trait(cx, ty, copy_def_id, &[])
115+
&& let ExprKind::Lit(lit_kind) = end.expr.kind
116+
&& let LitKind::Int(.., suffix_type) = lit_kind.node
117+
&& let LitIntType::Unsigned(UintTy::Usize) | LitIntType::Unsuffixed = suffix_type
118+
{
119+
true
120+
} else {
121+
false
122+
};
123+
124+
if should_emit_every_value || should_emit_of_len {
125+
span_lint_and_then(
126+
cx,
127+
SINGLE_RANGE_IN_VEC_INIT,
128+
span,
129+
&format!("{kind} of `Range` that is only one element"),
130+
|diag| {
131+
if should_emit_every_value {
132+
diag.span_suggestion(
133+
span,
134+
"if you wanted a `Vec` that contains every value in the range, try",
135+
format!("({start_snippet}..{end_snippet}).collect::<std::vec::Vec<{ty}>>()"),
136+
app,
137+
);
138+
}
139+
140+
if should_emit_of_len {
141+
diag.span_suggestion(
142+
inner_expr.span,
143+
format!("if you wanted {kind} of len {end_snippet}, try"),
144+
format!("{start_snippet}; {end_snippet}"),
145+
app,
146+
);
147+
}
148+
},
149+
);
150+
}
151+
}
152+
}
153+
}

tests/ui/single_element_loop.fixed

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//@run-rustfix
22
// Tests from for_loop.rs that don't have suggestions
33

4+
#![allow(clippy::single_range_in_vec_init)]
5+
46
#[warn(clippy::single_element_loop)]
57
fn main() {
68
let item1 = 2;

tests/ui/single_element_loop.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//@run-rustfix
22
// Tests from for_loop.rs that don't have suggestions
33

4+
#![allow(clippy::single_range_in_vec_init)]
5+
46
#[warn(clippy::single_element_loop)]
57
fn main() {
68
let item1 = 2;

tests/ui/single_element_loop.stderr

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: for loop over a single element
2-
--> $DIR/single_element_loop.rs:7:5
2+
--> $DIR/single_element_loop.rs:9:5
33
|
44
LL | / for item in &[item1] {
55
LL | | dbg!(item);
@@ -16,7 +16,7 @@ LL + }
1616
|
1717

1818
error: for loop over a single element
19-
--> $DIR/single_element_loop.rs:11:5
19+
--> $DIR/single_element_loop.rs:13:5
2020
|
2121
LL | / for item in [item1].iter() {
2222
LL | | dbg!(item);
@@ -32,7 +32,7 @@ LL + }
3232
|
3333

3434
error: for loop over a single element
35-
--> $DIR/single_element_loop.rs:15:5
35+
--> $DIR/single_element_loop.rs:17:5
3636
|
3737
LL | / for item in &[0..5] {
3838
LL | | dbg!(item);
@@ -48,7 +48,7 @@ LL + }
4848
|
4949

5050
error: for loop over a single element
51-
--> $DIR/single_element_loop.rs:19:5
51+
--> $DIR/single_element_loop.rs:21:5
5252
|
5353
LL | / for item in [0..5].iter_mut() {
5454
LL | | dbg!(item);
@@ -64,7 +64,7 @@ LL + }
6464
|
6565

6666
error: for loop over a single element
67-
--> $DIR/single_element_loop.rs:23:5
67+
--> $DIR/single_element_loop.rs:25:5
6868
|
6969
LL | / for item in [0..5] {
7070
LL | | dbg!(item);
@@ -80,7 +80,7 @@ LL + }
8080
|
8181

8282
error: for loop over a single element
83-
--> $DIR/single_element_loop.rs:27:5
83+
--> $DIR/single_element_loop.rs:29:5
8484
|
8585
LL | / for item in [0..5].into_iter() {
8686
LL | | dbg!(item);
@@ -96,7 +96,7 @@ LL + }
9696
|
9797

9898
error: for loop over a single element
99-
--> $DIR/single_element_loop.rs:46:5
99+
--> $DIR/single_element_loop.rs:48:5
100100
|
101101
LL | / for _ in [42] {
102102
LL | | let _f = |n: u32| {

tests/ui/single_range_in_vec_init.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//@aux-build:proc_macros.rs
2+
#![allow(clippy::no_effect, clippy::useless_vec, unused)]
3+
#![warn(clippy::single_range_in_vec_init)]
4+
#![feature(generic_arg_infer)]
5+
6+
#[macro_use]
7+
extern crate proc_macros;
8+
9+
macro_rules! a {
10+
() => {
11+
vec![0..200];
12+
};
13+
}
14+
15+
fn awa<T: PartialOrd>(start: T, end: T) {
16+
[start..end];
17+
}
18+
19+
fn awa_vec<T: PartialOrd>(start: T, end: T) {
20+
vec![start..end];
21+
}
22+
23+
fn main() {
24+
// Lint
25+
[0..200];
26+
vec![0..200];
27+
[0u8..200];
28+
[0usize..200];
29+
[0..200usize];
30+
vec![0u8..200];
31+
vec![0usize..200];
32+
vec![0..200usize];
33+
// Only suggest collect
34+
[0..200isize];
35+
vec![0..200isize];
36+
// Do not lint
37+
[0..200, 0..100];
38+
vec![0..200, 0..100];
39+
[0.0..200.0];
40+
vec![0.0..200.0];
41+
// `Copy` is not implemented for `Range`, so this doesn't matter
42+
// [0..200; 2];
43+
// [vec!0..200; 2];
44+
45+
// Unfortunately skips any macros
46+
a!();
47+
48+
// Skip external macros and procedural macros
49+
external! {
50+
[0..200];
51+
vec![0..200];
52+
}
53+
with_span! {
54+
span
55+
[0..200];
56+
vec![0..200];
57+
}
58+
}

0 commit comments

Comments
 (0)