]> git.proxmox.com Git - rustc.git/blobdiff - 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
diff --git a/src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs b/src/tools/clippy/clippy_lints/src/loops/while_let_on_iterator.rs
new file mode 100644 (file)
index 0000000..e5a4769
--- /dev/null
@@ -0,0 +1,171 @@
+use super::utils::{LoopNestVisitor, Nesting};
+use super::WHILE_LET_ON_ITERATOR;
+use crate::utils::usage::mutated_variables;
+use crate::utils::{
+    get_enclosing_block, get_trait_def_id, implements_trait, is_refutable, last_path_segment, match_trait_method,
+    path_to_local, path_to_local_id, paths, snippet_with_applicability, span_lint_and_sugg,
+};
+use if_chain::if_chain;
+use rustc_errors::Applicability;
+use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::{Expr, ExprKind, HirId, MatchSource, Node, PatKind};
+use rustc_lint::LateContext;
+use rustc_middle::hir::map::Map;
+
+use rustc_span::symbol::sym;
+
+pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+    if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind {
+        let pat = &arms[0].pat.kind;
+        if let (
+            &PatKind::TupleStruct(ref qpath, ref pat_args, _),
+            &ExprKind::MethodCall(ref method_path, _, ref method_args, _),
+        ) = (pat, &match_expr.kind)
+        {
+            let iter_expr = &method_args[0];
+
+            // Don't lint when the iterator is recreated on every iteration
+            if_chain! {
+                if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind;
+                if let Some(iter_def_id) = get_trait_def_id(cx, &paths::ITERATOR);
+                if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]);
+                then {
+                    return;
+                }
+            }
+
+            let lhs_constructor = last_path_segment(qpath);
+            if method_path.ident.name == sym::next
+                && match_trait_method(cx, match_expr, &paths::ITERATOR)
+                && lhs_constructor.ident.name == sym::Some
+                && (pat_args.is_empty()
+                    || !is_refutable(cx, &pat_args[0])
+                        && !is_used_inside(cx, iter_expr, &arms[0].body)
+                        && !is_iterator_used_after_while_let(cx, iter_expr)
+                        && !is_nested(cx, expr, &method_args[0]))
+            {
+                let mut applicability = Applicability::MachineApplicable;
+                let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability);
+                let loop_var = if pat_args.is_empty() {
+                    "_".to_string()
+                } else {
+                    snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned()
+                };
+                span_lint_and_sugg(
+                    cx,
+                    WHILE_LET_ON_ITERATOR,
+                    expr.span.with_hi(match_expr.span.hi()),
+                    "this loop could be written as a `for` loop",
+                    "try",
+                    format!("for {} in {}", loop_var, iterator),
+                    applicability,
+                );
+            }
+        }
+    }
+}
+
+fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool {
+    let def_id = match path_to_local(expr) {
+        Some(id) => id,
+        None => return false,
+    };
+    if let Some(used_mutably) = mutated_variables(container, cx) {
+        if used_mutably.contains(&def_id) {
+            return true;
+        }
+    }
+    false
+}
+
+fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool {
+    let def_id = match path_to_local(iter_expr) {
+        Some(id) => id,
+        None => return false,
+    };
+    let mut visitor = VarUsedAfterLoopVisitor {
+        def_id,
+        iter_expr_id: iter_expr.hir_id,
+        past_while_let: false,
+        var_used_after_while_let: false,
+    };
+    if let Some(enclosing_block) = get_enclosing_block(cx, def_id) {
+        walk_block(&mut visitor, enclosing_block);
+    }
+    visitor.var_used_after_while_let
+}
+
+fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
+    if_chain! {
+        if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id);
+        let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id);
+        if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node);
+        then {
+            return is_loop_nested(cx, loop_expr, iter_expr)
+        }
+    }
+    false
+}
+
+fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool {
+    let mut id = loop_expr.hir_id;
+    let iter_id = if let Some(id) = path_to_local(iter_expr) {
+        id
+    } else {
+        return true;
+    };
+    loop {
+        let parent = cx.tcx.hir().get_parent_node(id);
+        if parent == id {
+            return false;
+        }
+        match cx.tcx.hir().find(parent) {
+            Some(Node::Expr(expr)) => {
+                if let ExprKind::Loop(..) = expr.kind {
+                    return true;
+                };
+            },
+            Some(Node::Block(block)) => {
+                let mut block_visitor = LoopNestVisitor {
+                    hir_id: id,
+                    iterator: iter_id,
+                    nesting: Nesting::Unknown,
+                };
+                walk_block(&mut block_visitor, block);
+                if block_visitor.nesting == Nesting::RuledOut {
+                    return false;
+                }
+            },
+            Some(Node::Stmt(_)) => (),
+            _ => {
+                return false;
+            },
+        }
+        id = parent;
+    }
+}
+
+struct VarUsedAfterLoopVisitor {
+    def_id: HirId,
+    iter_expr_id: HirId,
+    past_while_let: bool,
+    var_used_after_while_let: bool,
+}
+
+impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor {
+    type Map = Map<'tcx>;
+
+    fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
+        if self.past_while_let {
+            if path_to_local_id(expr, self.def_id) {
+                self.var_used_after_while_let = true;
+            }
+        } else if self.iter_expr_id == expr.hir_id {
+            self.past_while_let = true;
+        }
+        walk_expr(self, expr);
+    }
+    fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+        NestedVisitorMap::None
+    }
+}