Skip to content

Commit ecebfe0

Browse files
nahuakangY-Nak
authored andcommitted
Move check_for_loop_arg back to mod; split into 4 lint files
1 parent 7cfdef6 commit ecebfe0

File tree

5 files changed

+176
-4
lines changed

5 files changed

+176
-4
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
7+
pub(super) fn check_explicit_into_iter_loop(cx: &LateContext<'_>, method_args: &'hir [Expr<'hir>], arg: &Expr<'_>) {
8+
let mut applicability = Applicability::MachineApplicable;
9+
let object = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability);
10+
span_lint_and_sugg(
11+
cx,
12+
EXPLICIT_INTO_ITER_LOOP,
13+
arg.span,
14+
"it is more concise to loop over containers instead of using explicit \
15+
iteration methods",
16+
"to write this more concisely, try",
17+
object.to_string(),
18+
applicability,
19+
);
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use super::EXPLICIT_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+
7+
pub(super) fn lint_iter_method(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) {
8+
let mut applicability = Applicability::MachineApplicable;
9+
let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
10+
let muta = if method_name == "iter_mut" { "mut " } else { "" };
11+
span_lint_and_sugg(
12+
cx,
13+
EXPLICIT_ITER_LOOP,
14+
arg.span,
15+
"it is more concise to loop over references to containers instead of using explicit \
16+
iteration methods",
17+
"to write this more concisely, try",
18+
format!("&{}{}", muta, object),
19+
applicability,
20+
)
21+
}
Lines changed: 45 additions & 0 deletions
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_arg_type(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+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use super::ITER_NEXT_LOOP;
2+
use crate::utils::span_lint;
3+
use rustc_hir::Expr;
4+
use rustc_lint::LateContext;
5+
6+
pub(super) fn lint(cx: &LateContext<'_>, expr: &Expr<'_>) {
7+
span_lint(
8+
cx,
9+
ITER_NEXT_LOOP,
10+
expr.span,
11+
"you are iterating over `Iterator::next()` which is an Option; this will compile but is \
12+
probably not what you want",
13+
);
14+
}

clippy_lints/src/loops/mod.rs

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
mod empty_loop;
22
mod explicit_counter_loop;
3-
mod for_loop_arg;
3+
mod explicit_into_iter_loop;
4+
mod explicit_iter_loop;
45
mod for_loop_over_map_kv;
56
mod for_loop_range;
7+
mod for_loops_over_fallibles;
68
mod for_mut_range_bound;
79
mod for_single_element_loop;
810
mod infinite_loop;
11+
mod iter_next_loop;
912
mod manual_flatten;
1013
mod manual_memcpy;
1114
mod needless_collect;
@@ -15,11 +18,13 @@ mod utils;
1518
mod while_let_loop;
1619
mod while_let_on_iterator;
1720

18-
use crate::utils::higher;
19-
use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
21+
use crate::utils::{higher, is_type_diagnostic_item, match_trait_method, match_type, paths};
22+
use rustc_hir::{Expr, ExprKind, LoopSource, Mutability, Pat};
2023
use rustc_lint::{LateContext, LateLintPass};
24+
use rustc_middle::ty::{self, Ty, TyS};
2125
use rustc_session::{declare_lint_pass, declare_tool_lint};
2226
use rustc_span::source_map::Span;
27+
use rustc_span::symbol::sym;
2328
use utils::{get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor};
2429

2530
declare_clippy_lint! {
@@ -588,10 +593,77 @@ fn check_for_loop<'tcx>(
588593
for_loop_range::check_for_loop_range(cx, pat, arg, body, expr);
589594
explicit_counter_loop::check_for_loop_explicit_counter(cx, pat, arg, body, expr);
590595
}
591-
for_loop_arg::check_for_loop_arg(cx, pat, arg, expr);
596+
check_for_loop_arg(cx, pat, arg, expr);
592597
for_loop_over_map_kv::check_for_loop_over_map_kv(cx, pat, arg, body, expr);
593598
for_mut_range_bound::check_for_mut_range_bound(cx, arg, body);
594599
for_single_element_loop::check_for_single_element_loop(cx, pat, arg, body, expr);
595600
same_item_push::detect_same_item_push(cx, pat, arg, body, expr);
596601
manual_flatten::check_manual_flatten(cx, pat, arg, body, span);
597602
}
603+
604+
fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) {
605+
let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used
606+
if let ExprKind::MethodCall(ref method, _, ref args, _) = arg.kind {
607+
// just the receiver, no arguments
608+
if args.len() == 1 {
609+
let method_name = &*method.ident.as_str();
610+
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
611+
if method_name == "iter" || method_name == "iter_mut" {
612+
if is_ref_iterable_type(cx, &args[0]) {
613+
explicit_iter_loop::lint_iter_method(cx, args, arg, method_name);
614+
}
615+
} else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) {
616+
let receiver_ty = cx.typeck_results().expr_ty(&args[0]);
617+
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]);
618+
if TyS::same_type(receiver_ty, receiver_ty_adjusted) {
619+
explicit_into_iter_loop::check_explicit_into_iter_loop(cx, args, arg);
620+
} else {
621+
let ref_receiver_ty = cx.tcx.mk_ref(
622+
cx.tcx.lifetimes.re_erased,
623+
ty::TypeAndMut {
624+
ty: receiver_ty,
625+
mutbl: Mutability::Not,
626+
},
627+
);
628+
if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) {
629+
explicit_iter_loop::lint_iter_method(cx, args, arg, method_name)
630+
}
631+
}
632+
} else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) {
633+
iter_next_loop::lint(cx, expr);
634+
next_loop_linted = true;
635+
}
636+
}
637+
}
638+
if !next_loop_linted {
639+
for_loops_over_fallibles::check_arg_type(cx, pat, arg);
640+
}
641+
}
642+
643+
/// Returns `true` if the type of expr is one that provides `IntoIterator` impls
644+
/// for `&T` and `&mut T`, such as `Vec`.
645+
#[rustfmt::skip]
646+
fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
647+
// no walk_ptrs_ty: calling iter() on a reference can make sense because it
648+
// will allow further borrows afterwards
649+
let ty = cx.typeck_results().expr_ty(e);
650+
is_iterable_array(ty, cx) ||
651+
is_type_diagnostic_item(cx, ty, sym::vec_type) ||
652+
match_type(cx, ty, &paths::LINKED_LIST) ||
653+
is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) ||
654+
is_type_diagnostic_item(cx, ty, sym!(hashset_type)) ||
655+
is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) ||
656+
match_type(cx, ty, &paths::BINARY_HEAP) ||
657+
match_type(cx, ty, &paths::BTREEMAP) ||
658+
match_type(cx, ty, &paths::BTREESET)
659+
}
660+
661+
fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
662+
// IntoIterator is currently only implemented for array sizes <= 32 in rustc
663+
match ty.kind() {
664+
ty::Array(_, n) => n
665+
.try_eval_usize(cx.tcx, cx.param_env)
666+
.map_or(false, |val| (0..=32).contains(&val)),
667+
_ => false,
668+
}
669+
}

0 commit comments

Comments
 (0)