Skip to content

Commit 408368a

Browse files
nahuakangY-Nak
authored andcommitted
Refactor check_for_loop_arg; rename manual_flatten's lint back to check_manual_flatten
1 parent 62ac4bd commit 408368a

File tree

3 files changed

+154
-144
lines changed

3 files changed

+154
-144
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use crate::utils::{
2+
is_type_diagnostic_item, match_trait_method, match_type, paths, snippet, snippet_with_applicability, span_lint,
3+
span_lint_and_help, span_lint_and_sugg,
4+
};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{Expr, ExprKind, Mutability, Pat};
7+
use rustc_lint::LateContext;
8+
use rustc_middle::ty::{self, Ty, TyS};
9+
use rustc_span::symbol::sym;
10+
11+
pub(super) fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) {
12+
let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used
13+
if let ExprKind::MethodCall(ref method, _, ref args, _) = arg.kind {
14+
// just the receiver, no arguments
15+
if args.len() == 1 {
16+
let method_name = &*method.ident.as_str();
17+
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
18+
if method_name == "iter" || method_name == "iter_mut" {
19+
if is_ref_iterable_type(cx, &args[0]) {
20+
lint_iter_method(cx, args, arg, method_name);
21+
}
22+
} else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) {
23+
let receiver_ty = cx.typeck_results().expr_ty(&args[0]);
24+
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]);
25+
if TyS::same_type(receiver_ty, receiver_ty_adjusted) {
26+
let mut applicability = Applicability::MachineApplicable;
27+
let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
28+
span_lint_and_sugg(
29+
cx,
30+
super::EXPLICIT_INTO_ITER_LOOP,
31+
arg.span,
32+
"it is more concise to loop over containers instead of using explicit \
33+
iteration methods",
34+
"to write this more concisely, try",
35+
object.to_string(),
36+
applicability,
37+
);
38+
} else {
39+
let ref_receiver_ty = cx.tcx.mk_ref(
40+
cx.tcx.lifetimes.re_erased,
41+
ty::TypeAndMut {
42+
ty: receiver_ty,
43+
mutbl: Mutability::Not,
44+
},
45+
);
46+
if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) {
47+
lint_iter_method(cx, args, arg, method_name)
48+
}
49+
}
50+
} else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) {
51+
span_lint(
52+
cx,
53+
super::ITER_NEXT_LOOP,
54+
expr.span,
55+
"you are iterating over `Iterator::next()` which is an Option; this will compile but is \
56+
probably not what you want",
57+
);
58+
next_loop_linted = true;
59+
}
60+
}
61+
}
62+
if !next_loop_linted {
63+
check_arg_type(cx, pat, arg);
64+
}
65+
}
66+
67+
/// Checks for `for` loops over `Option`s and `Result`s.
68+
fn check_arg_type(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) {
69+
let ty = cx.typeck_results().expr_ty(arg);
70+
if is_type_diagnostic_item(cx, ty, sym::option_type) {
71+
span_lint_and_help(
72+
cx,
73+
super::FOR_LOOPS_OVER_FALLIBLES,
74+
arg.span,
75+
&format!(
76+
"for loop over `{0}`, which is an `Option`. This is more readably written as an \
77+
`if let` statement",
78+
snippet(cx, arg.span, "_")
79+
),
80+
None,
81+
&format!(
82+
"consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`",
83+
snippet(cx, pat.span, "_"),
84+
snippet(cx, arg.span, "_")
85+
),
86+
);
87+
} else if is_type_diagnostic_item(cx, ty, sym::result_type) {
88+
span_lint_and_help(
89+
cx,
90+
super::FOR_LOOPS_OVER_FALLIBLES,
91+
arg.span,
92+
&format!(
93+
"for loop over `{0}`, which is a `Result`. This is more readably written as an \
94+
`if let` statement",
95+
snippet(cx, arg.span, "_")
96+
),
97+
None,
98+
&format!(
99+
"consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`",
100+
snippet(cx, pat.span, "_"),
101+
snippet(cx, arg.span, "_")
102+
),
103+
);
104+
}
105+
}
106+
107+
fn lint_iter_method(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) {
108+
let mut applicability = Applicability::MachineApplicable;
109+
let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
110+
let muta = if method_name == "iter_mut" { "mut " } else { "" };
111+
span_lint_and_sugg(
112+
cx,
113+
super::EXPLICIT_ITER_LOOP,
114+
arg.span,
115+
"it is more concise to loop over references to containers instead of using explicit \
116+
iteration methods",
117+
"to write this more concisely, try",
118+
format!("&{}{}", muta, object),
119+
applicability,
120+
)
121+
}
122+
123+
/// Returns `true` if the type of expr is one that provides `IntoIterator` impls
124+
/// for `&T` and `&mut T`, such as `Vec`.
125+
#[rustfmt::skip]
126+
fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
127+
// no walk_ptrs_ty: calling iter() on a reference can make sense because it
128+
// will allow further borrows afterwards
129+
let ty = cx.typeck_results().expr_ty(e);
130+
is_iterable_array(ty, cx) ||
131+
is_type_diagnostic_item(cx, ty, sym::vec_type) ||
132+
match_type(cx, ty, &paths::LINKED_LIST) ||
133+
is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) ||
134+
is_type_diagnostic_item(cx, ty, sym!(hashset_type)) ||
135+
is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) ||
136+
match_type(cx, ty, &paths::BINARY_HEAP) ||
137+
match_type(cx, ty, &paths::BTREEMAP) ||
138+
match_type(cx, ty, &paths::BTREESET)
139+
}
140+
141+
fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
142+
// IntoIterator is currently only implemented for array sizes <= 32 in rustc
143+
match ty.kind() {
144+
ty::Array(_, n) => n
145+
.try_eval_usize(cx.tcx, cx.param_env)
146+
.map_or(false, |val| (0..=32).contains(&val)),
147+
_ => false,
148+
}
149+
}

