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