]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
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 | ||
f20569fa XL |
6 | use rustc_hir as hir; |
7 | use rustc_hir::def_id::DefId; | |
8 | use rustc_middle::mir::{ | |
f2b60f7d FG |
9 | Body, CastKind, NonDivergingIntrinsic, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind, |
10 | Terminator, TerminatorKind, | |
f20569fa XL |
11 | }; |
12 | use rustc_middle::ty::subst::GenericArgKind; | |
13 | use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt}; | |
cdc7bbd5 | 14 | use rustc_semver::RustcVersion; |
f20569fa XL |
15 | use rustc_span::symbol::sym; |
16 | use rustc_span::Span; | |
f20569fa XL |
17 | use std::borrow::Cow; |
18 | ||
19 | type McfResult = Result<(), (Span, Cow<'static, str>)>; | |
20 | ||
923072b8 | 21 | pub fn is_min_const_fn<'a, 'tcx>(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv: Option<RustcVersion>) -> McfResult { |
f20569fa XL |
22 | let def_id = body.source.def_id(); |
23 | let mut current = def_id; | |
24 | loop { | |
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(..) | |
5e7ed085 | 34 | | ty::PredicateKind::Trait(..) |
f20569fa XL |
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), | |
c295e0f8 | 39 | ty::PredicateKind::Coerce(_) => panic!("coerce predicate on function: {:#?}", predicate), |
f20569fa XL |
40 | } |
41 | } | |
42 | match predicates.parent { | |
43 | Some(parent) => current = parent, | |
44 | None => break, | |
45 | } | |
46 | } | |
47 | ||
48 | for local in &body.local_decls { | |
49 | check_ty(tcx, local.ty, local.source_info.span)?; | |
50 | } | |
51 | // impl trait is gone in MIR, so check the return type manually | |
52 | check_ty( | |
53 | tcx, | |
54 | tcx.fn_sig(def_id).output().skip_binder(), | |
55 | body.local_decls.iter().next().unwrap().source_info.span, | |
56 | )?; | |
57 | ||
f2b60f7d | 58 | for bb in body.basic_blocks.iter() { |
cdc7bbd5 | 59 | check_terminator(tcx, body, bb.terminator(), msrv)?; |
f20569fa XL |
60 | for stmt in &bb.statements { |
61 | check_statement(tcx, body, def_id, stmt)?; | |
62 | } | |
63 | } | |
64 | Ok(()) | |
65 | } | |
66 | ||
5099ac24 FG |
67 | fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult { |
68 | for arg in ty.walk() { | |
f20569fa XL |
69 | let ty = match arg.unpack() { |
70 | GenericArgKind::Type(ty) => ty, | |
71 | ||
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, | |
75 | }; | |
76 | ||
77 | match ty.kind() { | |
78 | ty::Ref(_, _, hir::Mutability::Mut) => { | |
79 | return Err((span, "mutable references in const fn are unstable".into())); | |
80 | }, | |
81 | ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())), | |
82 | ty::FnPtr(..) => { | |
83 | return Err((span, "function pointers in const fn are unstable".into())); | |
84 | }, | |
f2b60f7d | 85 | ty::Dynamic(preds, _, _) => { |
f20569fa XL |
86 | for pred in preds.iter() { |
87 | match pred.skip_binder() { | |
88 | ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => { | |
89 | return Err(( | |
90 | span, | |
91 | "trait bounds other than `Sized` \ | |
92 | on const fn parameters are unstable" | |
93 | .into(), | |
94 | )); | |
95 | }, | |
96 | ty::ExistentialPredicate::Trait(trait_ref) => { | |
97 | if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() { | |
98 | return Err(( | |
99 | span, | |
100 | "trait bounds other than `Sized` \ | |
101 | on const fn parameters are unstable" | |
102 | .into(), | |
103 | )); | |
104 | } | |
105 | }, | |
106 | } | |
107 | } | |
108 | }, | |
109 | _ => {}, | |
110 | } | |
111 | } | |
112 | Ok(()) | |
113 | } | |
114 | ||
5099ac24 FG |
115 | fn check_rvalue<'tcx>( |
116 | tcx: TyCtxt<'tcx>, | |
117 | body: &Body<'tcx>, | |
118 | def_id: DefId, | |
119 | rvalue: &Rvalue<'tcx>, | |
120 | span: Span, | |
121 | ) -> McfResult { | |
f20569fa XL |
122 | match rvalue { |
123 | Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())), | |
f20569fa XL |
124 | Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => { |
125 | check_place(tcx, *place, span, body) | |
126 | }, | |
064997fb | 127 | Rvalue::CopyForDeref(place) => check_place(tcx, *place, span, body), |
923072b8 FG |
128 | Rvalue::Repeat(operand, _) |
129 | | Rvalue::Use(operand) | |
130 | | Rvalue::Cast( | |
131 | CastKind::PointerFromExposedAddress | |
132 | | CastKind::Misc | |
133 | | CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer), | |
134 | operand, | |
135 | _, | |
136 | ) => check_operand(tcx, operand, span, body), | |
f20569fa XL |
137 | Rvalue::Cast( |
138 | CastKind::Pointer( | |
139 | PointerCast::UnsafeFnPointer | PointerCast::ClosureFnPointer(_) | PointerCast::ReifyFnPointer, | |
140 | ), | |
141 | _, | |
142 | _, | |
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) { | |
146 | deref_ty.ty | |
147 | } else { | |
148 | // We cannot allow this for now. | |
149 | return Err((span, "unsizing casts are only allowed for references right now".into())); | |
150 | }; | |
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. | |
155 | Ok(()) | |
156 | } else { | |
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())) | |
159 | } | |
160 | }, | |
923072b8 FG |
161 | Rvalue::Cast(CastKind::PointerExposeAddress, _, _) => { |
162 | Err((span, "casting pointers to ints is unstable in const fn".into())) | |
163 | }, | |
f2b60f7d FG |
164 | Rvalue::Cast(CastKind::DynStar, _, _) => { |
165 | // FIXME(dyn-star) | |
166 | unimplemented!() | |
167 | }, | |
f20569fa XL |
168 | // binops are fine on integers |
169 | Rvalue::BinaryOp(_, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(_, box (lhs, rhs)) => { | |
170 | check_operand(tcx, lhs, span, body)?; | |
171 | check_operand(tcx, rhs, span, body)?; | |
172 | let ty = lhs.ty(body, tcx); | |
173 | if ty.is_integral() || ty.is_bool() || ty.is_char() { | |
174 | Ok(()) | |
175 | } else { | |
176 | Err(( | |
177 | span, | |
178 | "only int, `bool` and `char` operations are stable in const fn".into(), | |
179 | )) | |
180 | } | |
181 | }, | |
c295e0f8 | 182 | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) | Rvalue::ShallowInitBox(_, _) => Ok(()), |
f20569fa XL |
183 | Rvalue::UnaryOp(_, operand) => { |
184 | let ty = operand.ty(body, tcx); | |
185 | if ty.is_integral() || ty.is_bool() { | |
186 | check_operand(tcx, operand, span, body) | |
187 | } else { | |
188 | Err((span, "only int and `bool` operations are stable in const fn".into())) | |
189 | } | |
190 | }, | |
191 | Rvalue::Aggregate(_, operands) => { | |
192 | for operand in operands { | |
193 | check_operand(tcx, operand, span, body)?; | |
194 | } | |
195 | Ok(()) | |
196 | }, | |
197 | } | |
198 | } | |
199 | ||
5099ac24 FG |
200 | fn check_statement<'tcx>( |
201 | tcx: TyCtxt<'tcx>, | |
202 | body: &Body<'tcx>, | |
203 | def_id: DefId, | |
204 | statement: &Statement<'tcx>, | |
205 | ) -> McfResult { | |
f20569fa XL |
206 | let span = statement.source_info.span; |
207 | match &statement.kind { | |
208 | StatementKind::Assign(box (place, rval)) => { | |
209 | check_place(tcx, *place, span, body)?; | |
210 | check_rvalue(tcx, body, def_id, rval, span) | |
cdc7bbd5 | 211 | }, |
f20569fa | 212 | |
cdc7bbd5 | 213 | StatementKind::FakeRead(box (_, place)) => check_place(tcx, *place, span, body), |
f20569fa | 214 | // just an assignment |
04454e1e FG |
215 | StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => { |
216 | check_place(tcx, **place, span, body) | |
217 | }, | |
f20569fa | 218 | |
f2b60f7d FG |
219 | StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(tcx, op, span, body), |
220 | ||
221 | StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping( | |
222 | rustc_middle::mir::CopyNonOverlapping { dst, src, count }, | |
223 | )) => { | |
cdc7bbd5 XL |
224 | check_operand(tcx, dst, span, body)?; |
225 | check_operand(tcx, src, span, body)?; | |
226 | check_operand(tcx, count, span, body) | |
227 | }, | |
f20569fa XL |
228 | // These are all NOPs |
229 | StatementKind::StorageLive(_) | |
230 | | StatementKind::StorageDead(_) | |
231 | | StatementKind::Retag { .. } | |
232 | | StatementKind::AscribeUserType(..) | |
233 | | StatementKind::Coverage(..) | |
234 | | StatementKind::Nop => Ok(()), | |
235 | } | |
236 | } | |
237 | ||
5099ac24 | 238 | fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult { |
f20569fa XL |
239 | match operand { |
240 | Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, body), | |
241 | Operand::Constant(c) => match c.check_static_ptr(tcx) { | |
242 | Some(_) => Err((span, "cannot access `static` items in const fn".into())), | |
243 | None => Ok(()), | |
244 | }, | |
245 | } | |
246 | } | |
247 | ||
5099ac24 | 248 | fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult { |
f20569fa XL |
249 | let mut cursor = place.projection.as_ref(); |
250 | while let [ref proj_base @ .., elem] = *cursor { | |
251 | cursor = proj_base; | |
252 | match elem { | |
253 | ProjectionElem::Field(..) => { | |
17df50a5 | 254 | let base_ty = Place::ty_from(place.local, proj_base, body, tcx).ty; |
f20569fa XL |
255 | if let Some(def) = base_ty.ty_adt_def() { |
256 | // No union field accesses in `const fn` | |
257 | if def.is_union() { | |
258 | return Err((span, "accessing union fields is unstable".into())); | |
259 | } | |
260 | } | |
261 | }, | |
262 | ProjectionElem::ConstantIndex { .. } | |
263 | | ProjectionElem::Downcast(..) | |
264 | | ProjectionElem::Subslice { .. } | |
265 | | ProjectionElem::Deref | |
266 | | ProjectionElem::Index(_) => {}, | |
267 | } | |
268 | } | |
269 | ||
270 | Ok(()) | |
271 | } | |
272 | ||
5099ac24 | 273 | fn check_terminator<'a, 'tcx>( |
cdc7bbd5 XL |
274 | tcx: TyCtxt<'tcx>, |
275 | body: &'a Body<'tcx>, | |
276 | terminator: &Terminator<'tcx>, | |
923072b8 | 277 | msrv: Option<RustcVersion>, |
cdc7bbd5 | 278 | ) -> McfResult { |
f20569fa XL |
279 | let span = terminator.source_info.span; |
280 | match &terminator.kind { | |
281 | TerminatorKind::FalseEdge { .. } | |
282 | | TerminatorKind::FalseUnwind { .. } | |
283 | | TerminatorKind::Goto { .. } | |
284 | | TerminatorKind::Return | |
285 | | TerminatorKind::Resume | |
286 | | TerminatorKind::Unreachable => Ok(()), | |
287 | ||
288 | TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body), | |
289 | TerminatorKind::DropAndReplace { place, value, .. } => { | |
290 | check_place(tcx, *place, span, body)?; | |
291 | check_operand(tcx, value, span, body) | |
292 | }, | |
293 | ||
294 | TerminatorKind::SwitchInt { | |
295 | discr, | |
296 | switch_ty: _, | |
297 | targets: _, | |
298 | } => check_operand(tcx, discr, span, body), | |
299 | ||
300 | TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())), | |
301 | TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => { | |
302 | Err((span, "const fn generators are unstable".into())) | |
303 | }, | |
304 | ||
305 | TerminatorKind::Call { | |
306 | func, | |
307 | args, | |
308 | from_hir_call: _, | |
309 | destination: _, | |
923072b8 | 310 | target: _, |
f20569fa XL |
311 | cleanup: _, |
312 | fn_span: _, | |
313 | } => { | |
314 | let fn_ty = func.ty(body, tcx); | |
315 | if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { | |
cdc7bbd5 | 316 | if !is_const_fn(tcx, fn_def_id, msrv) { |
f20569fa XL |
317 | return Err(( |
318 | span, | |
319 | format!( | |
320 | "can only call other `const fn` within a `const fn`, \ | |
321 | but `{:?}` is not stable as `const fn`", | |
322 | func, | |
323 | ) | |
324 | .into(), | |
325 | )); | |
326 | } | |
327 | ||
328 | // HACK: This is to "unstabilize" the `transmute` intrinsic | |
329 | // within const fns. `transmute` is allowed in all other const contexts. | |
330 | // This won't really scale to more intrinsics or functions. Let's allow const | |
331 | // transmutes in const fn before we add more hacks to this. | |
923072b8 | 332 | if tcx.is_intrinsic(fn_def_id) && tcx.item_name(fn_def_id) == sym::transmute { |
f20569fa XL |
333 | return Err(( |
334 | span, | |
335 | "can only call `transmute` from const items, not `const fn`".into(), | |
336 | )); | |
337 | } | |
338 | ||
339 | check_operand(tcx, func, span, body)?; | |
340 | ||
341 | for arg in args { | |
342 | check_operand(tcx, arg, span, body)?; | |
343 | } | |
344 | Ok(()) | |
345 | } else { | |
346 | Err((span, "can only call other const fns within const fn".into())) | |
347 | } | |
348 | }, | |
349 | ||
350 | TerminatorKind::Assert { | |
351 | cond, | |
352 | expected: _, | |
353 | msg: _, | |
354 | target: _, | |
355 | cleanup: _, | |
356 | } => check_operand(tcx, cond, span, body), | |
357 | ||
358 | TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())), | |
359 | } | |
360 | } | |
cdc7bbd5 | 361 | |
923072b8 | 362 | fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: Option<RustcVersion>) -> bool { |
c295e0f8 | 363 | tcx.is_const_fn(def_id) |
cdc7bbd5 | 364 | && tcx.lookup_const_stability(def_id).map_or(true, |const_stab| { |
064997fb | 365 | if let rustc_attr::StabilityLevel::Stable { since, .. } = const_stab.level { |
cdc7bbd5 XL |
366 | // Checking MSRV is manually necessary because `rustc` has no such concept. This entire |
367 | // function could be removed if `rustc` provided a MSRV-aware version of `is_const_fn`. | |
368 | // as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262. | |
369 | crate::meets_msrv( | |
370 | msrv, | |
923072b8 | 371 | RustcVersion::parse(since.as_str()) |
cdc7bbd5 XL |
372 | .expect("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted"), |
373 | ) | |
374 | } else { | |
375 | // Unstable const fn with the feature enabled. | |
376 | msrv.is_none() | |
377 | } | |
378 | }) | |
379 | } |