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