Skip to content

Commit 349e5e1

Browse files
Add linter for a single element for loop
Signed-off-by: Patrick José Pereira <[email protected]>
1 parent 01dd31f commit 349e5e1

File tree

7 files changed

+119
-3
lines changed

7 files changed

+119
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1936,6 +1936,7 @@ Released 2018-09-13
19361936
[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
19371937
[`single_char_push_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_push_str
19381938
[`single_component_path_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_component_path_imports
1939+
[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
19391940
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
19401941
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
19411942
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
633633
&loops::NEEDLESS_RANGE_LOOP,
634634
&loops::NEVER_LOOP,
635635
&loops::SAME_ITEM_PUSH,
636+
&loops::SINGLE_ELEMENT_LOOP,
636637
&loops::WHILE_IMMUTABLE_CONDITION,
637638
&loops::WHILE_LET_LOOP,
638639
&loops::WHILE_LET_ON_ITERATOR,
@@ -1363,6 +1364,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13631364
LintId::of(&loops::NEEDLESS_RANGE_LOOP),
13641365
LintId::of(&loops::NEVER_LOOP),
13651366
LintId::of(&loops::SAME_ITEM_PUSH),
1367+
LintId::of(&loops::SINGLE_ELEMENT_LOOP),
13661368
LintId::of(&loops::WHILE_IMMUTABLE_CONDITION),
13671369
LintId::of(&loops::WHILE_LET_LOOP),
13681370
LintId::of(&loops::WHILE_LET_ON_ITERATOR),
@@ -1664,6 +1666,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16641666
LintId::of(&lifetimes::NEEDLESS_LIFETIMES),
16651667
LintId::of(&loops::EXPLICIT_COUNTER_LOOP),
16661668
LintId::of(&loops::MUT_RANGE_BOUND),
1669+
LintId::of(&loops::SINGLE_ELEMENT_LOOP),
16671670
LintId::of(&loops::WHILE_LET_LOOP),
16681671
LintId::of(&manual_strip::MANUAL_STRIP),
16691672
LintId::of(&manual_unwrap_or::MANUAL_UNWRAP_OR),

clippy_lints/src/loops.rs

+68-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ use crate::utils::sugg::Sugg;
44
use crate::utils::usage::{is_unused, mutated_variables};
55
use crate::utils::{
66
contains_name, get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait,
7-
is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, match_trait_method,
8-
match_type, match_var, multispan_sugg, qpath_res, snippet, snippet_with_applicability, snippet_with_macro_callsite,
9-
span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, SpanlessEq,
7+
indent_of, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment,
8+
match_trait_method, match_type, match_var, multispan_sugg, qpath_res, single_segment_path, snippet,
9+
snippet_with_applicability, snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg,
10+
span_lint_and_then, sugg, SpanlessEq,
1011
};
1112
use if_chain::if_chain;
1213
use rustc_ast::ast;
@@ -452,6 +453,31 @@ declare_clippy_lint! {
452453
"the same item is pushed inside of a for loop"
453454
}
454455

456+
declare_clippy_lint! {
457+
/// **What it does:** Checks whether a for loop has a single element.
458+
///
459+
/// **Why is this bad?** There is no reason to have a loop of a
460+
/// single element.
461+
/// **Known problems:** None
462+
///
463+
/// **Example:**
464+
/// ```rust
465+
/// let item1 = 2;
466+
/// for item in &[item1] {
467+
/// println!("{}", item);
468+
/// }
469+
/// ```
470+
/// could be written as
471+
/// ```rust
472+
/// let item1 = 2;
473+
/// let item = &item1;
474+
/// println!("{}", item);
475+
/// ```
476+
pub SINGLE_ELEMENT_LOOP,
477+
complexity,
478+
"there is no reason to have a single element loop"
479+
}
480+
455481
declare_lint_pass!(Loops => [
456482
MANUAL_MEMCPY,
457483
NEEDLESS_RANGE_LOOP,
@@ -469,6 +495,7 @@ declare_lint_pass!(Loops => [
469495
MUT_RANGE_BOUND,
470496
WHILE_IMMUTABLE_CONDITION,
471497
SAME_ITEM_PUSH,
498+
SINGLE_ELEMENT_LOOP,
472499
]);
473500

474501
impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -777,6 +804,7 @@ fn check_for_loop<'tcx>(
777804
check_for_loop_arg(cx, pat, arg, expr);
778805
check_for_loop_over_map_kv(cx, pat, arg, body, expr);
779806
check_for_mut_range_bound(cx, arg, body);
807+
check_for_single_element_loop(cx, pat, arg, body, expr);
780808
detect_same_item_push(cx, pat, arg, body, expr);
781809
}
782810

@@ -1866,6 +1894,43 @@ fn check_for_loop_over_map_kv<'tcx>(
18661894
}
18671895
}
18681896

1897+
fn check_for_single_element_loop<'tcx>(
1898+
cx: &LateContext<'tcx>,
1899+
pat: &'tcx Pat<'_>,
1900+
arg: &'tcx Expr<'_>,
1901+
body: &'tcx Expr<'_>,
1902+
expr: &'tcx Expr<'_>,
1903+
) {
1904+
if_chain! {
1905+
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg_expr) = arg.kind;
1906+
if let PatKind::Binding(.., target, _) = pat.kind;
1907+
if let ExprKind::Array(ref arg_expr_list) = arg_expr.kind;
1908+
if let [arg_expression] = arg_expr_list;
1909+
if let ExprKind::Path(ref list_item) = arg_expression.kind;
1910+
if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name);
1911+
if let ExprKind::Block(ref block, _) = body.kind;
1912+
if block.stmts.len() != 0;
1913+
1914+
then {
1915+
let for_span = get_span_of_entire_for_loop(expr);
1916+
let mut block_str = snippet(cx, block.span, "..").into_owned();
1917+
block_str.remove(0);
1918+
block_str.pop();
1919+
1920+
1921+
span_lint_and_sugg(
1922+
cx,
1923+
SINGLE_ELEMENT_LOOP,
1924+
for_span,
1925+
"for loop over a single element",
1926+
"try",
1927+
format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str),
1928+
Applicability::MachineApplicable
1929+
)
1930+
}
1931+
}
1932+
}
1933+
18691934
struct MutatePairDelegate<'a, 'tcx> {
18701935
cx: &'a LateContext<'tcx>,
18711936
hir_id_low: Option<HirId>,

src/lintlist/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -2125,6 +2125,13 @@ vec![
21252125
deprecation: None,
21262126
module: "single_component_path_imports",
21272127
},
2128+
Lint {
2129+
name: "single_element_loop",
2130+
group: "complexity",
2131+
desc: "there is no reason to have a single element loop",
2132+
deprecation: None,
2133+
module: "loops",
2134+
},
21282135
Lint {
21292136
name: "single_match",
21302137
group: "style",

tests/ui/single_element_loop.fixed

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// run-rustfix
2+
// Tests from for_loop.rs that don't have suggestions
3+
4+
#[warn(clippy::single_element_loop)]
5+
fn main() {
6+
let item1 = 2;
7+
{
8+
let item = &item1;
9+
println!("{}", item);
10+
}
11+
}

tests/ui/single_element_loop.rs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// run-rustfix
2+
// Tests from for_loop.rs that don't have suggestions
3+
4+
#[warn(clippy::single_element_loop)]
5+
fn main() {
6+
let item1 = 2;
7+
for item in &[item1] {
8+
println!("{}", item);
9+
}
10+
}

tests/ui/single_element_loop.stderr

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
error: for loop over a single element
2+
--> $DIR/single_element_loop.rs:7:5
3+
|
4+
LL | / for item in &[item1] {
5+
LL | | println!("{}", item);
6+
LL | | }
7+
| |_____^
8+
|
9+
= note: `-D clippy::single-element-loop` implied by `-D warnings`
10+
help: try
11+
|
12+
LL | {
13+
LL | let item = &item1;
14+
LL | println!("{}", item);
15+
LL | }
16+
|
17+
18+
error: aborting due to previous error
19+

0 commit comments

Comments
 (0)