]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/while_let_on_iterator.rs
Merge remote-tracking branch 'upstream/master' into rustup
[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 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;
7 use clippy_utils::{
8     get_enclosing_block, is_refutable, is_trait_method, last_path_segment, path_to_local, path_to_local_id,
9 };
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(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)
23         {
24             let iter_expr = &method_args[0];
25
26             // Don't lint when the iterator is recreated on every iteration
27             if_chain! {
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, &[]);
31                 then {
32                     return;
33                 }
34             }
35
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]))
45             {
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() {
49                     "_".to_string()
50                 } else {
51                     snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned()
52                 };
53                 span_lint_and_sugg(
54                     cx,
55                     WHILE_LET_ON_ITERATOR,
56                     expr.span.with_hi(match_expr.span.hi()),
57                     "this loop could be written as a `for` loop",
58                     "try",
59                     format!("for {} in {}", loop_var, iterator),
60                     applicability,
61                 );
62             }
63         }
64     }
65 }
66
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) {
69         Some(id) => id,
70         None => return false,
71     };
72     if let Some(used_mutably) = mutated_variables(container, cx) {
73         if used_mutably.contains(&def_id) {
74             return true;
75         }
76     }
77     false
78 }
79
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) {
82         Some(id) => id,
83         None => return false,
84     };
85     let mut visitor = VarUsedAfterLoopVisitor {
86         def_id,
87         iter_expr_id: iter_expr.hir_id,
88         past_while_let: false,
89         var_used_after_while_let: false,
90     };
91     if let Some(enclosing_block) = get_enclosing_block(cx, def_id) {
92         walk_block(&mut visitor, enclosing_block);
93     }
94     visitor.var_used_after_while_let
95 }
96
97 fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
98     if_chain! {
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);
102         then {
103             return is_loop_nested(cx, loop_expr, iter_expr)
104         }
105     }
106     false
107 }
108
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) {
112         id
113     } else {
114         return true;
115     };
116     loop {
117         let parent = cx.tcx.hir().get_parent_node(id);
118         if parent == id {
119             return false;
120         }
121         match cx.tcx.hir().find(parent) {
122             Some(Node::Expr(expr)) => {
123                 if let ExprKind::Loop(..) = expr.kind {
124                     return true;
125                 };
126             },
127             Some(Node::Block(block)) => {
128                 let mut block_visitor = LoopNestVisitor {
129                     hir_id: id,
130                     iterator: iter_id,
131                     nesting: Nesting::Unknown,
132                 };
133                 walk_block(&mut block_visitor, block);
134                 if block_visitor.nesting == Nesting::RuledOut {
135                     return false;
136                 }
137             },
138             Some(Node::Stmt(_)) => (),
139             _ => {
140                 return false;
141             },
142         }
143         id = parent;
144     }
145 }
146
147 struct VarUsedAfterLoopVisitor {
148     def_id: HirId,
149     iter_expr_id: HirId,
150     past_while_let: bool,
151     var_used_after_while_let: bool,
152 }
153
154 impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor {
155     type Map = Map<'tcx>;
156
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;
161             }
162         } else if self.iter_expr_id == expr.hir_id {
163             self.past_while_let = true;
164         }
165         walk_expr(self, expr);
166     }
167     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
168         NestedVisitorMap::None
169     }
170 }