]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/while_let_on_iterator.rs
Move some utils to clippy_utils::source module
[rust.git] / clippy_lints / src / loops / while_let_on_iterator.rs
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, is_refutable, is_trait_method, last_path_segment, path_to_local, path_to_local_id,
6     span_lint_and_sugg,
7 };
8 use clippy_utils::source::snippet_with_applicability;
9 use clippy_utils::ty::implements_trait;
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;
17
18 pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
19     if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind {
20         let pat = &arms[0].pat.kind;
21         if let (
22             &PatKind::TupleStruct(ref qpath, ref pat_args, _),
23             &ExprKind::MethodCall(ref method_path, _, ref method_args, _),
24         ) = (pat, &match_expr.kind)
25         {
26             let iter_expr = &method_args[0];
27
28             // Don't lint when the iterator is recreated on every iteration
29             if_chain! {
30                 if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind;
31                 if let Some(iter_def_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
32                 if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]);
33                 then {
34                     return;
35                 }
36             }
37
38             let lhs_constructor = last_path_segment(qpath);
39             if method_path.ident.name == sym::next
40                 && is_trait_method(cx, match_expr, sym::Iterator)
41                 && lhs_constructor.ident.name == sym::Some
42                 && (pat_args.is_empty()
43                     || !is_refutable(cx, &pat_args[0])
44                         && !is_used_inside(cx, iter_expr, &arms[0].body)
45                         && !is_iterator_used_after_while_let(cx, iter_expr)
46                         && !is_nested(cx, expr, &method_args[0]))
47             {
48                 let mut applicability = Applicability::MachineApplicable;
49                 let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability);
50                 let loop_var = if pat_args.is_empty() {
51                     "_".to_string()
52                 } else {
53                     snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned()
54                 };
55                 span_lint_and_sugg(
56                     cx,
57                     WHILE_LET_ON_ITERATOR,
58                     expr.span.with_hi(match_expr.span.hi()),
59                     "this loop could be written as a `for` loop",
60                     "try",
61                     format!("for {} in {}", loop_var, iterator),
62                     applicability,
63                 );
64             }
65         }
66     }
67 }
68
69 fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool {
70     let def_id = match path_to_local(expr) {
71         Some(id) => id,
72         None => return false,
73     };
74     if let Some(used_mutably) = mutated_variables(container, cx) {
75         if used_mutably.contains(&def_id) {
76             return true;
77         }
78     }
79     false
80 }
81
82 fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool {
83     let def_id = match path_to_local(iter_expr) {
84         Some(id) => id,
85         None => return false,
86     };
87     let mut visitor = VarUsedAfterLoopVisitor {
88         def_id,
89         iter_expr_id: iter_expr.hir_id,
90         past_while_let: false,
91         var_used_after_while_let: false,
92     };
93     if let Some(enclosing_block) = get_enclosing_block(cx, def_id) {
94         walk_block(&mut visitor, enclosing_block);
95     }
96     visitor.var_used_after_while_let
97 }
98
99 fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
100     if_chain! {
101         if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id);
102         let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id);
103         if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node);
104         then {
105             return is_loop_nested(cx, loop_expr, iter_expr)
106         }
107     }
108     false
109 }
110
111 fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
112     let mut id = loop_expr.hir_id;
113     let iter_id = if let Some(id) = path_to_local(iter_expr) {
114         id
115     } else {
116         return true;
117     };
118     loop {
119         let parent = cx.tcx.hir().get_parent_node(id);
120         if parent == id {
121             return false;
122         }
123         match cx.tcx.hir().find(parent) {
124             Some(Node::Expr(expr)) => {
125                 if let ExprKind::Loop(..) = expr.kind {
126                     return true;
127                 };
128             },
129             Some(Node::Block(block)) => {
130                 let mut block_visitor = LoopNestVisitor {
131                     hir_id: id,
132                     iterator: iter_id,
133                     nesting: Nesting::Unknown,
134                 };
135                 walk_block(&mut block_visitor, block);
136                 if block_visitor.nesting == Nesting::RuledOut {
137                     return false;
138                 }
139             },
140             Some(Node::Stmt(_)) => (),
141             _ => {
142                 return false;
143             },
144         }
145         id = parent;
146     }
147 }
148
149 struct VarUsedAfterLoopVisitor {
150     def_id: HirId,
151     iter_expr_id: HirId,
152     past_while_let: bool,
153     var_used_after_while_let: bool,
154 }
155
156 impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor {
157     type Map = Map<'tcx>;
158
159     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
160         if self.past_while_let {
161             if path_to_local_id(expr, self.def_id) {
162                 self.var_used_after_while_let = true;
163             }
164         } else if self.iter_expr_id == expr.hir_id {
165             self.past_while_let = true;
166         }
167         walk_expr(self, expr);
168     }
169     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
170         NestedVisitorMap::None
171     }
172 }