|
| 1 | +//! This pass checks HIR bodies that may be evaluated at compile-time (e.g., `const`, `static`, |
| 2 | +//! `const fn`) for structured control flow (e.g. `if`, `while`), which is forbidden in a const |
| 3 | +//! context. |
| 4 | +//! |
| 5 | +//! By the time the MIR const-checker runs, these high-level constructs have been lowered to |
| 6 | +//! control-flow primitives (e.g., `Goto`, `SwitchInt`), making it tough to properly attribute |
| 7 | +//! errors. We still look for those primitives in the MIR const-checker to ensure nothing slips |
| 8 | +//! through, but errors for structured control flow in a `const` should be emitted here. |
| 9 | +
|
| 10 | +use rustc::hir::def_id::DefId; |
| 11 | +use rustc::hir::intravisit::{Visitor, NestedVisitorMap}; |
| 12 | +use rustc::hir::map::Map; |
| 13 | +use rustc::hir; |
| 14 | +use rustc::session::Session; |
| 15 | +use rustc::ty::TyCtxt; |
| 16 | +use rustc::ty::query::Providers; |
| 17 | +use syntax::ast::Mutability; |
| 18 | +use syntax::span_err; |
| 19 | +use syntax_pos::Span; |
| 20 | + |
| 21 | +use std::fmt; |
| 22 | + |
| 23 | +#[derive(Copy, Clone)] |
| 24 | +enum ConstKind { |
| 25 | + Static, |
| 26 | + StaticMut, |
| 27 | + ConstFn, |
| 28 | + Const, |
| 29 | + AnonConst, |
| 30 | +} |
| 31 | + |
| 32 | +impl ConstKind { |
| 33 | + fn for_body(body: &hir::Body, hir_map: &Map<'_>) -> Option<Self> { |
| 34 | + let is_const_fn = |id| hir_map.fn_sig_by_hir_id(id).unwrap().header.is_const(); |
| 35 | + |
| 36 | + let owner = hir_map.body_owner(body.id()); |
| 37 | + let const_kind = match hir_map.body_owner_kind(owner) { |
| 38 | + hir::BodyOwnerKind::Const => Self::Const, |
| 39 | + hir::BodyOwnerKind::Static(Mutability::Mutable) => Self::StaticMut, |
| 40 | + hir::BodyOwnerKind::Static(Mutability::Immutable) => Self::Static, |
| 41 | + |
| 42 | + hir::BodyOwnerKind::Fn if is_const_fn(owner) => Self::ConstFn, |
| 43 | + hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => return None, |
| 44 | + }; |
| 45 | + |
| 46 | + Some(const_kind) |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +impl fmt::Display for ConstKind { |
| 51 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 52 | + let s = match self { |
| 53 | + Self::Static => "static", |
| 54 | + Self::StaticMut => "static mut", |
| 55 | + Self::Const | Self::AnonConst => "const", |
| 56 | + Self::ConstFn => "const fn", |
| 57 | + }; |
| 58 | + |
| 59 | + write!(f, "{}", s) |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: DefId) { |
| 64 | + let mut vis = CheckConstVisitor::new(tcx); |
| 65 | + tcx.hir().visit_item_likes_in_module(module_def_id, &mut vis.as_deep_visitor()); |
| 66 | +} |
| 67 | + |
| 68 | +pub(crate) fn provide(providers: &mut Providers<'_>) { |
| 69 | + *providers = Providers { |
| 70 | + check_mod_const_bodies, |
| 71 | + ..*providers |
| 72 | + }; |
| 73 | +} |
| 74 | + |
| 75 | +#[derive(Copy, Clone)] |
| 76 | +struct CheckConstVisitor<'tcx> { |
| 77 | + sess: &'tcx Session, |
| 78 | + hir_map: &'tcx Map<'tcx>, |
| 79 | + const_kind: Option<ConstKind>, |
| 80 | +} |
| 81 | + |
| 82 | +impl<'tcx> CheckConstVisitor<'tcx> { |
| 83 | + fn new(tcx: TyCtxt<'tcx>) -> Self { |
| 84 | + CheckConstVisitor { |
| 85 | + sess: &tcx.sess, |
| 86 | + hir_map: tcx.hir(), |
| 87 | + const_kind: None, |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + /// Emits an error when an unsupported expression is found in a const context. |
| 92 | + fn const_check_violated(&self, bad_op: &str, span: Span) { |
| 93 | + if self.sess.opts.debugging_opts.unleash_the_miri_inside_of_you { |
| 94 | + self.sess.span_warn(span, "skipping const checks"); |
| 95 | + return; |
| 96 | + } |
| 97 | + |
| 98 | + let const_kind = self.const_kind |
| 99 | + .expect("`const_check_violated` may only be called inside a const context"); |
| 100 | + |
| 101 | + span_err!(self.sess, span, E0744, "`{}` is not allowed in a `{}`", bad_op, const_kind); |
| 102 | + } |
| 103 | + |
| 104 | + /// Saves the parent `const_kind` before calling `f` and restores it afterwards. |
| 105 | + fn recurse_into(&mut self, kind: Option<ConstKind>, f: impl FnOnce(&mut Self)) { |
| 106 | + let parent_kind = self.const_kind; |
| 107 | + self.const_kind = kind; |
| 108 | + f(self); |
| 109 | + self.const_kind = parent_kind; |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> { |
| 114 | + fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> { |
| 115 | + NestedVisitorMap::OnlyBodies(&self.hir_map) |
| 116 | + } |
| 117 | + |
| 118 | + fn visit_anon_const(&mut self, anon: &'tcx hir::AnonConst) { |
| 119 | + let kind = Some(ConstKind::AnonConst); |
| 120 | + self.recurse_into(kind, |this| hir::intravisit::walk_anon_const(this, anon)); |
| 121 | + } |
| 122 | + |
| 123 | + fn visit_body(&mut self, body: &'tcx hir::Body) { |
| 124 | + let kind = ConstKind::for_body(body, self.hir_map); |
| 125 | + self.recurse_into(kind, |this| hir::intravisit::walk_body(this, body)); |
| 126 | + } |
| 127 | + |
| 128 | + fn visit_expr(&mut self, e: &'tcx hir::Expr) { |
| 129 | + match &e.kind { |
| 130 | + // Skip the following checks if we are not currently in a const context. |
| 131 | + _ if self.const_kind.is_none() => {} |
| 132 | + |
| 133 | + hir::ExprKind::Loop(_, _, source) => { |
| 134 | + self.const_check_violated(source.name(), e.span); |
| 135 | + } |
| 136 | + |
| 137 | + hir::ExprKind::Match(_, _, source) => { |
| 138 | + use hir::MatchSource::*; |
| 139 | + |
| 140 | + let op = match source { |
| 141 | + Normal => Some("match"), |
| 142 | + IfDesugar { .. } | IfLetDesugar { .. } => Some("if"), |
| 143 | + TryDesugar => Some("?"), |
| 144 | + AwaitDesugar => Some(".await"), |
| 145 | + |
| 146 | + // These are handled by `ExprKind::Loop` above. |
| 147 | + WhileDesugar | WhileLetDesugar | ForLoopDesugar => None, |
| 148 | + }; |
| 149 | + |
| 150 | + if let Some(op) = op { |
| 151 | + self.const_check_violated(op, e.span); |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + _ => {}, |
| 156 | + } |
| 157 | + |
| 158 | + hir::intravisit::walk_expr(self, e); |
| 159 | + } |
| 160 | +} |
0 commit comments