Skip to main content

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_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    // impl trait is gone in MIR, so check the return type manually
51    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        // Cleanup blocks are ignored entirely by const eval, so we can too:
65        // https://github.com/rust-lang/rust/blob/1dea922ea6e74f99a0e97de5cdb8174e4dea0444/compiler/rustc_const_eval/src/transform/check_consts/check.rs#L382
66        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            // No constraints on lifetimes or constants, except potentially
82            // constants' types, but `walk` will get to them as well.
83            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                // We cannot allow this for now.
170                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                // Casting/coercing things to slices is fine.
178                Ok(())
179            } else {
180                // We just can't allow trait objects until we have figured out trait method calls.
181                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        // binops are fine on integers
192        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        // just an assignment
238        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        // These are all NOPs
250        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                // FIXME: when analyzing a function with generic parameters, we may not have enough information to
366                // resolve to an instance. However, we could check if a host effect predicate can guarantee that
367                // this can be made a `const` call.
368                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                // HACK: This is to "unstabilize" the `transmute` intrinsic
385                // within const fns. `transmute` is allowed in all other const contexts.
386                // This won't really scale to more intrinsics or functions. Let's allow const
387                // transmutes in const fn before we add more hacks to this.
388                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
416/// Checks if the given `def_id` is a stable const fn, in respect to the given MSRV.
417pub 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                    // Checking MSRV is manually necessary because `rustc` has no such concept. This entire
430                    // function could be removed if `rustc` provided a MSRV-aware version of `is_stable_const_fn`.
431                    // as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.
432
433                    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                    // Unstable const fn, check if the feature is enabled.
442                    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    // FIXME(const_trait_impl, fee1-dead) revert to const destruct once it works again
449    #[expect(unused)]
450    fn is_ty_const_destruct_unused<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, body: &Body<'tcx>) -> bool {
451        // If this doesn't need drop at all, then don't select `[const] Destruct`.
452        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        // FIXME(const_trait_impl) constness
458        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}