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