Skip to main content

clippy_utils/
visitors.rs

1use crate::get_enclosing_block;
2use crate::msrvs::Msrv;
3use crate::qualify_min_const_fn::is_stable_const_fn_at;
4use crate::res::MaybeResPath;
5use crate::ty::needs_ordered_drop;
6use core::ops::ControlFlow;
7use rustc_ast::visit::{VisitorResult, try_visit};
8use rustc_hir::def::{CtorKind, DefKind, Res};
9use rustc_hir::intravisit::{self, Visitor, walk_block, walk_expr};
10use rustc_hir::{
11    self as hir, AmbigArg, AnonConst, Arm, Block, BlockCheckMode, Body, BodyId, CRATE_HIR_ID, Expr, ExprKind, HirId,
12    ItemId, ItemKind, LetExpr, Pat, QPath, Stmt, StructTailExpr, UnOp, UnsafeSource,
13};
14use rustc_lint::LateContext;
15use rustc_middle::hir::nested_filter;
16use rustc_middle::ty::adjustment::Adjust;
17use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults};
18use rustc_span::Span;
19
20mod internal {
21    /// Trait for visitor functions to control whether or not to descend to child nodes. Implemented
22    /// for only two types. `()` always descends. `Descend` allows controlled descent.
23    pub trait Continue {
24        fn descend(&self) -> bool;
25    }
26}
27use internal::Continue;
28
29impl Continue for () {
30    fn descend(&self) -> bool {
31        true
32    }
33}
34
35/// Allows for controlled descent when using visitor functions. Use `()` instead when always
36/// descending into child nodes.
37#[derive(Clone, Copy)]
38pub enum Descend {
39    Yes,
40    No,
41}
42impl From<bool> for Descend {
43    fn from(from: bool) -> Self {
44        if from { Self::Yes } else { Self::No }
45    }
46}
47impl Continue for Descend {
48    fn descend(&self) -> bool {
49        matches!(self, Self::Yes)
50    }
51}
52
53/// A type which can be visited.
54pub trait Visitable<'tcx> {
55    /// Calls the corresponding `visit_*` function on the visitor.
56    fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) -> V::Result;
57}
58impl<'tcx, T> Visitable<'tcx> for &'tcx [T]
59where
60    &'tcx T: Visitable<'tcx>,
61{
62    fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) -> V::Result {
63        for x in self {
64            try_visit!(x.visit(visitor));
65        }
66        V::Result::output()
67    }
68}
69impl<'tcx, A, B> Visitable<'tcx> for (A, B)
70where
71    A: Visitable<'tcx>,
72    B: Visitable<'tcx>,
73{
74    fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) -> V::Result {
75        let (a, b) = self;
76        try_visit!(a.visit(visitor));
77        b.visit(visitor)
78    }
79}
80impl<'tcx, T> Visitable<'tcx> for Option<T>
81where
82    T: Visitable<'tcx>,
83{
84    fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) -> V::Result {
85        if let Some(x) = self {
86            try_visit!(x.visit(visitor));
87        }
88        V::Result::output()
89    }
90}
91macro_rules! visitable_ref {
92    ($t:ident, $f:ident) => {
93        impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
94            fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) -> V::Result {
95                visitor.$f(self)
96            }
97        }
98    };
99}
100visitable_ref!(Arm, visit_arm);
101visitable_ref!(Block, visit_block);
102visitable_ref!(Body, visit_body);
103visitable_ref!(Expr, visit_expr);
104visitable_ref!(Stmt, visit_stmt);
105
106/// Calls the given function once for each expression contained. This does not enter any bodies or
107/// nested items.
108pub fn for_each_expr_without_closures<'tcx, B, C: Continue>(
109    node: impl Visitable<'tcx>,
110    f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>,
111) -> Option<B> {
112    struct V<F> {
113        f: F,
114    }
115    impl<'tcx, B, C: Continue, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>> Visitor<'tcx> for V<F> {
116        type Result = ControlFlow<B>;
117
118        fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) -> Self::Result {
119            match (self.f)(e) {
120                ControlFlow::Continue(c) if c.descend() => walk_expr(self, e),
121                ControlFlow::Break(b) => ControlFlow::Break(b),
122                ControlFlow::Continue(_) => ControlFlow::Continue(()),
123            }
124        }
125
126        // Avoid unnecessary `walk_*` calls.
127        fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx, AmbigArg>) -> Self::Result {
128            ControlFlow::Continue(())
129        }
130        fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) -> Self::Result {
131            ControlFlow::Continue(())
132        }
133        fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) -> Self::Result {
134            ControlFlow::Continue(())
135        }
136        // Avoid monomorphising all `visit_*` functions.
137        fn visit_nested_item(&mut self, _: ItemId) -> Self::Result {
138            ControlFlow::Continue(())
139        }
140    }
141    let mut v = V { f };
142    node.visit(&mut v).break_value()
143}
144
145/// Calls the given function once for each expression contained. This will enter bodies, but not
146/// nested items.
147pub fn for_each_expr<'tcx, B, C: Continue>(
148    tcx: TyCtxt<'tcx>,
149    node: impl Visitable<'tcx>,
150    f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>,
151) -> Option<B> {
152    struct V<'tcx, F> {
153        tcx: TyCtxt<'tcx>,
154        f: F,
155    }
156    impl<'tcx, B, C: Continue, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B, C>> Visitor<'tcx> for V<'tcx, F> {
157        type NestedFilter = nested_filter::OnlyBodies;
158        type Result = ControlFlow<B>;
159
160        fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
161            self.tcx
162        }
163
164        fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) -> Self::Result {
165            match (self.f)(e) {
166                ControlFlow::Continue(c) if c.descend() => walk_expr(self, e),
167                ControlFlow::Break(b) => ControlFlow::Break(b),
168                ControlFlow::Continue(_) => ControlFlow::Continue(()),
169            }
170        }
171
172        // Only walk closures
173        fn visit_anon_const(&mut self, _: &'tcx AnonConst) -> Self::Result {
174            ControlFlow::Continue(())
175        }
176        // Avoid unnecessary `walk_*` calls.
177        fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx, AmbigArg>) -> Self::Result {
178            ControlFlow::Continue(())
179        }
180        fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) -> Self::Result {
181            ControlFlow::Continue(())
182        }
183        fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) -> Self::Result {
184            ControlFlow::Continue(())
185        }
186        // Avoid monomorphising all `visit_*` functions.
187        fn visit_nested_item(&mut self, _: ItemId) -> Self::Result {
188            ControlFlow::Continue(())
189        }
190    }
191    let mut v = V { tcx, f };
192    node.visit(&mut v).break_value()
193}
194
195/// returns `true` if expr contains match expr desugared from try
196fn contains_try(expr: &Expr<'_>) -> bool {
197    for_each_expr_without_closures(expr, |e| {
198        if matches!(e.kind, ExprKind::Match(_, _, hir::MatchSource::TryDesugar(_))) {
199            ControlFlow::Break(())
200        } else {
201            ControlFlow::Continue(())
202        }
203    })
204    .is_some()
205}
206
207pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir Expr<'hir>, callback: F) -> bool
208where
209    F: FnMut(&'hir Expr<'hir>) -> bool,
210{
211    struct RetFinder<F> {
212        in_stmt: bool,
213        failed: bool,
214        cb: F,
215    }
216
217    struct WithStmtGuard<'a, F> {
218        val: &'a mut RetFinder<F>,
219        prev_in_stmt: bool,
220    }
221
222    impl<F> RetFinder<F> {
223        fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuard<'_, F> {
224            let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
225            WithStmtGuard {
226                val: self,
227                prev_in_stmt,
228            }
229        }
230    }
231
232    impl<F> std::ops::Deref for WithStmtGuard<'_, F> {
233        type Target = RetFinder<F>;
234
235        fn deref(&self) -> &Self::Target {
236            self.val
237        }
238    }
239
240    impl<F> std::ops::DerefMut for WithStmtGuard<'_, F> {
241        fn deref_mut(&mut self) -> &mut Self::Target {
242            self.val
243        }
244    }
245
246    impl<F> Drop for WithStmtGuard<'_, F> {
247        fn drop(&mut self) {
248            self.val.in_stmt = self.prev_in_stmt;
249        }
250    }
251
252    impl<'hir, F: FnMut(&'hir Expr<'hir>) -> bool> Visitor<'hir> for RetFinder<F> {
253        fn visit_stmt(&mut self, stmt: &'hir Stmt<'_>) {
254            intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt);
255        }
256
257        fn visit_expr(&mut self, expr: &'hir Expr<'_>) {
258            if self.failed {
259                return;
260            }
261            if self.in_stmt {
262                match expr.kind {
263                    ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
264                    _ => walk_expr(self, expr),
265                }
266            } else {
267                match expr.kind {
268                    ExprKind::If(cond, then, else_opt) => {
269                        self.inside_stmt(true).visit_expr(cond);
270                        self.visit_expr(then);
271                        if let Some(el) = else_opt {
272                            self.visit_expr(el);
273                        }
274                    },
275                    ExprKind::Match(cond, arms, _) => {
276                        self.inside_stmt(true).visit_expr(cond);
277                        for arm in arms {
278                            self.visit_expr(arm.body);
279                        }
280                    },
281                    ExprKind::Block(..) => walk_expr(self, expr),
282                    ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
283                    _ => self.failed |= !(self.cb)(expr),
284                }
285            }
286        }
287    }
288
289    !contains_try(expr) && {
290        let mut ret_finder = RetFinder {
291            in_stmt: false,
292            failed: false,
293            cb: callback,
294        };
295        ret_finder.visit_expr(expr);
296        !ret_finder.failed
297    }
298}
299
300/// Checks if the given resolved path is used in the given body.
301pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
302    for_each_expr(cx.tcx, cx.tcx.hir_body(body).value, |e| {
303        if let ExprKind::Path(p) = &e.kind
304            && cx.qpath_res(p, e.hir_id) == res
305        {
306            return ControlFlow::Break(());
307        }
308        ControlFlow::Continue(())
309    })
310    .is_some()
311}
312
313/// Checks if the given local is used.
314pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
315    for_each_expr(cx.tcx, visitable, |e| {
316        if e.res_local_id() == Some(id) {
317            ControlFlow::Break(())
318        } else {
319            ControlFlow::Continue(())
320        }
321    })
322    .is_some()
323}
324
325/// Checks if the given expression can be evaluated as a constant at the specified node
326pub fn is_const_evaluatable<'tcx>(tcx: TyCtxt<'tcx>, typeck: &'tcx TypeckResults<'tcx>, e: &'tcx Expr<'_>) -> bool {
327    for_each_expr(tcx, e, move |e| {
328        match e.kind {
329            ExprKind::ConstBlock(_) => return ControlFlow::Continue(Descend::No),
330            ExprKind::Call(
331                &Expr {
332                    kind: ExprKind::Path(ref p),
333                    hir_id,
334                    ..
335                },
336                _,
337            ) if typeck
338                .qpath_res(p, hir_id)
339                .opt_def_id()
340                .is_some_and(|id| is_stable_const_fn_at(tcx, CRATE_HIR_ID, id, Msrv::default())) => {},
341            ExprKind::MethodCall(..)
342                if typeck
343                    .type_dependent_def_id(e.hir_id)
344                    .is_some_and(|id| is_stable_const_fn_at(tcx, CRATE_HIR_ID, id, Msrv::default())) => {},
345            ExprKind::Binary(_, lhs, rhs)
346                if typeck.expr_ty(lhs).peel_refs().is_primitive_ty()
347                    && typeck.expr_ty(rhs).peel_refs().is_primitive_ty() => {},
348            ExprKind::Unary(UnOp::Deref, e) if typeck.expr_ty(e).is_raw_ptr() => (),
349            ExprKind::Unary(_, e) if typeck.expr_ty(e).peel_refs().is_primitive_ty() => (),
350            ExprKind::Index(base, _, _)
351                if matches!(typeck.expr_ty(base).peel_refs().kind(), ty::Slice(_) | ty::Array(..)) => {},
352            ExprKind::Path(ref p)
353                if matches!(
354                    typeck.qpath_res(p, e.hir_id),
355                    Res::Def(
356                        DefKind::Const { .. }
357                            | DefKind::AssocConst { .. }
358                            | DefKind::AnonConst
359                            | DefKind::ConstParam
360                            | DefKind::Ctor(..)
361                            | DefKind::Fn
362                            | DefKind::AssocFn,
363                        _
364                    ) | Res::SelfCtor(_)
365                ) => {},
366
367            ExprKind::AddrOf(..)
368            | ExprKind::Array(_)
369            | ExprKind::Block(..)
370            | ExprKind::Cast(..)
371            | ExprKind::DropTemps(_)
372            | ExprKind::Field(..)
373            | ExprKind::If(..)
374            | ExprKind::Let(..)
375            | ExprKind::Lit(_)
376            | ExprKind::Match(..)
377            | ExprKind::Repeat(..)
378            | ExprKind::Struct(..)
379            | ExprKind::Tup(_)
380            | ExprKind::Type(..)
381            | ExprKind::UnsafeBinderCast(..) => {},
382
383            _ => return ControlFlow::Break(()),
384        }
385        ControlFlow::Continue(Descend::Yes)
386    })
387    .is_none()
388}
389
390/// Checks if the given expression performs an unsafe operation outside of an unsafe block.
391pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
392    struct V<'a, 'tcx> {
393        cx: &'a LateContext<'tcx>,
394    }
395    impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
396        type NestedFilter = nested_filter::OnlyBodies;
397        type Result = ControlFlow<()>;
398
399        fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
400            self.cx.tcx
401        }
402        fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result {
403            match e.kind {
404                ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_raw_ptr() => {
405                    ControlFlow::Break(())
406                },
407                ExprKind::MethodCall(..)
408                    if self
409                        .cx
410                        .typeck_results()
411                        .type_dependent_def_id(e.hir_id)
412                        .is_some_and(|id| self.cx.tcx.fn_sig(id).skip_binder().safety().is_unsafe()) =>
413                {
414                    ControlFlow::Break(())
415                },
416                ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() {
417                    ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).skip_binder().safety().is_unsafe() => {
418                        ControlFlow::Break(())
419                    },
420                    ty::FnPtr(_, hdr) if hdr.safety().is_unsafe() => ControlFlow::Break(()),
421                    _ => walk_expr(self, e),
422                },
423                ExprKind::Path(ref p)
424                    if self
425                        .cx
426                        .qpath_res(p, e.hir_id)
427                        .opt_def_id()
428                        .is_some_and(|id| self.cx.tcx.is_mutable_static(id)) =>
429                {
430                    ControlFlow::Break(())
431                },
432                _ => walk_expr(self, e),
433            }
434        }
435        fn visit_block(&mut self, b: &'tcx Block<'_>) -> Self::Result {
436            if matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) {
437                ControlFlow::Continue(())
438            } else {
439                walk_block(self, b)
440            }
441        }
442        fn visit_nested_item(&mut self, id: ItemId) -> Self::Result {
443            if let ItemKind::Impl(i) = &self.cx.tcx.hir_item(id).kind
444                && let Some(of_trait) = i.of_trait
445                && of_trait.safety.is_unsafe()
446            {
447                ControlFlow::Break(())
448            } else {
449                ControlFlow::Continue(())
450            }
451        }
452    }
453    let mut v = V { cx };
454    v.visit_expr(e).is_break()
455}
456
457/// Checks if the given expression contains an unsafe block
458pub fn contains_unsafe_block<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
459    struct V<'cx, 'tcx> {
460        cx: &'cx LateContext<'tcx>,
461    }
462    impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
463        type Result = ControlFlow<()>;
464        type NestedFilter = nested_filter::OnlyBodies;
465        fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
466            self.cx.tcx
467        }
468
469        fn visit_block(&mut self, b: &'tcx Block<'_>) -> Self::Result {
470            if b.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
471                ControlFlow::Break(())
472            } else {
473                walk_block(self, b)
474            }
475        }
476    }
477    let mut v = V { cx };
478    v.visit_expr(e).is_break()
479}
480
481/// Runs the given function for each sub-expression producing the final value consumed by the parent
482/// of the give expression.
483///
484/// e.g. for the following expression
485/// ```rust,ignore
486/// if foo {
487///     f(0)
488/// } else {
489///     1 + 1
490/// }
491/// ```
492/// this will pass both `f(0)` and `1+1` to the given function.
493pub fn for_each_value_source<'tcx, B>(
494    e: &'tcx Expr<'tcx>,
495    f: &mut impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
496) -> ControlFlow<B> {
497    match e.kind {
498        ExprKind::Block(Block { expr: Some(e), .. }, _) => for_each_value_source(e, f),
499        ExprKind::Match(_, arms, _) => {
500            for arm in arms {
501                for_each_value_source(arm.body, f)?;
502            }
503            ControlFlow::Continue(())
504        },
505        ExprKind::If(_, if_expr, Some(else_expr)) => {
506            for_each_value_source(if_expr, f)?;
507            for_each_value_source(else_expr, f)
508        },
509        ExprKind::DropTemps(e) => for_each_value_source(e, f),
510        _ => f(e),
511    }
512}
513
514/// Runs the given function for each path expression referencing the given local which occur after
515/// the given expression.
516pub fn for_each_local_use_after_expr<'tcx, B>(
517    cx: &LateContext<'tcx>,
518    local_id: HirId,
519    expr_id: HirId,
520    f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
521) -> ControlFlow<B> {
522    struct V<'cx, 'tcx, F, B> {
523        cx: &'cx LateContext<'tcx>,
524        local_id: HirId,
525        expr_id: HirId,
526        found: bool,
527        res: ControlFlow<B>,
528        f: F,
529    }
530    impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, B> Visitor<'tcx> for V<'_, 'tcx, F, B> {
531        type NestedFilter = nested_filter::OnlyBodies;
532        fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
533            self.cx.tcx
534        }
535
536        fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
537            if !self.found {
538                if e.hir_id == self.expr_id {
539                    self.found = true;
540                } else {
541                    walk_expr(self, e);
542                }
543                return;
544            }
545            if self.res.is_break() {
546                return;
547            }
548            if e.res_local_id() == Some(self.local_id) {
549                self.res = (self.f)(e);
550            } else {
551                walk_expr(self, e);
552            }
553        }
554    }
555
556    if let Some(b) = get_enclosing_block(cx, local_id) {
557        let mut v = V {
558            cx,
559            local_id,
560            expr_id,
561            found: false,
562            res: ControlFlow::Continue(()),
563            f,
564        };
565        v.visit_block(b);
566        v.res
567    } else {
568        ControlFlow::Continue(())
569    }
570}
571
572// Calls the given function for every unconsumed temporary created by the expression. Note the
573// function is only guaranteed to be called for types which need to be dropped, but it may be called
574// for other types.
575#[expect(clippy::too_many_lines)]
576pub fn for_each_unconsumed_temporary<'tcx, B>(
577    cx: &LateContext<'tcx>,
578    e: &'tcx Expr<'tcx>,
579    mut f: impl FnMut(Ty<'tcx>) -> ControlFlow<B>,
580) -> ControlFlow<B> {
581    // Todo: Handle partially consumed values.
582    fn helper<'tcx, B>(
583        typeck: &'tcx TypeckResults<'tcx>,
584        consume: bool,
585        e: &'tcx Expr<'tcx>,
586        f: &mut impl FnMut(Ty<'tcx>) -> ControlFlow<B>,
587    ) -> ControlFlow<B> {
588        if !consume
589            || matches!(
590                typeck.expr_adjustments(e),
591                [adjust, ..] if matches!(adjust.kind, Adjust::Borrow(_) | Adjust::Deref(_))
592            )
593        {
594            match e.kind {
595                ExprKind::Path(QPath::Resolved(None, p))
596                    if matches!(p.res, Res::Def(DefKind::Ctor(_, CtorKind::Const), _)) =>
597                {
598                    f(typeck.expr_ty(e))?;
599                },
600                ExprKind::Path(_)
601                | ExprKind::Unary(UnOp::Deref, _)
602                | ExprKind::Index(..)
603                | ExprKind::Field(..)
604                | ExprKind::AddrOf(..) => (),
605                _ => f(typeck.expr_ty(e))?,
606            }
607        }
608        match e.kind {
609            ExprKind::AddrOf(_, _, e)
610            | ExprKind::Field(e, _)
611            | ExprKind::Unary(UnOp::Deref, e)
612            | ExprKind::Match(e, ..)
613            | ExprKind::Let(&LetExpr { init: e, .. }) => {
614                helper(typeck, false, e, f)?;
615            },
616            ExprKind::Block(&Block { expr: Some(e), .. }, _) | ExprKind::Cast(e, _) | ExprKind::Unary(_, e) => {
617                helper(typeck, true, e, f)?;
618            },
619            ExprKind::Call(callee, args) => {
620                helper(typeck, true, callee, f)?;
621                for arg in args {
622                    helper(typeck, true, arg, f)?;
623                }
624            },
625            ExprKind::MethodCall(_, receiver, args, _) => {
626                helper(typeck, true, receiver, f)?;
627                for arg in args {
628                    helper(typeck, true, arg, f)?;
629                }
630            },
631            ExprKind::Tup(args) | ExprKind::Array(args) => {
632                for arg in args {
633                    helper(typeck, true, arg, f)?;
634                }
635            },
636            ExprKind::Use(expr, _) => {
637                helper(typeck, true, expr, f)?;
638            },
639            ExprKind::Index(borrowed, consumed, _)
640            | ExprKind::Assign(borrowed, consumed, _)
641            | ExprKind::AssignOp(_, borrowed, consumed) => {
642                helper(typeck, false, borrowed, f)?;
643                helper(typeck, true, consumed, f)?;
644            },
645            ExprKind::Binary(_, lhs, rhs) => {
646                helper(typeck, true, lhs, f)?;
647                helper(typeck, true, rhs, f)?;
648            },
649            ExprKind::Struct(_, fields, default) => {
650                for field in fields {
651                    helper(typeck, true, field.expr, f)?;
652                }
653                if let StructTailExpr::Base(default) = default {
654                    helper(typeck, false, default, f)?;
655                }
656            },
657            ExprKind::If(cond, then, else_expr) => {
658                helper(typeck, true, cond, f)?;
659                helper(typeck, true, then, f)?;
660                if let Some(else_expr) = else_expr {
661                    helper(typeck, true, else_expr, f)?;
662                }
663            },
664            ExprKind::Type(e, _) | ExprKind::UnsafeBinderCast(_, e, _) => {
665                helper(typeck, consume, e, f)?;
666            },
667
668            // Either drops temporaries, jumps out of the current expression, or has no sub expression.
669            ExprKind::DropTemps(_)
670            | ExprKind::Ret(_)
671            | ExprKind::Become(_)
672            | ExprKind::Break(..)
673            | ExprKind::Yield(..)
674            | ExprKind::Block(..)
675            | ExprKind::Loop(..)
676            | ExprKind::Repeat(..)
677            | ExprKind::Lit(_)
678            | ExprKind::ConstBlock(_)
679            | ExprKind::Closure { .. }
680            | ExprKind::Path(_)
681            | ExprKind::Continue(_)
682            | ExprKind::InlineAsm(_)
683            | ExprKind::OffsetOf(..)
684            | ExprKind::Err(_) => (),
685        }
686        ControlFlow::Continue(())
687    }
688    helper(cx.typeck_results(), true, e, &mut f)
689}
690
691pub fn any_temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
692    for_each_unconsumed_temporary(cx, e, |ty| {
693        if needs_ordered_drop(cx, ty) {
694            ControlFlow::Break(())
695        } else {
696            ControlFlow::Continue(())
697        }
698    })
699    .is_break()
700}
701
702/// Runs the given function for each path expression referencing the given local which occur after
703/// the given expression.
704pub fn for_each_local_assignment<'tcx, B>(
705    cx: &LateContext<'tcx>,
706    local_id: HirId,
707    f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
708) -> ControlFlow<B> {
709    struct V<'cx, 'tcx, F, B> {
710        cx: &'cx LateContext<'tcx>,
711        local_id: HirId,
712        res: ControlFlow<B>,
713        f: F,
714    }
715    impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, B> Visitor<'tcx> for V<'_, 'tcx, F, B> {
716        type NestedFilter = nested_filter::OnlyBodies;
717        fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
718            self.cx.tcx
719        }
720
721        fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
722            if let ExprKind::Assign(lhs, rhs, _) = e.kind
723                && self.res.is_continue()
724                && lhs.res_local_id() == Some(self.local_id)
725            {
726                self.res = (self.f)(rhs);
727                self.visit_expr(rhs);
728            } else {
729                walk_expr(self, e);
730            }
731        }
732    }
733
734    if let Some(b) = get_enclosing_block(cx, local_id) {
735        let mut v = V {
736            cx,
737            local_id,
738            res: ControlFlow::Continue(()),
739            f,
740        };
741        v.visit_block(b);
742        v.res
743    } else {
744        ControlFlow::Continue(())
745    }
746}
747
748pub fn contains_break_or_continue(expr: &Expr<'_>) -> bool {
749    for_each_expr_without_closures(expr, |e| {
750        if matches!(e.kind, ExprKind::Break(..) | ExprKind::Continue(..)) {
751            ControlFlow::Break(())
752        } else {
753            ControlFlow::Continue(())
754        }
755    })
756    .is_some()
757}
758
759/// If the local is only used once in `visitable` returns the path expression referencing the given
760/// local
761pub fn local_used_once<'tcx>(
762    cx: &LateContext<'tcx>,
763    visitable: impl Visitable<'tcx>,
764    id: HirId,
765) -> Option<&'tcx Expr<'tcx>> {
766    let mut expr = None;
767
768    let cf = for_each_expr(cx.tcx, visitable, |e| {
769        if e.res_local_id() == Some(id) && expr.replace(e).is_some() {
770            ControlFlow::Break(())
771        } else {
772            ControlFlow::Continue(())
773        }
774    });
775    if cf.is_some() {
776        return None;
777    }
778
779    expr
780}