]> git.lizzy.rs Git - rust.git/blob - clippy_utils/src/eager_or_lazy.rs
Auto merge of #92816 - tmiasko:rm-llvm-asm, r=Amanieu
[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::ty::{all_predicates_of, is_copy};
13 use crate::visitors::is_const_evaluatable;
14 use rustc_hir::def::{DefKind, Res};
15 use rustc_hir::intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor};
16 use rustc_hir::{def_id::DefId, Block, Expr, ExprKind, QPath, UnOp};
17 use rustc_lint::LateContext;
18 use rustc_middle::ty::{self, PredicateKind};
19 use rustc_span::{sym, Symbol};
20 use std::cmp;
21 use std::ops;
22
23 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
24 enum EagernessSuggestion {
25     // The expression is cheap and should be evaluated eagerly
26     Eager,
27     // The expression may be cheap, so don't suggested lazy evaluation; or the expression may not be safe to switch to
28     // eager evaluation.
29     NoChange,
30     // The expression is likely expensive and should be evaluated lazily.
31     Lazy,
32     // The expression cannot be placed into a closure.
33     ForceNoChange,
34 }
35 impl ops::BitOr for EagernessSuggestion {
36     type Output = Self;
37     fn bitor(self, rhs: Self) -> Self {
38         cmp::max(self, rhs)
39     }
40 }
41 impl ops::BitOrAssign for EagernessSuggestion {
42     fn bitor_assign(&mut self, rhs: Self) {
43         *self = *self | rhs;
44     }
45 }
46
47 /// Determine the eagerness of the given function call.
48 fn fn_eagerness<'tcx>(
49     cx: &LateContext<'tcx>,
50     fn_id: DefId,
51     name: Symbol,
52     args: &'tcx [Expr<'_>],
53 ) -> EagernessSuggestion {
54     use EagernessSuggestion::{Eager, Lazy, NoChange};
55     let name = name.as_str();
56
57     let ty = match cx.tcx.impl_of_method(fn_id) {
58         Some(id) => cx.tcx.type_of(id),
59         None => return Lazy,
60     };
61
62     if (name.starts_with("as_") || name == "len" || name == "is_empty") && args.len() == 1 {
63         if matches!(
64             cx.tcx.crate_name(fn_id.krate),
65             sym::std | sym::core | sym::alloc | sym::proc_macro
66         ) {
67             Eager
68         } else {
69             NoChange
70         }
71     } else if let ty::Adt(def, subs) = ty.kind() {
72         // Types where the only fields are generic types (or references to) with no trait bounds other
73         // than marker traits.
74         // Due to the limited operations on these types functions should be fairly cheap.
75         if def
76             .variants
77             .iter()
78             .flat_map(|v| v.fields.iter())
79             .any(|x| matches!(cx.tcx.type_of(x.did).peel_refs().kind(), ty::Param(_)))
80             && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() {
81                 PredicateKind::Trait(pred) => cx.tcx.trait_def(pred.trait_ref.def_id).is_marker,
82                 _ => true,
83             })
84             && subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_)))
85         {
86             // Limit the function to either `(self) -> bool` or `(&self) -> bool`
87             match &**cx.tcx.fn_sig(fn_id).skip_binder().inputs_and_output {
88                 [arg, res] if !arg.is_mutable_ptr() && arg.peel_refs() == ty && res.is_bool() => NoChange,
89                 _ => Lazy,
90             }
91         } else {
92             Lazy
93         }
94     } else {
95         Lazy
96     }
97 }
98
99 #[allow(clippy::too_many_lines)]
100 fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessSuggestion {
101     struct V<'cx, 'tcx> {
102         cx: &'cx LateContext<'tcx>,
103         eagerness: EagernessSuggestion,
104     }
105
106     impl<'cx, 'tcx> Visitor<'tcx> for V<'cx, 'tcx> {
107         type Map = ErasedMap<'tcx>;
108         fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
109             NestedVisitorMap::None
110         }
111
112         fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
113             use EagernessSuggestion::{ForceNoChange, Lazy, NoChange};
114             if self.eagerness == ForceNoChange {
115                 return;
116             }
117             match e.kind {
118                 ExprKind::Call(
119                     &Expr {
120                         kind: ExprKind::Path(ref path),
121                         hir_id,
122                         ..
123                     },
124                     args,
125                 ) => match self.cx.qpath_res(path, hir_id) {
126                     Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) => (),
127                     Res::Def(_, id) if self.cx.tcx.is_promotable_const_fn(id) => (),
128                     // No need to walk the arguments here, `is_const_evaluatable` already did
129                     Res::Def(..) if is_const_evaluatable(self.cx, e) => {
130                         self.eagerness |= NoChange;
131                         return;
132                     },
133                     Res::Def(_, id) => match path {
134                         QPath::Resolved(_, p) => {
135                             self.eagerness |= fn_eagerness(self.cx, id, p.segments.last().unwrap().ident.name, args);
136                         },
137                         QPath::TypeRelative(_, name) => {
138                             self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, args);
139                         },
140                         QPath::LangItem(..) => self.eagerness = Lazy,
141                     },
142                     _ => self.eagerness = Lazy,
143                 },
144                 // No need to walk the arguments here, `is_const_evaluatable` already did
145                 ExprKind::MethodCall(..) if is_const_evaluatable(self.cx, e) => {
146                     self.eagerness |= NoChange;
147                     return;
148                 },
149                 ExprKind::MethodCall(name, _, args, _) => {
150                     self.eagerness |= self
151                         .cx
152                         .typeck_results()
153                         .type_dependent_def_id(e.hir_id)
154                         .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, args));
155                 },
156                 ExprKind::Index(_, e) => {
157                     let ty = self.cx.typeck_results().expr_ty_adjusted(e);
158                     if is_copy(self.cx, ty) && !ty.is_ref() {
159                         self.eagerness |= NoChange;
160                     } else {
161                         self.eagerness = Lazy;
162                     }
163                 },
164
165                 // Dereferences should be cheap, but dereferencing a raw pointer earlier may not be safe.
166                 ExprKind::Unary(UnOp::Deref, e) if !self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => (),
167                 ExprKind::Unary(UnOp::Deref, _) => self.eagerness |= NoChange,
168
169                 ExprKind::Unary(_, e)
170                     if matches!(
171                         self.cx.typeck_results().expr_ty(e).kind(),
172                         ty::Bool | ty::Int(_) | ty::Uint(_),
173                     ) => {},
174                 ExprKind::Binary(_, lhs, rhs)
175                     if self.cx.typeck_results().expr_ty(lhs).is_primitive()
176                         && self.cx.typeck_results().expr_ty(rhs).is_primitive() => {},
177
178                 // Can't be moved into a closure
179                 ExprKind::Break(..)
180                 | ExprKind::Continue(_)
181                 | ExprKind::Ret(_)
182                 | ExprKind::InlineAsm(_)
183                 | ExprKind::Yield(..)
184                 | ExprKind::Err => {
185                     self.eagerness = ForceNoChange;
186                     return;
187                 },
188
189                 // Memory allocation, custom operator, loop, or call to an unknown function
190                 ExprKind::Box(_)
191                 | ExprKind::Unary(..)
192                 | ExprKind::Binary(..)
193                 | ExprKind::Loop(..)
194                 | ExprKind::Call(..) => self.eagerness = Lazy,
195
196                 ExprKind::ConstBlock(_)
197                 | ExprKind::Array(_)
198                 | ExprKind::Tup(_)
199                 | ExprKind::Lit(_)
200                 | ExprKind::Cast(..)
201                 | ExprKind::Type(..)
202                 | ExprKind::DropTemps(_)
203                 | ExprKind::Let(..)
204                 | ExprKind::If(..)
205                 | ExprKind::Match(..)
206                 | ExprKind::Closure(..)
207                 | ExprKind::Field(..)
208                 | ExprKind::Path(_)
209                 | ExprKind::AddrOf(..)
210                 | ExprKind::Struct(..)
211                 | ExprKind::Repeat(..)
212                 | ExprKind::Block(Block { stmts: [], .. }, _) => (),
213
214                 // Assignment might be to a local defined earlier, so don't eagerly evaluate.
215                 // Blocks with multiple statements might be expensive, so don't eagerly evaluate.
216                 // TODO: Actually check if either of these are true here.
217                 ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Block(..) => self.eagerness |= NoChange,
218             }
219             walk_expr(self, e);
220         }
221     }
222
223     let mut v = V {
224         cx,
225         eagerness: EagernessSuggestion::Eager,
226     };
227     v.visit_expr(e);
228     v.eagerness
229 }
230
231 /// Whether the given expression should be changed to evaluate eagerly
232 pub fn switch_to_eager_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
233     expr_eagerness(cx, expr) == EagernessSuggestion::Eager
234 }
235
236 /// Whether the given expression should be changed to evaluate lazily
237 pub fn switch_to_lazy_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
238     expr_eagerness(cx, expr) == EagernessSuggestion::Lazy
239 }