]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/transform/qualify_min_const_fn.rs
Tidy
[rust.git] / src / librustc_mir / transform / qualify_min_const_fn.rs
1 use rustc::hir::def_id::DefId;
2 use rustc::hir;
3 use rustc::mir::*;
4 use rustc::ty::{self, Predicate, Ty, TyCtxt, adjustment::{PointerCast}};
5 use std::borrow::Cow;
6 use syntax_pos::Span;
7 use syntax::symbol::{sym, Symbol};
8 use syntax::attr;
9
10 type McfResult = Result<(), (Span, Cow<'static, str>)>;
11
12 pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>) -> McfResult {
13     let mut current = def_id;
14     loop {
15         let predicates = tcx.predicates_of(current);
16         for (predicate, _) in predicates.predicates {
17             match predicate {
18                 | Predicate::RegionOutlives(_)
19                 | Predicate::TypeOutlives(_)
20                 | Predicate::WellFormed(_)
21                 | Predicate::Projection(_)
22                 | Predicate::ConstEvaluatable(..) => continue,
23                 | Predicate::ObjectSafe(_) => {
24                     bug!("object safe predicate on function: {:#?}", predicate)
25                 }
26                 Predicate::ClosureKind(..) => {
27                     bug!("closure kind predicate on function: {:#?}", predicate)
28                 }
29                 Predicate::Subtype(_) => bug!("subtype predicate on function: {:#?}", predicate),
30                 Predicate::Trait(pred) => {
31                     if Some(pred.def_id()) == tcx.lang_items().sized_trait() {
32                         continue;
33                     }
34                     match pred.skip_binder().self_ty().kind {
35                         ty::Param(ref p) => {
36                             let generics = tcx.generics_of(current);
37                             let def = generics.type_param(p, tcx);
38                             let span = tcx.def_span(def.def_id);
39                             return Err((
40                                 span,
41                                 "trait bounds other than `Sized` \
42                                  on const fn parameters are unstable"
43                                     .into(),
44                             ));
45                         }
46                         // other kinds of bounds are either tautologies
47                         // or cause errors in other passes
48                         _ => continue,
49                     }
50                 }
51             }
52         }
53         match predicates.parent {
54             Some(parent) => current = parent,
55             None => break,
56         }
57     }
58
59     for local in &body.local_decls {
60         check_ty(tcx, local.ty, local.source_info.span, def_id)?;
61     }
62     // impl trait is gone in MIR, so check the return type manually
63     check_ty(
64         tcx,
65         tcx.fn_sig(def_id).output().skip_binder(),
66         body.local_decls.iter().next().unwrap().source_info.span,
67         def_id,
68     )?;
69
70     for bb in body.basic_blocks() {
71         check_terminator(tcx, body, def_id, bb.terminator())?;
72         for stmt in &bb.statements {
73             check_statement(tcx, body, def_id, stmt)?;
74         }
75     }
76     Ok(())
77 }
78
79 fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, fn_def_id: DefId) -> McfResult {
80     for ty in ty.walk() {
81         match ty.kind {
82             ty::Ref(_, _, hir::Mutability::Mutable) => return Err((
83                 span,
84                 "mutable references in const fn are unstable".into(),
85             )),
86             ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())),
87             ty::FnPtr(..) => {
88                 if !tcx.const_fn_is_allowed_fn_ptr(fn_def_id) {
89                     return Err((span, "function pointers in const fn are unstable".into()))
90                 }
91             }
92             ty::Dynamic(preds, _) => {
93                 for pred in preds.iter() {
94                     match pred.skip_binder() {
95                         | ty::ExistentialPredicate::AutoTrait(_)
96                         | ty::ExistentialPredicate::Projection(_) => {
97                             return Err((
98                                 span,
99                                 "trait bounds other than `Sized` \
100                                  on const fn parameters are unstable"
101                                     .into(),
102                             ))
103                         }
104                         ty::ExistentialPredicate::Trait(trait_ref) => {
105                             if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() {
106                                 return Err((
107                                     span,
108                                     "trait bounds other than `Sized` \
109                                      on const fn parameters are unstable"
110                                         .into(),
111                                 ));
112                             }
113                         }
114                     }
115                 }
116             }
117             _ => {}
118         }
119     }
120     Ok(())
121 }
122
123 fn check_rvalue(
124     tcx: TyCtxt<'tcx>,
125     body: &Body<'tcx>,
126     def_id: DefId,
127     rvalue: &Rvalue<'tcx>,
128     span: Span,
129 ) -> McfResult {
130     match rvalue {
131         Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => {
132             check_operand(tcx, operand, span, def_id, body)
133         }
134         Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) => {
135             check_place(tcx, place, span, def_id, body)
136         }
137         Rvalue::Cast(CastKind::Misc, operand, cast_ty) => {
138             use rustc::ty::cast::CastTy;
139             let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast");
140             let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
141             match (cast_in, cast_out) {
142                 (CastTy::Ptr(_), CastTy::Int(_)) | (CastTy::FnPtr, CastTy::Int(_)) => Err((
143                     span,
144                     "casting pointers to ints is unstable in const fn".into(),
145                 )),
146                 (CastTy::RPtr(_), CastTy::Float) => bug!(),
147                 (CastTy::RPtr(_), CastTy::Int(_)) => bug!(),
148                 (CastTy::Ptr(_), CastTy::RPtr(_)) => bug!(),
149                 _ => check_operand(tcx, operand, span, def_id, body),
150             }
151         }
152         Rvalue::Cast(CastKind::Pointer(PointerCast::MutToConstPointer), operand, _)
153         | Rvalue::Cast(CastKind::Pointer(PointerCast::ArrayToPointer), operand, _) => {
154             check_operand(tcx, operand, span, def_id, body)
155         }
156         Rvalue::Cast(CastKind::Pointer(PointerCast::UnsafeFnPointer), _, _) |
157         Rvalue::Cast(CastKind::Pointer(PointerCast::ClosureFnPointer(_)), _, _) |
158         Rvalue::Cast(CastKind::Pointer(PointerCast::ReifyFnPointer), _, _) => Err((
159             span,
160             "function pointer casts are not allowed in const fn".into(),
161         )),
162         Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), _, _) => Err((
163             span,
164             "unsizing casts are not allowed in const fn".into(),
165         )),
166         // binops are fine on integers
167         Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => {
168             check_operand(tcx, lhs, span, def_id, body)?;
169             check_operand(tcx, rhs, span, def_id, body)?;
170             let ty = lhs.ty(body, tcx);
171             if ty.is_integral() || ty.is_bool() || ty.is_char() {
172                 Ok(())
173             } else {
174                 Err((
175                     span,
176                     "only int, `bool` and `char` operations are stable in const fn".into(),
177                 ))
178             }
179         }
180         Rvalue::NullaryOp(NullOp::SizeOf, _) => Ok(()),
181         Rvalue::NullaryOp(NullOp::Box, _) => Err((
182             span,
183             "heap allocations are not allowed in const fn".into(),
184         )),
185         Rvalue::UnaryOp(_, operand) => {
186             let ty = operand.ty(body, tcx);
187             if ty.is_integral() || ty.is_bool() {
188                 check_operand(tcx, operand, span, def_id, body)
189             } else {
190                 Err((
191                     span,
192                     "only int and `bool` operations are stable in const fn".into(),
193                 ))
194             }
195         }
196         Rvalue::Aggregate(_, operands) => {
197             for operand in operands {
198                 check_operand(tcx, operand, span, def_id, body)?;
199             }
200             Ok(())
201         }
202     }
203 }
204
205 fn check_statement(
206     tcx: TyCtxt<'tcx>,
207     body: &Body<'tcx>,
208     def_id: DefId,
209     statement: &Statement<'tcx>,
210 ) -> McfResult {
211     let span = statement.source_info.span;
212     match &statement.kind {
213         StatementKind::Assign(box(place, rval)) => {
214             check_place(tcx, place, span, def_id, body)?;
215             check_rvalue(tcx, body, def_id, rval, span)
216         }
217
218         | StatementKind::FakeRead(FakeReadCause::ForMatchedPlace, _)
219         if !tcx.features().const_if_match
220         => {
221             Err((span, "loops and conditional expressions are not stable in const fn".into()))
222         }
223
224         StatementKind::FakeRead(_, place) => check_place(tcx, place, span, def_id, body),
225
226         // just an assignment
227         StatementKind::SetDiscriminant { place, .. } => check_place(tcx, place, span, def_id, body),
228
229         | StatementKind::InlineAsm { .. } => {
230             Err((span, "cannot use inline assembly in const fn".into()))
231         }
232
233         // These are all NOPs
234         | StatementKind::StorageLive(_)
235         | StatementKind::StorageDead(_)
236         | StatementKind::Retag { .. }
237         | StatementKind::AscribeUserType(..)
238         | StatementKind::Nop => Ok(()),
239     }
240 }
241
242 fn check_operand(
243     tcx: TyCtxt<'tcx>,
244     operand: &Operand<'tcx>,
245     span: Span,
246     def_id: DefId,
247     body: &Body<'tcx>
248 ) -> McfResult {
249     match operand {
250         Operand::Move(place) | Operand::Copy(place) => {
251             check_place(tcx, place, span, def_id, body)
252         }
253         Operand::Constant(c) => match c.check_static_ptr(tcx) {
254             Some(_) => Err((span, "cannot access `static` items in const fn".into())),
255             None => Ok(()),
256         },
257     }
258 }
259
260 fn check_place(
261     tcx: TyCtxt<'tcx>,
262     place: &Place<'tcx>,
263     span: Span,
264     def_id: DefId,
265     body: &Body<'tcx>
266 ) -> McfResult {
267     let mut cursor = place.projection.as_ref();
268     while let &[ref proj_base @ .., elem] = cursor {
269         cursor = proj_base;
270         match elem {
271             ProjectionElem::Downcast(..) if !tcx.features().const_if_match
272                 => return Err((span, "`match` or `if let` in `const fn` is unstable".into())),
273             ProjectionElem::Downcast(_symbol, _variant_index) => {}
274
275             ProjectionElem::Field(..) => {
276                 let base_ty = Place::ty_from(&place.base, &proj_base, body, tcx).ty;
277                 if let Some(def) = base_ty.ty_adt_def() {
278                     // No union field accesses in `const fn`
279                     if def.is_union() {
280                         if !feature_allowed(tcx, def_id, sym::const_fn_union) {
281                             return Err((span, "accessing union fields is unstable".into()));
282                         }
283                     }
284                 }
285             }
286             ProjectionElem::ConstantIndex { .. }
287             | ProjectionElem::Subslice { .. }
288             | ProjectionElem::Deref
289             | ProjectionElem::Index(_) => {}
290         }
291     }
292
293     Ok(())
294 }
295
296 /// Returns whether `allow_internal_unstable(..., <feature_gate>, ...)` is present.
297 fn feature_allowed(
298     tcx: TyCtxt<'tcx>,
299     def_id: DefId,
300     feature_gate: Symbol,
301 ) -> bool {
302     attr::allow_internal_unstable(&tcx.get_attrs(def_id), &tcx.sess.diagnostic())
303         .map_or(false, |mut features| features.any(|name| name == feature_gate))
304 }
305
306 fn check_terminator(
307     tcx: TyCtxt<'tcx>,
308     body: &'a Body<'tcx>,
309     def_id: DefId,
310     terminator: &Terminator<'tcx>,
311 ) -> McfResult {
312     let span = terminator.source_info.span;
313     match &terminator.kind {
314         | TerminatorKind::Goto { .. }
315         | TerminatorKind::Return
316         | TerminatorKind::Resume => Ok(()),
317
318         TerminatorKind::Drop { location, .. } => {
319             check_place(tcx, location, span, def_id, body)
320         }
321         TerminatorKind::DropAndReplace { location, value, .. } => {
322             check_place(tcx, location, span, def_id, body)?;
323             check_operand(tcx, value, span, def_id, body)
324         },
325
326         | TerminatorKind::FalseEdges { .. }
327         | TerminatorKind::SwitchInt { .. }
328         if !tcx.features().const_if_match
329         => Err((
330             span,
331             "loops and conditional expressions are not stable in const fn".into(),
332         )),
333
334         TerminatorKind::FalseEdges { .. } => Ok(()),
335         TerminatorKind::SwitchInt { discr, switch_ty: _, values: _, targets: _ } => {
336             check_operand(tcx, discr, span, def_id, body)
337         }
338
339         // FIXME(ecstaticmorse): We probably want to allow `Unreachable` unconditionally.
340         TerminatorKind::Unreachable if tcx.features().const_if_match => Ok(()),
341
342         | TerminatorKind::Abort | TerminatorKind::Unreachable => {
343             Err((span, "const fn with unreachable code is not stable".into()))
344         }
345         | TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
346             Err((span, "const fn generators are unstable".into()))
347         }
348
349         TerminatorKind::Call {
350             func,
351             args,
352             from_hir_call: _,
353             destination: _,
354             cleanup: _,
355         } => {
356             let fn_ty = func.ty(body, tcx);
357             if let ty::FnDef(def_id, _) = fn_ty.kind {
358                 if !tcx.is_min_const_fn(def_id) {
359                     return Err((
360                         span,
361                         format!(
362                             "can only call other `const fn` within a `const fn`, \
363                              but `{:?}` is not stable as `const fn`",
364                             func,
365                         )
366                         .into(),
367                     ));
368                 }
369
370                 check_operand(tcx, func, span, def_id, body)?;
371
372                 for arg in args {
373                     check_operand(tcx, arg, span, def_id, body)?;
374                 }
375                 Ok(())
376             } else {
377                 Err((span, "can only call other const fns within const fn".into()))
378             }
379         }
380
381         TerminatorKind::Assert {
382             cond,
383             expected: _,
384             msg: _,
385             target: _,
386             cleanup: _,
387         } => check_operand(tcx, cond, span, def_id, body),
388
389         TerminatorKind::FalseUnwind { .. } => {
390             Err((span, "loops are not allowed in const fn".into()))
391         },
392     }
393 }