]> git.lizzy.rs Git - rust.git/blob - clippy_utils/src/eager_or_lazy.rs
81cd99c0558dce7f8f149eab452c72562d06d147
[rust.git] / clippy_utils / src / eager_or_lazy.rs
1 //! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa.
2 //!
3 //! Things to consider:
4 //!  - has the expression side-effects?
5 //!  - is the expression computationally expensive?
6 //!
7 //! See lints:
8 //!  - unnecessary-lazy-evaluations
9 //!  - or-fun-call
10 //!  - option-if-let-else
11
12 use crate::{is_ctor_or_promotable_const_function, is_type_diagnostic_item, match_type, paths};
13 use rustc_hir::def::{DefKind, Res};
14
15 use rustc_hir::intravisit;
16 use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
17
18 use rustc_hir::{Block, Expr, ExprKind, Path, QPath};
19 use rustc_lint::LateContext;
20 use rustc_middle::hir::map::Map;
21 use rustc_span::sym;
22
23 /// Is the expr pure (is it free from side-effects)?
24 /// This function is named so to stress that it isn't exhaustive and returns FNs.
25 fn identify_some_pure_patterns(expr: &Expr<'_>) -> bool {
26     match expr.kind {
27         ExprKind::Lit(..) | ExprKind::ConstBlock(..) | ExprKind::Path(..) | ExprKind::Field(..) => true,
28         ExprKind::AddrOf(_, _, addr_of_expr) => identify_some_pure_patterns(addr_of_expr),
29         ExprKind::Tup(tup_exprs) => tup_exprs.iter().all(|expr| identify_some_pure_patterns(expr)),
30         ExprKind::Struct(_, fields, expr) => {
31             fields.iter().all(|f| identify_some_pure_patterns(f.expr))
32                 && expr.map_or(true, |e| identify_some_pure_patterns(e))
33         },
34         ExprKind::Call(
35             &Expr {
36                 kind:
37                     ExprKind::Path(QPath::Resolved(
38                         _,
39                         Path {
40                             res: Res::Def(DefKind::Ctor(..) | DefKind::Variant, ..),
41                             ..
42                         },
43                     )),
44                 ..
45             },
46             args,
47         ) => args.iter().all(|expr| identify_some_pure_patterns(expr)),
48         ExprKind::Block(
49             &Block {
50                 stmts,
51                 expr: Some(expr),
52                 ..
53             },
54             _,
55         ) => stmts.is_empty() && identify_some_pure_patterns(expr),
56         ExprKind::Box(..)
57         | ExprKind::Array(..)
58         | ExprKind::Call(..)
59         | ExprKind::MethodCall(..)
60         | ExprKind::Binary(..)
61         | ExprKind::Unary(..)
62         | ExprKind::Cast(..)
63         | ExprKind::Type(..)
64         | ExprKind::DropTemps(..)
65         | ExprKind::Loop(..)
66         | ExprKind::If(..)
67         | ExprKind::Match(..)
68         | ExprKind::Closure(..)
69         | ExprKind::Block(..)
70         | ExprKind::Assign(..)
71         | ExprKind::AssignOp(..)
72         | ExprKind::Index(..)
73         | ExprKind::Break(..)
74         | ExprKind::Continue(..)
75         | ExprKind::Ret(..)
76         | ExprKind::InlineAsm(..)
77         | ExprKind::LlvmInlineAsm(..)
78         | ExprKind::Repeat(..)
79         | ExprKind::Yield(..)
80         | ExprKind::Err => false,
81     }
82 }
83
84 /// Identify some potentially computationally expensive patterns.
85 /// This function is named so to stress that its implementation is non-exhaustive.
86 /// It returns FNs and FPs.
87 fn identify_some_potentially_expensive_patterns<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
88     // Searches an expression for method calls or function calls that aren't ctors
89     struct FunCallFinder<'a, 'tcx> {
90         cx: &'a LateContext<'tcx>,
91         found: bool,
92     }
93
94     impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> {
95         type Map = Map<'tcx>;
96
97         fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
98             let call_found = match &expr.kind {
99                 // ignore enum and struct constructors
100                 ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr),
101                 ExprKind::Index(obj, _) => {
102                     let ty = self.cx.typeck_results().expr_ty(obj);
103                     is_type_diagnostic_item(self.cx, ty, sym::hashmap_type)
104                         || match_type(self.cx, ty, &paths::BTREEMAP)
105                 },
106                 ExprKind::MethodCall(..) => true,
107                 _ => false,
108             };
109
110             if call_found {
111                 self.found |= true;
112             }
113
114             if !self.found {
115                 intravisit::walk_expr(self, expr);
116             }
117         }
118
119         fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
120             NestedVisitorMap::None
121         }
122     }
123
124     let mut finder = FunCallFinder { cx, found: false };
125     finder.visit_expr(expr);
126     finder.found
127 }
128
129 pub fn is_eagerness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
130     !identify_some_potentially_expensive_patterns(cx, expr) && identify_some_pure_patterns(expr)
131 }
132
133 pub fn is_lazyness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
134     identify_some_potentially_expensive_patterns(cx, expr)
135 }