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