Skip to content

Commit 73cd954

Browse files
committed
Add iter_once and iter_empty lints
1 parent 8f39061 commit 73cd954

13 files changed

+383
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3651,11 +3651,13 @@ Released 2018-09-13
36513651
[`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements
36523652
[`iter_cloned_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_cloned_collect
36533653
[`iter_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_count
3654+
[`iter_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_empty
36543655
[`iter_next_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_loop
36553656
[`iter_next_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice
36563657
[`iter_not_returning_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_not_returning_iterator
36573658
[`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth
36583659
[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
3660+
[`iter_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_once
36593661
[`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
36603662
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
36613663
[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain

clippy_lints/src/iter_once_empty.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet;
3+
use rustc_ast::ast::{Expr, ExprKind};
4+
use rustc_errors::Applicability;
5+
use rustc_lint::{EarlyContext, EarlyLintPass};
6+
use rustc_session::{declare_lint_pass, declare_tool_lint};
7+
8+
declare_clippy_lint! {
9+
/// ### What it does
10+
///
11+
/// Checks for usage of:
12+
///
13+
/// - `[foo].iter()`
14+
/// - `[foo].iter_mut()`
15+
/// - `[foo].into_iter()`
16+
/// - `Some(foo).iter()`
17+
/// - `Some(foo).iter_mut()`
18+
/// - `Some(foo).into_iter()`
19+
///
20+
/// ### Why is this bad?
21+
///
22+
/// It is simpler to use the once function from the standard library:
23+
///
24+
/// ### Example
25+
///
26+
/// ```rust
27+
/// let a = [123].iter();
28+
/// let b = Some(123).into_iter();
29+
/// ```
30+
/// Use instead:
31+
/// ```rust
32+
/// use std::iter;
33+
/// let a = iter::once(&123);
34+
/// let b = iter::once(123);
35+
/// ```
36+
///
37+
/// ### Known problems
38+
///
39+
/// The type of the resulting iterator might become incompatible with its usage
40+
#[clippy::version = "1.64.0"]
41+
pub ITER_ONCE,
42+
nursery,
43+
"Iterator for array of length 1"
44+
}
45+
46+
declare_clippy_lint! {
47+
/// ### What it does
48+
///
49+
/// Checks for usage of:
50+
///
51+
/// - `[].iter()`
52+
/// - `[].iter_mut()`
53+
/// - `[].into_iter()`
54+
/// - `None.iter()`
55+
/// - `None.iter_mut()`
56+
/// - `None.into_iter()`
57+
///
58+
/// ### Why is this bad?
59+
///
60+
/// It is simpler to use the empty function from the standard library:
61+
///
62+
/// ### Example
63+
///
64+
/// ```rust
65+
/// use std::{slice, option};
66+
/// let a: slice::Iter<i32> = [].iter();
67+
/// let f: option::IntoIter<i32> = None.into_iter();
68+
/// ```
69+
/// Use instead:
70+
/// ```rust
71+
/// use std::iter;
72+
/// let a: iter::Empty<i32> = iter::empty();
73+
/// let b: iter::Empty<i32> = iter::empty();
74+
/// ```
75+
///
76+
/// ### Known problems
77+
///
78+
/// The type of the resulting iterator might become incompatible with its usage
79+
#[clippy::version = "1.64.0"]
80+
pub ITER_EMPTY,
81+
nursery,
82+
"Iterator for empty array"
83+
}
84+
85+
declare_lint_pass!(IterOnceEmpty => [ITER_ONCE, ITER_EMPTY]);
86+
87+
impl EarlyLintPass for IterOnceEmpty {
88+
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
89+
if expr.span.from_expansion() {
90+
// Don't lint match expressions present in
91+
// macro_rules! block
92+
return;
93+
}
94+
95+
let (method_name, args) = if let ExprKind::MethodCall(seg, args, _) = &expr.kind {
96+
(seg.ident.as_str(), args)
97+
} else {
98+
return;
99+
};
100+
let arg = if args.len() == 1 {
101+
&args[0]
102+
} else {
103+
return;
104+
};
105+
106+
let item = match &arg.kind {
107+
ExprKind::Array(v) if v.len() <= 1 => v.first(),
108+
ExprKind::Path(None, p) => {
109+
if p.segments.len() == 1 && p.segments[0].ident.name == rustc_span::sym::None {
110+
None
111+
} else {
112+
return;
113+
}
114+
},
115+
ExprKind::Call(f, some_args) if some_args.len() == 1 => {
116+
if let ExprKind::Path(None, p) = &f.kind {
117+
if p.segments.len() == 1 && p.segments[0].ident.name == rustc_span::sym::Some {
118+
Some(&some_args[0])
119+
} else {
120+
return;
121+
}
122+
} else {
123+
return;
124+
}
125+
},
126+
_ => return,
127+
};
128+
129+
if let Some(i) = item {
130+
let (sugg, msg) = match method_name {
131+
"iter" => (
132+
format!("std::iter::once(&{})", snippet(cx, i.span, "...")),
133+
"this `iter` call can be replaced with std::iter::once",
134+
),
135+
"iter_mut" => (
136+
format!("std::iter::once(&mut {})", snippet(cx, i.span, "...")),
137+
"this `iter_mut` call can be replaced with std::iter::once",
138+
),
139+
"into_iter" => (
140+
format!("std::iter::once({})", snippet(cx, i.span, "...")),
141+
"this `into_iter` call can be replaced with std::iter::once",
142+
),
143+
_ => return,
144+
};
145+
span_lint_and_sugg(cx, ITER_ONCE, expr.span, msg, "try", sugg, Applicability::Unspecified);
146+
} else {
147+
let msg = match method_name {
148+
"iter" => "this `iter call` can be replaced with std::iter::empty",
149+
"iter_mut" => "this `iter_mut` call can be replaced with std::iter::empty",
150+
"into_iter" => "this `into_iter` call can be replaced with std::iter::empty",
151+
_ => return,
152+
};
153+
span_lint_and_sugg(
154+
cx,
155+
ITER_EMPTY,
156+
expr.span,
157+
msg,
158+
"try",
159+
"std::iter::empty()".to_string(),
160+
Applicability::Unspecified,
161+
);
162+
}
163+
}
164+
}

clippy_lints/src/lib.register_lints.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ store.register_lints(&[
200200
invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED,
201201
items_after_statements::ITEMS_AFTER_STATEMENTS,
202202
iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR,
203+
iter_once_empty::ITER_EMPTY,
204+
iter_once_empty::ITER_ONCE,
203205
large_const_arrays::LARGE_CONST_ARRAYS,
204206
large_enum_variant::LARGE_ENUM_VARIANT,
205207
large_include_file::LARGE_INCLUDE_FILE,

clippy_lints/src/lib.register_nursery.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
1414
LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE),
1515
LintId::of(let_if_seq::USELESS_LET_IF_SEQ),
1616
LintId::of(matches::SIGNIFICANT_DROP_IN_SCRUTINEE),
17+
LintId::of(methods::ITER_EMPTY),
18+
LintId::of(methods::ITER_ONCE),
1719
LintId::of(methods::ITER_WITH_DRAIN),
1820
LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN),
1921
LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),

clippy_lints/src/lib.register_pedantic.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
4141
LintId::of(invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS),
4242
LintId::of(items_after_statements::ITEMS_AFTER_STATEMENTS),
4343
LintId::of(iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR),
44+
LintId::of(iter_once_empty::ITER_EMPTY),
45+
LintId::of(iter_once_empty::ITER_ONCE),
4446
LintId::of(large_stack_arrays::LARGE_STACK_ARRAYS),
4547
LintId::of(let_underscore::LET_UNDERSCORE_DROP),
4648
LintId::of(literal_representation::LARGE_DIGIT_GROUPS),

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ mod invalid_upcast_comparisons;
258258
mod invalid_utf8_in_unchecked;
259259
mod items_after_statements;
260260
mod iter_not_returning_iterator;
261+
mod iter_once_empty;
261262
mod large_const_arrays;
262263
mod large_enum_variant;
263264
mod large_include_file;
@@ -931,6 +932,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
931932
store.register_late_pass(|| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked));
932933
store.register_late_pass(|| Box::new(std_instead_of_core::StdReexports::default()));
933934
store.register_late_pass(|| Box::new(manual_instant_elapsed::ManualInstantElapsed));
935+
store.register_early_pass(|| Box::new(iter_once_empty::IterOnceEmpty));
934936
// add lints here, do not remove this comment, it's used in `new_lint`
935937
}
936938

clippy_utils/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
484484
}
485485
fn find_primitive<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx {
486486
let single = |ty| tcx.incoherent_impls(ty).iter().copied();
487+
#[allow(clippy::iter_empty)]
487488
let empty = || [].iter().copied();
488489
match name {
489490
"bool" => single(BoolSimplifiedType),

tests/ui/iter_empty.fixed

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// run-rustfix
2+
#![warn(clippy::iter_empty)]
3+
#![allow(clippy::iter_next_slice, clippy::redundant_clone)]
4+
5+
fn array() {
6+
assert_eq!(std::iter::empty().next(), Option::<i32>::None);
7+
assert_eq!(std::iter::empty().next(), Option::<&mut i32>::None);
8+
assert_eq!(std::iter::empty().next(), Option::<&i32>::None);
9+
assert_eq!(std::iter::empty().next(), Option::<i32>::None);
10+
assert_eq!(std::iter::empty().next(), Option::<&mut i32>::None);
11+
assert_eq!(std::iter::empty().next(), Option::<&i32>::None);
12+
13+
// Don't trigger on non-iter methods
14+
let _: Option<String> = None.clone();
15+
let _: [String; 0] = [].clone();
16+
}
17+
18+
macro_rules! in_macros {
19+
() => {
20+
assert_eq!([].into_iter().next(), Option::<i32>::None);
21+
assert_eq!([].iter_mut().next(), Option::<&mut i32>::None);
22+
assert_eq!([].iter().next(), Option::<&i32>::None);
23+
assert_eq!(None.into_iter().next(), Option::<i32>::None);
24+
assert_eq!(None.iter_mut().next(), Option::<&mut i32>::None);
25+
assert_eq!(None.iter().next(), Option::<&i32>::None);
26+
};
27+
}
28+
29+
fn main() {
30+
array();
31+
in_macros!();
32+
}

tests/ui/iter_empty.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// run-rustfix
2+
#![warn(clippy::iter_empty)]
3+
#![allow(clippy::iter_next_slice, clippy::redundant_clone)]
4+
5+
fn array() {
6+
assert_eq!([].into_iter().next(), Option::<i32>::None);
7+
assert_eq!([].iter_mut().next(), Option::<&mut i32>::None);
8+
assert_eq!([].iter().next(), Option::<&i32>::None);
9+
assert_eq!(None.into_iter().next(), Option::<i32>::None);
10+
assert_eq!(None.iter_mut().next(), Option::<&mut i32>::None);
11+
assert_eq!(None.iter().next(), Option::<&i32>::None);
12+
13+
// Don't trigger on non-iter methods
14+
let _: Option<String> = None.clone();
15+
let _: [String; 0] = [].clone();
16+
}
17+
18+
macro_rules! in_macros {
19+
() => {
20+
assert_eq!([].into_iter().next(), Option::<i32>::None);
21+
assert_eq!([].iter_mut().next(), Option::<&mut i32>::None);
22+
assert_eq!([].iter().next(), Option::<&i32>::None);
23+
assert_eq!(None.into_iter().next(), Option::<i32>::None);
24+
assert_eq!(None.iter_mut().next(), Option::<&mut i32>::None);
25+
assert_eq!(None.iter().next(), Option::<&i32>::None);
26+
};
27+
}
28+
29+
fn main() {
30+
array();
31+
in_macros!();
32+
}

tests/ui/iter_empty.stderr

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
error: this `into_iter` call can be replaced with std::iter::empty
2+
--> $DIR/iter_empty.rs:6:16
3+
|
4+
LL | assert_eq!([].into_iter().next(), Option::<i32>::None);
5+
| ^^^^^^^^^^^^^^ help: try: `std::iter::empty()`
6+
|
7+
= note: `-D clippy::iter-empty` implied by `-D warnings`
8+
9+
error: this `iter_mut` call can be replaced with std::iter::empty
10+
--> $DIR/iter_empty.rs:7:16
11+
|
12+
LL | assert_eq!([].iter_mut().next(), Option::<&mut i32>::None);
13+
| ^^^^^^^^^^^^^ help: try: `std::iter::empty()`
14+
15+
error: this `iter call` can be replaced with std::iter::empty
16+
--> $DIR/iter_empty.rs:8:16
17+
|
18+
LL | assert_eq!([].iter().next(), Option::<&i32>::None);
19+
| ^^^^^^^^^ help: try: `std::iter::empty()`
20+
21+
error: this `into_iter` call can be replaced with std::iter::empty
22+
--> $DIR/iter_empty.rs:9:16
23+
|
24+
LL | assert_eq!(None.into_iter().next(), Option::<i32>::None);
25+
| ^^^^^^^^^^^^^^^^ help: try: `std::iter::empty()`
26+
27+
error: this `iter_mut` call can be replaced with std::iter::empty
28+
--> $DIR/iter_empty.rs:10:16
29+
|
30+
LL | assert_eq!(None.iter_mut().next(), Option::<&mut i32>::None);
31+
| ^^^^^^^^^^^^^^^ help: try: `std::iter::empty()`
32+
33+
error: this `iter call` can be replaced with std::iter::empty
34+
--> $DIR/iter_empty.rs:11:16
35+
|
36+
LL | assert_eq!(None.iter().next(), Option::<&i32>::None);
37+
| ^^^^^^^^^^^ help: try: `std::iter::empty()`
38+
39+
error: aborting due to 6 previous errors
40+

tests/ui/iter_once.fixed

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// run-rustfix
2+
#![warn(clippy::iter_once)]
3+
#![allow(clippy::iter_next_slice, clippy::redundant_clone)]
4+
5+
fn array() {
6+
assert_eq!(std::iter::once(123).next(), Some(123));
7+
assert_eq!(std::iter::once(&mut 123).next(), Some(&mut 123));
8+
assert_eq!(std::iter::once(&123).next(), Some(&123));
9+
assert_eq!(std::iter::once(123).next(), Some(123));
10+
assert_eq!(std::iter::once(&mut 123).next(), Some(&mut 123));
11+
assert_eq!(std::iter::once(&123).next(), Some(&123));
12+
13+
// Don't trigger on non-iter methods
14+
let _: Option<String> = Some("test".to_string()).clone();
15+
let _: [String; 1] = ["test".to_string()].clone();
16+
}
17+
18+
macro_rules! in_macros {
19+
() => {
20+
assert_eq!([123].into_iter().next(), Some(123));
21+
assert_eq!([123].iter_mut().next(), Some(&mut 123));
22+
assert_eq!([123].iter().next(), Some(&123));
23+
assert_eq!(Some(123).into_iter().next(), Some(123));
24+
assert_eq!(Some(123).iter_mut().next(), Some(&mut 123));
25+
assert_eq!(Some(123).iter().next(), Some(&123));
26+
};
27+
}
28+
29+
fn main() {
30+
array();
31+
in_macros!();
32+
}

0 commit comments

Comments
 (0)