Skip to content

Commit 43d19f6

Browse files
committed
Auto merge of #6824 - Y-Nak:refactor_loops_module, r=flip1995
Refactor: organize loops file into loops module (Delegated) `@flip1995` `@nahuakang` closes #6693 r? `@flip1995` As we talked about in the PM of Zulip, this PR is a delegated PR from `@nahuakang.` Changes from the last commit of #6693: 1. Unify the name of the main entries of all modules to check, that was pointed out [here](#6693 (comment)) 2. Simplify ` check_for_loop_arg`, that was pointed out [here](#6693 (comment)) and [here](#6693 (comment)) 3. Resolve conflicts changelog: Refactor `loops.rs` file into `loops` module.
2 parents ece3543 + 19c886b commit 43d19f6

21 files changed

+3386
-3173
lines changed

clippy_lints/src/loops.rs

-3,173
This file was deleted.

clippy_lints/src/loops/empty_loop.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use super::EMPTY_LOOP;
2+
use crate::utils::{is_in_panic_handler, is_no_std_crate, span_lint_and_help};
3+
4+
use rustc_hir::{Block, Expr};
5+
use rustc_lint::LateContext;
6+
7+
pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) {
8+
if loop_block.stmts.is_empty() && loop_block.expr.is_none() && !is_in_panic_handler(cx, expr) {
9+
let msg = "empty `loop {}` wastes CPU cycles";
10+
let help = if is_no_std_crate(cx.tcx.hir().krate()) {
11+
"you should either use `panic!()` or add a call pausing or sleeping the thread to the loop body"
12+
} else {
13+
"you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body"
14+
};
15+
span_lint_and_help(cx, EMPTY_LOOP, expr.span, msg, None, help);
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use super::{
2+
get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT_COUNTER_LOOP,
3+
};
4+
use crate::utils::{get_enclosing_block, is_integer_const, snippet_with_applicability, span_lint_and_sugg};
5+
use if_chain::if_chain;
6+
use rustc_errors::Applicability;
7+
use rustc_hir::intravisit::{walk_block, walk_expr};
8+
use rustc_hir::{Expr, Pat};
9+
use rustc_lint::LateContext;
10+
11+
// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be
12+
// incremented exactly once in the loop body, and initialized to zero
13+
// at the start of the loop.
14+
pub(super) fn check<'tcx>(
15+
cx: &LateContext<'tcx>,
16+
pat: &'tcx Pat<'_>,
17+
arg: &'tcx Expr<'_>,
18+
body: &'tcx Expr<'_>,
19+
expr: &'tcx Expr<'_>,
20+
) {
21+
// Look for variables that are incremented once per loop iteration.
22+
let mut increment_visitor = IncrementVisitor::new(cx);
23+
walk_expr(&mut increment_visitor, body);
24+
25+
// For each candidate, check the parent block to see if
26+
// it's initialized to zero at the start of the loop.
27+
if let Some(block) = get_enclosing_block(&cx, expr.hir_id) {
28+
for id in increment_visitor.into_results() {
29+
let mut initialize_visitor = InitializeVisitor::new(cx, expr, id);
30+
walk_block(&mut initialize_visitor, block);
31+
32+
if_chain! {
33+
if let Some((name, initializer)) = initialize_visitor.get_result();
34+
if is_integer_const(cx, initializer, 0);
35+
then {
36+
let mut applicability = Applicability::MachineApplicable;
37+
38+
let for_span = get_span_of_entire_for_loop(expr);
39+
40+
span_lint_and_sugg(
41+
cx,
42+
EXPLICIT_COUNTER_LOOP,
43+
for_span.with_hi(arg.span.hi()),
44+
&format!("the variable `{}` is used as a loop counter", name),
45+
"consider using",
46+
format!(
47+
"for ({}, {}) in {}.enumerate()",
48+
name,
49+
snippet_with_applicability(cx, pat.span, "item", &mut applicability),
50+
make_iterator_snippet(cx, arg, &mut applicability),
51+
),
52+
applicability,
53+
);
54+
}
55+
}
56+
}
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use super::EXPLICIT_INTO_ITER_LOOP;
2+
use crate::utils::{snippet_with_applicability, span_lint_and_sugg};
3+
use rustc_errors::Applicability;
4+
use rustc_hir::Expr;
5+
use rustc_lint::LateContext;
6+
use rustc_middle::ty::TyS;
7+
8+
pub(super) fn check(cx: &LateContext<'_>, args: &'hir [Expr<'hir>], arg: &Expr<'_>) {
9+
let receiver_ty = cx.typeck_results().expr_ty(&args[0]);
10+
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]);
11+
if !TyS::same_type(receiver_ty, receiver_ty_adjusted) {
12+
return;
13+
}
14+
15+
let mut applicability = Applicability::MachineApplicable;
16+
let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
17+
span_lint_and_sugg(
18+
cx,
19+
EXPLICIT_INTO_ITER_LOOP,
20+
arg.span,
21+
"it is more concise to loop over containers instead of using explicit \
22+
iteration methods",
23+
"to write this more concisely, try",
24+
object.to_string(),
25+
applicability,
26+
);
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use super::EXPLICIT_ITER_LOOP;
2+
use crate::utils::{match_trait_method, snippet_with_applicability, span_lint_and_sugg};
3+
use rustc_errors::Applicability;
4+
use rustc_hir::{Expr, Mutability};
5+
use rustc_lint::LateContext;
6+
use rustc_middle::ty::{self, Ty, TyS};
7+
use rustc_span::symbol::sym;
8+
9+
use crate::utils::{is_type_diagnostic_item, match_type, paths};
10+
11+
pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) {
12+
let should_lint = match method_name {
13+
"iter" | "iter_mut" => is_ref_iterable_type(cx, &args[0]),
14+
"into_iter" if match_trait_method(cx, arg, &paths::INTO_ITERATOR) => {
15+
let receiver_ty = cx.typeck_results().expr_ty(&args[0]);
16+
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]);
17+
let ref_receiver_ty = cx.tcx.mk_ref(
18+
cx.tcx.lifetimes.re_erased,
19+
ty::TypeAndMut {
20+
ty: receiver_ty,
21+
mutbl: Mutability::Not,
22+
},
23+
);
24+
TyS::same_type(receiver_ty_adjusted, ref_receiver_ty)
25+
},
26+
_ => false,
27+
};
28+
29+
if !should_lint {
30+
return;
31+
}
32+
33+
let mut applicability = Applicability::MachineApplicable;
34+
let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
35+
let muta = if method_name == "iter_mut" { "mut " } else { "" };
36+
span_lint_and_sugg(
37+
cx,
38+
EXPLICIT_ITER_LOOP,
39+
arg.span,
40+
"it is more concise to loop over references to containers instead of using explicit \
41+
iteration methods",
42+
"to write this more concisely, try",
43+
format!("&{}{}", muta, object),
44+
applicability,
45+
)
46+
}
47+
48+
/// Returns `true` if the type of expr is one that provides `IntoIterator` impls
49+
/// for `&T` and `&mut T`, such as `Vec`.
50+
#[rustfmt::skip]
51+
fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
52+
// no walk_ptrs_ty: calling iter() on a reference can make sense because it
53+
// will allow further borrows afterwards
54+
let ty = cx.typeck_results().expr_ty(e);
55+
is_iterable_array(ty, cx) ||
56+
is_type_diagnostic_item(cx, ty, sym::vec_type) ||
57+
match_type(cx, ty, &paths::LINKED_LIST) ||
58+
is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) ||
59+
is_type_diagnostic_item(cx, ty, sym!(hashset_type)) ||
60+
is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) ||
61+
match_type(cx, ty, &paths::BINARY_HEAP) ||
62+
match_type(cx, ty, &paths::BTREEMAP) ||
63+
match_type(cx, ty, &paths::BTREESET)
64+
}
65+
66+
fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
67+
// IntoIterator is currently only implemented for array sizes <= 32 in rustc
68+
match ty.kind() {
69+
ty::Array(_, n) => n
70+
.try_eval_usize(cx.tcx, cx.param_env)
71+
.map_or(false, |val| (0..=32).contains(&val)),
72+
_ => false,
73+
}
74+
}

