about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-10-03 00:05:05 +0000
committerbors <bors@rust-lang.org>2022-10-03 00:05:05 +0000
commit2be6c4ae5b075028a6c61ba75daaf22fb7921c85 (patch)
tree6bf52e692593d836420fa05c4d34706cab08d89a
parentbef93d3b1464b6d467482d47a53c62aadaa9f41c (diff)
parent26bb36636ca9d71958055e05053946d22fcfbdbb (diff)
downloadrust-2be6c4ae5b075028a6c61ba75daaf22fb7921c85.tar.gz
rust-2be6c4ae5b075028a6c61ba75daaf22fb7921c85.zip
Auto merge of #8762 - Jarcho:visitor, r=Jarcho
Replace `expr_visitor` with  `for_each_expr`

This is a minor change which uses `ControlFlow` rather than a boolean. This also runs the visitor rather than returning the visitor, which results in a small readability win as well.

changelog: None
-rw-r--r--clippy_lints/src/blocks_in_if_conditions.rs68
-rw-r--r--clippy_lints/src/cognitive_complexity.rs63
-rw-r--r--clippy_lints/src/functions/must_use.rs77
-rw-r--r--clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs91
-rw-r--r--clippy_lints/src/implicit_return.rs12
-rw-r--r--clippy_lints/src/methods/str_splitn.rs20
-rw-r--r--clippy_lints/src/methods/unnecessary_filter_map.rs51
-rw-r--r--clippy_lints/src/needless_late_init.rs34
-rw-r--r--clippy_lints/src/operators/assign_op_pattern.rs39
-rw-r--r--clippy_lints/src/panic_in_result_fn.rs19
-rw-r--r--clippy_lints/src/read_zero_byte_vec.rs40
-rw-r--r--clippy_lints/src/returns.rs39
-rw-r--r--clippy_lints/src/suspicious_trait_impl.rs33
-rw-r--r--clippy_lints/src/unwrap_in_result.rs72
-rw-r--r--clippy_utils/src/lib.rs19
-rw-r--r--clippy_utils/src/macros.rs81
-rw-r--r--clippy_utils/src/ptr.rs37
-rw-r--r--clippy_utils/src/usage.rs49
-rw-r--r--clippy_utils/src/visitors.rs164
19 files changed, 413 insertions, 595 deletions
diff --git a/clippy_lints/src/blocks_in_if_conditions.rs b/clippy_lints/src/blocks_in_if_conditions.rs
index 4df4d6ddf41..569bf27c3e7 100644
--- a/clippy_lints/src/blocks_in_if_conditions.rs
+++ b/clippy_lints/src/blocks_in_if_conditions.rs
@@ -3,10 +3,11 @@ use clippy_utils::get_parent_expr;
 use clippy_utils::higher;
 use clippy_utils::source::snippet_block_with_applicability;
 use clippy_utils::ty::implements_trait;
+use clippy_utils::visitors::{for_each_expr, Descend};
+use core::ops::ControlFlow;
 use if_chain::if_chain;
 use rustc_errors::Applicability;
-use rustc_hir::intravisit::{walk_expr, Visitor};
-use rustc_hir::{BlockCheckMode, Closure, Expr, ExprKind};
+use rustc_hir::{BlockCheckMode, Expr, ExprKind};
 use rustc_lint::{LateContext, LateLintPass, LintContext};
 use rustc_middle::lint::in_external_macro;
 use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -44,39 +45,6 @@ declare_clippy_lint! {
 
 declare_lint_pass!(BlocksInIfConditions => [BLOCKS_IN_IF_CONDITIONS]);
 
-struct ExVisitor<'a, 'tcx> {
-    found_block: Option<&'tcx Expr<'tcx>>,
-    cx: &'a LateContext<'tcx>,
-}
-
-impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> {
-    fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
-        if let ExprKind::Closure(&Closure { body, .. }) = expr.kind {
-            // do not lint if the closure is called using an iterator (see #1141)
-            if_chain! {
-                if let Some(parent) = get_parent_expr(self.cx, expr);
-                if let ExprKind::MethodCall(_, self_arg, ..) = &parent.kind;
-                let caller = self.cx.typeck_results().expr_ty(self_arg);
-                if let Some(iter_id) = self.cx.tcx.get_diagnostic_item(sym::Iterator);
-                if implements_trait(self.cx, caller, iter_id, &[]);
-                then {
-                    return;
-                }
-            }
-
-            let body = self.cx.tcx.hir().body(body);
-            let ex = &body.value;
-            if let ExprKind::Block(block, _) = ex.kind {
-                if !body.value.span.from_expansion() && !block.stmts.is_empty() {
-                    self.found_block = Some(ex);
-                    return;
-                }
-            }
-        }
-        walk_expr(self, expr);
-    }
-}
-
 const BRACED_EXPR_MESSAGE: &str = "omit braces around single expression condition";
 const COMPLEX_BLOCK_MESSAGE: &str = "in an `if` condition, avoid complex blocks or closures with blocks; \
                                     instead, move the block or closure higher and bind it with a `let`";
@@ -144,11 +112,31 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
                     }
                 }
             } else {
-                let mut visitor = ExVisitor { found_block: None, cx };
-                walk_expr(&mut visitor, cond);
-                if let Some(block) = visitor.found_block {
-                    span_lint(cx, BLOCKS_IN_IF_CONDITIONS, block.span, COMPLEX_BLOCK_MESSAGE);
-                }
+                let _: Option<!> = for_each_expr(cond, |e| {
+                    if let ExprKind::Closure(closure) = e.kind {
+                        // do not lint if the closure is called using an iterator (see #1141)
+                        if_chain! {
+                            if let Some(parent) = get_parent_expr(cx, e);
+                            if let ExprKind::MethodCall(_, self_arg, _, _) = &parent.kind;
+                            let caller = cx.typeck_results().expr_ty(self_arg);
+                            if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
+                            if implements_trait(cx, caller, iter_id, &[]);
+                            then {
+                                return ControlFlow::Continue(Descend::No);
+                            }
+                        }
+
+                        let body = cx.tcx.hir().body(closure.body);
+                        let ex = &body.value;
+                        if let ExprKind::Block(block, _) = ex.kind {
+                            if !body.value.span.from_expansion() && !block.stmts.is_empty() {
+                                span_lint(cx, BLOCKS_IN_IF_CONDITIONS, ex.span, COMPLEX_BLOCK_MESSAGE);
+                                return ControlFlow::Continue(Descend::No);
+                            }
+                        }
+                    }
+                    ControlFlow::Continue(Descend::Yes)
+                });
             }
         }
     }
diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs
index fed04ae7f3d..77af3b53d63 100644
--- a/clippy_lints/src/cognitive_complexity.rs
+++ b/clippy_lints/src/cognitive_complexity.rs
@@ -3,10 +3,12 @@
 use clippy_utils::diagnostics::span_lint_and_help;
 use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::visitors::for_each_expr;
 use clippy_utils::LimitStack;
+use core::ops::ControlFlow;
 use rustc_ast::ast::Attribute;
-use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
-use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId};
+use rustc_hir::intravisit::FnKind;
+use rustc_hir::{Body, ExprKind, FnDecl, HirId};
 use rustc_lint::{LateContext, LateLintPass, LintContext};
 use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::source_map::Span;
@@ -61,11 +63,27 @@ impl CognitiveComplexity {
             return;
         }
 
