]> git.lizzy.rs Git - rust.git/blob - src/librustc_passes/check_const.rs
697a3ae873cc83d951c91e13238ee160bd517380
[rust.git] / src / librustc_passes / check_const.rs
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::ty::TyCtxt;
15 use rustc::ty::query::Providers;
16 use rustc_feature::Features;
17 use syntax::ast::Mutability;
18 use syntax::feature_gate::{emit_feature_err, GateIssue};
19 use syntax::span_err;
20 use syntax_pos::{sym, Span};
21 use rustc_error_codes::*;
22
23 use std::fmt;
24
25 /// An expression that is not *always* legal in a const context.
26 #[derive(Clone, Copy)]
27 enum NonConstExpr {
28     Loop(hir::LoopSource),
29     Match(hir::MatchSource),
30 }
31
32 impl NonConstExpr {
33     fn name(self) -> &'static str {
34         match self {
35             Self::Loop(src) => src.name(),
36             Self::Match(src) => src.name(),
37         }
38     }
39
40     /// Returns `true` if all feature gates required to enable this expression are turned on, or
41     /// `None` if there is no feature gate corresponding to this expression.
42     fn is_feature_gate_enabled(self, features: &Features) -> Option<bool> {
43         use hir::MatchSource::*;
44         match self {
45             | Self::Match(Normal)
46             | Self::Match(IfDesugar { .. })
47             | Self::Match(IfLetDesugar { .. })
48             => Some(features.const_if_match),
49
50             _ => None,
51         }
52     }
53 }
54
55 #[derive(Copy, Clone)]
56 enum ConstKind {
57     Static,
58     StaticMut,
59     ConstFn,
60     Const,
61     AnonConst,
62 }
63
64 impl ConstKind {
65     fn for_body(body: &hir::Body, hir_map: &Map<'_>) -> Option<Self> {
66         let is_const_fn = |id| hir_map.fn_sig_by_hir_id(id).unwrap().header.is_const();
67
68         let owner = hir_map.body_owner(body.id());
69         let const_kind = match hir_map.body_owner_kind(owner) {
70             hir::BodyOwnerKind::Const => Self::Const,
71             hir::BodyOwnerKind::Static(Mutability::Mutable) => Self::StaticMut,
72             hir::BodyOwnerKind::Static(Mutability::Immutable) => Self::Static,
73
74             hir::BodyOwnerKind::Fn if is_const_fn(owner) => Self::ConstFn,
75             hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => return None,
76         };
77
78         Some(const_kind)
79     }
80 }
81
82 impl fmt::Display for ConstKind {
83     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84         let s = match self {
85             Self::Static => "static",
86             Self::StaticMut => "static mut",
87             Self::Const | Self::AnonConst => "const",
88             Self::ConstFn => "const fn",
89         };
90
91         write!(f, "{}", s)
92     }
93 }
94
95 fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: DefId) {
96     let mut vis = CheckConstVisitor::new(tcx);
97     tcx.hir().visit_item_likes_in_module(module_def_id, &mut vis.as_deep_visitor());
98 }
99
100 pub(crate) fn provide(providers: &mut Providers<'_>) {
101     *providers = Providers {
102         check_mod_const_bodies,
103         ..*providers
104     };
105 }
106
107 #[derive(Copy, Clone)]
108 struct CheckConstVisitor<'tcx> {
109     tcx: TyCtxt<'tcx>,
110     const_kind: Option<ConstKind>,
111 }
112
113 impl<'tcx> CheckConstVisitor<'tcx> {
114     fn new(tcx: TyCtxt<'tcx>) -> Self {
115         CheckConstVisitor {
116             tcx,
117             const_kind: None,
118         }
119     }
120
121     /// Emits an error when an unsupported expression is found in a const context.
122     fn const_check_violated(&self, expr: NonConstExpr, span: Span) {
123         match expr.is_feature_gate_enabled(self.tcx.features()) {
124             // Don't emit an error if the user has enabled the requisite feature gates.
125             Some(true) => return,
126
127             // Users of `-Zunleash-the-miri-inside-of-you` must use feature gates when possible.
128             None if self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you => {
129                 self.tcx.sess.span_warn(span, "skipping const checks");
130                 return;
131             }
132
133             _ => {}
134         }
135
136         let const_kind = self.const_kind
137             .expect("`const_check_violated` may only be called inside a const context");
138
139         let msg = format!("`{}` is not allowed in a `{}`", expr.name(), const_kind);
140         match expr {
141             | NonConstExpr::Match(hir::MatchSource::Normal)
142             | NonConstExpr::Match(hir::MatchSource::IfDesugar { .. })
143             | NonConstExpr::Match(hir::MatchSource::IfLetDesugar { .. })
144             => emit_feature_err(
145                 &self.tcx.sess.parse_sess,
146                 sym::const_if_match,
147                 span,
148                 GateIssue::Language,
149                 &msg
150             ),
151
152             _ => span_err!(self.tcx.sess, span, E0744, "{}", msg),
153         }
154     }
155
156     /// Saves the parent `const_kind` before calling `f` and restores it afterwards.
157     fn recurse_into(&mut self, kind: Option<ConstKind>, f: impl FnOnce(&mut Self)) {
158         let parent_kind = self.const_kind;
159         self.const_kind = kind;
160         f(self);
161         self.const_kind = parent_kind;
162     }
163 }
164
165 impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
166     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
167         NestedVisitorMap::OnlyBodies(&self.tcx.hir())
168     }
169
170     fn visit_anon_const(&mut self, anon: &'tcx hir::AnonConst) {
171         let kind = Some(ConstKind::AnonConst);
172         self.recurse_into(kind, |this| hir::intravisit::walk_anon_const(this, anon));
173     }
174
175     fn visit_body(&mut self, body: &'tcx hir::Body) {
176         let kind = ConstKind::for_body(body, self.tcx.hir());
177         self.recurse_into(kind, |this| hir::intravisit::walk_body(this, body));
178     }
179
180     fn visit_expr(&mut self, e: &'tcx hir::Expr) {
181         match &e.kind {
182             // Skip the following checks if we are not currently in a const context.
183             _ if self.const_kind.is_none() => {}
184
185             hir::ExprKind::Loop(_, _, source) => {
186                 self.const_check_violated(NonConstExpr::Loop(*source), e.span);
187             }
188
189             hir::ExprKind::Match(_, _, source) => {
190                 let non_const_expr = match source {
191                     // These are handled by `ExprKind::Loop` above.
192                     | hir::MatchSource::WhileDesugar
193                     | hir::MatchSource::WhileLetDesugar
194                     | hir::MatchSource::ForLoopDesugar
195                     => None,
196
197                     _ => Some(NonConstExpr::Match(*source)),
198                 };
199
200                 if let Some(expr) = non_const_expr {
201                     self.const_check_violated(expr, e.span);
202                 }
203             }
204
205             _ => {},
206         }
207
208         hir::intravisit::walk_expr(self, e);
209     }
210 }