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