clippy_lints/src/loops/manual_flatten.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_span::source_map::Span;
88

99
/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the
1010
/// iterator element is used.
11-
pub(super) fn lint<'tcx>(
11+
pub(super) fn check_manual_flatten<'tcx>(
1212
cx: &LateContext<'tcx>,
1313
pat: &'tcx Pat<'_>,
1414
arg: &'tcx Expr<'_>,

clippy_lints/src/loops/mod.rs

Lines changed: 4 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod for_loop_arg;
12
mod manual_flatten;
23
mod utils;
34

@@ -27,7 +28,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
2728
use rustc_middle::hir::map::Map;
2829
use rustc_middle::lint::in_external_macro;
2930
use rustc_middle::middle::region;
30-
use rustc_middle::ty::{self, Ty, TyS};
31+
use rustc_middle::ty::{self, Ty};
3132
use rustc_session::{declare_lint_pass, declare_tool_lint};
3233
use rustc_span::source_map::Span;
3334
use rustc_span::symbol::{sym, Ident, Symbol};
@@ -861,12 +862,12 @@ fn check_for_loop<'tcx>(
861862
check_for_loop_range(cx, pat, arg, body, expr);
862863
check_for_loop_explicit_counter(cx, pat, arg, body, expr);
863864
}
864-
check_for_loop_arg(cx, pat, arg, expr);
865+
for_loop_arg::check_for_loop_arg(cx, pat, arg, expr);
865866
check_for_loop_over_map_kv(cx, pat, arg, body, expr);
866867
check_for_mut_range_bound(cx, arg, body);
867868
check_for_single_element_loop(cx, pat, arg, body, expr);
868869
detect_same_item_push(cx, pat, arg, body, expr);
869-
manual_flatten::lint(cx, pat, arg, body, span);
870+
manual_flatten::check_manual_flatten(cx, pat, arg, body, span);
870871
}
871872

872873
// this function assumes the given expression is a `for` loop.
@@ -1682,118 +1683,6 @@ fn is_end_eq_array_len<'tcx>(
16821683
false
16831684
}
16841685

