Skip to content

Commit 679fa9f

Browse files
committed
Auto merge of #9187 - sgued:iter-once, r=flip1995
Add lint recommending using `std::iter::once` and `std::iter::empty` ``` changelog: [`iter_once`]: add new lint changelog: [`iter_empty`]: add new lint ``` fixes #9186 - \[ ] Followed [lint naming conventions][lint_naming] - \[x] Added passing UI tests (including committed `.stderr` file) - \[x] `cargo test` passes locally - \[x] Executed `cargo dev update_lints` - \[x] Added lint documentation - \[x] Run `cargo dev fmt` [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints The lint doesn't really follow the naming conventions. I don't have any better idea so I'm open to suggestions.
2 parents 84df61c + af4885c commit 679fa9f

16 files changed

+524
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3802,6 +3802,8 @@ Released 2018-09-13
38023802
[`iter_not_returning_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_not_returning_iterator
38033803
[`iter_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth
38043804
[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
3805+
[`iter_on_empty_collections`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_empty_collections
3806+
[`iter_on_single_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_on_single_items
38053807
[`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
38063808
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
38073809
[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain

clippy_lints/src/dereference.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,7 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
760760
.and_then(|subs| subs.get(1..))
761761
{
762762
Some(subs) => cx.tcx.mk_substs(subs.iter().copied()),
763-
None => cx.tcx.mk_substs([].iter()),
763+
None => cx.tcx.mk_substs(std::iter::empty::<ty::subst::GenericArg<'_>>()),
764764
} && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() {
765765
// Trait methods taking `&self`
766766
sub_ty

clippy_lints/src/derive.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,10 @@ fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) ->
516516
tcx.mk_predicates(ty_predicates.iter().map(|&(p, _)| p).chain(
517517
params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| {
518518
tcx.mk_predicate(Binder::dummy(PredicateKind::Trait(TraitPredicate {
519-
trait_ref: TraitRef::new(eq_trait_id, tcx.mk_substs([tcx.mk_param_from_def(param)].into_iter())),
519+
trait_ref: TraitRef::new(
520+
eq_trait_id,
521+
tcx.mk_substs(std::iter::once(tcx.mk_param_from_def(param))),
522+
),
520523
constness: BoundConstness::NotConst,
521524
polarity: ImplPolarity::Positive,
522525
})))

clippy_lints/src/lib.register_lints.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ store.register_lints(&[
315315
methods::ITER_NEXT_SLICE,
316316
methods::ITER_NTH,
317317
methods::ITER_NTH_ZERO,
318+
methods::ITER_ON_EMPTY_COLLECTIONS,
319+
methods::ITER_ON_SINGLE_ITEMS,
318320
methods::ITER_OVEREAGER_CLONED,
319321
methods::ITER_SKIP_NEXT,
320322
methods::ITER_WITH_DRAIN,

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_ON_EMPTY_COLLECTIONS),
18+
LintId::of(methods::ITER_ON_SINGLE_ITEMS),
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),
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet;
3+
use clippy_utils::{get_expr_use_or_unification_node, is_lang_ctor, is_no_std_crate};
4+
5+
use rustc_errors::Applicability;
6+
use rustc_hir::LangItem::{OptionNone, OptionSome};
7+
use rustc_hir::{Expr, ExprKind, Node};
8+
use rustc_lint::LateContext;
9+
10+
use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS};
11+
12+
enum IterType {
13+
Iter,
14+
IterMut,
15+
IntoIter,
16+
}
17+
18+
impl IterType {
19+
fn ref_prefix(&self) -> &'static str {
20+
match self {
21+
Self::Iter => "&",
22+
Self::IterMut => "&mut ",
23+
Self::IntoIter => "",
24+
}
25+
}
26+
}
27+
28+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) {
29+
let item = match &recv.kind {
30+
ExprKind::Array(v) if v.len() <= 1 => v.first(),
31+
ExprKind::Path(p) => {
32+
if is_lang_ctor(cx, p, OptionNone) {
33+
None
34+
} else {
35+
return;
36+
}
37+
},
38+
ExprKind::Call(f, some_args) if some_args.len() == 1 => {
39+
if let ExprKind::Path(p) = &f.kind {
40+
if is_lang_ctor(cx, p, OptionSome) {
41+
Some(&some_args[0])
42+
} else {
43+
return;
44+
}
45+
} else {
46+
return;
47+
}
48+
},
49+
_ => return,
50+
};
51+
let iter_type = match method_name {
52+
"iter" => IterType::Iter,
53+
"iter_mut" => IterType::IterMut,
54+
"into_iter" => IterType::IntoIter,
55+
_ => return,
56+
};
57+
58+
let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) {
59+
Some((Node::Expr(parent), child_id)) => match parent.kind {
60+
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false,
61+
ExprKind::If(_, _, _)
62+
| ExprKind::Match(_, _, _)
63+
| ExprKind::Closure(_)
64+
| ExprKind::Ret(_)
65+
| ExprKind::Break(_, _) => true,
66+
_ => false,
67+
},
68+
Some((Node::Stmt(_) | Node::Local(_), _)) => false,
69+
_ => true,
70+
};
71+
72+
if is_unified {
73+
return;
74+
}
75+
76+
if let Some(i) = item {
77+
let sugg = format!(
78+
"{}::iter::once({}{})",
79+
if is_no_std_crate(cx) { "core" } else { "std" },
80+
iter_type.ref_prefix(),
81+
snippet(cx, i.span, "...")
82+
);
83+
span_lint_and_sugg(
84+
cx,
85+
ITER_ON_SINGLE_ITEMS,
86+
expr.span,
87+
&format!("`{method_name}` call on a collection with only one item"),
88+
"try",
89+
sugg,
90+
Applicability::MaybeIncorrect,
91+
);
92+
} else {
93+
span_lint_and_sugg(
94+
cx,
95+
ITER_ON_EMPTY_COLLECTIONS,
96+
expr.span,
97+
&format!("`{method_name}` call on an empty collection"),
98+
"try",
99+
if is_no_std_crate(cx) {
100+
"core::iter::empty()".to_string()
101+
} else {
102+
"std::iter::empty()".to_string()
103+
},
104+
Applicability::MaybeIncorrect,
105+
);
106+
}
107+
}

clippy_lints/src/methods/mod.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ mod iter_count;
3333
mod iter_next_slice;
3434
mod iter_nth;
3535
mod iter_nth_zero;
36+
mod iter_on_single_or_empty_collections;
3637
mod iter_overeager_cloned;
3738
mod iter_skip_next;
3839
mod iter_with_drain;
@@ -2304,6 +2305,69 @@ declare_clippy_lint! {
23042305
more clearly with `if .. else ..`"
23052306
}
23062307

2308+
declare_clippy_lint! {
2309+
/// ### What it does
2310+
///
2311+
/// Checks for calls to `iter`, `iter_mut` or `into_iter` on collections containing a single item
2312+
///
2313+
/// ### Why is this bad?
2314+
///
2315+
/// It is simpler to use the once function from the standard library:
2316+
///
2317+
/// ### Example
2318+
///
2319+
/// ```rust
2320+
/// let a = [123].iter();
2321+
/// let b = Some(123).into_iter();
2322+
/// ```
2323+
/// Use instead:
2324+
/// ```rust
2325+
/// use std::iter;
2326+
/// let a = iter::once(&123);
2327+
/// let b = iter::once(123);
2328+
/// ```
2329+
///
2330+
/// ### Known problems
2331+
///
2332+
/// The type of the resulting iterator might become incompatible with its usage
2333+
#[clippy::version = "1.64.0"]
2334+
pub ITER_ON_SINGLE_ITEMS,
2335+
nursery,
2336+
"Iterator for array of length 1"
2337+
}
2338+
2339+
declare_clippy_lint! {
2340+
/// ### What it does
2341+
///
2342+
/// Checks for calls to `iter`, `iter_mut` or `into_iter` on empty collections
2343+
///
2344+
/// ### Why is this bad?
2345+
///
2346+
/// It is simpler to use the empty function from the standard library:
2347+
///
2348+
/// ### Example
2349+
///
2350+
/// ```rust
2351+
/// use std::{slice, option};
2352+
/// let a: slice::Iter<i32> = [].iter();
2353+
/// let f: option::IntoIter<i32> = None.into_iter();
2354+
/// ```
2355+
/// Use instead:
2356+
/// ```rust
2357+
/// use std::iter;
2358+
/// let a: iter::Empty<i32> = iter::empty();
2359+
/// let b: iter::Empty<i32> = iter::empty();
2360+
/// ```
2361+
///
2362+
/// ### Known problems
2363+
///
2364+
/// The type of the resulting iterator might become incompatible with its usage
2365+
#[clippy::version = "1.64.0"]
2366+
pub ITER_ON_EMPTY_COLLECTIONS,
2367+
nursery,
2368+
"Iterator for empty array"
2369+
}
2370+
23072371
pub struct Methods {
23082372
avoid_breaking_exported_api: bool,
23092373
msrv: Option<RustcVersion>,
@@ -2406,6 +2470,8 @@ impl_lint_pass!(Methods => [
24062470
NEEDLESS_OPTION_TAKE,
24072471
NO_EFFECT_REPLACE,
24082472
OBFUSCATED_IF_ELSE,
2473+
ITER_ON_SINGLE_ITEMS,
2474+
ITER_ON_EMPTY_COLLECTIONS
24092475
]);
24102476

24112477
/// Extracts a method call name, args, and `Span` of the method name.
@@ -2708,6 +2774,9 @@ impl Methods {
27082774
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv),
27092775
("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
27102776
("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
2777+
("iter" | "iter_mut" | "into_iter", []) => {
2778+
iter_on_single_or_empty_collections::check(cx, expr, name, recv);
2779+
},
27112780
("join", [join_arg]) => {
27122781
if let Some(("collect", _, span)) = method_call(recv) {
27132782
unnecessary_join::check(cx, expr, recv, join_arg, span);

clippy_lints/src/redundant_slicing.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use rustc_middle::ty::adjustment::{Adjust, AutoBorrow, AutoBorrowMutability};
1111
use rustc_middle::ty::subst::GenericArg;
1212
use rustc_session::{declare_lint_pass, declare_tool_lint};
1313

14+
use std::iter;
15+
1416
declare_clippy_lint! {
1517
/// ### What it does
1618
/// Checks for redundant slicing expressions which use the full range, and
@@ -134,7 +136,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing {
134136
} else if let Some(target_id) = cx.tcx.lang_items().deref_target() {
135137
if let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions(
136138
cx.param_env,
137-
cx.tcx.mk_projection(target_id, cx.tcx.mk_substs([GenericArg::from(indexed_ty)].into_iter())),
139+
cx.tcx.mk_projection(target_id, cx.tcx.mk_substs(iter::once(GenericArg::from(indexed_ty)))),
138140
) {
139141
if deref_ty == expr_ty {
140142
let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0;

clippy_lints/src/utils/conf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ define_Conf! {
350350
/// Lint: DISALLOWED_SCRIPT_IDENTS.
351351
///
352352
/// The list of unicode scripts allowed to be used in the scope.
353-
(allowed_scripts: Vec<String> = ["Latin"].iter().map(ToString::to_string).collect()),
353+
(allowed_scripts: Vec<String> = vec!["Latin".to_string()]),
354354
/// Lint: NON_SEND_FIELDS_IN_SEND_TY.
355355
///
356356
/// Whether to apply the raw pointer heuristic to determine if a type is `Send`.

clippy_lints/src/utils/internal_lints/metadata_collector.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ fn get_lint_group_and_level_or_lint(
797797
let result = cx.lint_store.check_lint_name(
798798
lint_name,
799799
Some(sym::clippy),
800-
&[Ident::with_dummy_span(sym::clippy)].into_iter().collect(),
800+
&std::iter::once(Ident::with_dummy_span(sym::clippy)).collect(),
801801
);
802802
if let CheckLintNameResult::Tool(Ok(lint_lst)) = result {
803803
if let Some(group) = get_lint_group(cx, lint_lst[0]) {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// run-rustfix
2+
#![warn(clippy::iter_on_empty_collections)]
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+
// Don't trigger on match or if branches
18+
let _ = match 123 {
19+
123 => [].iter(),
20+
_ => ["test"].iter(),
21+
};
22+
23+
let _ = if false { ["test"].iter() } else { [].iter() };
24+
}
25+
26+
macro_rules! in_macros {
27+
() => {
28+
assert_eq!([].into_iter().next(), Option::<i32>::None);
29+
assert_eq!([].iter_mut().next(), Option::<&mut i32>::None);
30+
assert_eq!([].iter().next(), Option::<&i32>::None);
31+
assert_eq!(None.into_iter().next(), Option::<i32>::None);
32+
assert_eq!(None.iter_mut().next(), Option::<&mut i32>::None);
33+
assert_eq!(None.iter().next(), Option::<&i32>::None);
34+
};
35+
}
36+
37+
// Don't trigger on a `None` that isn't std's option
38+
mod custom_option {
39+
#[allow(unused)]
40+
enum CustomOption {
41+
Some(i32),
42+
None,
43+
}
44+
45+
impl CustomOption {
46+
fn iter(&self) {}
47+
fn iter_mut(&mut self) {}
48+
fn into_iter(self) {}
49+
}
50+
use CustomOption::*;
51+
52+
pub fn custom_option() {
53+
None.iter();
54+
None.iter_mut();
55+
None.into_iter();
56+
}
57+
}
58+
59+
fn main() {
60+
array();
61+
custom_option::custom_option();
62+
in_macros!();
63+
}

0 commit comments

Comments
 (0)