1use crate::consts::{ConstEvalCtxt, FullInt};
13use crate::sym;
14use crate::ty::all_predicates_of;
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::adjustment::{Adjust, DerefAdjustKind};
22use rustc_middle::ty::{self, TyCtxt};
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(tcx: TyCtxt<'_>, fn_id: DefId, name: Symbol, have_one_arg: bool) -> EagernessSuggestion {
52 use EagernessSuggestion::{Eager, Lazy, NoChange};
53
54 let ty = match tcx.impl_of_assoc(fn_id) {
55 Some(id) => 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 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 tcx.type_of(x.did)
75 .instantiate_identity()
76 .skip_norm_wip()
77 .peel_refs()
78 .kind(),
79 ty::Param(_)
80 )
81 }) && all_predicates_of(tcx, fn_id).all(|(pred, _)| match pred.kind().skip_binder() {
82 ty::ClauseKind::Trait(pred) => tcx.trait_def(pred.trait_ref.def_id).is_marker,
83 _ => true,
84 }) && subs.types().all(|x| matches!(x.peel_refs().kind(), ty::Param(_)))
85 {
86 match &**tcx
88 .fn_sig(fn_id)
89 .instantiate_identity()
90 .skip_norm_wip()
91 .skip_binder()
92 .inputs_and_output
93 {
94 [arg, res] if !arg.is_mutable_ptr() && arg.peel_refs() == ty && res.is_bool() => NoChange,
95 _ => Lazy,
96 }
97 } else {
98 Lazy
99 }
100 } else {
101 Lazy
102 }
103}
104
105fn res_has_significant_drop(res: Res, ecx: &ConstEvalCtxt<'_>, e: &Expr<'_>) -> bool {
106 if let Res::Def(DefKind::Ctor(..) | DefKind::Variant | DefKind::Enum | DefKind::Struct, _)
107 | Res::SelfCtor(_)
108 | Res::SelfTyAlias { .. } = res
109 {
110 ecx.typeck.expr_ty(e).has_significant_drop(ecx.tcx, ecx.typing_env)
111 } else {
112 false
113 }
114}
115
116#[expect(clippy::too_many_lines)]
117fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessSuggestion {
118 struct V<'tcx> {
119 ecx: ConstEvalCtxt<'tcx>,
120 eagerness: EagernessSuggestion,
121 }
122
123 impl<'tcx> Visitor<'tcx> for V<'tcx> {
124 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
125 use EagernessSuggestion::{ForceNoChange, Lazy, NoChange};
126 if self.eagerness == ForceNoChange {
127 return;
128 }
129
130 if self
133 .ecx
134 .typeck
135 .expr_adjustments(e)
136 .iter()
137 .any(|adj| matches!(adj.kind, Adjust::Deref(DerefAdjustKind::Overloaded(_))))
138 {
139 self.eagerness |= NoChange;
140 return;
141 }
142
143 match e.kind {
144 ExprKind::Call(
145 &Expr {
146 kind: ExprKind::Path(ref path),
147 hir_id,
148 ..
149 },
150 args,
151 ) => match self.ecx.typeck.qpath_res(path, hir_id) {
152 res @ (Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_)) => {
153 if res_has_significant_drop(res, &self.ecx, e) {
154 self.eagerness = ForceNoChange;
155 return;
156 }
157 },
158 Res::Def(_, id) if self.ecx.tcx.is_promotable_const_fn(id) => (),
159 Res::Def(..) if is_const_evaluatable(self.ecx.tcx, self.ecx.typeck, e) => {
161 self.eagerness |= NoChange;
162 return;
163 },
164 Res::Def(_, id) => match path {
165 QPath::Resolved(_, p) => {
166 self.eagerness |= fn_eagerness(
167 self.ecx.tcx,
168 id,
169 p.segments.last().unwrap().ident.name,
170 !args.is_empty(),
171 );
172 },
173 QPath::TypeRelative(_, name) => {
174 self.eagerness |= fn_eagerness(self.ecx.tcx, id, name.ident.name, !args.is_empty());
175 },
176 },
177 _ => self.eagerness = Lazy,
178 },
179 ExprKind::MethodCall(..) if is_const_evaluatable(self.ecx.tcx, self.ecx.typeck, e) => {
181 self.eagerness |= NoChange;
182 return;
183 },
184 #[expect(clippy::match_same_arms)] ExprKind::Struct(path, ..) => {
186 if res_has_significant_drop(self.ecx.typeck.qpath_res(path, e.hir_id), &self.ecx, e) {
187 self.eagerness = ForceNoChange;
188 return;
189 }
190 },
191 ExprKind::Path(ref path) => {
192 if res_has_significant_drop(self.ecx.typeck.qpath_res(path, e.hir_id), &self.ecx, e) {
193 self.eagerness = ForceNoChange;
194 return;
195 }
196 },
197 ExprKind::MethodCall(name, ..) => {
198 self.eagerness |= self
199 .ecx
200 .typeck
201 .type_dependent_def_id(e.hir_id)
202 .map_or(Lazy, |id| fn_eagerness(self.ecx.tcx, id, name.ident.name, true));
203 },
204 ExprKind::Index(_, e, _) => {
205 let ty = self.ecx.typeck.expr_ty_adjusted(e);
206 if self.ecx.tcx.type_is_copy_modulo_regions(self.ecx.typing_env, ty) && !ty.is_ref() {
207 self.eagerness |= NoChange;
208 } else {
209 self.eagerness = Lazy;
210 }
211 },
212
213 ExprKind::Unary(UnOp::Neg, right) if self.ecx.eval(right).is_none() => {
215 self.eagerness |= NoChange;
216 },
217
218 ExprKind::Unary(UnOp::Deref, e) if self.ecx.typeck.expr_ty(e).builtin_deref(true).is_none() => {
220 self.eagerness |= NoChange;
221 },
222 ExprKind::Unary(UnOp::Deref, e) if !self.ecx.typeck.expr_ty(e).is_raw_ptr() => (),
224 ExprKind::Unary(UnOp::Deref, _) => self.eagerness |= NoChange,
225 ExprKind::Unary(_, e)
226 if matches!(self.ecx.typeck.expr_ty(e).kind(), ty::Bool | ty::Int(_) | ty::Uint(_),) => {},
227
228 ExprKind::Binary(op, _, right)
234 if matches!(op.node, BinOpKind::Shl | BinOpKind::Shr) && self.ecx.eval(right).is_none() =>
235 {
236 self.eagerness |= NoChange;
237 },
238
239 ExprKind::Binary(op, left, right)
240 if matches!(op.node, BinOpKind::Div | BinOpKind::Rem)
241 && let right_ty = self.ecx.typeck.expr_ty(right)
242 && let left = self.ecx.eval(left)
243 && let right = self.ecx.eval(right).and_then(|c| c.int_value(self.ecx.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.ecx.typeck.expr_ty(e).is_floating_point()
262 && (self.ecx.eval(left).is_none() || self.ecx.eval(right).is_none()) =>
263 {
264 self.eagerness |= NoChange;
265 },
266
267 ExprKind::Binary(_, lhs, rhs)
268 if self.ecx.typeck.expr_ty(lhs).is_primitive() && self.ecx.typeck.expr_ty(rhs).is_primitive() => {},
269
270 ExprKind::Break(..)
272 | ExprKind::Continue(_)
273 | ExprKind::Ret(_)
274 | ExprKind::Become(_)
275 | ExprKind::InlineAsm(_)
276 | ExprKind::Yield(..)
277 | ExprKind::Err(_) => {
278 self.eagerness = ForceNoChange;
279 return;
280 },
281
282 ExprKind::Unary(..) | ExprKind::Binary(..) | ExprKind::Loop(..) | ExprKind::Call(..) => {
284 self.eagerness = Lazy;
285 },
286
287 ExprKind::ConstBlock(_)
288 | ExprKind::Array(_)
289 | ExprKind::Tup(_)
290 | ExprKind::Use(..)
291 | ExprKind::Lit(_)
292 | ExprKind::Cast(..)
293 | ExprKind::Type(..)
294 | ExprKind::DropTemps(_)
295 | ExprKind::Let(..)
296 | ExprKind::If(..)
297 | ExprKind::Match(..)
298 | ExprKind::Closure { .. }
299 | ExprKind::Field(..)
300 | ExprKind::AddrOf(..)
301 | ExprKind::Repeat(..)
302 | ExprKind::Block(Block { stmts: [], .. }, _)
303 | ExprKind::OffsetOf(..)
304 | ExprKind::UnsafeBinderCast(..) => (),
305
306 ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Block(..) => self.eagerness |= NoChange,
310 }
311 walk_expr(self, e);
312 }
313 }
314
315 let mut v = V {
316 ecx: ConstEvalCtxt::new(cx),
317 eagerness: EagernessSuggestion::Eager,
318 };
319 v.visit_expr(e);
320 v.eagerness
321}
322
323pub fn switch_to_eager_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
325 expr_eagerness(cx, expr) == EagernessSuggestion::Eager
326}
327
328pub fn switch_to_lazy_eval<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
330 expr_eagerness(cx, expr) == EagernessSuggestion::Lazy
331}