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.
7 use rustc_hir
::def_id
::DefId
;
8 use rustc_middle
::mir
::{
9 Body
, CastKind
, NullOp
, Operand
, Place
, ProjectionElem
, Rvalue
, Statement
, StatementKind
, Terminator
,
12 use rustc_middle
::ty
::subst
::GenericArgKind
;
13 use rustc_middle
::ty
::{self, adjustment::PointerCast, Ty, TyCtxt}
;
14 use rustc_semver
::RustcVersion
;
15 use rustc_span
::symbol
::sym
;
19 type McfResult
= Result
<(), (Span
, Cow
<'
static, str>)>;
21 pub fn is_min_const_fn
<'a
, 'tcx
>(tcx
: TyCtxt
<'tcx
>, body
: &'a Body
<'tcx
>, msrv
: Option
<RustcVersion
>) -> McfResult
{
22 let def_id
= body
.source
.def_id();
23 let mut current
= def_id
;
25 let predicates
= tcx
.predicates_of(current
);
26 for (predicate
, _
) in predicates
.predicates
{
27 match predicate
.kind().skip_binder() {
28 ty
::PredicateKind
::RegionOutlives(_
)
29 | ty
::PredicateKind
::TypeOutlives(_
)
30 | ty
::PredicateKind
::WellFormed(_
)
31 | ty
::PredicateKind
::Projection(_
)
32 | ty
::PredicateKind
::ConstEvaluatable(..)
33 | ty
::PredicateKind
::ConstEquate(..)
34 | ty
::PredicateKind
::Trait(..)
35 | ty
::PredicateKind
::TypeWellFormedFromEnv(..) => continue,
36 ty
::PredicateKind
::ObjectSafe(_
) => panic
!("object safe predicate on function: {:#?}", predicate
),
37 ty
::PredicateKind
::ClosureKind(..) => panic
!("closure kind predicate on function: {:#?}", predicate
),
38 ty
::PredicateKind
::Subtype(_
) => panic
!("subtype predicate on function: {:#?}", predicate
),
39 ty
::PredicateKind
::Coerce(_
) => panic
!("coerce predicate on function: {:#?}", predicate
),
42 match predicates
.parent
{
43 Some(parent
) => current
= parent
,
48 for local
in &body
.local_decls
{
49 check_ty(tcx
, local
.ty
, local
.source_info
.span
)?
;
51 // impl trait is gone in MIR, so check the return type manually
54 tcx
.fn_sig(def_id
).output().skip_binder(),
55 body
.local_decls
.iter().next().unwrap().source_info
.span
,
58 for bb
in body
.basic_blocks() {
59 check_terminator(tcx
, body
, bb
.terminator(), msrv
)?
;
60 for stmt
in &bb
.statements
{
61 check_statement(tcx
, body
, def_id
, stmt
)?
;
67 fn check_ty
<'tcx
>(tcx
: TyCtxt
<'tcx
>, ty
: Ty
<'tcx
>, span
: Span
) -> McfResult
{
68 for arg
in ty
.walk() {
69 let ty
= match arg
.unpack() {
70 GenericArgKind
::Type(ty
) => ty
,
72 // No constraints on lifetimes or constants, except potentially
73 // constants' types, but `walk` will get to them as well.
74 GenericArgKind
::Lifetime(_
) | GenericArgKind
::Const(_
) => continue,
78 ty
::Ref(_
, _
, hir
::Mutability
::Mut
) => {
79 return Err((span
, "mutable references in const fn are unstable".into()));
81 ty
::Opaque(..) => return Err((span
, "`impl Trait` in const fn is unstable".into())),
83 return Err((span
, "function pointers in const fn are unstable".into()));
85 ty
::Dynamic(preds
, _
) => {
86 for pred
in preds
.iter() {
87 match pred
.skip_binder() {
88 ty
::ExistentialPredicate
::AutoTrait(_
) | ty
::ExistentialPredicate
::Projection(_
) => {
91 "trait bounds other than `Sized` \
92 on const fn parameters are unstable"
96 ty
::ExistentialPredicate
::Trait(trait_ref
) => {
97 if Some(trait_ref
.def_id
) != tcx
.lang_items().sized_trait() {
100 "trait bounds other than `Sized` \
101 on const fn parameters are unstable"
115 fn check_rvalue
<'tcx
>(
119 rvalue
: &Rvalue
<'tcx
>,
123 Rvalue
::ThreadLocalRef(_
) => Err((span
, "cannot access thread local storage in const fn".into())),
124 Rvalue
::Len(place
) | Rvalue
::Discriminant(place
) | Rvalue
::Ref(_
, _
, place
) | Rvalue
::AddressOf(_
, place
) => {
125 check_place(tcx
, *place
, span
, body
)
127 Rvalue
::CopyForDeref(place
) => check_place(tcx
, *place
, span
, body
),
128 Rvalue
::Repeat(operand
, _
)
129 | Rvalue
::Use(operand
)
131 CastKind
::PointerFromExposedAddress
133 | CastKind
::Pointer(PointerCast
::MutToConstPointer
| PointerCast
::ArrayToPointer
),
136 ) => check_operand(tcx
, operand
, span
, body
),
139 PointerCast
::UnsafeFnPointer
| PointerCast
::ClosureFnPointer(_
) | PointerCast
::ReifyFnPointer
,
143 ) => Err((span
, "function pointer casts are not allowed in const fn".into())),
144 Rvalue
::Cast(CastKind
::Pointer(PointerCast
::Unsize
), op
, cast_ty
) => {
145 let pointee_ty
= if let Some(deref_ty
) = cast_ty
.builtin_deref(true) {
148 // We cannot allow this for now.
149 return Err((span
, "unsizing casts are only allowed for references right now".into()));
151 let unsized_ty
= tcx
.struct_tail_erasing_lifetimes(pointee_ty
, tcx
.param_env(def_id
));
152 if let ty
::Slice(_
) | ty
::Str
= unsized_ty
.kind() {
153 check_operand(tcx
, op
, span
, body
)?
;
154 // Casting/coercing things to slices is fine.
157 // We just can't allow trait objects until we have figured out trait method calls.
158 Err((span
, "unsizing casts are not allowed in const fn".into()))
161 Rvalue
::Cast(CastKind
::PointerExposeAddress
, _
, _
) => {
162 Err((span
, "casting pointers to ints is unstable in const fn".into()))
164 // binops are fine on integers
165 Rvalue
::BinaryOp(_
, box (lhs
, rhs
)) | Rvalue
::CheckedBinaryOp(_
, box (lhs
, rhs
)) => {
166 check_operand(tcx
, lhs
, span
, body
)?
;
167 check_operand(tcx
, rhs
, span
, body
)?
;
168 let ty
= lhs
.ty(body
, tcx
);
169 if ty
.is_integral() || ty
.is_bool() || ty
.is_char() {
174 "only int, `bool` and `char` operations are stable in const fn".into(),
178 Rvalue
::NullaryOp(NullOp
::SizeOf
| NullOp
::AlignOf
, _
) | Rvalue
::ShallowInitBox(_
, _
) => Ok(()),
179 Rvalue
::UnaryOp(_
, operand
) => {
180 let ty
= operand
.ty(body
, tcx
);
181 if ty
.is_integral() || ty
.is_bool() {
182 check_operand(tcx
, operand
, span
, body
)
184 Err((span
, "only int and `bool` operations are stable in const fn".into()))
187 Rvalue
::Aggregate(_
, operands
) => {
188 for operand
in operands
{
189 check_operand(tcx
, operand
, span
, body
)?
;
196 fn check_statement
<'tcx
>(
200 statement
: &Statement
<'tcx
>,
202 let span
= statement
.source_info
.span
;
203 match &statement
.kind
{
204 StatementKind
::Assign(box (place
, rval
)) => {
205 check_place(tcx
, *place
, span
, body
)?
;
206 check_rvalue(tcx
, body
, def_id
, rval
, span
)
209 StatementKind
::FakeRead(box (_
, place
)) => check_place(tcx
, *place
, span
, body
),
210 // just an assignment
211 StatementKind
::SetDiscriminant { place, .. }
| StatementKind
::Deinit(place
) => {
212 check_place(tcx
, **place
, span
, body
)
215 StatementKind
::CopyNonOverlapping(box rustc_middle
::mir
::CopyNonOverlapping { dst, src, count }
) => {
216 check_operand(tcx
, dst
, span
, body
)?
;
217 check_operand(tcx
, src
, span
, body
)?
;
218 check_operand(tcx
, count
, span
, body
)
220 // These are all NOPs
221 StatementKind
::StorageLive(_
)
222 | StatementKind
::StorageDead(_
)
223 | StatementKind
::Retag { .. }
224 | StatementKind
::AscribeUserType(..)
225 | StatementKind
::Coverage(..)
226 | StatementKind
::Nop
=> Ok(()),
230 fn check_operand
<'tcx
>(tcx
: TyCtxt
<'tcx
>, operand
: &Operand
<'tcx
>, span
: Span
, body
: &Body
<'tcx
>) -> McfResult
{
232 Operand
::Move(place
) | Operand
::Copy(place
) => check_place(tcx
, *place
, span
, body
),
233 Operand
::Constant(c
) => match c
.check_static_ptr(tcx
) {
234 Some(_
) => Err((span
, "cannot access `static` items in const fn".into())),
240 fn check_place
<'tcx
>(tcx
: TyCtxt
<'tcx
>, place
: Place
<'tcx
>, span
: Span
, body
: &Body
<'tcx
>) -> McfResult
{
241 let mut cursor
= place
.projection
.as_ref();
242 while let [ref proj_base @
.., elem
] = *cursor
{
245 ProjectionElem
::Field(..) => {
246 let base_ty
= Place
::ty_from(place
.local
, proj_base
, body
, tcx
).ty
;
247 if let Some(def
) = base_ty
.ty_adt_def() {
248 // No union field accesses in `const fn`
250 return Err((span
, "accessing union fields is unstable".into()));
254 ProjectionElem
::ConstantIndex { .. }
255 | ProjectionElem
::Downcast(..)
256 | ProjectionElem
::Subslice { .. }
257 | ProjectionElem
::Deref
258 | ProjectionElem
::Index(_
) => {}
,
265 fn check_terminator
<'a
, 'tcx
>(
267 body
: &'a Body
<'tcx
>,
268 terminator
: &Terminator
<'tcx
>,
269 msrv
: Option
<RustcVersion
>,
271 let span
= terminator
.source_info
.span
;
272 match &terminator
.kind
{
273 TerminatorKind
::FalseEdge { .. }
274 | TerminatorKind
::FalseUnwind { .. }
275 | TerminatorKind
::Goto { .. }
276 | TerminatorKind
::Return
277 | TerminatorKind
::Resume
278 | TerminatorKind
::Unreachable
=> Ok(()),
280 TerminatorKind
::Drop { place, .. }
=> check_place(tcx
, *place
, span
, body
),
281 TerminatorKind
::DropAndReplace { place, value, .. }
=> {
282 check_place(tcx
, *place
, span
, body
)?
;
283 check_operand(tcx
, value
, span
, body
)
286 TerminatorKind
::SwitchInt
{
290 } => check_operand(tcx
, discr
, span
, body
),
292 TerminatorKind
::Abort
=> Err((span
, "abort is not stable in const fn".into())),
293 TerminatorKind
::GeneratorDrop
| TerminatorKind
::Yield { .. }
=> {
294 Err((span
, "const fn generators are unstable".into()))
297 TerminatorKind
::Call
{
306 let fn_ty
= func
.ty(body
, tcx
);
307 if let ty
::FnDef(fn_def_id
, _
) = *fn_ty
.kind() {
308 if !is_const_fn(tcx
, fn_def_id
, msrv
) {
312 "can only call other `const fn` within a `const fn`, \
313 but `{:?}` is not stable as `const fn`",
320 // HACK: This is to "unstabilize" the `transmute` intrinsic
321 // within const fns. `transmute` is allowed in all other const contexts.
322 // This won't really scale to more intrinsics or functions. Let's allow const
323 // transmutes in const fn before we add more hacks to this.
324 if tcx
.is_intrinsic(fn_def_id
) && tcx
.item_name(fn_def_id
) == sym
::transmute
{
327 "can only call `transmute` from const items, not `const fn`".into(),
331 check_operand(tcx
, func
, span
, body
)?
;
334 check_operand(tcx
, arg
, span
, body
)?
;
338 Err((span
, "can only call other const fns within const fn".into()))
342 TerminatorKind
::Assert
{
348 } => check_operand(tcx
, cond
, span
, body
),
350 TerminatorKind
::InlineAsm { .. }
=> Err((span
, "cannot use inline assembly in const fn".into())),
354 fn is_const_fn(tcx
: TyCtxt
<'_
>, def_id
: DefId
, msrv
: Option
<RustcVersion
>) -> bool
{
355 tcx
.is_const_fn(def_id
)
356 && tcx
.lookup_const_stability(def_id
).map_or(true, |const_stab
| {
357 if let rustc_attr
::StabilityLevel
::Stable { since, .. }
= const_stab
.level
{
358 // Checking MSRV is manually necessary because `rustc` has no such concept. This entire
359 // function could be removed if `rustc` provided a MSRV-aware version of `is_const_fn`.
360 // as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.
363 RustcVersion
::parse(since
.as_str())
364 .expect("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted"),
367 // Unstable const fn with the feature enabled.