1use crate::consts::{ConstEvalCtxt, FullInt};
13use crate::ty::{all_predicates_of, is_copy};
14use crate::visitors::is_const_evaluatable;
15use rustc_hir::def::{DefKind, Res};
16use rustc_hir::def_id::DefId;
17use rustc_hir::intravisit::{Visitor, walk_expr};
18use rustc_hir::{BinOpKind, Block, Expr, ExprKind, QPath, UnOp};
19use rustc_lint::LateContext;
20use rustc_middle::ty;
21use rustc_middle::ty::adjustment::Adjust;
22use rustc_span::{Symbol, sym};
23use std::{cmp, ops};
24
25#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
26enum EagernessSuggestion {
27 Eager,
29 NoChange,
32 Lazy,
34 ForceNoChange,
36}
37impl ops::BitOr for EagernessSuggestion {
38 type Output = Self;
39 fn bitor(self, rhs: Self) -> Self {
40 cmp::max(self, rhs)
41 }
42}
43impl ops::BitOrAssign for EagernessSuggestion {
44 fn bitor_assign(&mut self, rhs: Self) {
45 *self = *self | rhs;
46 }
47}
48
49fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg: bool) -> EagernessSuggestion {
51 use EagernessSuggestion::{Eager, Lazy, NoChange};
52 let name = name.as_str();
53
54 let ty = match cx.tcx.impl_of_method(fn_id) {
55 Some(id) => cx.tcx.type_of(id).instantiate_identity(),
56 None => return Lazy,
57 };
58
59 if (name.starts_with("as_") || name == "len" || name == "is_empty") && have_one_arg {
60 if matches!(
61 cx.tcx.crate_name(fn_id.krate),
62 sym::std | sym::core | sym::alloc | sym::proc_macro
63 ) {
64 Eager
65 } else {
66 NoChange
67 }
68 } else if let ty::Adt(def, subs) = ty.kind() {
69 if def.variants().iter().flat_map(|v| v.fields.iter()).any(|x| {
73 matches!(
74 cx.tcx.type_of(x.did).instantiate_identity().peel_refs().kind(),
75 ty::Param(_)
76 )
77 }) && all_predicates_of(cx.tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() {
78 ty::ClauseKind::Trait(pred) => cx.tcx.trait_def(pred.trait_ref.def_id).is_marker,
79 _ => true,
80 }) && subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_)))
81 {
82 match &**cx
84 .tcx
85 .fn_sig(fn_id)
86 .instantiate_identity()
87 .skip_binder()
88 .inputs_and_output
89 {
90 [arg, res] if !arg.is_mutable_ptr() && arg.peel_refs() == ty && res.is_bool() => NoChange,
91 _ => Lazy,
92 }
93 } else {
94 Lazy
95 }
96 } else {
97 Lazy
98 }
99}
100
101fn res_has_significant_drop(res: Res, cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
102 if let Res::Def(DefKind::Ctor(..) | DefKind::Variant | DefKind::Enum | DefKind::Struct, _)
103 | Res::SelfCtor(_)
104 | Res::SelfTyAlias { .. } = res
105 {
106 cx.typeck_results()
107 .expr_ty(e)
108 .has_significant_drop(cx.tcx, cx.typing_env())
109 } else {
110 false
111 }
112}
113
114#[expect(clippy::too_many_lines)]
115fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessSuggestion {
116 struct V<'cx, 'tcx> {
117 cx: &'cx LateContext<'tcx>,
118 eagerness: EagernessSuggestion,
119 }
120
121 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
122 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
123 use EagernessSuggestion::{ForceNoChange, Lazy, NoChange};
124 if self.eagerness == ForceNoChange {
125 return;
126 }
127
128 if self
131 .cx
132 .typeck_results()
133 .expr_adjustments(e)
134 .iter()
135 .any(|adj| matches!(adj.kind, Adjust::Deref(Some(_))))
136 {
137 self.eagerness |= NoChange;
138 return;
139 }
140
141 match e.kind {
142 ExprKind::Call(
143 &Expr {
144 kind: ExprKind::Path(ref path),
145 hir_id,
146 ..
147 },
148 args,
149 ) => match self.cx.qpath_res(path, hir_id) {
150 res @ (Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_)) => {
151 if res_has_significant_drop(res, self.cx, e) {
152 self.eagerness = ForceNoChange;
153 return;
154 }
155 },
156 Res::Def(_, id) if self.cx.tcx.is_promotable_const_fn(id) => (),
157 Res::Def(..) if is_const_evaluatable(self.cx, e) => {
159 self.eagerness |= NoChange;
160 return;
161 },
162 Res::Def(_, id) => match path {
163 QPath::Resolved(_, p) => {
164 self.eagerness |=
165 fn_eagerness(self.cx, id, p.segments.last().unwrap().ident.name, !args.is_empty());
166 },
167 QPath::TypeRelative(_, name) => {
168 self.eagerness |= fn_eagerness(self.cx, id, name.ident.name, !args.is_empty());
169 },
170 QPath::LangItem(..) => self.eagerness = Lazy,
171 },
172 _ => self.eagerness = Lazy,
173 },
174 ExprKind::MethodCall(..) if is_const_evaluatable(self.cx, e) => {
176 self.eagerness |= NoChange;
177 return;
178 },
179 #[expect(clippy::match_same_arms)] ExprKind::Struct(path, ..) => {
181 if res_has_significant_drop(self.cx.qpath_res(path, e.hir_id), self.cx, e) {
182 self.eagerness = ForceNoChange;
183 return;
184 }
185 },
186 ExprKind::Path(ref path) => {
187 if res_has_significant_drop(self.cx.qpath_res(path, e.hir_id), self.cx, e) {
188 self.eagerness = ForceNoChange;
189 return;
190 }
191 },
192 ExprKind::MethodCall(name, ..) => {
193 self.eagerness |= self
194 .cx
195 .typeck_results()
196 .type_dependent_def_id(e.hir_id)
197 .map_or(Lazy, |id| fn_eagerness(self.cx, id, name.ident.name, true));
198 },
199 ExprKind::Index(_, e, _) => {
200 let ty = self.cx.typeck_results().expr_ty_adjusted(e);
201 if is_copy(self.cx, ty) && !ty.is_ref() {
202 self.eagerness |= NoChange;
203 } else {
204 self.eagerness = Lazy;
205 }
206 },
207
208 ExprKind::Unary(UnOp::Neg, right) if ConstEvalCtxt::new(self.cx).eval(right).is_none() => {
210 self.eagerness |= NoChange;
211 },
212
213 ExprKind::Unary(UnOp::Deref, e)
215 if self.cx.typeck_results().expr_ty(e).builtin_deref(true).is_none() =>
216 {
217 self.eagerness |= NoChange;
218 },
219 ExprKind::Unary(UnOp::Deref, e) if !self.cx.typeck_results().expr_ty(e).is_raw_ptr() => (),
221 ExprKind::Unary(UnOp::Deref, _) => self.eagerness |= NoChange,
222 ExprKind::Unary(_, e)
223 if matches!(
224 self.cx.typeck_results().expr_ty(e).kind(),
225 ty::Bool | ty::Int(_) | ty::Uint(_),
226 ) => {},
227
228 ExprKind::Binary(op, _, right)
234 if matches!(op.node, BinOpKind::Shl | BinOpKind::Shr)
235 && ConstEvalCtxt::new(self.cx).eval(right).is_none() =>
236 {
237 self.eagerness |= NoChange;
238 },
239
240 ExprKind::Binary(op, left, right)
241 if matches!(op.node, BinOpKind::Div | BinOpKind::Rem)
242 && let right_ty = self.cx.typeck_results().expr_ty(right)
243 && let ecx = ConstEvalCtxt::new(self.cx)
244 && let left = ecx.eval(left)
245 && let right = ecx.eval(right).and_then(|c| c.int_value(self.cx.tcx, right_ty))
246 && matches!(
247 (left, right),
248 (_, None)
250 | (None, Some(FullInt::S(-1)))
252 ) =>
253 {
254 self.eagerness |= NoChange;
255 },
256
257 ExprKind::Binary(op, left, right)
262 if matches!(op.node, BinOpKind::Add | BinOpKind::Sub | BinOpKind::Mul)
263 && !self.cx.typeck_results().expr_ty(e).is_floating_point()
264 && let ecx = ConstEvalCtxt::new(self.cx)
265 && (ecx.eval(left).is_none() || ecx.eval(right).is_none()) =>
266 {
267 self.eagerness |= NoChange;
268 },
269
270 ExprKind::Binary(_, lhs, rhs)
271 if self.cx.typeck_results().expr_ty(lhs).is_primitive()
272 && self.cx.typeck_results().expr_ty(rhs).is_primitive() => {},
273
274 ExprKind::Break(..)
276 | ExprKind::Continue(_)
277 | ExprKind::Ret(_)
278 | ExprKind::Become(_)
279 | ExprKind::InlineAsm(_)
280 | ExprKind::Yield(..)
281 | ExprKind::Err(_) => {
282 self.eagerness = ForceNoChange;
283 return;
284 },
285
286 ExprKind::Unary(..) | ExprKind::Binary(..) | ExprKind::Loop(..) | ExprKind::Call(..) => {
288 self.eagerness = Lazy;
289 },
290
291 ExprKind::ConstBlock(_)
292 | ExprKind::Array(_)
293 | ExprKind::Tup(_)
294 | ExprKind::Lit(_)
295 | ExprKind::Cast(..)
296 | ExprKind::Type(..)
297 | ExprKind::DropTemps(_)
298 | ExprKind::Let(..)
299 | ExprKind::If(..)
300 | ExprKind::Match(..)
301 | ExprKind::Closure { .. }
302 | ExprKind::Field(..)
303 | ExprKind::AddrOf(..)
304 | ExprKind::Repeat(..)
305 | ExprKind::Block(Block { stmts: [], .. }, _)
306 | ExprKind::OffsetOf(..)
307 | ExprKind::UnsafeBinderCast(..) => (),
308
309 ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Block(..) => self.eagerness |= NoChange,
313 }
314 walk_expr(self, e);
315 }
316 }
317
318 let mut v = V {
319 cx,
320 eagerness: EagernessSuggestion::Eager,
321 };
322 v.visit_expr(e);
323 v.eagerness
324}
325
326pub fn switch_to_eager_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
328 expr_eagerness(cx, expr) == EagernessSuggestion::Eager
329}
330
331pub fn switch_to_lazy_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
333 expr_eagerness(cx, expr) == EagernessSuggestion::Lazy
334}