1 //! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa.
3 //! Things to consider:
4 //! - has the expression side-effects?
5 //! - is the expression computationally expensive?
8 //! - unnecessary-lazy-evaluations
10 //! - option-if-let-else
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};
23 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
24 enum EagernessSuggestion {
25 // The expression is cheap and should be evaluated eagerly
27 // The expression may be cheap, so don't suggested lazy evaluation; or the expression may not be safe to switch to
30 // The expression is likely expensive and should be evaluated lazily.
32 // The expression cannot be placed into a closure.
35 impl ops::BitOr for EagernessSuggestion {
37 fn bitor(self, rhs: Self) -> Self {
41 impl ops::BitOrAssign for EagernessSuggestion {
42 fn bitor_assign(&mut self, rhs: Self) {
47 /// Determine the eagerness of the given function call.
48 fn fn_eagerness<'tcx>(
49 cx: &LateContext<'tcx>,
52 args: &'tcx [Expr<'_>],
53 ) -> EagernessSuggestion {
54 use EagernessSuggestion::{Eager, Lazy, NoChange};
55 let name = name.as_str();
57 let ty = match cx.tcx.impl_of_method(fn_id) {
58 Some(id) => cx.tcx.type_of(id),
62 if (name.starts_with("as_") || name == "len" || name == "is_empty") && args.len() == 1 {
64 cx.tcx.crate_name(fn_id.krate),
65 sym::std | sym::core | sym::alloc | sym::proc_macro
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.
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,
84 && subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_)))
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,
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,
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
112 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
113 use EagernessSuggestion::{ForceNoChange, Lazy, NoChange};
114 if self.eagerness == ForceNoChange {
120 kind: ExprKind::Path(ref path),
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;
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);
137 QPath::TypeRelative(_, name) => {
138 self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, args);
140 QPath::LangItem(..) => self.eagerness = Lazy,
142 _ => self.eagerness = Lazy,
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;
149 ExprKind::MethodCall(name, _, args, _) => {
150 self.eagerness |= self
153 .type_dependent_def_id(e.hir_id)
154 .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, args));
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;
161 self.eagerness = Lazy;
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,
169 ExprKind::Unary(_, e)
171 self.cx.typeck_results().expr_ty(e).kind(),
172 ty::Bool | ty::Int(_) | ty::Uint(_),
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() => {},
178 // Can't be moved into a closure
180 | ExprKind::Continue(_)
182 | ExprKind::InlineAsm(_)
183 | ExprKind::LlvmInlineAsm(_)
184 | ExprKind::Yield(..)
186 self.eagerness = ForceNoChange;
190 // Memory allocation, custom operator, loop, or call to an unknown function
192 | ExprKind::Unary(..)
193 | ExprKind::Binary(..)
195 | ExprKind::Call(..) => self.eagerness = Lazy,
197 ExprKind::ConstBlock(_)
203 | ExprKind::DropTemps(_)
206 | ExprKind::Match(..)
207 | ExprKind::Closure(..)
208 | ExprKind::Field(..)
210 | ExprKind::AddrOf(..)
211 | ExprKind::Struct(..)
212 | ExprKind::Repeat(..)
213 | ExprKind::Block(Block { stmts: [], .. }, _) => (),
215 // Assignment might be to a local defined earlier, so don't eagerly evaluate.
216 // Blocks with multiple statements might be expensive, so don't eagerly evaluate.
217 // TODO: Actually check if either of these are true here.
218 ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Block(..) => self.eagerness |= NoChange,
226 eagerness: EagernessSuggestion::Eager,
232 /// Whether the given expression should be changed to evaluate eagerly
233 pub fn switch_to_eager_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
234 expr_eagerness(cx, expr) == EagernessSuggestion::Eager
237 /// Whether the given expression should be changed to evaluate lazily
238 pub fn switch_to_lazy_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
239 expr_eagerness(cx, expr) == EagernessSuggestion::Lazy