-        let expr = &body.value;
+        let expr = body.value;
+
+        let mut cc = 1u64;
+        let mut returns = 0u64;
+        let _: Option<!> = for_each_expr(expr, |e| {
+            match e.kind {
+                ExprKind::If(_, _, _) => {
+                    cc += 1;
+                },
+                ExprKind::Match(_, arms, _) => {
+                    if arms.len() > 1 {
+                        cc += 1;
+                    }
+                    cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
+                },
+                ExprKind::Ret(_) => returns += 1,
+                _ => {},
+            }
+            ControlFlow::Continue(())
+        });
 
-        let mut helper = CcHelper { cc: 1, returns: 0 };
-        helper.visit_expr(expr);
-        let CcHelper { cc, returns } = helper;
         let ret_ty = cx.typeck_results().node_type(expr.hir_id);
         let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym::Result) {
             returns
@@ -74,13 +92,12 @@ impl CognitiveComplexity {
             (returns / 2)
         };
 
-        let mut rust_cc = cc;
         // prevent degenerate cases where unreachable code contains `return` statements
-        if rust_cc >= ret_adjust {
-            rust_cc -= ret_adjust;
+        if cc >= ret_adjust {
+            cc -= ret_adjust;
         }
 
-        if rust_cc > self.limit.limit() {
+        if cc > self.limit.limit() {
             let fn_span = match kind {
                 FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
                 FnKind::Closure => {
@@ -107,7 +124,7 @@ impl CognitiveComplexity {
                 COGNITIVE_COMPLEXITY,
                 fn_span,
                 &format!(
-                    "the function has a cognitive complexity of ({rust_cc}/{})",
+                    "the function has a cognitive complexity of ({cc}/{})",
                     self.limit.limit()
                 ),
                 None,
@@ -140,27 +157,3 @@ impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity {
         self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity");
     }
 }
-
-struct CcHelper {
-    cc: u64,
-    returns: u64,
-}
-
-impl<'tcx> Visitor<'tcx> for CcHelper {
-    fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
-        walk_expr(self, e);
-        match e.kind {
-            ExprKind::If(_, _, _) => {
-                self.cc += 1;
-            },
-            ExprKind::Match(_, arms, _) => {
-                if arms.len() > 1 {
-                    self.cc += 1;
-                }
-                self.cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
-            },
-            ExprKind::Ret(_) => self.returns += 1,
-            _ => {},
-        }
-    }
-}
diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs
index 977c8ee594f..d263804f32c 100644
--- a/clippy_lints/src/functions/must_use.rs
+++ b/clippy_lints/src/functions/must_use.rs
@@ -1,7 +1,7 @@
 use rustc_ast::ast::Attribute;
 use rustc_errors::Applicability;
 use rustc_hir::def_id::{DefIdSet, LocalDefId};
-use rustc_hir::{self as hir, def::Res, intravisit, QPath};
+use rustc_hir::{self as hir, def::Res, QPath};
 use rustc_lint::{LateContext, LintContext};
 use rustc_middle::{
     lint::in_external_macro,
@@ -13,8 +13,11 @@ use clippy_utils::attrs::is_proc_macro;
 use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
 use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::is_must_use_ty;
+use clippy_utils::visitors::for_each_expr;
 use clippy_utils::{match_def_path, return_ty, trait_ref_of_method};
 
+use core::ops::ControlFlow;
+
 use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT};
 
 pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
@@ -200,79 +203,65 @@ fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &m
     }
 }
 
-struct StaticMutVisitor<'a, 'tcx> {
-    cx: &'a LateContext<'tcx>,
-    mutates_static: bool,
+fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
+    use hir::ExprKind::{Field, Index, Path};
+
+    match e.kind {
+        Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)),
+        Path(_) => true,
+        Field(inner, _) | Index(inner, _) => is_mutated_static(inner),
+        _ => false,
+    }
 }
 
-impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
-    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
+fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
+    for_each_expr(body.value, |e| {
         use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
 
-        if self.mutates_static {
-            return;
-        }
-        match expr.kind {
+        match e.kind {
             Call(_, args) => {
                 let mut tys = DefIdSet::default();
                 for arg in args {
-                    if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
+                    if cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
                         && is_mutable_ty(
-                            self.cx,
-                            self.cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
+                            cx,
+                            cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
                             arg.span,
                             &mut tys,
                         )
                         && is_mutated_static(arg)
                     {
-                        self.mutates_static = true;
-                        return;
+                        return ControlFlow::Break(());
                     }
                     tys.clear();
                 }
+                ControlFlow::Continue(())
             },
             MethodCall(_, receiver, args, _) => {
                 let mut tys = DefIdSet::default();
                 for arg in std::iter::once(receiver).chain(args.iter()) {
-                    if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
+                    if cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
                         && is_mutable_ty(
-                            self.cx,
-                            self.cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
+                            cx,
+                            cx.tcx.typeck(arg.hir_id.owner.def_id).expr_ty(arg),
                             arg.span,
                             &mut tys,
                         )
                         && is_mutated_static(arg)
                     {
-                        self.mutates_static = true;
-                        return;
+                        return ControlFlow::Break(());
                     }
                     tys.clear();
                 }
+                ControlFlow::Continue(())
             },
-            Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target) => {
-                self.mutates_static |= is_mutated_static(target);
+            Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target)
+                if is_mutated_static(target) =>
+            {
+                ControlFlow::Break(())
             },
-            _ => {},
+            _ => ControlFlow::Continue(()),
         }
-    }
-}
-
-fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
-    use hir::ExprKind::{Field, Index, Path};
-
-    match e.kind {
-        Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)),
-        Path(_) => true,
-        Field(inner, _) | Index(inner, _) => is_mutated_static(inner),
-        _ => false,
-    }
-}
-
-fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
-    let mut v = StaticMutVisitor {
-        cx,
-        mutates_static: false,
-    };
-    intravisit::walk_expr(&mut v, body.value);
-    v.mutates_static
+    })
+    .is_some()
 }
diff --git a/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs b/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs
index 0b50431fbaa..b7595d101e0 100644
--- a/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs
+++ b/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs
@@ -5,8 +5,11 @@ use rustc_span::def_id::LocalDefId;
 
 use clippy_utils::diagnostics::span_lint;
 use clippy_utils::ty::type_is_unsafe_function;
+use clippy_utils::visitors::for_each_expr_with_closures;
 use clippy_utils::{iter_input_pats, path_to_local};
 
+use core::ops::ControlFlow;
+
 use super::NOT_UNSAFE_PTR_ARG_DEREF;
 
 pub(super) fn check_fn<'tcx>(
@@ -39,21 +42,34 @@ fn check_raw_ptr<'tcx>(
     body: &'tcx hir::Body<'tcx>,
     def_id: LocalDefId,
 ) {
-    let expr = &body.value;
     if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(def_id) {
         let raw_ptrs = iter_input_pats(decl, body)
             .filter_map(|arg| raw_ptr_arg(cx, arg))
             .collect::<HirIdSet>();
 
         if !raw_ptrs.is_empty() {
-            let typeck_results = cx.tcx.typeck_body(body.id());
-            let mut v = DerefVisitor {
-                cx,
-                ptrs: raw_ptrs,
-                typeck_results,
-            };
-
-            intravisit::walk_expr(&mut v, expr);
+            let typeck = cx.tcx.typeck_body(body.id());
+            let _: Option<!> = for_each_expr_with_closures(cx, body.value, |e| {
+                match e.kind {
+                    hir::ExprKind::Call(f, args) if type_is_unsafe_function(cx, typeck.expr_ty(f)) => {
+                        for arg in args {
+                            check_arg(cx, &raw_ptrs, arg);
+                        }
+                    },
+                    hir::ExprKind::MethodCall(_, recv, args, _) => {
+                        let def_id = typeck.type_dependent_def_id(e.hir_id).unwrap();
+                        if cx.tcx.fn_sig(def_id).skip_binder().unsafety == hir::Unsafety::Unsafe {
+                            check_arg(cx, &raw_ptrs, recv);
+                            for arg in args {
+                                check_arg(cx, &raw_ptrs, arg);
+                            }
+                        }
+                    },
+                    hir::ExprKind::Unary(hir::UnOp::Deref, ptr) => check_arg(cx, &raw_ptrs, ptr),
+                    _ => (),
+                }
+                ControlFlow::Continue(())
+            });
         }
     }
 }
