1 use super::utils::{LoopNestVisitor, Nesting};
2 use super::WHILE_LET_ON_ITERATOR;
3 use clippy_utils::diagnostics::span_lint_and_sugg;
4 use clippy_utils::source::snippet_with_applicability;
5 use clippy_utils::ty::implements_trait;
6 use clippy_utils::usage::mutated_variables;
8 get_enclosing_block, is_refutable, is_trait_method, last_path_segment, path_to_local, path_to_local_id,
10 use if_chain::if_chain;
11 use rustc_errors::Applicability;
12 use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
13 use rustc_hir::{Expr, ExprKind, HirId, MatchSource, Node, PatKind};
14 use rustc_lint::LateContext;
15 use rustc_middle::hir::map::Map;
16 use rustc_span::symbol::sym;
18 pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
19 if let ExprKind::Match(match_expr, arms, MatchSource::WhileLetDesugar) = expr.kind {
20 let pat = &arms[0].pat.kind;
21 if let (&PatKind::TupleStruct(ref qpath, pat_args, _), &ExprKind::MethodCall(method_path, _, method_args, _)) =
22 (pat, &match_expr.kind)
24 let iter_expr = &method_args[0];
26 // Don't lint when the iterator is recreated on every iteration
28 if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind;
29 if let Some(iter_def_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
30 if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]);
36 let lhs_constructor = last_path_segment(qpath);
37 if method_path.ident.name == sym::next
38 && is_trait_method(cx, match_expr, sym::Iterator)
39 && lhs_constructor.ident.name == sym::Some
40 && (pat_args.is_empty()
41 || !is_refutable(cx, pat_args[0])
42 && !is_used_inside(cx, iter_expr, arms[0].body)
43 && !is_iterator_used_after_while_let(cx, iter_expr)
44 && !is_nested(cx, expr, &method_args[0]))
46 let mut applicability = Applicability::MachineApplicable;
47 let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability);
48 let loop_var = if pat_args.is_empty() {
51 snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned()
55 WHILE_LET_ON_ITERATOR,
56 expr.span.with_hi(match_expr.span.hi()),
57 "this loop could be written as a `for` loop",
59 format!("for {} in {}", loop_var, iterator),
67 fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool {
68 let def_id = match path_to_local(expr) {
72 if let Some(used_mutably) = mutated_variables(container, cx) {
73 if used_mutably.contains(&def_id) {
80 fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool {
81 let def_id = match path_to_local(iter_expr) {
85 let mut visitor = VarUsedAfterLoopVisitor {
87 iter_expr_id: iter_expr.hir_id,
88 past_while_let: false,
89 var_used_after_while_let: false,
91 if let Some(enclosing_block) = get_enclosing_block(cx, def_id) {
92 walk_block(&mut visitor, enclosing_block);
94 visitor.var_used_after_while_let
97 fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
99 if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id);
100 let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id);
101 if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node);
103 return is_loop_nested(cx, loop_expr, iter_expr)
109 fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
110 let mut id = loop_expr.hir_id;
111 let iter_id = if let Some(id) = path_to_local(iter_expr) {
117 let parent = cx.tcx.hir().get_parent_node(id);
121 match cx.tcx.hir().find(parent) {
122 Some(Node::Expr(expr)) => {
123 if let ExprKind::Loop(..) = expr.kind {
127 Some(Node::Block(block)) => {
128 let mut block_visitor = LoopNestVisitor {
131 nesting: Nesting::Unknown,
133 walk_block(&mut block_visitor, block);
134 if block_visitor.nesting == Nesting::RuledOut {
138 Some(Node::Stmt(_)) => (),
147 struct VarUsedAfterLoopVisitor {
150 past_while_let: bool,
151 var_used_after_while_let: bool,
154 impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor {
155 type Map = Map<'tcx>;
157 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
158 if self.past_while_let {
159 if path_to_local_id(expr, self.def_id) {
160 self.var_used_after_while_let = true;
162 } else if self.iter_expr_id == expr.hir_id {
163 self.past_while_let = true;
165 walk_expr(self, expr);
167 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
168 NestedVisitorMap::None