Skip to content

Commit d0b657c

Browse files
nahuakangY-Nak
authored andcommitted
Refactor while let on iterator lint to its module; rename for loop explicit counter to explicit counter loop
1 parent 455d047 commit d0b657c

File tree

3 files changed

+179
-166
lines changed

3 files changed

+179
-166
lines changed

clippy_lints/src/loops/mod.rs

Lines changed: 8 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
mod explicit_counter_loop;
12
mod for_loop_arg;
2-
mod for_loop_explicit_counter;
33
mod for_loop_over_map_kv;
44
mod for_loop_range;
55
mod for_mut_range_bound;
@@ -11,27 +11,20 @@ mod needless_collect;
1111
mod never_loop;
1212
mod same_item_push;
1313
mod utils;
14+
mod while_let_on_iterator;
1415

1516
use crate::utils::sugg::Sugg;
16-
use crate::utils::usage::mutated_variables;
1717
use crate::utils::{
18-
get_enclosing_block, get_trait_def_id, higher, implements_trait, is_in_panic_handler, is_no_std_crate,
19-
is_refutable, last_path_segment, match_trait_method, path_to_local, path_to_local_id, paths,
20-
snippet_with_applicability, span_lint_and_help, span_lint_and_sugg, sugg,
18+
higher, is_in_panic_handler, is_no_std_crate, snippet_with_applicability, span_lint_and_help, span_lint_and_sugg,
19+
sugg,
2120
};
22-
use if_chain::if_chain;
2321
use rustc_errors::Applicability;
24-
use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
25-
use rustc_hir::{Block, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, PatKind, StmtKind};
22+
use rustc_hir::{Block, Expr, ExprKind, LoopSource, MatchSource, Pat, StmtKind};
2623
use rustc_lint::{LateContext, LateLintPass, LintContext};
27-
use rustc_middle::hir::map::Map;
2824
use rustc_middle::lint::in_external_macro;
2925
use rustc_session::{declare_lint_pass, declare_tool_lint};
3026
use rustc_span::source_map::Span;
31-
use rustc_span::symbol::sym;
32-
use utils::{
33-
get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor, LoopNestVisitor, Nesting,
34-
};
27+
use utils::{get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor};
3528

3629
declare_clippy_lint! {
3730
/// **What it does:** Checks for for-loops that manually copy items between
@@ -625,54 +618,8 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
625618
}
626619
}
627620
}
628-
if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind {
629-
let pat = &arms[0].pat.kind;
630-
if let (
631-
&PatKind::TupleStruct(ref qpath, ref pat_args, _),
632-
&ExprKind::MethodCall(ref method_path, _, ref method_args, _),
633-
) = (pat, &match_expr.kind)
634-
{
635-
let iter_expr = &method_args[0];
636-
637-
// Don't lint when the iterator is recreated on every iteration
638-
if_chain! {
639-
if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind;
640-
if let Some(iter_def_id) = get_trait_def_id(cx, &paths::ITERATOR);
641-
if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]);
642-
then {
643-
return;
644-
}
645-
}
646621

647-
let lhs_constructor = last_path_segment(qpath);
648-
if method_path.ident.name == sym::next
649-
&& match_trait_method(cx, match_expr, &paths::ITERATOR)
650-
&& lhs_constructor.ident.name == sym::Some
651-
&& (pat_args.is_empty()
652-
|| !is_refutable(cx, &pat_args[0])
653-
&& !is_used_inside(cx, iter_expr, &arms[0].body)
654-
&& !is_iterator_used_after_while_let(cx, iter_expr)
655-
&& !is_nested(cx, expr, &method_args[0]))
656-
{
657-
let mut applicability = Applicability::MachineApplicable;
658-
let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability);
659-
let loop_var = if pat_args.is_empty() {
660-
"_".to_string()
661-
} else {
662-
snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned()
663-
};
664-
span_lint_and_sugg(
665-
cx,
666-
WHILE_LET_ON_ITERATOR,
667-
expr.span.with_hi(match_expr.span.hi()),
668-
"this loop could be written as a `for` loop",
669-
"try",
670-
format!("for {} in {}", loop_var, iterator),
671-
applicability,
672-
);
673-
}
674-
}
675-
}
622+
while_let_on_iterator::check_while_let_on_iterator(cx, expr);
676623

