]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs
New upstream version 1.55.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / loops / while_let_on_iterator.rs
1 use super::WHILE_LET_ON_ITERATOR;
2 use clippy_utils::diagnostics::span_lint_and_sugg;
3 use clippy_utils::source::snippet_with_applicability;
4 use clippy_utils::{
5 get_enclosing_loop_or_closure, is_refutable, is_trait_method, match_def_path, paths, visitors::is_res_used,
6 };
7 use if_chain::if_chain;
8 use rustc_errors::Applicability;
9 use rustc_hir::intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor};
10 use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, MatchSource, Node, PatKind, QPath, UnOp};
11 use rustc_lint::LateContext;
12 use rustc_span::{symbol::sym, Span, Symbol};
13
14 pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
15 let (scrutinee_expr, iter_expr, some_pat, loop_expr) = if_chain! {
16 if let ExprKind::Match(scrutinee_expr, [arm, _], MatchSource::WhileLetDesugar) = expr.kind;
17 // check for `Some(..)` pattern
18 if let PatKind::TupleStruct(QPath::Resolved(None, pat_path), some_pat, _) = arm.pat.kind;
19 if let Res::Def(_, pat_did) = pat_path.res;
20 if match_def_path(cx, pat_did, &paths::OPTION_SOME);
21 // check for call to `Iterator::next`
22 if let ExprKind::MethodCall(method_name, _, [iter_expr], _) = scrutinee_expr.kind;
23 if method_name.ident.name == sym::next;
24 if is_trait_method(cx, scrutinee_expr, sym::Iterator);
25 if let Some(iter_expr) = try_parse_iter_expr(cx, iter_expr);
26 // get the loop containing the match expression
27 if let Some((_, Node::Expr(loop_expr))) = cx.tcx.hir().parent_iter(expr.hir_id).nth(1);
28 if !uses_iter(cx, &iter_expr, arm.body);
29 then {
30 (scrutinee_expr, iter_expr, some_pat, loop_expr)
31 } else {
32 return;
33 }
34 };
35
36 let mut applicability = Applicability::MachineApplicable;
37 let loop_var = if let Some(some_pat) = some_pat.first() {
38 if is_refutable(cx, some_pat) {
39 // Refutable patterns don't work with for loops.
40 return;
41 }
42 snippet_with_applicability(cx, some_pat.span, "..", &mut applicability)
43 } else {
44 "_".into()
45 };
46
47 // If the iterator is a field or the iterator is accessed after the loop is complete it needs to be
48 // borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used
49 // afterwards a mutable borrow of a field isn't necessary.
50 let ref_mut = if !iter_expr.fields.is_empty() || needs_mutable_borrow(cx, &iter_expr, loop_expr) {
51 "&mut "
52 } else {
53 ""
54 };
55
56 let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability);
57 span_lint_and_sugg(
58 cx,
59 WHILE_LET_ON_ITERATOR,
60 expr.span.with_hi(scrutinee_expr.span.hi()),
61 "this loop could be written as a `for` loop",
62 "try",
63 format!("for {} in {}{}", loop_var, ref_mut, iterator),
64 applicability,
65 );
66 }
67
68 #[derive(Debug)]
69 struct IterExpr {
70 /// The span of the whole expression, not just the path and fields stored here.
71 span: Span,
72 /// The fields used, in order of child to parent.
73 fields: Vec<Symbol>,
74 /// The path being used.
75 path: Res,
76 }
77 /// Parses any expression to find out which field of which variable is used. Will return `None` if
78 /// the expression might have side effects.
79 fn try_parse_iter_expr(cx: &LateContext<'_>, mut e: &Expr<'_>) -> Option<IterExpr> {
80 let span = e.span;
81 let mut fields = Vec::new();
82 loop {
83 match e.kind {
84 ExprKind::Path(ref path) => {
85 break Some(IterExpr {
86 span,
87 fields,
88 path: cx.qpath_res(path, e.hir_id),
89 });
90 },
91 ExprKind::Field(base, name) => {
92 fields.push(name.name);
93 e = base;
94 },
95 // Dereferencing a pointer has no side effects and doesn't affect which field is being used.
96 ExprKind::Unary(UnOp::Deref, base) if cx.typeck_results().expr_ty(base).is_ref() => e = base,
97
98 // Shouldn't have side effects, but there's no way to trace which field is used. So forget which fields have
99 // already been seen.
100 ExprKind::Index(base, idx) if !idx.can_have_side_effects() => {
101 fields.clear();
102 e = base;
103 },
104 ExprKind::Unary(UnOp::Deref, base) => {
105 fields.clear();
106 e = base;
107 },
108
109 // No effect and doesn't affect which field is being used.
110 ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _) => e = base,
111 _ => break None,
112 }
113 }
114 }
115
116 fn is_expr_same_field(cx: &LateContext<'_>, mut e: &Expr<'_>, mut fields: &[Symbol], path_res: Res) -> bool {
117 loop {
118 match (&e.kind, fields) {
119 (&ExprKind::Field(base, name), [head_field, tail_fields @ ..]) if name.name == *head_field => {
120 e = base;
121 fields = tail_fields;
122 },
123 (ExprKind::Path(path), []) => {
124 break cx.qpath_res(path, e.hir_id) == path_res;
125 },
126 (&(ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _)), _) => e = base,
127 _ => break false,
128 }
129 }
130 }
131
132 /// Checks if the given expression is the same field as, is a child of, or is the parent of the
133 /// given field. Used to check if the expression can be used while the given field is borrowed
134 /// mutably. e.g. if checking for `x.y`, then `x.y`, `x.y.z`, and `x` will all return true, but
135 /// `x.z`, and `y` will return false.
136 fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fields: &[Symbol], path_res: Res) -> bool {
137 match expr.kind {
138 ExprKind::Field(base, name) => {
139 if let Some((head_field, tail_fields)) = fields.split_first() {
140 if name.name == *head_field && is_expr_same_field(cx, base, fields, path_res) {
141 return true;
142 }
143 // Check if the expression is a parent field
144 let mut fields_iter = tail_fields.iter();
145 while let Some(field) = fields_iter.next() {
146 if *field == name.name && is_expr_same_field(cx, base, fields_iter.as_slice(), path_res) {
147 return true;
148 }
149 }
150 }
151
152 // Check if the expression is a child field.
153 let mut e = base;
154 loop {
155 match e.kind {
156 ExprKind::Field(..) if is_expr_same_field(cx, e, fields, path_res) => break true,
157 ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base,
158 ExprKind::Path(ref path) if fields.is_empty() => {
159 break cx.qpath_res(path, e.hir_id) == path_res;
160 },
161 _ => break false,
162 }
163 }
164 },
165 // If the path matches, this is either an exact match, or the expression is a parent of the field.
166 ExprKind::Path(ref path) => cx.qpath_res(path, expr.hir_id) == path_res,
167 ExprKind::DropTemps(base) | ExprKind::Type(base, _) | ExprKind::AddrOf(_, _, base) => {
168 is_expr_same_child_or_parent_field(cx, base, fields, path_res)
169 },
170 _ => false,
171 }
172 }
173
174 /// Strips off all field and path expressions. This will return true if a field or path has been
175 /// skipped. Used to skip them after failing to check for equality.
176 fn skip_fields_and_path(expr: &'tcx Expr<'_>) -> (Option<&'tcx Expr<'tcx>>, bool) {
177 let mut e = expr;
178 let e = loop {
179 match e.kind {
180 ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base,
181 ExprKind::Path(_) => return (None, true),
182 _ => break e,
183 }
184 };
185 (Some(e), e.hir_id != expr.hir_id)
186 }
187
188 /// Checks if the given expression uses the iterator.
189 fn uses_iter(cx: &LateContext<'tcx>, iter_expr: &IterExpr, container: &'tcx Expr<'_>) -> bool {
190 struct V<'a, 'b, 'tcx> {
191 cx: &'a LateContext<'tcx>,
192 iter_expr: &'b IterExpr,
193 uses_iter: bool,
194 }
195 impl Visitor<'tcx> for V<'_, '_, 'tcx> {
196 type Map = ErasedMap<'tcx>;
197 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
198 NestedVisitorMap::None
199 }
200
201 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
202 if self.uses_iter {
203 // return
204 } else if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
205 self.uses_iter = true;
206 } else if let (e, true) = skip_fields_and_path(e) {
207 if let Some(e) = e {
208 self.visit_expr(e);
209 }
210 } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
211 if is_res_used(self.cx, self.iter_expr.path, id) {
212 self.uses_iter = true;
213 }
214 } else {
215 walk_expr(self, e);
216 }
217 }
218 }
219
220 let mut v = V {
221 cx,
222 iter_expr,
223 uses_iter: false,
224 };
225 v.visit_expr(container);
226 v.uses_iter
227 }
228
229 #[allow(clippy::too_many_lines)]
230 fn needs_mutable_borrow(cx: &LateContext<'tcx>, iter_expr: &IterExpr, loop_expr: &'tcx Expr<'_>) -> bool {
231 struct AfterLoopVisitor<'a, 'b, 'tcx> {
232 cx: &'a LateContext<'tcx>,
233 iter_expr: &'b IterExpr,
234 loop_id: HirId,
235 after_loop: bool,
236 used_iter: bool,
237 }
238 impl Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> {
239 type Map = ErasedMap<'tcx>;
240 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
241 NestedVisitorMap::None
242 }
243
244 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
245 if self.used_iter {
246 return;
247 }
248 if self.after_loop {
249 if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
250 self.used_iter = true;
251 } else if let (e, true) = skip_fields_and_path(e) {
252 if let Some(e) = e {
253 self.visit_expr(e);
254 }
255 } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
256 self.used_iter = is_res_used(self.cx, self.iter_expr.path, id);
257 } else {
258 walk_expr(self, e);
259 }
260 } else if self.loop_id == e.hir_id {
261 self.after_loop = true;
262 } else {
263 walk_expr(self, e);
264 }
265 }
266 }
267
268 struct NestedLoopVisitor<'a, 'b, 'tcx> {
269 cx: &'a LateContext<'tcx>,
270 iter_expr: &'b IterExpr,
271 local_id: HirId,
272 loop_id: HirId,
273 after_loop: bool,
274 found_local: bool,
275 used_after: bool,
276 }
277 impl Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> {
278 type Map = ErasedMap<'tcx>;
279 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
280 NestedVisitorMap::None
281 }
282
283 fn visit_local(&mut self, l: &'tcx Local<'_>) {
284 if !self.after_loop {
285 l.pat.each_binding_or_first(&mut |_, id, _, _| {
286 if id == self.local_id {
287 self.found_local = true;
288 }
289 });
290 }
291 if let Some(e) = l.init {
292 self.visit_expr(e);
293 }
294 }
295
296 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
297 if self.used_after {
298 return;
299 }
300 if self.after_loop {
301 if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) {
302 self.used_after = true;
303 } else if let (e, true) = skip_fields_and_path(e) {
304 if let Some(e) = e {
305 self.visit_expr(e);
306 }
307 } else if let ExprKind::Closure(_, _, id, _, _) = e.kind {
308 self.used_after = is_res_used(self.cx, self.iter_expr.path, id);
309 } else {
310 walk_expr(self, e);
311 }
312 } else if e.hir_id == self.loop_id {
313 self.after_loop = true;
314 } else {
315 walk_expr(self, e);
316 }
317 }
318 }
319
320 if let Some(e) = get_enclosing_loop_or_closure(cx.tcx, loop_expr) {
321 // The iterator expression will be used on the next iteration (for loops), or on the next call (for
322 // closures) unless it is declared within the enclosing expression. TODO: Check for closures
323 // used where an `FnOnce` type is expected.
324 let local_id = match iter_expr.path {
325 Res::Local(id) => id,
326 _ => return true,
327 };
328 let mut v = NestedLoopVisitor {
329 cx,
330 iter_expr,
331 local_id,
332 loop_id: loop_expr.hir_id,
333 after_loop: false,
334 found_local: false,
335 used_after: false,
336 };
337 v.visit_expr(e);
338 v.used_after || !v.found_local
339 } else {
340 let mut v = AfterLoopVisitor {
341 cx,
342 iter_expr,
343 loop_id: loop_expr.hir_id,
344 after_loop: false,
345 used_iter: false,
346 };
347 v.visit_expr(&cx.tcx.hir().body(cx.enclosing_body.unwrap()).value);
348 v.used_iter
349 }
350 }