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, 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(cx
: &LateContext
<'_
>, fn_id
: DefId
, name
: Symbol
, have_one_arg
: bool
) -> EagernessSuggestion
{
49 use EagernessSuggestion
::{Eager, Lazy, NoChange}
;
50 let name
= name
.as_str();
52 let ty
= match cx
.tcx
.impl_of_method(fn_id
) {
53 Some(id
) => cx
.tcx
.type_of(id
).subst_identity(),
57 if (name
.starts_with("as_") || name
== "len" || name
== "is_empty") && have_one_arg
{
59 cx
.tcx
.crate_name(fn_id
.krate
),
60 sym
::std
| sym
::core
| sym
::alloc
| sym
::proc_macro
66 } else if let ty
::Adt(def
, subs
) = ty
.kind() {
67 // Types where the only fields are generic types (or references to) with no trait bounds other
68 // than marker traits.
69 // Due to the limited operations on these types functions should be fairly cheap.
73 .flat_map(|v
| v
.fields
.iter())
74 .any(|x
| matches
!(cx
.tcx
.type_of(x
.did
).subst_identity().peel_refs().kind(), ty
::Param(_
)))
75 && all_predicates_of(cx
.tcx
, fn_id
).all(|(pred
, _
)| match pred
.kind().skip_binder() {
76 PredicateKind
::Clause(ty
::Clause
::Trait(pred
)) => cx
.tcx
.trait_def(pred
.trait_ref
.def_id
).is_marker
,
79 && subs
.types().all(|x
| matches
!(x
.peel_refs().kind(), ty
::Param(_
)))
81 // Limit the function to either `(self) -> bool` or `(&self) -> bool`
82 match &**cx
.tcx
.fn_sig(fn_id
).subst_identity().skip_binder().inputs_and_output
{
83 [arg
, res
] if !arg
.is_mutable_ptr() && arg
.peel_refs() == ty
&& res
.is_bool() => NoChange
,
94 fn res_has_significant_drop(res
: Res
, cx
: &LateContext
<'_
>, e
: &Expr
<'_
>) -> bool
{
95 if let Res
::Def(DefKind
::Ctor(..) | DefKind
::Variant
, _
) | Res
::SelfCtor(_
) = res
{
98 .has_significant_drop(cx
.tcx
, cx
.param_env
)
104 #[expect(clippy::too_many_lines)]
105 fn expr_eagerness
<'tcx
>(cx
: &LateContext
<'tcx
>, e
: &'tcx Expr
<'_
>) -> EagernessSuggestion
{
106 struct V
<'cx
, 'tcx
> {
107 cx
: &'cx LateContext
<'tcx
>,
108 eagerness
: EagernessSuggestion
,
111 impl<'cx
, 'tcx
> Visitor
<'tcx
> for V
<'cx
, 'tcx
> {
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 @
(Res
::Def(DefKind
::Ctor(..) | DefKind
::Variant
, _
) | Res
::SelfCtor(_
)) => {
127 if res_has_significant_drop(res
, self.cx
, e
) {
128 self.eagerness
= ForceNoChange
;
132 Res
::Def(_
, id
) if self.cx
.tcx
.is_promotable_const_fn(id
) => (),
133 // No need to walk the arguments here, `is_const_evaluatable` already did
134 Res
::Def(..) if is_const_evaluatable(self.cx
, e
) => {
135 self.eagerness
|= NoChange
;
138 Res
::Def(_
, id
) => match path
{
139 QPath
::Resolved(_
, p
) => {
141 fn_eagerness(self.cx
, id
, p
.segments
.last().unwrap().ident
.name
, !args
.is_empty());
143 QPath
::TypeRelative(_
, name
) => {
144 self.eagerness
|= fn_eagerness(self.cx
, id
, name
.ident
.name
, !args
.is_empty());
146 QPath
::LangItem(..) => self.eagerness
= Lazy
,
148 _
=> self.eagerness
= Lazy
,
150 // No need to walk the arguments here, `is_const_evaluatable` already did
151 ExprKind
::MethodCall(..) if is_const_evaluatable(self.cx
, e
) => {
152 self.eagerness
|= NoChange
;
155 ExprKind
::Path(ref path
) => {
156 if res_has_significant_drop(self.cx
.qpath_res(path
, e
.hir_id
), self.cx
, e
) {
157 self.eagerness
= ForceNoChange
;
161 ExprKind
::MethodCall(name
, ..) => {
162 self.eagerness
|= self
165 .type_dependent_def_id(e
.hir_id
)
166 .map_or(Lazy
, |id
| fn_eagerness(self.cx
, id
, name
.ident
.name
, true));
168 ExprKind
::Index(_
, e
) => {
169 let ty
= self.cx
.typeck_results().expr_ty_adjusted(e
);
170 if is_copy(self.cx
, ty
) && !ty
.is_ref() {
171 self.eagerness
|= NoChange
;
173 self.eagerness
= Lazy
;
177 // Dereferences should be cheap, but dereferencing a raw pointer earlier may not be safe.
178 ExprKind
::Unary(UnOp
::Deref
, e
) if !self.cx
.typeck_results().expr_ty(e
).is_unsafe_ptr() => (),
179 ExprKind
::Unary(UnOp
::Deref
, _
) => self.eagerness
|= NoChange
,
181 ExprKind
::Unary(_
, e
)
183 self.cx
.typeck_results().expr_ty(e
).kind(),
184 ty
::Bool
| ty
::Int(_
) | ty
::Uint(_
),
186 ExprKind
::Binary(_
, lhs
, rhs
)
187 if self.cx
.typeck_results().expr_ty(lhs
).is_primitive()
188 && self.cx
.typeck_results().expr_ty(rhs
).is_primitive() => {}
,
190 // Can't be moved into a closure
192 | ExprKind
::Continue(_
)
194 | ExprKind
::InlineAsm(_
)
195 | ExprKind
::Yield(..)
196 | ExprKind
::Err(_
) => {
197 self.eagerness
= ForceNoChange
;
201 // Memory allocation, custom operator, loop, or call to an unknown function
202 ExprKind
::Unary(..) | ExprKind
::Binary(..) | ExprKind
::Loop(..) | ExprKind
::Call(..) => {
203 self.eagerness
= Lazy
;
206 ExprKind
::ConstBlock(_
)
212 | ExprKind
::DropTemps(_
)
215 | ExprKind
::Match(..)
216 | ExprKind
::Closure { .. }
217 | ExprKind
::Field(..)
218 | ExprKind
::AddrOf(..)
219 | ExprKind
::Struct(..)
220 | ExprKind
::Repeat(..)
221 | ExprKind
::Block(Block { stmts: [], .. }
, _
) => (),
223 // Assignment might be to a local defined earlier, so don't eagerly evaluate.
224 // Blocks with multiple statements might be expensive, so don't eagerly evaluate.
225 // TODO: Actually check if either of these are true here.
226 ExprKind
::Assign(..) | ExprKind
::AssignOp(..) | ExprKind
::Block(..) => self.eagerness
|= NoChange
,
234 eagerness
: EagernessSuggestion
::Eager
,
240 /// Whether the given expression should be changed to evaluate eagerly
241 pub fn switch_to_eager_eval
<'tcx
>(cx
: &'_ LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) -> bool
{
242 expr_eagerness(cx
, expr
) == EagernessSuggestion
::Eager
245 /// Whether the given expression should be changed to evaluate lazily
246 pub fn switch_to_lazy_eval
<'tcx
>(cx
: &'_ LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) -> bool
{
247 expr_eagerness(cx
, expr
) == EagernessSuggestion
::Lazy