677624
if let Some((cond, body)) = higher::while_loop(&expr) {
678625
infinite_loop::check_infinite_loop(cx, cond, body);
@@ -693,7 +640,7 @@ fn check_for_loop<'tcx>(
693640
let is_manual_memcpy_triggered = manual_memcpy::detect_manual_memcpy(cx, pat, arg, body, expr);
694641
if !is_manual_memcpy_triggered {
695642
for_loop_range::check_for_loop_range(cx, pat, arg, body, expr);
696-
for_loop_explicit_counter::check_for_loop_explicit_counter(cx, pat, arg, body, expr);
643+
explicit_counter_loop::check_for_loop_explicit_counter(cx, pat, arg, body, expr);
697644
}
698645
for_loop_arg::check_for_loop_arg(cx, pat, arg, expr);
699646
for_loop_over_map_kv::check_for_loop_over_map_kv(cx, pat, arg, body, expr);
@@ -773,61 +720,6 @@ impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> {
773720
}
774721
}
775722

776-
fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool {
777-
let def_id = match path_to_local(expr) {
778-
Some(id) => id,
779-
None => return false,
780-
};
781-
if let Some(used_mutably) = mutated_variables(container, cx) {
782-
if used_mutably.contains(&def_id) {
783-
return true;
784-
}
785-
}
786-
false
787-
}
788-
789-
fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool {
790-
let def_id = match path_to_local(iter_expr) {
791-
Some(id) => id,
792-
None => return false,
793-
};
794-
let mut visitor = VarUsedAfterLoopVisitor {
795-
def_id,
796-
iter_expr_id: iter_expr.hir_id,
797-
past_while_let: false,
798-
var_used_after_while_let: false,
799-
};
800-
if let Some(enclosing_block) = get_enclosing_block(cx, def_id) {
801-
walk_block(&mut visitor, enclosing_block);
802-
}
803-
visitor.var_used_after_while_let
804-
}
805-
806-
struct VarUsedAfterLoopVisitor {
807-
def_id: HirId,
808-
iter_expr_id: HirId,
809-
past_while_let: bool,
810-
var_used_after_while_let: bool,
811-
}
812-
813-
impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor {
814-
type Map = Map<'tcx>;
815-
816-
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
817-
if self.past_while_let {
818-
if path_to_local_id(expr, self.def_id) {
819-
self.var_used_after_while_let = true;
820-
}
821-
} else if self.iter_expr_id == expr.hir_id {
822-
self.past_while_let = true;
823-
}
824-
walk_expr(self, expr);
825-
}
826-
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
827-
NestedVisitorMap::None
828-
}
829-
}
830-
831723
/// If a block begins with a statement (possibly a `let` binding) and has an
832724
/// expression, return it.
833725
fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
@@ -863,53 +755,3 @@ fn is_simple_break_expr(expr: &Expr<'_>) -> bool {
863755
_ => false,
864756
}
865757
}
866-
867-
fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
868-
if_chain! {
869-
if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id);
870-
let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id);
871-
if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node);
872-
then {
873-
return is_loop_nested(cx, loop_expr, iter_expr)
874-
}
875-
}
876-
false
877-
}
878-
879-
fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
880-
let mut id = loop_expr.hir_id;
881-
let iter_id = if let Some(id) = path_to_local(iter_expr) {
882-
id
883-
} else {
884-
return true;
885-
};
886-
loop {
887-
let parent = cx.tcx.hir().get_parent_node(id);
888-
if parent == id {
889-
return false;
890-
}
891-
match cx.tcx.hir().find(parent) {
892-
Some(Node::Expr(expr)) => {
893-
if let ExprKind::Loop(..) = expr.kind {
894-
return true;
895-
};
896-
},
897-
Some(Node::Block(block)) => {
898-
let mut block_visitor = LoopNestVisitor {
899-
hir_id: id,
900-
iterator: iter_id,
901-
nesting: Nesting::Unknown,
902-
};
903-
walk_block(&mut block_visitor, block);
904-
if block_visitor.nesting == Nesting::RuledOut {
905-
return false;
906-
}
907-
},
908-
Some(Node::Stmt(_)) => (),
909-
_ => {
910-
return false;
911-
},
912-
}
913-
id = parent;
914-
}
915-
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
use super::utils::{LoopNestVisitor, Nesting};
2+
use super::WHILE_LET_ON_ITERATOR;
3+
use crate::utils::usage::mutated_variables;
4+
use crate::utils::{
5+
get_enclosing_block, get_trait_def_id, implements_trait, is_refutable, last_path_segment, match_trait_method,
6+
path_to_local, path_to_local_id, paths, snippet_with_applicability, span_lint_and_sugg,
7+
};
8+
use if_chain::if_chain;
9+
use rustc_errors::Applicability;
10+
use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
11+
use rustc_hir::{Expr, ExprKind, HirId, MatchSource, Node, PatKind};
12+
use rustc_lint::LateContext;
13+
use rustc_middle::hir::map::Map;
14+
15+
use rustc_span::symbol::sym;
16+
17+
pub(super) fn check_while_let_on_iterator(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
18+
if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind {
19+
let pat = &arms[0].pat.kind;
20+
if let (
21+
&PatKind::TupleStruct(ref qpath, ref pat_args, _),
22+
&ExprKind::MethodCall(ref method_path, _, ref method_args, _),
23+
) = (pat, &match_expr.kind)
24+
{
25+
let iter_expr = &method_args[0];
26+
27+
// Don't lint when the iterator is recreated on every iteration
28+
if_chain! {
29+
if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind;
30+
if let Some(iter_def_id) = get_trait_def_id(cx, &paths::ITERATOR);
31+
if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]);
32+
then {
33+
return;
34+
}
35+
}
36+
37+
let lhs_constructor = last_path_segment(qpath);
38+
if method_path.ident.name == sym::next
39+
&& match_trait_method(cx, match_expr, &paths::ITERATOR)
40+
&& lhs_constructor.ident.name == sym::Some
41+
&& (pat_args.is_empty()
42+
|| !is_refutable(cx, &pat_args[0])
43+
&& !is_used_inside(cx, iter_expr, &arms[0].body)
44+
&& !is_iterator_used_after_while_let(cx, iter_expr)
45+
&& !is_nested(cx, expr, &method_args[0]))
46+
{
47+
let mut applicability = Applicability::MachineApplicable;
48+
let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability);
49+
let loop_var = if pat_args.is_empty() {
50+
"_".to_string()
51+
} else {
52+
snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned()
53+
};
54+
span_lint_and_sugg(
55+
cx,
56+
WHILE_LET_ON_ITERATOR,
57+
expr.span.with_hi(match_expr.span.hi()),
58+
"this loop could be written as a `for` loop",
59+
"try",
60+
format!("for {} in {}", loop_var, iterator),
61+
applicability,
62+
);
63+
}
64+
}
65+
}
66+
}
67+
68+
fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool {
69+
let def_id = match path_to_local(expr) {
70+
Some(id) => id,
71+
None => return false,
72+
};
73+
if let Some(used_mutably) = mutated_variables(container, cx) {
74+
if used_mutably.contains(&def_id) {
75+
return true;
76+
}
77+
}
78+
false
79+
}
80+
81+
fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool {
82+
let def_id = match path_to_local(iter_expr) {
83+
Some(id) => id,
84+
None => return false,
85+
};
86+
let mut visitor = VarUsedAfterLoopVisitor {
87+
def_id,
88+
iter_expr_id: iter_expr.hir_id,
89+
past_while_let: false,
90+
var_used_after_while_let: false,
91+
};
92+
if let Some(enclosing_block) = get_enclosing_block(cx, def_id) {
93+
walk_block(&mut visitor, enclosing_block);
94+
}
95+
visitor.var_used_after_while_let
96+
}
97+
98+
fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
99+
if_chain! {
100+
if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id);
101+
let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id);
102+
if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node);
103+
then {
104+
return is_loop_nested(cx, loop_expr, iter_expr)
105+
}
106+
}
107+
false
108+
}
109+
110+
fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
111+
let mut id = loop_expr.hir_id;
112+
let iter_id = if let Some(id) = path_to_local(iter_expr) {
113+
id
114+
} else {
115+
return true;
116+
};
117+
loop {
118+
let parent = cx.tcx.hir().get_parent_node(id);
119+
if parent == id {
120+
return false;
121+
}
122+
match cx.tcx.hir().find(parent) {
123+
Some(Node::Expr(expr)) => {
124+
if let ExprKind::Loop(..) = expr.kind {
125+
return true;
126+
};
127+
},
128+
Some(Node::Block(block)) => {
129+
let mut block_visitor = LoopNestVisitor {
130+
hir_id: id,
131+
iterator: iter_id,
132+
nesting: Nesting::Unknown,
133+
};
134+
walk_block(&mut block_visitor, block);
135+
if block_visitor.nesting == Nesting::RuledOut {
136+
return false;
137+
}
138+
},
139+
Some(Node::Stmt(_)) => (),
140+
_ => {
141+
return false;
142+
},
143+
}
144+
id = parent;
145+
}
146+
}
147+
148+
struct VarUsedAfterLoopVisitor {
149+
def_id: HirId,
150+
iter_expr_id: HirId,
151+
past_while_let: bool,
152+
var_used_after_while_let: bool,
153+
}
154+
155+
impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor {
156+
type Map = Map<'tcx>;
157+
158+
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
159+
if self.past_while_let {
160+
if path_to_local_id(expr, self.def_id) {
161+
self.var_used_after_while_let = true;
162+
}
163+
} else if self.iter_expr_id == expr.hir_id {
164+
self.past_while_let = true;
165+
}
166+
walk_expr(self, expr);
167+
}
168+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
169+
NestedVisitorMap::None
170+
}
171+
}

0 commit comments

Comments
 (0)