Skip to content

Commit 823d9dd

Browse files
committed
Auto merge of rust-lang#10934 - Centri3:single_range_in_vec_init, r=giraffate
new lint [`single_range_in_vec_init`] Lints on `vec![0..200]` (or `[0..200]`), suggesting either `(0..200).collect::<Vec<i32>>()` or `[0; 200]`. Haven't tested it with anything that isn't primitive. Probably should! Closes rust-lang#10932 changelog: new lint [`single_range_in_vec_init`]
2 parents ffe9525 + 830d307 commit 823d9dd

12 files changed

+393
-33
lines changed

Diff for: CHANGELOG.md

+1
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

Diff for: clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
570570
crate::significant_drop_tightening::SIGNIFICANT_DROP_TIGHTENING_INFO,
571571
crate::single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES_INFO,
572572
crate::single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS_INFO,
573+
crate::single_range_in_vec_init::SINGLE_RANGE_IN_VEC_INIT_INFO,
573574
crate::size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT_INFO,
574575
crate::size_of_ref::SIZE_OF_REF_INFO,
575576
crate::slow_vector_initialization::SLOW_VECTOR_INITIALIZATION_INFO,

Diff for: clippy_lints/src/lib.rs

+2
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

Diff for: clippy_lints/src/single_range_in_vec_init.rs

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

Diff for: tests/ui/single_element_loop.fixed

+2
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;

Diff for: tests/ui/single_element_loop.rs

+2
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;

Diff for: tests/ui/single_element_loop.stderr

+7-7
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| {

Diff for: tests/ui/single_range_in_vec_init.rs

+58
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)