@@ -70,54 +86,13 @@ fn raw_ptr_arg(cx: &LateContext<'_>, arg: &hir::Param<'_>) -> Option<hir::HirId>
     }
 }
 
-struct DerefVisitor<'a, 'tcx> {
-    cx: &'a LateContext<'tcx>,
-    ptrs: HirIdSet,
-    typeck_results: &'a ty::TypeckResults<'tcx>,
-}
-
-impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> {
-    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
-        match expr.kind {
-            hir::ExprKind::Call(f, args) => {
-                let ty = self.typeck_results.expr_ty(f);
-
-                if type_is_unsafe_function(self.cx, ty) {
-                    for arg in args {
-                        self.check_arg(arg);
-                    }
-                }
-            },
-            hir::ExprKind::MethodCall(_, receiver, args, _) => {
-                let def_id = self.typeck_results.type_dependent_def_id(expr.hir_id).unwrap();
-                let base_type = self.cx.tcx.type_of(def_id);
-
-                if type_is_unsafe_function(self.cx, base_type) {
-                    self.check_arg(receiver);
-                    for arg in args {
-                        self.check_arg(arg);
-                    }
-                }
-            },
-            hir::ExprKind::Unary(hir::UnOp::Deref, ptr) => self.check_arg(ptr),
-            _ => (),
-        }
-
-        intravisit::walk_expr(self, expr);
-    }
-}
-
-impl<'a, 'tcx> DerefVisitor<'a, 'tcx> {
-    fn check_arg(&self, ptr: &hir::Expr<'_>) {
-        if let Some(id) = path_to_local(ptr) {
-            if self.ptrs.contains(&id) {
-                span_lint(
-                    self.cx,
-                    NOT_UNSAFE_PTR_ARG_DEREF,
-                    ptr.span,
-                    "this public function might dereference a raw pointer but is not marked `unsafe`",
-                );
-            }
-        }
+fn check_arg(cx: &LateContext<'_>, raw_ptrs: &HirIdSet, arg: &hir::Expr<'_>) {
+    if path_to_local(arg).map_or(false, |id| raw_ptrs.contains(&id)) {
+        span_lint(
+            cx,
+            NOT_UNSAFE_PTR_ARG_DEREF,
+            arg.span,
+            "this public function might dereference a raw pointer but is not marked `unsafe`",
+        );
     }
 }
diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs
index cfc988da233..946d04eff6f 100644
--- a/clippy_lints/src/implicit_return.rs
+++ b/clippy_lints/src/implicit_return.rs
@@ -2,10 +2,11 @@ use clippy_utils::{
     diagnostics::span_lint_hir_and_then,
     get_async_fn_body, is_async_fn,
     source::{snippet_with_applicability, snippet_with_context, walk_span_to_context},
-    visitors::expr_visitor_no_bodies,
+    visitors::for_each_expr,
 };
+use core::ops::ControlFlow;
 use rustc_errors::Applicability;
-use rustc_hir::intravisit::{FnKind, Visitor};
+use rustc_hir::intravisit::FnKind;
 use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId};
 use rustc_lint::{LateContext, LateLintPass, LintContext};
 use rustc_middle::lint::in_external_macro;
@@ -152,7 +153,7 @@ fn lint_implicit_returns(
 
         ExprKind::Loop(block, ..) => {
             let mut add_return = false;
-            expr_visitor_no_bodies(|e| {
+            let _: Option<!> = for_each_expr(block, |e| {
                 if let ExprKind::Break(dest, sub_expr) = e.kind {
                     if dest.target_id.ok() == Some(expr.hir_id) {
                         if call_site_span.is_none() && e.span.ctxt() == ctxt {
@@ -167,9 +168,8 @@ fn lint_implicit_returns(
                         }
                     }
                 }
-                true
-            })
-            .visit_block(block);
+                ControlFlow::Continue(())
+            });
             if add_return {
                 #[expect(clippy::option_if_let_else)]
                 if let Some(span) = call_site_span {
diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs
index 9ca4d65550d..ae3594bd36c 100644
--- a/clippy_lints/src/methods/str_splitn.rs
+++ b/clippy_lints/src/methods/str_splitn.rs
@@ -2,11 +2,11 @@ use clippy_utils::consts::{constant, Constant};
 use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
 use clippy_utils::source::snippet_with_context;
 use clippy_utils::usage::local_used_after_expr;
-use clippy_utils::visitors::expr_visitor;
+use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
 use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths};
+use core::ops::ControlFlow;
 use if_chain::if_chain;
 use rustc_errors::Applicability;
-use rustc_hir::intravisit::Visitor;
 use rustc_hir::{
     BindingAnnotation, Expr, ExprKind, HirId, LangItem, Local, MatchSource, Node, Pat, PatKind, QPath, Stmt, StmtKind,
 };
@@ -211,7 +211,7 @@ fn indirect_usage<'tcx>(
     binding: HirId,
     ctxt: SyntaxContext,
 ) -> Option<IndirectUsage<'tcx>> {
-    if let StmtKind::Local(Local {
+    if let StmtKind::Local(&Local {
         pat: Pat {
             kind: PatKind::Binding(BindingAnnotation::NONE, _, ident, None),
             ..
@@ -222,14 +222,12 @@ fn indirect_usage<'tcx>(
     }) = stmt.kind
     {
         let mut path_to_binding = None;
-        expr_visitor(cx, |expr| {
-            if path_to_local_id(expr, binding) {
-                path_to_binding = Some(expr);
+        let _: Option<!> = for_each_expr_with_closures(cx, init_expr, |e| {
+            if path_to_local_id(e, binding) {
+                path_to_binding = Some(e);
             }
-
-            path_to_binding.is_none()
-        })
-        .visit_expr(init_expr);
+            ControlFlow::Continue(Descend::from(path_to_binding.is_none()))
+        });
 
         let mut parents = cx.tcx.hir().parent_iter(path_to_binding?.hir_id);
         let iter_usage = parse_iter_usage(cx, ctxt, &mut parents)?;
@@ -250,7 +248,7 @@ fn indirect_usage<'tcx>(
             ..
         } = iter_usage
         {
-            if parent_id == *local_hir_id {
+            if parent_id == local_hir_id {
                 return Some(IndirectUsage {
                     name: ident.name,
                     span: stmt.span,
diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs
index 50434a5658a..1cef6226ad4 100644
--- a/clippy_lints/src/methods/unnecessary_filter_map.rs
+++ b/clippy_lints/src/methods/unnecessary_filter_map.rs
@@ -2,9 +2,10 @@ use super::utils::clone_or_copy_needed;
 use clippy_utils::diagnostics::span_lint;
 use clippy_utils::ty::is_copy;
 use clippy_utils::usage::mutated_variables;
+use clippy_utils::visitors::{for_each_expr, Descend};
 use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id};
+use core::ops::ControlFlow;
 use rustc_hir as hir;
-use rustc_hir::intravisit::{walk_expr, Visitor};
 use rustc_hir::LangItem::{OptionNone, OptionSome};
 use rustc_lint::LateContext;
 use rustc_middle::ty;
@@ -13,7 +14,7 @@ use rustc_span::sym;
 use super::UNNECESSARY_FILTER_MAP;
 use super::UNNECESSARY_FIND_MAP;
 
-pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, name: &str) {
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, arg: &'tcx hir::Expr<'tcx>, name: &str) {
     if !is_trait_method(cx, expr, sym::Iterator) {
         return;
     }
@@ -26,10 +27,16 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
 
         let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, body.value);
 
-        let mut return_visitor = ReturnVisitor::new(cx, arg_id);
-        return_visitor.visit_expr(body.value);
-        found_mapping |= return_visitor.found_mapping;
-        found_filtering |= return_visitor.found_filtering;
+        let _: Option<!> = for_each_expr(body.value, |e| {
+            if let hir::ExprKind::Ret(Some(e)) = &e.kind {
+                let (found_mapping_res, found_filtering_res) = check_expression(cx, arg_id, e);
+                found_mapping |= found_mapping_res;
+                found_filtering |= found_filtering_res;
+                ControlFlow::Continue(Descend::No)
+            } else {
+                ControlFlow::Continue(Descend::Yes)
+            }
+        });
 
         let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
         let sugg = if !found_filtering {
@@ -97,35 +104,3 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
         _ => (true, true),
     }
 }
-
-struct ReturnVisitor<'a, 'tcx> {
-    cx: &'a LateContext<'tcx>,
-    arg_id: hir::HirId,
-    // Found a non-None return that isn't Some(input)
-    found_mapping: bool,
-    // Found a return that isn't Some
-    found_filtering: bool,
-}
-
-impl<'a, 'tcx> ReturnVisitor<'a, 'tcx> {
-    fn new(cx: &'a LateContext<'tcx>, arg_id: hir::HirId) -> ReturnVisitor<'a, 'tcx> {
-        ReturnVisitor {
-            cx,
-            arg_id,
-            found_mapping: false,
-            found_filtering: false,
-        }
-    }
-}
-
-impl<'a, 'tcx> Visitor<'tcx> for ReturnVisitor<'a, 'tcx> {
-    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
-        if let hir::ExprKind::Ret(Some(expr)) = &expr.kind {
-            let (found_mapping, found_filtering) = check_expression(self.cx, self.arg_id, expr);
-            self.found_mapping |= found_mapping;
-            self.found_filtering |= found_filtering;
-        } else {
-            walk_expr(self, expr);
-        }
-    }
-}
diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs
index cbad53f4450..9d26e590086 100644
--- a/clippy_lints/src/needless_late_init.rs
+++ b/clippy_lints/src/needless_late_init.rs
@@ -2,9 +2,9 @@ use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::path_to_local;
 use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::needs_ordered_drop;
-use clippy_utils::visitors::{expr_visitor, expr_visitor_no_bodies, is_local_used};
+use clippy_utils::visitors::{for_each_expr, for_each_expr_with_closures, is_local_used};
+use core::ops::ControlFlow;
 use rustc_errors::{Applicability, MultiSpan};
-use rustc_hir::intravisit::Visitor;
 use rustc_hir::{
     BindingAnnotation, Block, Expr, ExprKind, HirId, Local, LocalSource, MatchSource, Node, Pat, PatKind, Stmt,
     StmtKind,
@@ -64,31 +64,25 @@ declare_clippy_lint! {
 declare_lint_pass!(NeedlessLateInit => [NEEDLESS_LATE_INIT]);
 
 fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> bool {
-    let mut seen = false;
-    expr_visitor(cx, |expr| {
-        if let ExprKind::Assign(..) = expr.kind {
-            seen = true;
+    for_each_expr_with_closures(cx, stmt, |e| {
+        if matches!(e.kind, ExprKind::Assign(..)) {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(())
         }
-
-        !seen
     })
-    .visit_stmt(stmt);
-
-    seen
+    .is_some()
 }
 
 fn contains_let(cond: &Expr<'_>) -> bool {
-    let mut seen = false;
-    expr_visitor_no_bodies(|expr| {
-        if let ExprKind::Let(_) = expr.kind {
-            seen = true;
+    for_each_expr(cond, |e| {
+        if matches!(e.kind, ExprKind::Let(_)) {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(())
         }
-
-        !seen
     })
-    .visit_expr(cond);
-
-    seen
+    .is_some()
 }
 
 fn stmt_needs_ordered_drop(cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
diff --git a/clippy_lints/src/operators/assign_op_pattern.rs b/clippy_lints/src/operators/assign_op_pattern.rs
index 2d5d5d143ff..26bca7c306a 100644
--- a/clippy_lints/src/operators/assign_op_pattern.rs
+++ b/clippy_lints/src/operators/assign_op_pattern.rs
@@ -2,11 +2,12 @@ use clippy_utils::binop_traits;
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::source::snippet_opt;
 use clippy_utils::ty::implements_trait;
+use clippy_utils::visitors::for_each_expr;
 use clippy_utils::{eq_expr_value, trait_ref_of_method};
+use core::ops::ControlFlow;
 use if_chain::if_chain;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
-use rustc_hir::intravisit::{walk_expr, Visitor};
 use rustc_hir_analysis::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
 use rustc_lint::LateContext;
 use rustc_middle::mir::FakeReadCause;
@@ -65,15 +66,19 @@ pub(super) fn check<'tcx>(
             }
         };
 
-        let mut visitor = ExprVisitor {
-            assignee,
-            counter: 0,
-            cx,
-        };
-
-        walk_expr(&mut visitor, e);
+        let mut found = false;
+        let found_multiple = for_each_expr(e, |e| {
+            if eq_expr_value(cx, assignee, e) {
+                if found {
+                    return ControlFlow::Break(());
+                }
+                found = true;
+            }
+            ControlFlow::Continue(())
+        })
+        .is_some();
 
-        if visitor.counter == 1 {
+        if found && !found_multiple {
             // a = a op b
             if eq_expr_value(cx, assignee, l) {
                 lint(assignee, r);
@@ -98,22 +103,6 @@ pub(super) fn check<'tcx>(
     }
 }
 
-struct ExprVisitor<'a, 'tcx> {
-    assignee: &'a hir::Expr<'a>,
-    counter: u8,
-    cx: &'a LateContext<'tcx>,
-}
-
-impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> {
-    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
-        if eq_expr_value(self.cx, self.assignee, expr) {
-            self.counter += 1;
-        }
-
-        walk_expr(self, expr);
-    }
-}
-
 fn imm_borrows_in_expr(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> hir::HirIdSet {
     struct S(hir::HirIdSet);
     impl Delegate<'_> for S {
diff --git a/clippy_lints/src/panic_in_result_fn.rs b/clippy_lints/src/panic_in_result_fn.rs
index 4aa0d9227ab..efec12489a9 100644
--- a/clippy_lints/src/panic_in_result_fn.rs
+++ b/clippy_lints/src/panic_in_result_fn.rs
@@ -2,9 +2,10 @@ use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::macros::root_macro_call_first_node;
 use clippy_utils::return_ty;
 use clippy_utils::ty::is_type_diagnostic_item;
-use clippy_utils::visitors::expr_visitor_no_bodies;
+use clippy_utils::visitors::{for_each_expr, Descend};
+use core::ops::ControlFlow;
 use rustc_hir as hir;
-use rustc_hir::intravisit::{FnKind, Visitor};
+use rustc_hir::intravisit::FnKind;
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::{sym, Span};
@@ -58,18 +59,20 @@ impl<'tcx> LateLintPass<'tcx> for PanicInResultFn {
 
 fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir::Body<'tcx>) {
     let mut panics = Vec::new();
-    expr_visitor_no_bodies(|expr| {
-        let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return true };
+    let _: Option<!> = for_each_expr(body.value, |e| {
+        let Some(macro_call) = root_macro_call_first_node(cx, e) else {
+            return ControlFlow::Continue(Descend::Yes);
+        };
         if matches!(
             cx.tcx.item_name(macro_call.def_id).as_str(),
             "unimplemented" | "unreachable" | "panic" | "todo" | "assert" | "assert_eq" | "assert_ne"
         ) {
             panics.push(macro_call.span);
-            return false;
+            ControlFlow::Continue(Descend::No)
+        } else {
+            ControlFlow::Continue(Descend::Yes)
         }
-        true
-    })
-    .visit_expr(body.value);
+    });
     if !panics.is_empty() {
         span_lint_and_then(
             cx,
diff --git a/clippy_lints/src/read_zero_byte_vec.rs b/clippy_lints/src/read_zero_byte_vec.rs
index ae80b6f1269..fa107858863 100644
--- a/clippy_lints/src/read_zero_byte_vec.rs
+++ b/clippy_lints/src/read_zero_byte_vec.rs
@@ -2,9 +2,10 @@ use clippy_utils::{
     diagnostics::{span_lint, span_lint_and_sugg},
     higher::{get_vec_init_kind, VecInitKind},
     source::snippet,
-    visitors::expr_visitor_no_bodies,
+    visitors::for_each_expr,
 };
-use hir::{intravisit::Visitor, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind};
+use core::ops::ControlFlow;
+use hir::{Expr, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind};
 use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_lint::{LateContext, LateLintPass};
@@ -58,10 +59,8 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
                 && let PatKind::Binding(_, _, ident, _) = pat.kind
                 && let Some(vec_init_kind) = get_vec_init_kind(cx, init)
             {
-                // finds use of `_.read(&mut v)`
-                let mut read_found = false;
-                let mut visitor = expr_visitor_no_bodies(|expr| {
-                    if let ExprKind::MethodCall(path, _self, [arg], _) = expr.kind
+                let visitor = |expr: &Expr<'_>| {
+                    if let ExprKind::MethodCall(path, _, [arg], _) = expr.kind
                         && let PathSegment { ident: read_or_read_exact, .. } = *path
                         && matches!(read_or_read_exact.as_str(), "read" | "read_exact")
                         && let ExprKind::AddrOf(_, hir::Mutability::Mut, inner) = arg.kind
@@ -69,27 +68,22 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
                         && let [inner_seg] = inner_path.segments
                         && ident.name == inner_seg.ident.name
                     {
-                        read_found = true;
+                        ControlFlow::Break(())
+                    } else {
+                        ControlFlow::Continue(())
                     }
-                    !read_found
-                });
+                };
 
-                let next_stmt_span;
-                if idx == block.stmts.len() - 1 {
+                let (read_found, next_stmt_span) =
+                if let Some(next_stmt) = block.stmts.get(idx + 1) {
+                    // case { .. stmt; stmt; .. }
+                    (for_each_expr(next_stmt, visitor).is_some(), next_stmt.span)
+                } else if let Some(e) = block.expr {
                     // case { .. stmt; expr }
-                    if let Some(e) = block.expr {
-                        visitor.visit_expr(e);
-                        next_stmt_span = e.span;
-                    } else {
-                        return;
-                    }
+                    (for_each_expr(e, visitor).is_some(), e.span)
                 } else {
-                    // case { .. stmt; stmt; .. }
-                    let next_stmt = &block.stmts[idx + 1];
-                    visitor.visit_stmt(next_stmt);
-                    next_stmt_span = next_stmt.span;
-                }
-                drop(visitor);
+                    return
+                };
 
                 if read_found && !next_stmt_span.from_expansion() {
                     let applicability = Applicability::MaybeIncorrect;
diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs
index f758f4cff8b..2b2a41d1601 100644
--- a/clippy_lints/src/returns.rs
+++ b/clippy_lints/src/returns.rs
@@ -1,9 +1,11 @@
 use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
 use clippy_utils::source::{snippet_opt, snippet_with_context};
+use clippy_utils::visitors::{for_each_expr, Descend};
 use clippy_utils::{fn_def_id, path_to_local_id};
+use core::ops::ControlFlow;
 use if_chain::if_chain;
 use rustc_errors::Applicability;
-use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
+use rustc_hir::intravisit::FnKind;
 use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, PatKind, StmtKind};
 use rustc_lint::{LateContext, LateLintPass, LintContext};
 use rustc_middle::lint::in_external_macro;
@@ -270,33 +272,20 @@ fn emit_return_lint(
 }
 
 fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
-    let mut visitor = BorrowVisitor { cx, borrows: false };
-    walk_expr(&mut visitor, expr);
-    visitor.borrows
-}
-
-struct BorrowVisitor<'a, 'tcx> {
-    cx: &'a LateContext<'tcx>,
-    borrows: bool,
-}
-
-impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> {
-    fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
-        if self.borrows || expr.span.from_expansion() {
-            return;
-        }
-
-        if let Some(def_id) = fn_def_id(self.cx, expr) {
-            self.borrows = self
-                .cx
+    for_each_expr(expr, |e| {
+        if let Some(def_id) = fn_def_id(cx, e)
+            && cx
                 .tcx
                 .fn_sig(def_id)
-                .output()
                 .skip_binder()
+                .output()
                 .walk()
-                .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
+                .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)))
+        {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(Descend::from(!expr.span.from_expansion()))
         }
-
-        walk_expr(self, expr);
-    }
+    })
+    .is_some()
 }
diff --git a/clippy_lints/src/suspicious_trait_impl.rs b/clippy_lints/src/suspicious_trait_impl.rs
index d47ed459387..b57b484bdc8 100644
--- a/clippy_lints/src/suspicious_trait_impl.rs
+++ b/clippy_lints/src/suspicious_trait_impl.rs
@@ -1,8 +1,9 @@
 use clippy_utils::diagnostics::span_lint;
+use clippy_utils::visitors::for_each_expr;
 use clippy_utils::{binop_traits, trait_ref_of_method, BINOP_TRAITS, OP_ASSIGN_TRAITS};
+use core::ops::ControlFlow;
 use if_chain::if_chain;
 use rustc_hir as hir;
-use rustc_hir::intravisit::{walk_expr, Visitor};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 
@@ -92,25 +93,17 @@ impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl {
 }
 
 fn count_binops(expr: &hir::Expr<'_>) -> u32 {
-    let mut visitor = BinaryExprVisitor::default();
-    visitor.visit_expr(expr);
-    visitor.nb_binops
-}
-
-#[derive(Default)]
-struct BinaryExprVisitor {
-    nb_binops: u32,
-}
-
-impl<'tcx> Visitor<'tcx> for BinaryExprVisitor {
-    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
-        match expr.kind {
+    let mut count = 0u32;
+    let _: Option<!> = for_each_expr(expr, |e| {
+        if matches!(
+            e.kind,
             hir::ExprKind::Binary(..)
-            | hir::ExprKind::Unary(hir::UnOp::Not | hir::UnOp::Neg, _)
-            | hir::ExprKind::AssignOp(..) => self.nb_binops += 1,
-            _ => {},
+                | hir::ExprKind::Unary(hir::UnOp::Not | hir::UnOp::Neg, _)
+                | hir::ExprKind::AssignOp(..)
+        ) {
+            count += 1;
         }
-
-        walk_expr(self, expr);
-    }
+        ControlFlow::Continue(())
+    });
+    count
 }
diff --git a/clippy_lints/src/unwrap_in_result.rs b/clippy_lints/src/unwrap_in_result.rs
index baa53ba664f..a69719b127b 100644
--- a/clippy_lints/src/unwrap_in_result.rs
+++ b/clippy_lints/src/unwrap_in_result.rs
@@ -1,12 +1,12 @@
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::visitors::for_each_expr;
 use clippy_utils::{method_chain_args, return_ty};
+use core::ops::ControlFlow;
 use if_chain::if_chain;
 use rustc_hir as hir;
-use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::{Expr, ImplItemKind};
+use rustc_hir::ImplItemKind;
 use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty;
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::{sym, Span};
 
@@ -73,51 +73,37 @@ impl<'tcx> LateLintPass<'tcx> for UnwrapInResult {
     }
 }
 
-struct FindExpectUnwrap<'a, 'tcx> {
-    lcx: &'a LateContext<'tcx>,
-    typeck_results: &'tcx ty::TypeckResults<'tcx>,
-    result: Vec<Span>,
-}
-
-impl<'a, 'tcx> Visitor<'tcx> for FindExpectUnwrap<'a, 'tcx> {
-    fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
-        // check for `expect`
-        if let Some(arglists) = method_chain_args(expr, &["expect"]) {
-            let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
-            if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
-                || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
-            {
-                self.result.push(expr.span);
+fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) {
+    if let ImplItemKind::Fn(_, body_id) = impl_item.kind {
+        let body = cx.tcx.hir().body(body_id);
+        let typeck = cx.tcx.typeck(impl_item.def_id.def_id);
+        let mut result = Vec::new();
+        let _: Option<!> = for_each_expr(body.value, |e| {
+            // check for `expect`
+            if let Some(arglists) = method_chain_args(e, &["expect"]) {
+                let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs();
+                if is_type_diagnostic_item(cx, receiver_ty, sym::Option)
+                    || is_type_diagnostic_item(cx, receiver_ty, sym::Result)
+                {
+                    result.push(e.span);
+                }
             }
-        }
 
-        // check for `unwrap`
-        if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
-            let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs();
-            if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
-                || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
-            {
-                self.result.push(expr.span);
+            // check for `unwrap`
+            if let Some(arglists) = method_chain_args(e, &["unwrap"]) {
+                let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs();
+                if is_type_diagnostic_item(cx, receiver_ty, sym::Option)
+                    || is_type_diagnostic_item(cx, receiver_ty, sym::Result)
+                {
+                    result.push(e.span);
+                }
             }
-        }
 
-        // and check sub-expressions
-        intravisit::walk_expr(self, expr);
-    }
-}
-
-fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) {
-    if let ImplItemKind::Fn(_, body_id) = impl_item.kind {
-        let body = cx.tcx.hir().body(body_id);
-        let mut fpu = FindExpectUnwrap {
-            lcx: cx,
-            typeck_results: cx.tcx.typeck(impl_item.def_id.def_id),
-            result: Vec::new(),
-        };
-        fpu.visit_expr(body.value);
+            ControlFlow::Continue(())
+        });
 
         // if we've found one, lint
-        if !fpu.result.is_empty() {
+        if !result.is_empty() {
             span_lint_and_then(
                 cx,
                 UNWRAP_IN_RESULT,
@@ -125,7 +111,7 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tc
                 "used unwrap or expect in a function that returns result or option",
                 move |diag| {
                     diag.help("unwrap and expect should not be used in a function that returns result or option");
-                    diag.span_note(fpu.result, "potential non-recoverable error(s)");
+                    diag.span_note(result, "potential non-recoverable error(s)");
                 },
             );
         }
diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs
index 8f67fa109fc..3caa6380e09 100644
--- a/clippy_utils/src/lib.rs
+++ b/clippy_utils/src/lib.rs
@@ -3,6 +3,7 @@
 #![feature(control_flow_enum)]
 #![feature(let_chains)]
 #![feature(lint_reasons)]
+#![feature(never_type)]
 #![feature(once_cell)]
 #![feature(rustc_private)]
 #![recursion_limit = "512"]
@@ -65,6 +66,7 @@ pub use self::hir_utils::{
     both, count_eq, eq_expr_value, hash_expr, hash_stmt, over, HirEqInterExpr, SpanlessEq, SpanlessHash,
 };
 
+use core::ops::ControlFlow;
 use std::collections::hash_map::Entry;
 use std::hash::BuildHasherDefault;
 use std::sync::OnceLock;
@@ -113,7 +115,7 @@ use rustc_target::abi::Integer;
 
 use crate::consts::{constant, Constant};
 use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param};
-use crate::visitors::expr_visitor_no_bodies;
+use crate::visitors::for_each_expr;
 
 pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
     if let Ok(version) = RustcVersion::parse(msrv) {
@@ -1193,17 +1195,14 @@ pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool {
 
 /// Returns `true` if `expr` contains a return expression
 pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
-    let mut found = false;
-    expr_visitor_no_bodies(|expr| {
-        if !found {
-            if let hir::ExprKind::Ret(..) = &expr.kind {
-                found = true;
-            }
+    for_each_expr(expr, |e| {
+        if matches!(e.kind, hir::ExprKind::Ret(..)) {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(())
         }
-        !found
     })
-    .visit_expr(expr);
-    found
+    .is_some()
 }
 
 /// Extends the span to the beginning of the spans line, incl. whitespaces.
diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs
index 079c8f50f12..dda21b90390 100644
--- a/clippy_utils/src/macros.rs
+++ b/clippy_utils/src/macros.rs
@@ -2,7 +2,7 @@
 
 use crate::is_path_diagnostic_item;
 use crate::source::snippet_opt;
-use crate::visitors::expr_visitor_no_bodies;
+use crate::visitors::{for_each_expr, Descend};
 
 use arrayvec::ArrayVec;
 use itertools::{izip, Either, Itertools};
@@ -270,20 +270,19 @@ fn find_assert_args_inner<'a, const N: usize>(
     };
     let mut args = ArrayVec::new();
     let mut panic_expn = None;
-    expr_visitor_no_bodies(|e| {
+    let _: Option<!> = for_each_expr(expr, |e| {
         if args.is_full() {
             if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
                 panic_expn = PanicExpn::parse(cx, e);
             }
-            panic_expn.is_none()
+            ControlFlow::Continue(Descend::from(panic_expn.is_none()))
         } else if is_assert_arg(cx, e, expn) {
             args.push(e);
-            false
+            ControlFlow::Continue(Descend::No)
         } else {
-            true
+            ControlFlow::Continue(Descend::Yes)
         }
-    })
-    .visit_expr(expr);
+    });
     let args = args.into_inner().ok()?;
     // if no `panic!(..)` is found, use `PanicExpn::Empty`
     // to indicate that the default assertion message is used
@@ -297,22 +296,19 @@ fn find_assert_within_debug_assert<'a>(
     expn: ExpnId,
     assert_name: Symbol,
 ) -> Option<(&'a Expr<'a>, ExpnId)> {
-    let mut found = None;
-    expr_visitor_no_bodies(|e| {
-        if found.is_some() || !e.span.from_expansion() {
-            return false;
+    for_each_expr(expr, |e| {
+        if !e.span.from_expansion() {
+            return ControlFlow::Continue(Descend::No);
         }
         let e_expn = e.span.ctxt().outer_expn();
         if e_expn == expn {
-            return true;
-        }
-        if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
-            found = Some((e, e_expn));
+            ControlFlow::Continue(Descend::Yes)
+        } else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
+            ControlFlow::Break((e, e_expn))
+        } else {
+            ControlFlow::Continue(Descend::No)
         }
-        false
     })
-    .visit_expr(expr);
-    found
 }
 
 fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
@@ -396,16 +392,14 @@ impl FormatString {
         });
 
         let mut parts = Vec::new();
-        expr_visitor_no_bodies(|expr| {
-            if let ExprKind::Lit(lit) = &expr.kind {
-                if let LitKind::Str(symbol, _) = lit.node {
-                    parts.push(symbol);
-                }
+        let _: Option<!> = for_each_expr(pieces, |expr| {
+            if let ExprKind::Lit(lit) = &expr.kind
+                && let LitKind::Str(symbol, _) = lit.node
+            {
+                parts.push(symbol);
             }
-
-            true
-        })
-        .visit_expr(pieces);
+            ControlFlow::Continue(())
+        });
 
         Some(Self {
             span,
@@ -431,7 +425,7 @@ impl<'tcx> FormatArgsValues<'tcx> {
     fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
         let mut pos_to_value_index = Vec::new();
         let mut value_args = Vec::new();
-        expr_visitor_no_bodies(|expr| {
+        let _: Option<!> = for_each_expr(args, |expr| {
             if expr.span.ctxt() == args.span.ctxt() {
                 // ArgumentV1::new_<format_trait>(<val>)
                 // ArgumentV1::from_usize(<val>)
@@ -453,16 +447,13 @@ impl<'tcx> FormatArgsValues<'tcx> {
 
                     pos_to_value_index.push(val_idx);
                 }
-
-                true
+                ControlFlow::Continue(Descend::Yes)
             } else {
                 // assume that any expr with a differing span is a value
                 value_args.push(expr);
-
-                false
+                ControlFlow::Continue(Descend::No)
             }
-        })
-        .visit_expr(args);
+        });
 
         Self {
             value_args,
@@ -866,22 +857,20 @@ impl<'tcx> FormatArgsExpn<'tcx> {
     }
 
     pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
-        let mut format_args = None;
-        expr_visitor_no_bodies(|e| {
-            if format_args.is_some() {
-                return false;
-            }
+        for_each_expr(expr, |e| {
             let e_ctxt = e.span.ctxt();
             if e_ctxt == expr.span.ctxt() {
-                return true;
-            }
-            if e_ctxt.outer_expn().is_descendant_of(expn_id) {
-                format_args = FormatArgsExpn::parse(cx, e);
+                ControlFlow::Continue(Descend::Yes)
+            } else if e_ctxt.outer_expn().is_descendant_of(expn_id) {
+                if let Some(args) = FormatArgsExpn::parse(cx, e) {
+                    ControlFlow::Break(args)
+                } else {
+                    ControlFlow::Continue(Descend::No)
+                }
+            } else {
+                ControlFlow::Continue(Descend::No)
             }
-            false
         })
-        .visit_expr(expr);
-        format_args
     }
 
     /// Source callsite span of all inputs
diff --git a/clippy_utils/src/ptr.rs b/clippy_utils/src/ptr.rs
index 0226f74906b..88837d8a143 100644
--- a/clippy_utils/src/ptr.rs
+++ b/clippy_utils/src/ptr.rs
@@ -1,7 +1,7 @@
 use crate::source::snippet;
-use crate::visitors::expr_visitor_no_bodies;
+use crate::visitors::{for_each_expr, Descend};
 use crate::{path_to_local_id, strip_pat_refs};
-use rustc_hir::intravisit::Visitor;
+use core::ops::ControlFlow;
 use rustc_hir::{Body, BodyId, ExprKind, HirId, PatKind};
 use rustc_lint::LateContext;
 use rustc_span::Span;
@@ -30,28 +30,23 @@ fn extract_clone_suggestions<'tcx>(
     replace: &[(&'static str, &'static str)],
     body: &'tcx Body<'_>,
 ) -> Option<Vec<(Span, Cow<'static, str>)>> {
-    let mut abort = false;
     let mut spans = Vec::new();
-    expr_visitor_no_bodies(|expr| {
-        if abort {
-            return false;
-        }
-        if let ExprKind::MethodCall(seg, recv, [], _) = expr.kind {
-            if path_to_local_id(recv, id) {
-                if seg.ident.name.as_str() == "capacity" {
-                    abort = true;
-                    return false;
-                }
-                for &(fn_name, suffix) in replace {
-                    if seg.ident.name.as_str() == fn_name {
-                        spans.push((expr.span, snippet(cx, recv.span, "_") + suffix));
-                        return false;
-                    }
+    for_each_expr(body, |e| {
+        if let ExprKind::MethodCall(seg, recv, [], _) = e.kind
+            && path_to_local_id(recv, id)
+        {
+            if seg.ident.as_str() == "capacity" {
+                return ControlFlow::Break(());
+            }
+            for &(fn_name, suffix) in replace {
+                if seg.ident.as_str() == fn_name {
+                    spans.push((e.span, snippet(cx, recv.span, "_") + suffix));
+                    return ControlFlow::Continue(Descend::No);
                 }
             }
         }
-        !abort
+        ControlFlow::Continue(Descend::Yes)
     })
-    .visit_body(body);
-    if abort { None } else { Some(spans) }
+    .is_none()
+    .then_some(spans)
 }
diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs
index 3221b82aed4..b5ec3fef3e0 100644
--- a/clippy_utils/src/usage.rs
+++ b/clippy_utils/src/usage.rs
@@ -1,5 +1,6 @@
 use crate as utils;
-use crate::visitors::{expr_visitor, expr_visitor_no_bodies};
+use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend};
+use core::ops::ControlFlow;
 use rustc_hir as hir;
 use rustc_hir::intravisit::{self, Visitor};
 use rustc_hir::HirIdSet;
@@ -148,28 +149,17 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
 }
 
 pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
-    let mut seen_return_break_continue = false;
-    expr_visitor_no_bodies(|ex| {
-        if seen_return_break_continue {
-            return false;
-        }
-        match &ex.kind {
-            ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
-                seen_return_break_continue = true;
-            },
+    for_each_expr(expression, |e| {
+        match e.kind {
+            ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => ControlFlow::Break(()),
             // Something special could be done here to handle while or for loop
             // desugaring, as this will detect a break if there's a while loop
             // or a for loop inside the expression.
-            _ => {
-                if ex.span.from_expansion() {
-                    seen_return_break_continue = true;
-                }
-            },
+            _ if e.span.from_expansion() => ControlFlow::Break(()),
+            _ => ControlFlow::Continue(()),
         }
-        !seen_return_break_continue
     })
-    .visit_expr(expression);
-    seen_return_break_continue
+    .is_some()
 }
 
 pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
@@ -200,23 +190,16 @@ pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr
         return true;
     }
 
-    let mut used_after_expr = false;
     let mut past_expr = false;
-    expr_visitor(cx, |expr| {
-        if used_after_expr {
-            return false;
-        }
-
-        if expr.hir_id == after.hir_id {
+    for_each_expr_with_closures(cx, block, |e| {
+        if e.hir_id == after.hir_id {
             past_expr = true;
-            return false;
-        }
-
-        if past_expr && utils::path_to_local_id(expr, local_id) {
-            used_after_expr = true;
+            ControlFlow::Continue(Descend::No)
+        } else if past_expr && utils::path_to_local_id(e, local_id) {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(Descend::Yes)
         }
-        !used_after_expr
     })
-    .visit_block(block);
-    used_after_expr
+    .is_some()
 }
diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs
index 232d571902b..d4294f18fd5 100644
--- a/clippy_utils/src/visitors.rs
+++ b/clippy_utils/src/visitors.rs
@@ -5,14 +5,13 @@ use rustc_hir as hir;
 use rustc_hir::def::{CtorKind, DefKind, Res};
 use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
 use rustc_hir::{
-    Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, Pat, QPath, Stmt, UnOp,
-    UnsafeSource, Unsafety,
+    AnonConst, Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, Pat, QPath,
+    Stmt, UnOp, UnsafeSource, Unsafety,
 };
 use rustc_lint::LateContext;
-use rustc_middle::hir::map::Map;
 use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::adjustment::Adjust;
-use rustc_middle::ty::{self, Ty, TypeckResults};
+use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults};
 use rustc_span::Span;
 
 mod internal {
@@ -48,6 +47,26 @@ impl Continue for Descend {
     }
 }
 
+/// A type which can be visited.
+pub trait Visitable<'tcx> {
+    /// Calls the corresponding `visit_*` function on the visitor.
+    fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
+}
+macro_rules! visitable_ref {
+    ($t:ident, $f:ident) => {
+        impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
+            fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
+                visitor.$f(self);
+            }
+        }
+    };
+}
+visitable_ref!(Arm, visit_arm);
+visitable_ref!(Block, visit_block);
+visitable_ref!(Body, visit_body);
+visitable_ref!(Expr, visit_expr);
+visitable_ref!(Stmt, visit_stmt);
+
 /// Calls the given function once for each expression contained. This does not enter any bodies or
 /// nested items.
 pub fn for_each_expr<'tcx, B, C: Continue>(
@@ -82,57 +101,63 @@ pub fn for_each_expr<'tcx, B, C: Continue>(
     v.res
 }
 
-/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
-/// bodies (i.e. closures) are visited.
-/// If the callback returns `true`, the expr just provided to the callback is walked.
-#[must_use]
-pub fn expr_visitor<'tcx>(cx: &LateContext<'tcx>, f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
-    struct V<'tcx, F> {
-        hir: Map<'tcx>,
+/// Calls the given function once for each expression contained. This will enter bodies, but not
+/// nested items.
+pub fn for_each_expr_with_closures<'tcx, B, C: Continue>(
+    cx: &LateContext<'tcx>,
+    node: impl Visitable<'tcx>,
+    f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>,
+) -> Option<B> {
+    struct V<'tcx, B, F> {
+        tcx: TyCtxt<'tcx>,
         f: F,
+        res: Option<B>,
     }
-    impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> {
+    impl<'tcx, B, C: Continue, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>> Visitor<'tcx> for V<'tcx, B, F> {
         type NestedFilter = nested_filter::OnlyBodies;
         fn nested_visit_map(&mut self) -> Self::Map {
-            self.hir
+            self.tcx.hir()
         }
 
-        fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
-            if (self.f)(expr) {
-                walk_expr(self, expr);
+        fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
+            if self.res.is_some() {
+                return;
             }
-        }
-    }
-    V { hir: cx.tcx.hir(), f }
-}
-
-/// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
-/// bodies (i.e. closures) are not visited.
-/// If the callback returns `true`, the expr just provided to the callback is walked.
-#[must_use]
-pub fn expr_visitor_no_bodies<'tcx>(f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
-    struct V<F>(F);
-    impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<F> {
-        fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
-            if (self.0)(e) {
-                walk_expr(self, e);
+            match (self.f)(e) {
+                ControlFlow::Continue(c) if c.descend() => walk_expr(self, e),
+                ControlFlow::Break(b) => self.res = Some(b),
+                ControlFlow::Continue(_) => (),
             }
         }
+
+        // Only walk closures
+        fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
+        // Avoid unnecessary `walk_*` calls.
+        fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx>) {}
+        fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {}
+        fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {}
+        // Avoid monomorphising all `visit_*` functions.
+        fn visit_nested_item(&mut self, _: ItemId) {}
     }
-    V(f)
+    let mut v = V {
+        tcx: cx.tcx,
+        f,
+        res: None,
+    };
+    node.visit(&mut v);
+    v.res
 }
 
 /// returns `true` if expr contains match expr desugared from try
 fn contains_try(expr: &hir::Expr<'_>) -> bool {
-    let mut found = false;
-    expr_visitor_no_bodies(|e| {
-        if !found {
-            found = matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar));
+    for_each_expr(expr, |e| {
+        if matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar)) {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(())
         }
-        !found
     })
-    .visit_expr(expr);
-    found
+    .is_some()
 }
 
 pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
@@ -228,68 +253,29 @@ where
     }
 }
 
-/// A type which can be visited.
-pub trait Visitable<'tcx> {
-    /// Calls the corresponding `visit_*` function on the visitor.
-    fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
-}
-macro_rules! visitable_ref {
-    ($t:ident, $f:ident) => {
-        impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
-            fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
-                visitor.$f(self);
-            }
-        }
-    };
-}
-visitable_ref!(Arm, visit_arm);
-visitable_ref!(Block, visit_block);
-visitable_ref!(Body, visit_body);
-visitable_ref!(Expr, visit_expr);
-visitable_ref!(Stmt, visit_stmt);
-
-// impl<'tcx, I: IntoIterator> Visitable<'tcx> for I
-// where
-//     I::Item: Visitable<'tcx>,
-// {
-//     fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
-//         for x in self {
-//             x.visit(visitor);
-//         }
-//     }
-// }
-
 /// Checks if the given resolved path is used in the given body.
 pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
-    let mut found = false;
-    expr_visitor(cx, |e| {
-        if found {
-            return false;
-        }
-
+    for_each_expr_with_closures(cx, cx.tcx.hir().body(body).value, |e| {
         if let ExprKind::Path(p) = &e.kind {
             if cx.qpath_res(p, e.hir_id) == res {
-                found = true;
+                return ControlFlow::Break(());
             }
         }
-        !found
+        ControlFlow::Continue(())
     })
-    .visit_expr(cx.tcx.hir().body(body).value);
-    found
+    .is_some()
 }
 
 /// Checks if the given local is used.
 pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
-    let mut is_used = false;
-    let mut visitor = expr_visitor(cx, |expr| {
-        if !is_used {
-            is_used = path_to_local_id(expr, id);
+    for_each_expr_with_closures(cx, visitable, |e| {
+        if path_to_local_id(e, id) {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(())
         }
-        !is_used
-    });
-    visitable.visit(&mut visitor);
-    drop(visitor);
-    is_used
+    })
+    .is_some()
 }
 
 /// Checks if the given expression is a constant.