--- /dev/null
+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
+ }
+}