1685-
fn lint_iter_method(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) {
1686-
let mut applicability = Applicability::MachineApplicable;
1687-
let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
1688-
let muta = if method_name == "iter_mut" { "mut " } else { "" };
1689-
span_lint_and_sugg(
1690-
cx,
1691-
EXPLICIT_ITER_LOOP,
1692-
arg.span,
1693-
"it is more concise to loop over references to containers instead of using explicit \
1694-
iteration methods",
1695-
"to write this more concisely, try",
1696-
format!("&{}{}", muta, object),
1697-
applicability,
1698-
)
1699-
}
1700-
1701-
fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) {
1702-
let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used
1703-
if let ExprKind::MethodCall(ref method, _, ref args, _) = arg.kind {
1704-
// just the receiver, no arguments
1705-
if args.len() == 1 {
1706-
let method_name = &*method.ident.as_str();
1707-
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
1708-
if method_name == "iter" || method_name == "iter_mut" {
1709-
if is_ref_iterable_type(cx, &args[0]) {
1710-
lint_iter_method(cx, args, arg, method_name);
1711-
}
1712-
} else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) {
1713-
let receiver_ty = cx.typeck_results().expr_ty(&args[0]);
1714-
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]);
1715-
if TyS::same_type(receiver_ty, receiver_ty_adjusted) {
1716-
let mut applicability = Applicability::MachineApplicable;
1717-
let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
1718-
span_lint_and_sugg(
1719-
cx,
1720-
EXPLICIT_INTO_ITER_LOOP,
1721-
arg.span,
1722-
"it is more concise to loop over containers instead of using explicit \
1723-
iteration methods",
1724-
"to write this more concisely, try",
1725-
object.to_string(),
1726-
applicability,
1727-
);
1728-
} else {
1729-
let ref_receiver_ty = cx.tcx.mk_ref(
1730-
cx.tcx.lifetimes.re_erased,
1731-
ty::TypeAndMut {
1732-
ty: receiver_ty,
1733-
mutbl: Mutability::Not,
1734-
},
1735-
);
1736-
if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) {
1737-
lint_iter_method(cx, args, arg, method_name)
1738-
}
1739-
}
1740-
} else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) {
1741-
span_lint(
1742-
cx,
1743-
ITER_NEXT_LOOP,
1744-
expr.span,
1745-
"you are iterating over `Iterator::next()` which is an Option; this will compile but is \
1746-
probably not what you want",
1747-
);
1748-
next_loop_linted = true;
1749-
}
1750-
}
1751-
}
1752-
if !next_loop_linted {
1753-
check_arg_type(cx, pat, arg);
1754-
}
1755-
}
1756-
1757-
/// Checks for `for` loops over `Option`s and `Result`s.
1758-
fn check_arg_type(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) {
1759-
let ty = cx.typeck_results().expr_ty(arg);
1760-
if is_type_diagnostic_item(cx, ty, sym::option_type) {
1761-
span_lint_and_help(
1762-
cx,
1763-
FOR_LOOPS_OVER_FALLIBLES,
1764-
arg.span,
1765-
&format!(
1766-
"for loop over `{0}`, which is an `Option`. This is more readably written as an \
1767-
`if let` statement",
1768-
snippet(cx, arg.span, "_")
1769-
),
1770-
None,
1771-
&format!(
1772-
"consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`",
1773-
snippet(cx, pat.span, "_"),
1774-
snippet(cx, arg.span, "_")
1775-
),
1776-
);
1777-
} else if is_type_diagnostic_item(cx, ty, sym::result_type) {
1778-
span_lint_and_help(
1779-
cx,
1780-
FOR_LOOPS_OVER_FALLIBLES,
1781-
arg.span,
1782-
&format!(
1783-
"for loop over `{0}`, which is a `Result`. This is more readably written as an \
1784-
`if let` statement",
1785-
snippet(cx, arg.span, "_")
1786-
),
1787-
None,
1788-
&format!(
1789-
"consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`",
1790-
snippet(cx, pat.span, "_"),
1791-
snippet(cx, arg.span, "_")
1792-
),
1793-
);
1794-
}
1795-
}
1796-
17971686
// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be
17981687
// incremented exactly once in the loop body, and initialized to zero
17991688
// at the start of the loop.
@@ -2270,34 +2159,6 @@ impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor {
22702159
}
22712160
}
22722161

2273-
/// Returns `true` if the type of expr is one that provides `IntoIterator` impls
2274-
/// for `&T` and `&mut T`, such as `Vec`.
2275-
#[rustfmt::skip]
2276-
fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
2277-
// no walk_ptrs_ty: calling iter() on a reference can make sense because it
2278-
// will allow further borrows afterwards
2279-
let ty = cx.typeck_results().expr_ty(e);
2280-
is_iterable_array(ty, cx) ||
2281-
is_type_diagnostic_item(cx, ty, sym::vec_type) ||
2282-
match_type(cx, ty, &paths::LINKED_LIST) ||
2283-
is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) ||
2284-
is_type_diagnostic_item(cx, ty, sym!(hashset_type)) ||
2285-
is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) ||
2286-
match_type(cx, ty, &paths::BINARY_HEAP) ||
2287-
match_type(cx, ty, &paths::BTREEMAP) ||
2288-
match_type(cx, ty, &paths::BTREESET)
2289-
}
2290-
2291-
fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
2292-
// IntoIterator is currently only implemented for array sizes <= 32 in rustc
2293-
match ty.kind() {
2294-
ty::Array(_, n) => n
2295-
.try_eval_usize(cx.tcx, cx.param_env)
2296-
.map_or(false, |val| (0..=32).contains(&val)),
2297-
_ => false,
2298-
}
2299-
}
2300-
23012162
/// If a block begins with a statement (possibly a `let` binding) and has an
23022163
/// expression, return it.
23032164
fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {

0 commit comments

Comments
 (0)