]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/loops/while_let_on_iterator.rs
Auto merge of #6823 - Jarcho:diagnostic_items, r=phansch
[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, 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(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 }