clippy_utils/
qualify_min_const_fn.rs

1// This code used to be a part of `rustc` but moved to Clippy as a result of
2// https://github.com/rust-lang/rust/issues/76618. Because of that, it contains unused code and some
3// of terminologies might not be relevant in the context of Clippy. Note that its behavior might
4// differ from the time of `rustc` even if the name stays the same.
5
6use 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    // impl trait is gone in MIR, so check the return type manually
36    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        // Cleanup blocks are ignored entirely by const eval, so we can too:
45        // https://github.com/rust-lang/rust/blob/1dea922ea6e74f99a0e97de5cdb8174e4dea0444/compiler/rustc_const_eval/src/transform/check_consts/check.rs#L382
46        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            // No constraints on lifetimes or constants, except potentially
62            // constants' types, but `walk` will get to them as well.
63            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                // We cannot allow this for now.
146                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                // Casting/coercing things to slices is fine.
154                Ok(())
155            } else {
156                // We just can't allow trait objects until we have figured out trait method calls.
157                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            // FIXME(dyn-star)
165            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        // binops are fine on integers
172        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        // just an assignment
223        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        // These are all NOPs
237        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                // HACK: This is to "unstabilize" the `transmute` intrinsic
365                // within const fns. `transmute` is allowed in all other const contexts.
366                // This won't really scale to more intrinsics or functions. Let's allow const
367                // transmutes in const fn before we add more hacks to this.
368                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                // Checking MSRV is manually necessary because `rustc` has no such concept. This entire
401                // function could be removed if `rustc` provided a MSRV-aware version of `is_stable_const_fn`.
402                // as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.
403
404                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                // Unstable const fn, check if the feature is enabled.
413                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    // FIXME(const_trait_impl, fee1-dead) revert to const destruct once it works again
420    #[expect(unused)]
421    fn is_ty_const_destruct_unused<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool {
422        // If this doesn't need drop at all, then don't select `~const Destruct`.
423        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        // FIXME(const_trait_impl) constness
429        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}