]> 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 ce00106dd4d8087afc501f234cfd442ca73c1047..823df5cb7517eaeb641da7100dce261df64544df 100644 (file)
@@ -1,37 +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;
-
-/// returns `true` if expr contains match expr desugared from try
-fn contains_try(expr: &hir::Expr<'_>) -> bool {
-    struct TryFinder {
-        found: bool,
+use rustc_middle::ty;
+
+/// 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
@@ -133,62 +164,6 @@ fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
     }
 }
 
-pub struct LocalUsedVisitor<'hir> {
-    hir: Map<'hir>,
-    pub local_hir_id: HirId,
-    pub used: bool,
-}
-
-impl<'hir> LocalUsedVisitor<'hir> {
-    pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self {
-        Self {
-            hir: cx.tcx.hir(),
-            local_hir_id,
-            used: false,
-        }
-    }
-
-    fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
-        visit(self, t);
-        std::mem::replace(&mut self.used, false)
-    }
-
-    pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool {
-        self.check(arm, Self::visit_arm)
-    }
-
-    pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool {
-        self.check(body, Self::visit_body)
-    }
-
-    pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool {
-        self.check(expr, Self::visit_expr)
-    }
-
-    pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool {
-        self.check(stmt, Self::visit_stmt)
-    }
-}
-
-impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
-    type Map = Map<'v>;
-
-    fn visit_expr(&mut self, expr: &'v Expr<'v>) {
-        if self.used {
-            return;
-        }
-        if path_to_local_id(expr, self.local_hir_id) {
-            self.used = true;
-        } else {
-            walk_expr(self, expr);
-        }
-    }
-
-    fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
-        NestedVisitorMap::OnlyBodies(self.hir)
-    }
-}
-
 /// A type which can be visited.
 pub trait Visitable<'tcx> {
     /// Calls the corresponding `visit_*` function on the visitor.
@@ -203,60 +178,142 @@ fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
         }
     };
 }
+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);
+//         }
+//     }
+// }
 
-/// 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;
             }
-
-            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);
+            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(_)
+                    ) => {},
+
+                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(..) => (),
+
+                _ => {
+                    self.is_const = false;
+                    return;
+                },
             }
+            walk_expr(self, e);
         }
     }
 
-    let mut v = V { cx, res, found: false };
-    v.visit_expr(&cx.tcx.hir().body(body).value);
-    v.found
+    let mut v = V { cx, is_const: true };
+    v.visit_expr(e);
+    v.is_const
 }