]> git.lizzy.rs Git - rust.git/blob - src/librustc_passes/check_const.rs
Rollup merge of #68582 - LeSeulArtichaut:code-explanations, r=Dylan-DPC
[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::map::Map;
11 use rustc::session::config::nightly_options;
12 use rustc::session::parse::feature_err;
13 use rustc::ty::query::Providers;
14 use rustc::ty::TyCtxt;
15 use rustc_errors::struct_span_err;
16 use rustc_hir as hir;
17 use rustc_hir::def_id::DefId;
18 use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
19 use rustc_span::{sym, Span, Symbol};
20 use syntax::ast::Mutability;
21
22 use std::fmt;
23
24 /// An expression that is not *always* legal in a const context.
25 #[derive(Clone, Copy)]
26 enum NonConstExpr {
27     Loop(hir::LoopSource),
28     Match(hir::MatchSource),
29     OrPattern,
30 }
31
32 impl NonConstExpr {
33     fn name(self) -> String {
34         match self {
35             Self::Loop(src) => format!("`{}`", src.name()),
36             Self::Match(src) => format!("`{}`", src.name()),
37             Self::OrPattern => format!("or-pattern"),
38         }
39     }
40
41     fn required_feature_gates(self) -> Option<&'static [Symbol]> {
42         use hir::LoopSource::*;
43         use hir::MatchSource::*;
44
45         let gates: &[_] = match self {
46             Self::Match(Normal)
47             | Self::Match(IfDesugar { .. })
48             | Self::Match(IfLetDesugar { .. })
49             | Self::OrPattern => &[sym::const_if_match],
50
51             Self::Loop(Loop) => &[sym::const_loop],
52
53             Self::Loop(While)
54             | Self::Loop(WhileLet)
55             | Self::Match(WhileDesugar)
56             | Self::Match(WhileLetDesugar) => &[sym::const_loop, sym::const_if_match],
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)]
68 enum ConstKind {
69     Static,
70     StaticMut,
71     ConstFn,
72     Const,
73     AnonConst,
74 }
75
76 impl ConstKind {
77     fn for_body(body: &hir::Body<'_>, hir_map: &Map<'_>) -> Option<Self> {
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,
83             hir::BodyOwnerKind::Static(Mutability::Mut) => Self::StaticMut,
84             hir::BodyOwnerKind::Static(Mutability::Not) => Self::Static,
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
94 impl 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
107 fn 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
112 pub(crate) fn provide(providers: &mut Providers<'_>) {
113     *providers = Providers { check_mod_const_bodies, ..*providers };
114 }
115
116 #[derive(Copy, Clone)]
117 struct CheckConstVisitor<'tcx> {
118     tcx: TyCtxt<'tcx>,
119     const_kind: Option<ConstKind>,
120 }
121
122 impl<'tcx> CheckConstVisitor<'tcx> {
123     fn new(tcx: TyCtxt<'tcx>) -> Self {
124         CheckConstVisitor { tcx, const_kind: None }
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
146         let const_kind = self
147             .const_kind
148             .expect("`const_check_violated` may only be called inside a const context");
149         let msg = format!("{} is not allowed in a `{}`", expr.name(), const_kind);
150
151         let required_gates = required_gates.unwrap_or(&[]);
152         let missing_gates: Vec<_> =
153             required_gates.iter().copied().filter(|&g| !features.enabled(g)).collect();
154
155         match missing_gates.as_slice() {
156             &[] => struct_span_err!(self.tcx.sess, span, E0744, "{}", msg).emit(),
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)
162                     .note(
163                         "`#![feature(const_loop)]` alone is not sufficient, \
164                            since this loop expression contains an implicit conditional",
165                     )
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
201 impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> {
202     type Map = Map<'tcx>;
203
204     fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<'_, Self::Map> {
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);
210         self.recurse_into(kind, |this| intravisit::walk_anon_const(this, anon));
211     }
212
213     fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) {
214         let kind = ConstKind::for_body(body, self.tcx.hir());
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)
225     }
226
227     fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) {
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.
239                     hir::MatchSource::WhileDesugar
240                     | hir::MatchSource::WhileLetDesugar
241                     | hir::MatchSource::ForLoopDesugar => None,
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
251             _ => {}
252         }
253
254         intravisit::walk_expr(self, e);
255     }
256 }