1use crate::msrvs::{self, Msrv};
7use hir::LangItem;
8use rustc_attr_parsing::{RustcVersion, StableSince};
9use rustc_const_eval::check_consts::ConstCx;
10use rustc_hir as hir;
11use rustc_hir::def_id::DefId;
12use rustc_infer::infer::TyCtxtInferExt;
13use rustc_infer::traits::Obligation;
14use rustc_lint::LateContext;
15use rustc_middle::mir::{
16 Body, CastKind, NonDivergingIntrinsic, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind,
17 Terminator, TerminatorKind,
18};
19use rustc_middle::traits::{BuiltinImplSource, ImplSource, ObligationCause};
20use rustc_middle::ty::adjustment::PointerCoercion;
21use rustc_middle::ty::{self, GenericArgKind, TraitRef, Ty, TyCtxt};
22use rustc_span::Span;
23use rustc_span::symbol::sym;
24use rustc_trait_selection::traits::{ObligationCtxt, SelectionContext};
25use std::borrow::Cow;
26
27type McfResult = Result<(), (Span, Cow<'static, str>)>;
28
29pub fn is_min_const_fn<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, msrv: Msrv) -> McfResult {
30 let def_id = body.source.def_id();
31
32 for local in &body.local_decls {
33 check_ty(cx, local.ty, local.source_info.span, msrv)?;
34 }
35 check_ty(
37 cx,
38 cx.tcx.fn_sig(def_id).instantiate_identity().output().skip_binder(),
39 body.local_decls.iter().next().unwrap().source_info.span,
40 msrv,
41 )?;
42
43 for bb in &*body.basic_blocks {
44 if !bb.is_cleanup {
47 check_terminator(cx, body, bb.terminator(), msrv)?;
48 for stmt in &bb.statements {
49 check_statement(cx, body, def_id, stmt, msrv)?;
50 }
51 }
52 }
53 Ok(())
54}
55
56fn check_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, msrv: Msrv) -> McfResult {
57 for arg in ty.walk() {
58 let ty = match arg.unpack() {
59 GenericArgKind::Type(ty) => ty,
60
61 GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue,
64 };
65
66 match ty.kind() {
67 ty::Ref(_, _, hir::Mutability::Mut) if !msrv.meets(cx, msrvs::CONST_MUT_REFS) => {
68 return Err((span, "mutable references in const fn are unstable".into()));
69 },
70 ty::Alias(ty::Opaque, ..) => return Err((span, "`impl Trait` in const fn is unstable".into())),
71 ty::FnPtr(..) => {
72 return Err((span, "function pointers in const fn are unstable".into()));
73 },
74 ty::Dynamic(preds, _, _) => {
75 for pred in *preds {
76 match pred.skip_binder() {
77 ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => {
78 return Err((
79 span,
80 "trait bounds other than `Sized` \
81 on const fn parameters are unstable"
82 .into(),
83 ));
84 },
85 ty::ExistentialPredicate::Trait(trait_ref) => {
86 if Some(trait_ref.def_id) != cx.tcx.lang_items().sized_trait() {
87 return Err((
88 span,
89 "trait bounds other than `Sized` \
90 on const fn parameters are unstable"
91 .into(),
92 ));
93 }
94 },
95 }
96 }
97 },
98 _ => {},
99 }
100 }
101 Ok(())
102}
103
104fn check_rvalue<'tcx>(
105 cx: &LateContext<'tcx>,
106 body: &Body<'tcx>,
107 def_id: DefId,
108 rvalue: &Rvalue<'tcx>,
109 span: Span,
110 msrv: Msrv,
111) -> McfResult {
112 match rvalue {
113 Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())),
114 Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::RawPtr(_, place) => {
115 check_place(cx, *place, span, body, msrv)
116 },
117 Rvalue::CopyForDeref(place) => check_place(cx, *place, span, body, msrv),
118 Rvalue::Repeat(operand, _)
119 | Rvalue::Use(operand)
120 | Rvalue::WrapUnsafeBinder(operand, _)
121 | Rvalue::Cast(
122 CastKind::PointerWithExposedProvenance
123 | CastKind::IntToInt
124 | CastKind::FloatToInt
125 | CastKind::IntToFloat
126 | CastKind::FloatToFloat
127 | CastKind::FnPtrToPtr
128 | CastKind::PtrToPtr
129 | CastKind::PointerCoercion(PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, _),
130 operand,
131 _,
132 ) => check_operand(cx, operand, span, body, msrv),
133 Rvalue::Cast(
134 CastKind::PointerCoercion(
135 PointerCoercion::UnsafeFnPointer
136 | PointerCoercion::ClosureFnPointer(_)
137 | PointerCoercion::ReifyFnPointer,
138 _,
139 ),
140 _,
141 _,
142 ) => Err((span, "function pointer casts are not allowed in const fn".into())),
143 Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize, _), op, cast_ty) => {
144 let Some(pointee_ty) = cast_ty.builtin_deref(true) else {
145 return Err((span, "unsizing casts are only allowed for references right now".into()));
147 };
148 let unsized_ty = cx
149 .tcx
150 .struct_tail_for_codegen(pointee_ty, ty::TypingEnv::post_analysis(cx.tcx, def_id));
151 if let ty::Slice(_) | ty::Str = unsized_ty.kind() {
152 check_operand(cx, op, span, body, msrv)?;
153 Ok(())
155 } else {
156 Err((span, "unsizing casts are not allowed in const fn".into()))
158 }
159 },
160 Rvalue::Cast(CastKind::PointerExposeProvenance, _, _) => {
161 Err((span, "casting pointers to ints is unstable in const fn".into()))
162 },
163 Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::DynStar, _), _, _) => {
164 unimplemented!()
166 },
167 Rvalue::Cast(CastKind::Transmute, _, _) => Err((
168 span,
169 "transmute can attempt to turn pointers into integers, so is unstable in const fn".into(),
170 )),
171 Rvalue::BinaryOp(_, box (lhs, rhs)) => {
173 check_operand(cx, lhs, span, body, msrv)?;
174 check_operand(cx, rhs, span, body, msrv)?;
175 let ty = lhs.ty(body, cx.tcx);
176 if ty.is_integral() || ty.is_bool() || ty.is_char() {
177 Ok(())
178 } else {
179 Err((
180 span,
181 "only int, `bool` and `char` operations are stable in const fn".into(),
182 ))
183 }
184 },
185 Rvalue::NullaryOp(
186 NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_) | NullOp::UbChecks | NullOp::ContractChecks,
187 _,
188 )
189 | Rvalue::ShallowInitBox(_, _) => Ok(()),
190 Rvalue::UnaryOp(_, operand) => {
191 let ty = operand.ty(body, cx.tcx);
192 if ty.is_integral() || ty.is_bool() {
193 check_operand(cx, operand, span, body, msrv)
194 } else {
195 Err((span, "only int and `bool` operations are stable in const fn".into()))
196 }
197 },
198 Rvalue::Aggregate(_, operands) => {
199 for operand in operands {
200 check_operand(cx, operand, span, body, msrv)?;
201 }
202 Ok(())
203 },
204 }
205}
206
207fn check_statement<'tcx>(
208 cx: &LateContext<'tcx>,
209 body: &Body<'tcx>,
210 def_id: DefId,
211 statement: &Statement<'tcx>,
212 msrv: Msrv,
213) -> McfResult {
214 let span = statement.source_info.span;
215 match &statement.kind {
216 StatementKind::Assign(box (place, rval)) => {
217 check_place(cx, *place, span, body, msrv)?;
218 check_rvalue(cx, body, def_id, rval, span, msrv)
219 },
220
221 StatementKind::FakeRead(box (_, place)) => check_place(cx, *place, span, body, msrv),
222 StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => {
224 check_place(cx, **place, span, body, msrv)
225 },
226
227 StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(cx, op, span, body, msrv),
228
229 StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping(
230 rustc_middle::mir::CopyNonOverlapping { dst, src, count },
231 )) => {
232 check_operand(cx, dst, span, body, msrv)?;
233 check_operand(cx, src, span, body, msrv)?;
234 check_operand(cx, count, span, body, msrv)
235 },
236 StatementKind::StorageLive(_)
238 | StatementKind::StorageDead(_)
239 | StatementKind::Retag { .. }
240 | StatementKind::AscribeUserType(..)
241 | StatementKind::PlaceMention(..)
242 | StatementKind::Coverage(..)
243 | StatementKind::ConstEvalCounter
244 | StatementKind::BackwardIncompatibleDropHint { .. }
245 | StatementKind::Nop => Ok(()),
246 }
247}
248
249fn check_operand<'tcx>(
250 cx: &LateContext<'tcx>,
251 operand: &Operand<'tcx>,
252 span: Span,
253 body: &Body<'tcx>,
254 msrv: Msrv,
255) -> McfResult {
256 match operand {
257 Operand::Move(place) => {
258 if !place.projection.as_ref().is_empty()
259 && !is_ty_const_destruct(cx.tcx, place.ty(&body.local_decls, cx.tcx).ty, body)
260 {
261 return Err((
262 span,
263 "cannot drop locals with a non constant destructor in const fn".into(),
264 ));
265 }
266
267 check_place(cx, *place, span, body, msrv)
268 },
269 Operand::Copy(place) => check_place(cx, *place, span, body, msrv),
270 Operand::Constant(c) => match c.check_static_ptr(cx.tcx) {
271 Some(_) => Err((span, "cannot access `static` items in const fn".into())),
272 None => Ok(()),
273 },
274 }
275}
276
277fn check_place<'tcx>(
278 cx: &LateContext<'tcx>,
279 place: Place<'tcx>,
280 span: Span,
281 body: &Body<'tcx>,
282 msrv: Msrv,
283) -> McfResult {
284 for (base, elem) in place.as_ref().iter_projections() {
285 match elem {
286 ProjectionElem::Field(..) => {
287 if base.ty(body, cx.tcx).ty.is_union() && !msrv.meets(cx, msrvs::CONST_FN_UNION) {
288 return Err((span, "accessing union fields is unstable".into()));
289 }
290 },
291 ProjectionElem::Deref => match base.ty(body, cx.tcx).ty.kind() {
292 ty::RawPtr(_, hir::Mutability::Mut) => {
293 return Err((span, "dereferencing raw mut pointer in const fn is unstable".into()));
294 },
295 ty::RawPtr(_, hir::Mutability::Not) if !msrv.meets(cx, msrvs::CONST_RAW_PTR_DEREF) => {
296 return Err((span, "dereferencing raw const pointer in const fn is unstable".into()));
297 },
298 _ => (),
299 },
300 ProjectionElem::ConstantIndex { .. }
301 | ProjectionElem::OpaqueCast(..)
302 | ProjectionElem::Downcast(..)
303 | ProjectionElem::Subslice { .. }
304 | ProjectionElem::Subtype(_)
305 | ProjectionElem::Index(_)
306 | ProjectionElem::UnwrapUnsafeBinder(_) => {},
307 }
308 }
309
310 Ok(())
311}
312
313fn check_terminator<'tcx>(
314 cx: &LateContext<'tcx>,
315 body: &Body<'tcx>,
316 terminator: &Terminator<'tcx>,
317 msrv: Msrv,
318) -> McfResult {
319 let span = terminator.source_info.span;
320 match &terminator.kind {
321 TerminatorKind::FalseEdge { .. }
322 | TerminatorKind::FalseUnwind { .. }
323 | TerminatorKind::Goto { .. }
324 | TerminatorKind::Return
325 | TerminatorKind::UnwindResume
326 | TerminatorKind::UnwindTerminate(_)
327 | TerminatorKind::Unreachable => Ok(()),
328 TerminatorKind::Drop { place, .. } => {
329 if !is_ty_const_destruct(cx.tcx, place.ty(&body.local_decls, cx.tcx).ty, body) {
330 return Err((
331 span,
332 "cannot drop locals with a non constant destructor in const fn".into(),
333 ));
334 }
335 Ok(())
336 },
337 TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(cx, discr, span, body, msrv),
338 TerminatorKind::CoroutineDrop | TerminatorKind::Yield { .. } => {
339 Err((span, "const fn coroutines are unstable".into()))
340 },
341 TerminatorKind::Call {
342 func,
343 args,
344 call_source: _,
345 destination: _,
346 target: _,
347 unwind: _,
348 fn_span: _,
349 }
350 | TerminatorKind::TailCall { func, args, fn_span: _ } => {
351 let fn_ty = func.ty(body, cx.tcx);
352 if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
353 if !is_stable_const_fn(cx, fn_def_id, msrv) {
354 return Err((
355 span,
356 format!(
357 "can only call other `const fn` within a `const fn`, \
358 but `{func:?}` is not stable as `const fn`",
359 )
360 .into(),
361 ));
362 }
363
364 if cx.tcx.is_intrinsic(fn_def_id, sym::transmute) {
369 return Err((
370 span,
371 "can only call `transmute` from const items, not `const fn`".into(),
372 ));
373 }
374
375 check_operand(cx, func, span, body, msrv)?;
376
377 for arg in args {
378 check_operand(cx, &arg.node, span, body, msrv)?;
379 }
380 Ok(())
381 } else {
382 Err((span, "can only call other const fns within const fn".into()))
383 }
384 },
385 TerminatorKind::Assert {
386 cond,
387 expected: _,
388 msg: _,
389 target: _,
390 unwind: _,
391 } => check_operand(cx, cond, span, body, msrv),
392 TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
393 }
394}
395
396fn is_stable_const_fn(cx: &LateContext<'_>, def_id: DefId, msrv: Msrv) -> bool {
397 cx.tcx.is_const_fn(def_id)
398 && cx.tcx.lookup_const_stability(def_id).is_none_or(|const_stab| {
399 if let rustc_attr_parsing::StabilityLevel::Stable { since, .. } = const_stab.level {
400 let const_stab_rust_version = match since {
405 StableSince::Version(version) => version,
406 StableSince::Current => RustcVersion::CURRENT,
407 StableSince::Err => return false,
408 };
409
410 msrv.meets(cx, const_stab_rust_version)
411 } else {
412 cx.tcx.features().enabled(const_stab.feature) && msrv.current(cx).is_none()
414 }
415 })
416}
417
418fn is_ty_const_destruct<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool {
419 #[expect(unused)]
421 fn is_ty_const_destruct_unused<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool {
422 if !ty.needs_drop(tcx, body.typing_env(tcx)) {
424 return false;
425 }
426
427 let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(body.typing_env(tcx));
428 let obligation = Obligation::new(
430 tcx,
431 ObligationCause::dummy_with_span(body.span),
432 param_env,
433 TraitRef::new(tcx, tcx.require_lang_item(LangItem::Destruct, Some(body.span)), [ty]),
434 );
435
436 let mut selcx = SelectionContext::new(&infcx);
437 let Some(impl_src) = selcx.select(&obligation).ok().flatten() else {
438 return false;
439 };
440
441 if !matches!(
442 impl_src,
443 ImplSource::Builtin(BuiltinImplSource::Misc, _) | ImplSource::Param(_)
444 ) {
445 return false;
446 }
447
448 let ocx = ObligationCtxt::new(&infcx);
449 ocx.register_obligations(impl_src.nested_obligations());
450 ocx.select_all_or_error().is_empty()
451 }
452
453 !ty.needs_drop(tcx, ConstCx::new(tcx, body).typing_env)
454}