]> git.proxmox.com Git - rustc.git/blame - src/librustc_passes/check_const.rs
New upstream version 1.42.0+dfsg0+pve1
[rustc.git] / src / librustc_passes / check_const.rs
CommitLineData
d9bb1a4e
FG
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
d9bb1a4e 10use rustc::hir::map::Map;
d9bb1a4e 11use rustc::session::config::nightly_options;
46de9a89
FG
12use rustc::session::parse::feature_err;
13use rustc::ty::query::Providers;
14use rustc::ty::TyCtxt;
15use rustc_errors::struct_span_err;
16use rustc_hir as hir;
17use rustc_hir::def_id::DefId;
18use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
19use rustc_span::{sym, Span, Symbol};
d9bb1a4e 20use syntax::ast::Mutability;
d9bb1a4e
FG
21
22use std::fmt;
23
24/// An expression that is not *always* legal in a const context.
25#[derive(Clone, Copy)]
26enum NonConstExpr {
27 Loop(hir::LoopSource),
28 Match(hir::MatchSource),
46de9a89 29 OrPattern,
d9bb1a4e
FG
30}
31
32impl NonConstExpr {
46de9a89 33 fn name(self) -> String {
d9bb1a4e 34 match self {
46de9a89
FG
35 Self::Loop(src) => format!("`{}`", src.name()),
36 Self::Match(src) => format!("`{}`", src.name()),
37 Self::OrPattern => format!("or-pattern"),
d9bb1a4e
FG
38 }
39 }
40
41 fn required_feature_gates(self) -> Option<&'static [Symbol]> {
d9bb1a4e 42 use hir::LoopSource::*;
46de9a89 43 use hir::MatchSource::*;
d9bb1a4e
FG
44
45 let gates: &[_] = match self {
46de9a89 46 Self::Match(Normal)
d9bb1a4e
FG
47 | Self::Match(IfDesugar { .. })
48 | Self::Match(IfLetDesugar { .. })
46de9a89 49 | Self::OrPattern => &[sym::const_if_match],
d9bb1a4e 50
46de9a89 51 Self::Loop(Loop) => &[sym::const_loop],
d9bb1a4e 52
46de9a89 53 Self::Loop(While)
d9bb1a4e
FG
54 | Self::Loop(WhileLet)
55 | Self::Match(WhileDesugar)
46de9a89 56 | Self::Match(WhileLetDesugar) => &[sym::const_loop, sym::const_if_match],
d9bb1a4e
FG
57
58 // A `for` loop's desugaring contains a call to `IntoIterator::into_iter`,
59 // so they are not yet allowed with `#![feature(const_loop)]`.
60 _ => return None,
61 };
62
63 Some(gates)
64 }
65}
66
67#[derive(Copy, Clone)]
68enum ConstKind {
69 Static,
70 StaticMut,
71 ConstFn,
72 Const,
73 AnonConst,
74}
75
76impl ConstKind {
46de9a89 77 fn for_body(body: &hir::Body<'_>, hir_map: &Map<'_>) -> Option<Self> {
d9bb1a4e
FG
78 let is_const_fn = |id| hir_map.fn_sig_by_hir_id(id).unwrap().header.is_const();
79
80 let owner = hir_map.body_owner(body.id());
81 let const_kind = match hir_map.body_owner_kind(owner) {
82 hir::BodyOwnerKind::Const => Self::Const,
46de9a89
FG
83 hir::BodyOwnerKind::Static(Mutability::Mut) => Self::StaticMut,
84 hir::BodyOwnerKind::Static(Mutability::Not) => Self::Static,
d9bb1a4e
FG
85
86 hir::BodyOwnerKind::Fn if is_const_fn(owner) => Self::ConstFn,
87 hir::BodyOwnerKind::Fn | hir::BodyOwnerKind::Closure => return None,
88 };
89
90 Some(const_kind)
91 }
92}
93
94impl fmt::Display for ConstKind {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 let s = match self {
97 Self::Static => "static",
98 Self::StaticMut => "static mut",
99 Self::Const | Self::AnonConst => "const",
100 Self::ConstFn => "const fn",
101 };
102
103 write!(f, "{}", s)
104 }
105}
106
107fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: DefId) {
108 let mut vis = CheckConstVisitor::new(tcx);
109 tcx.hir().visit_item_likes_in_module(module_def_id, &mut vis.as_deep_visitor());
110}
111
112pub(crate) fn provide(providers: &mut Providers<'_>) {
46de9a89 113 *providers = Providers { check_mod_const_bodies, ..*providers };
d9bb1a4e
FG
114}
115
116#[derive(Copy, Clone)]
117struct CheckConstVisitor<'tcx> {
118 tcx: TyCtxt<'tcx>,
119 const_kind: Option<ConstKind>,
120}
121
122impl<'tcx> CheckConstVisitor<'tcx> {
123 fn new(tcx: TyCtxt<'tcx>) -> Self {
46de9a89 124 CheckConstVisitor { tcx, const_kind: None }
d9bb1a4e
FG
125 }
126
127 /// Emits an error when an unsupported expression is found in a const context.
128 fn const_check_violated(&self, expr: NonConstExpr, span: Span) {
129 let features = self.tcx.features();
130 let required_gates = expr.required_feature_gates();
131 match required_gates {
132 // Don't emit an error if the user has enabled the requisite feature gates.
133 Some(gates) if gates.iter().all(|&g| features.enabled(g)) => return,
134
135 // `-Zunleash-the-miri-inside-of-you` only works for expressions that don't have a
136 // corresponding feature gate. This encourages nightly users to use feature gates when
137 // possible.
138 None if self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you => {
139 self.tcx.sess.span_warn(span, "skipping const checks");
140 return;
141 }
142
143 _ => {}
144 }
145
46de9a89
FG
146 let const_kind = self
147 .const_kind
d9bb1a4e 148 .expect("`const_check_violated` may only be called inside a const context");
46de9a89 149 let msg = format!("{} is not allowed in a `{}`", expr.name(), const_kind);
d9bb1a4e
FG
150
151 let required_gates = required_gates.unwrap_or(&[]);
46de9a89
FG
152 let missing_gates: Vec<_> =
153 required_gates.iter().copied().filter(|&g| !features.enabled(g)).collect();
d9bb1a4e
FG
154
155 match missing_gates.as_slice() {
46de9a89 156 &[] => struct_span_err!(self.tcx.sess, span, E0744, "{}", msg).emit(),
d9bb1a4e
FG
157
158 // If the user enabled `#![feature(const_loop)]` but not `#![feature(const_if_match)]`,
159 // explain why their `while` loop is being rejected.
160 &[gate @ sym::const_if_match] if required_gates.contains(&sym::const_loop) => {
161 feature_err(&self.tcx.sess.parse_sess, gate, span, &msg)
46de9a89
FG
162 .note(
163 "`#![feature(const_loop)]` alone is not sufficient, \
164 since this loop expression contains an implicit conditional",
165 )
d9bb1a4e
FG
166 .emit();
167 }
168
169 &[missing_primary, ref missing_secondary @ ..] => {
170 let mut err = feature_err(&self.tcx.sess.parse_sess, missing_primary, span, &msg);
171
172 // If multiple feature gates would be required to enable this expression, include
173 // them as help messages. Don't emit a separate error for each missing feature gate.
174 //
175 // FIXME(ecstaticmorse): Maybe this could be incorporated into `feature_err`? This
176 // is a pretty narrow case, however.
177 if nightly_options::is_nightly_build() {
178 for gate in missing_secondary {
179 let note = format!(
180 "add `#![feature({})]` to the crate attributes to enable",
181 gate,
182 );
183 err.help(&note);
184 }
185 }
186
187 err.emit();
188 }
189 }
190 }
191
192 /// Saves the parent `const_kind` before calling `f` and restores it afterwards.
193 fn recurse_into(&mut self, kind: Option<ConstKind>, f: impl FnOnce(&mut Self)) {
194 let parent_kind = self.const_kind;
195 self.const_kind = kind;
196 f(self);
197 self.const_kind = parent_kind;
198 }
199}
200
201impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
46de9a89
FG
202 type Map = Map<'tcx>;
203
204 fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<'_, Self::Map> {
d9bb1a4e
FG
205 NestedVisitorMap::OnlyBodies(&self.tcx.hir())
206 }
207
208 fn visit_anon_const(&mut self, anon: &'tcx hir::AnonConst) {
209 let kind = Some(ConstKind::AnonConst);
46de9a89 210 self.recurse_into(kind, |this| intravisit::walk_anon_const(this, anon));
d9bb1a4e
FG
211 }
212
46de9a89 213 fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) {
d9bb1a4e 214 let kind = ConstKind::for_body(body, self.tcx.hir());
46de9a89
FG
215 self.recurse_into(kind, |this| intravisit::walk_body(this, body));
216 }
217
218 fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {
219 if self.const_kind.is_some() {
220 if let hir::PatKind::Or { .. } = p.kind {
221 self.const_check_violated(NonConstExpr::OrPattern, p.span);
222 }
223 }
224 intravisit::walk_pat(self, p)
d9bb1a4e
FG
225 }
226
46de9a89 227 fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
d9bb1a4e
FG
228 match &e.kind {
229 // Skip the following checks if we are not currently in a const context.
230 _ if self.const_kind.is_none() => {}
231
232 hir::ExprKind::Loop(_, _, source) => {
233 self.const_check_violated(NonConstExpr::Loop(*source), e.span);
234 }
235
236 hir::ExprKind::Match(_, _, source) => {
237 let non_const_expr = match source {
238 // These are handled by `ExprKind::Loop` above.
46de9a89 239 hir::MatchSource::WhileDesugar
d9bb1a4e 240 | hir::MatchSource::WhileLetDesugar
46de9a89 241 | hir::MatchSource::ForLoopDesugar => None,
d9bb1a4e
FG
242
243 _ => Some(NonConstExpr::Match(*source)),
244 };
245
246 if let Some(expr) = non_const_expr {
247 self.const_check_violated(expr, e.span);
248 }
249 }
250
46de9a89 251 _ => {}
d9bb1a4e
FG
252 }
253
46de9a89 254 intravisit::walk_expr(self, e);
d9bb1a4e
FG
255 }
256}