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