clippy_lints/src/loops/for_kv_map.rs

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use super::FOR_KV_MAP;
2+
use crate::utils::visitors::LocalUsedVisitor;
3+
use crate::utils::{is_type_diagnostic_item, match_type, multispan_sugg, paths, snippet, span_lint_and_then, sugg};
4+
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind};
5+
use rustc_lint::LateContext;
6+
use rustc_middle::ty;
7+
8+
/// Checks for the `FOR_KV_MAP` lint.
9+
pub(super) fn check<'tcx>(
10+
cx: &LateContext<'tcx>,
11+
pat: &'tcx Pat<'_>,
12+
arg: &'tcx Expr<'_>,
13+
body: &'tcx Expr<'_>,
14+
expr: &'tcx Expr<'_>,
15+
) {
16+
let pat_span = pat.span;
17+
18+
if let PatKind::Tuple(ref pat, _) = pat.kind {
19+
if pat.len() == 2 {
20+
let arg_span = arg.span;
21+
let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() {
22+
ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) {
23+
(key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl),
24+
(_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not),
25+
_ => return,
26+
},
27+
_ => return,
28+
};
29+
let mutbl = match mutbl {
30+
Mutability::Not => "",
31+
Mutability::Mut => "_mut",
32+
};
33+
let arg = match arg.kind {
34+
ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) => &**expr,
35+
_ => arg,
36+
};
37+
38+
if is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || match_type(cx, ty, &paths::BTREEMAP) {
39+
span_lint_and_then(
40+
cx,
41+
FOR_KV_MAP,
42+
expr.span,
43+
&format!("you seem to want to iterate on a map's {}s", kind),
44+
|diag| {
45+
let map = sugg::Sugg::hir(cx, arg, "map");
46+
multispan_sugg(
47+
diag,
48+
"use the corresponding method",
49+
vec![
50+
(pat_span, snippet(cx, new_pat_span, kind).into_owned()),
51+
(arg_span, format!("{}.{}s{}()", map.maybe_par(), kind, mutbl)),
52+
],
53+
);
54+
},
55+
);
56+
}
57+
}
58+
}
59+
}
60+
61+
/// Returns `true` if the pattern is a `PatWild` or an ident prefixed with `_`.
62+
fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool {
63+
match *pat {
64+
PatKind::Wild => true,
65+
PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => {
66+
!LocalUsedVisitor::new(cx, id).check_expr(body)
67+
},
68+
_ => false,
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use super::FOR_LOOPS_OVER_FALLIBLES;
2+
use crate::utils::{is_type_diagnostic_item, snippet, span_lint_and_help};
3+
use rustc_hir::{Expr, Pat};
4+
use rustc_lint::LateContext;
5+
use rustc_span::symbol::sym;
6+
7+
/// Checks for `for` loops over `Option`s and `Result`s.
8+
pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) {
9+
let ty = cx.typeck_results().expr_ty(arg);
10+
if is_type_diagnostic_item(cx, ty, sym::option_type) {
11+
span_lint_and_help(
12+
cx,
13+
FOR_LOOPS_OVER_FALLIBLES,
14+
arg.span,
15+
&format!(
16+
"for loop over `{0}`, which is an `Option`. This is more readably written as an \
17+
`if let` statement",
18+
snippet(cx, arg.span, "_")
19+
),
20+
None,
21+
&format!(
22+
"consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`",
23+
snippet(cx, pat.span, "_"),
24+
snippet(cx, arg.span, "_")
25+
),
26+
);
27+
} else if is_type_diagnostic_item(cx, ty, sym::result_type) {
28+
span_lint_and_help(
29+
cx,
30+
FOR_LOOPS_OVER_FALLIBLES,
31+
arg.span,
32+
&format!(
33+
"for loop over `{0}`, which is a `Result`. This is more readably written as an \
34+
`if let` statement",
35+
snippet(cx, arg.span, "_")
36+
),
37+
None,
38+
&format!(
39+
"consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`",
40+
snippet(cx, pat.span, "_"),
41+
snippet(cx, arg.span, "_")
42+
),
43+
);
44+
}
45+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use super::ITER_NEXT_LOOP;
2+
use crate::utils::{match_trait_method, paths, span_lint};
3+
use rustc_hir::Expr;
4+
use rustc_lint::LateContext;
5+
6+
pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, expr: &Expr<'_>) -> bool {
7+
if match_trait_method(cx, arg, &paths::ITERATOR) {
8+
span_lint(
9+
cx,
10+
ITER_NEXT_LOOP,
11+
expr.span,
12+
"you are iterating over `Iterator::next()` which is an Option; this will compile but is \
13+
probably not what you want",
14+
);
15+
true
16+
} else {
17+
false
18+
}
19+
}
+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use super::utils::make_iterator_snippet;
2+
use super::MANUAL_FLATTEN;
3+
use crate::utils::{is_ok_ctor, is_some_ctor, path_to_local_id, span_lint_and_then};
4+
use if_chain::if_chain;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, StmtKind};
7+
use rustc_lint::LateContext;
8+
use rustc_span::source_map::Span;
9+
10+
/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the
11+
/// iterator element is used.
12+
pub(super) fn check<'tcx>(
13+
cx: &LateContext<'tcx>,
14+
pat: &'tcx Pat<'_>,
15+
arg: &'tcx Expr<'_>,
16+
body: &'tcx Expr<'_>,
17+
span: Span,
18+
) {
19+
if let ExprKind::Block(ref block, _) = body.kind {
20+
// Ensure the `if let` statement is the only expression or statement in the for-loop
21+
let inner_expr = if block.stmts.len() == 1 && block.expr.is_none() {
22+
let match_stmt = &block.stmts[0];
23+
if let StmtKind::Semi(inner_expr) = match_stmt.kind {
24+
Some(inner_expr)
25+
} else {
26+
None
27+
}
28+
} else if block.stmts.is_empty() {
29+
block.expr
30+
} else {
31+
None
32+
};
33+
34+
if_chain! {
35+
if let Some(inner_expr) = inner_expr;
36+
if let ExprKind::Match(
37+
ref match_expr, ref match_arms, MatchSource::IfLetDesugar{ contains_else_clause: false }
38+
) = inner_expr.kind;
39+
// Ensure match_expr in `if let` statement is the same as the pat from the for-loop
40+
if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
41+
if path_to_local_id(match_expr, pat_hir_id);
42+
// Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
43+
if let PatKind::TupleStruct(QPath::Resolved(None, path), _, _) = match_arms[0].pat.kind;
44+
let some_ctor = is_some_ctor(cx, path.res);
45+
let ok_ctor = is_ok_ctor(cx, path.res);
46+
if some_ctor || ok_ctor;
47+
let if_let_type = if some_ctor { "Some" } else { "Ok" };
48+
49+
then {
50+
// Prepare the error message
51+
let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type);
52+
53+
// Prepare the help message
54+
let mut applicability = Applicability::MaybeIncorrect;
55+
let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability);
56+
57+
span_lint_and_then(
58+
cx,
59+
MANUAL_FLATTEN,
60+
span,
61+
&msg,
62+
|diag| {
63+
let sugg = format!("{}.flatten()", arg_snippet);
64+
diag.span_suggestion(
65+
arg.span,
66+
"try",
67+
sugg,
68+
Applicability::MaybeIncorrect,
69+
);
70+
diag.span_help(
71+
inner_expr.span,
72+
"...and remove the `if let` statement in the for loop",
73+
);
74+
}
75+
);
76+
}
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)