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