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