]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
New upstream version 1.70.0+dfsg1
[rustc.git] / src / tools / clippy / 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, 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(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: bool) -> EagernessSuggestion {
49 use EagernessSuggestion::{Eager, Lazy, NoChange};
50 let name = name.as_str();
51
52 let ty = match cx.tcx.impl_of_method(fn_id) {
53 Some(id) => cx.tcx.type_of(id).subst_identity(),
54 None => return Lazy,
55 };
56
57 if (name.starts_with("as_") || name == "len" || name == "is_empty") && have_one_arg {
58 if matches!(
59 cx.tcx.crate_name(fn_id.krate),
60 sym::std | sym::core | sym::alloc | sym::proc_macro
61 ) {
62 Eager
63 } else {
64 NoChange
65 }
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.
70 if def
71 .variants()
72 .iter()
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,
77 _ => true,
78 })
79 && subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_)))
80 {
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,
84 _ => Lazy,
85 }
86 } else {
87 Lazy
88 }
89 } else {
90 Lazy
91 }
92 }
93
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 {
96 cx.typeck_results()
97 .expr_ty(e)
98 .has_significant_drop(cx.tcx, cx.param_env)
99 } else {
100 false
101 }
102 }
103
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,
109 }
110
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 {
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 @ (Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_)) => {
127 if res_has_significant_drop(res, self.cx, e) {
128 self.eagerness = ForceNoChange;
129 return;
130 }
131 },
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;
136 return;
137 },
138 Res::Def(_, id) => match path {
139 QPath::Resolved(_, p) => {
140 self.eagerness |=
141 fn_eagerness(self.cx, id, p.segments.last().unwrap().ident.name, !args.is_empty());
142 },
143 QPath::TypeRelative(_, name) => {
144 self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, !args.is_empty());
145 },
146 QPath::LangItem(..) => self.eagerness = Lazy,
147 },
148 _ => self.eagerness = Lazy,
149 },
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;
153 return;
154 },
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;
158 return;
159 }
160 },
161 ExprKind::MethodCall(name, ..) => {
162 self.eagerness |= self
163 .cx
164 .typeck_results()
165 .type_dependent_def_id(e.hir_id)
166 .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, true));
167 },
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;
172 } else {
173 self.eagerness = Lazy;
174 }
175 },
176
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,
180
181 ExprKind::Unary(_, e)
182 if matches!(
183 self.cx.typeck_results().expr_ty(e).kind(),
184 ty::Bool | ty::Int(_) | ty::Uint(_),
185 ) => {},
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() => {},
189
190 // Can't be moved into a closure
191 ExprKind::Break(..)
192 | ExprKind::Continue(_)
193 | ExprKind::Ret(_)
194 | ExprKind::InlineAsm(_)
195 | ExprKind::Yield(..)
196 | ExprKind::Err(_) => {
197 self.eagerness = ForceNoChange;
198 return;
199 },
200
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;
204 },
205
206 ExprKind::ConstBlock(_)
207 | ExprKind::Array(_)
208 | ExprKind::Tup(_)
209 | ExprKind::Lit(_)
210 | ExprKind::Cast(..)
211 | ExprKind::Type(..)
212 | ExprKind::DropTemps(_)
213 | ExprKind::Let(..)
214 | ExprKind::If(..)
215 | ExprKind::Match(..)
216 | ExprKind::Closure { .. }
217 | ExprKind::Field(..)
218 | ExprKind::AddrOf(..)
219 | ExprKind::Struct(..)
220 | ExprKind::Repeat(..)
221 | ExprKind::Block(Block { stmts: [], .. }, _) => (),
222
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,
227 }
228 walk_expr(self, e);
229 }
230 }
231
232 let mut v = V {
233 cx,
234 eagerness: EagernessSuggestion::Eager,
235 };
236 v.visit_expr(e);
237 v.eagerness
238 }
239
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
243 }
244
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
248 }