]> git.lizzy.rs Git - rust.git/blobdiff - clippy_utils/src/visitors.rs
Improve heuristics for determining whether eager of lazy evaluation is preferred
[rust.git] / clippy_utils / src / visitors.rs
index 503effbdad5725069eb4ddc268a08c80ae7b9014..823df5cb7517eaeb641da7100dce261df64544df 100644 (file)
@@ -1,38 +1,68 @@
 use crate::path_to_local_id;
 use rustc_hir as hir;
-use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor};
-use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt};
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor};
+use rustc_hir::{Arm, Block, Body, BodyId, Expr, ExprKind, HirId, Stmt, UnOp};
 use rustc_lint::LateContext;
 use rustc_middle::hir::map::Map;
-use std::ops::ControlFlow;
+use rustc_middle::ty;
 
-/// returns `true` if expr contains match expr desugared from try
-fn contains_try(expr: &hir::Expr<'_>) -> bool {
-    struct TryFinder {
-        found: bool,
+/// 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>,
+        f: F,
     }
+    impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> {
+        type Map = Map<'tcx>;
+        fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+            NestedVisitorMap::OnlyBodies(self.hir)
+        }
 
-    impl<'hir> intravisit::Visitor<'hir> for TryFinder {
-        type Map = Map<'hir>;
+        fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+            if (self.f)(expr) {
+                walk_expr(self, expr);
+            }
+        }
+    }
+    V { hir: cx.tcx.hir(), f }
+}
 
-        fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
-            intravisit::NestedVisitorMap::None
+/// 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> {
+        type Map = intravisit::ErasedMap<'tcx>;
+        fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
+            NestedVisitorMap::None
         }
 
-        fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
-            if self.found {
-                return;
-            }
-            match expr.kind {
-                hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true,
-                _ => intravisit::walk_expr(self, expr),
+        fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+            if (self.0)(e) {
+                walk_expr(self, e);
             }
         }
     }
+    V(f)
+}
 
-    let mut visitor = TryFinder { found: false };
-    visitor.visit_expr(expr);
-    visitor.found
+/// 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));
+        }
+        !found
+    })
+    .visit_expr(expr);
+    found
 }
 
 pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
@@ -165,103 +195,125 @@ fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
 //     }
 // }
 
-/// Calls the given function for each break expression.
-pub fn visit_break_exprs<'tcx>(
-    node: impl Visitable<'tcx>,
-    f: impl FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>),
-) {
-    struct V<F>(F);
-    impl<'tcx, F: FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>)> Visitor<'tcx> for V<F> {
-        type Map = ErasedMap<'tcx>;
-        fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
-            NestedVisitorMap::None
+/// 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;
         }
 
-        fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
-            if let ExprKind::Break(dest, sub_expr) = e.kind {
-                self.0(e, dest, sub_expr);
+        if let ExprKind::Path(p) = &e.kind {
+            if cx.qpath_res(p, e.hir_id) == res {
+                found = true;
             }
-            walk_expr(self, e);
         }
-    }
+        !found
+    })
+    .visit_expr(&cx.tcx.hir().body(body).value);
+    found
+}
 
-    node.visit(&mut V(f));
+/// Checks if the given local is used.
+pub fn is_local_used(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);
+        }
+        !is_used
+    });
+    visitable.visit(&mut visitor);
+    drop(visitor);
+    is_used
 }
 
-/// Checks if the given resolved path is used in the given body.
-pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
+/// Checks if the given expression is a constant.
+pub fn is_const_evaluatable(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
     struct V<'a, 'tcx> {
         cx: &'a LateContext<'tcx>,
-        res: Res,
-        found: bool,
+        is_const: bool,
     }
-    impl Visitor<'tcx> for V<'_, 'tcx> {
+    impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
         type Map = Map<'tcx>;
         fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
             NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
         }
 
         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
-            if self.found {
+            if !self.is_const {
                 return;
             }
+            match e.kind {
+                ExprKind::ConstBlock(_) => return,
+                ExprKind::Call(
+                    &Expr {
+                        kind: ExprKind::Path(ref p),
+                        hir_id,
+                        ..
+                    },
+                    _,
+                ) if self
+                    .cx
+                    .qpath_res(p, hir_id)
+                    .opt_def_id()
+                    .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
+                ExprKind::MethodCall(..)
+                    if self
+                        .cx
+                        .typeck_results()
+                        .type_dependent_def_id(e.hir_id)
+                        .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
+                ExprKind::Binary(_, lhs, rhs)
+                    if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty()
+                        && self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {},
+                ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (),
+                ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (),
+                ExprKind::Index(base, _)
+                    if matches!(
+                        self.cx.typeck_results().expr_ty(base).peel_refs().kind(),
+                        ty::Slice(_) | ty::Array(..)
+                    ) => {},
+                ExprKind::Path(ref p)
+                    if matches!(
+                        self.cx.qpath_res(p, e.hir_id),
+                        Res::Def(
+                            DefKind::Const
+                                | DefKind::AssocConst
+                                | DefKind::AnonConst
+                                | DefKind::ConstParam
+                                | DefKind::Ctor(..)
+                                | DefKind::Fn
+                                | DefKind::AssocFn,
+                            _
+                        ) | Res::SelfCtor(_)
+                    ) => {},
 
-            if let ExprKind::Path(p) = &e.kind {
-                if self.cx.qpath_res(p, e.hir_id) == self.res {
-                    self.found = true;
-                }
-            } else {
-                walk_expr(self, e);
-            }
-        }
-    }
-
-    let mut v = V { cx, res, found: false };
-    v.visit_expr(&cx.tcx.hir().body(body).value);
-    v.found
-}
-
-/// Calls the given function for each usage of the given local.
-pub fn for_each_local_usage<'tcx, B>(
-    cx: &LateContext<'tcx>,
-    visitable: impl Visitable<'tcx>,
-    id: HirId,
-    f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
-) -> ControlFlow<B> {
-    struct V<'tcx, B, F> {
-        map: Map<'tcx>,
-        id: HirId,
-        f: F,
-        res: ControlFlow<B>,
-    }
-    impl<'tcx, B, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>> Visitor<'tcx> for V<'tcx, B, F> {
-        type Map = Map<'tcx>;
-        fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
-            NestedVisitorMap::OnlyBodies(self.map)
-        }
+                ExprKind::AddrOf(..)
+                | ExprKind::Array(_)
+                | ExprKind::Block(..)
+                | ExprKind::Cast(..)
+                | ExprKind::DropTemps(_)
+                | ExprKind::Field(..)
+                | ExprKind::If(..)
+                | ExprKind::Let(..)
+                | ExprKind::Lit(_)
+                | ExprKind::Match(..)
+                | ExprKind::Repeat(..)
+                | ExprKind::Struct(..)
+                | ExprKind::Tup(_)
+                | ExprKind::Type(..) => (),
 
-        fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
-            if self.res.is_continue() {
-                if path_to_local_id(e, self.id) {
-                    self.res = (self.f)(e);
-                } else {
-                    walk_expr(self, e);
-                }
+                _ => {
+                    self.is_const = false;
+                    return;
+                },
             }
+            walk_expr(self, e);
         }
     }
 
-    let mut v = V {
-        map: cx.tcx.hir(),
-        id,
-        f,
-        res: ControlFlow::CONTINUE,
-    };
-    visitable.visit(&mut v);
-    v.res
-}
-
-/// Checks if the given local is used.
-pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
-    for_each_local_usage(cx, visitable, id, |_| ControlFlow::BREAK).is_break()
+    let mut v = V { cx, is_const: true };
+    v.visit_expr(e);
+    v.is_const
 }