]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs
Merge tag 'debian/1.52.1+dfsg1-1_exp2' into proxmox/buster
[rustc.git] / src / tools / clippy / clippy_lints / src / loops / while_let_on_iterator.rs
CommitLineData
f20569fa
XL
1use super::utils::{LoopNestVisitor, Nesting};
2use super::WHILE_LET_ON_ITERATOR;
3use crate::utils::usage::mutated_variables;
4use 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};
8use if_chain::if_chain;
9use rustc_errors::Applicability;
10use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
11use rustc_hir::{Expr, ExprKind, HirId, MatchSource, Node, PatKind};
12use rustc_lint::LateContext;
13use rustc_middle::hir::map::Map;
14
15use rustc_span::symbol::sym;
16
17pub(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
68fn 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
81fn 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
98fn 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
110fn 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
148struct VarUsedAfterLoopVisitor {
149 def_id: HirId,
150 iter_expr_id: HirId,
151 past_while_let: bool,
152 var_used_after_while_let: bool,
153}
154
155impl<'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}