]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
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 | } |